feat(vm): File::Stat/File/Dir — real-FS metadata, link ops, and Dir instances - #346
Merged
Conversation
…ta surface
Horodate: 2026-08-10 09:40 CEST
Extends the File::Stat accessor surface and the File.* class-method
predicates toward MRI 4.0.5:
File::Stat instance methods (new):
setuid? setgid? sticky? world_readable? grpowned?
readable_real? writable_real? executable_real?
rdev blocks dev_major dev_minor rdev_major rdev_minor
birthtime (NotImplementedError, as MRI on unsupported platforms/FS)
File.* class methods (new):
pipe? socket? blockdev? chardev? setuid? setgid? sticky?
owned? grpowned? world_readable? world_writable? identical?
atime ctime birthtime readable_real? writable_real? executable_real?
statFields now carries rdev/blocks (from syscall.Stat_t on unix; 0 on
windows/wasm). Access checks refactored around a shared permFor so the
effective-id (accessible) and real-id (accessibleReal) predicates share
one owner/group/other decision. Device numbers decomposed with the glibc
gnu_dev_major/minor encoding.
Deferred (not in this commit): File.link/symlink/readlink/realdirpath/
truncate/fnmatch?, Dir recursive glob + Dir instances, full File::Stat#inspect
and real birthtime (platform-specific).
Verification (darwin, go 1.26.4, GOWORK=off, CGO=0):
- behaviour cross-checked against ruby 4.0.5
- go test ./... exit 0
- CI-exact whole-package coverage gate: only the pre-existing tolerated
partials remain (new code 100%, incl. error branches)
- gofmt + go vet clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Horodate: 2026-08-10 09:47 CEST
File.* class methods (new): link, symlink, readlink, truncate,
realdirpath, path — each with MRI-faithful arity/type checks and error
classes (Errno::EEXIST/EINVAL/ENOENT), verified against ruby 4.0.5.
Dir instances (new): rbgo's Dir had only class methods; this adds the
DirObj value and Dir.new / Dir.open plus the instance surface —
read, pos, tell (alias of pos), seek, pos=, rewind, path, to_path
(alias path/to_path), fileno, close, each, each_child. Semantics match
MRI: a closed handle raises IOError on every read-side method (path/
to_path keep working); fileno raises NotImplementedError (no dirfd);
Dir.open with a block closes the handle on exit, even on a raised
exception, and returns the block value; a missing path raises a
SystemCallError. Blockless each/each_child return an Enumerator.
Deferred: File.fnmatch? + File::FNM_* flags, Dir recursive/brace glob
(** / {a,b} / char-class flags), File.chmod/lchmod symlink variants,
full File::Stat#inspect and real birthtime (platform-specific).
Verification (darwin, go 1.26.4, GOWORK=off, CGO=0):
- behaviour cross-checked against ruby 4.0.5 and through rbgo itself
- go test ./... exit 0
- CI-exact whole-package coverage gate: only the pre-existing tolerated
partials remain (new code 100%, incl. error branches)
- gofmt + go vet clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Horodate: 2026-08-10 09:54 CEST The File::Stat / File / Dir work lifts the measured ruby/spec total to 12583 passing (from 12382 at this branch's base). Lock in the win at the measured total minus the usual 30-example margin. Measured: passing examples 12583 (files loaded 2104, failed to load 102), ratchet OK, exit 0 — via a freshly built binary. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # scripts/conformance/rubyspec/FLOOR
tannevaled
added a commit
that referenced
this pull request
Aug 10, 2026
2026-08-10 11:40 CEST Implements the File/Dir pattern-matching items deferred by the File/Dir PR (#346), with a self-contained matcher (Go's path/filepath.Match supports neither '**', '{}', nor the FNM_* flags). What / deferred items now implemented: - File.fnmatch? / File.fnmatch (pattern, path, flags=0) and the File::FNM_* constants (NOESCAPE=1, PATHNAME=2, DOTMATCH=4, CASEFOLD=8, EXTGLOB=16, SYSCASE=0). Full matcher: '*' (no '/' under PATHNAME), '**' (recursive '**/' segment under PATHNAME, else '*'), '?', '[set]' with ranges + negation + ']'-closes-empty-class semantics, '{a,b}' under EXTGLOB (nested/empty alts), '\'-escapes unless NOESCAPE, case fold, and the leading-'.' rule unless DOTMATCH. - Dir.glob / Dir.[] over the real filesystem: String or Array patterns, positional flags plus base:/sort:/flags: keywords, block form (yields, returns nil), '{a,b}' brace expansion, '**'/'**/' recursion (zero dirs, hidden dirs skipped without DOTMATCH), '?', '[set]', the "." synthetic entry MRI emits, trailing-'/' directory-only matches, and dedup+sort. Verification: - fnmatch matcher truth table (70+ rows) and Dir.glob over a known tree captured from ruby 4.0.5 (File.fnmatch? / Dir.glob); rbgo matches byte for byte. - New code at 100% coverage incl. error branches (CI-exact -coverpkg gate: only the pre-existing tolerated partials remain below 100%). - go test ./... green; gofmt + go vet clean. - ruby/spec ratchet: 12698 passing; FLOOR bumped 12553 -> 12668. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tannevaled
added a commit
that referenced
this pull request
Aug 10, 2026
…s) (#348) * feat(vm): File.fnmatch? + Dir.glob full patterns (WIP) 2026-08-10 11:05 CEST WIP: self-contained fnmatch matcher (*, **, ?, [set], {alt}, FNM_* flags) and Dir.glob/Dir.[] engine over the real filesystem. Verified against ruby 4.0.5 for fnmatch (70+ cases) and glob over a known tree. Tests next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(vm): File.fnmatch? + Dir.glob full patterns (**, {}, [set], flags) 2026-08-10 11:40 CEST Implements the File/Dir pattern-matching items deferred by the File/Dir PR (#346), with a self-contained matcher (Go's path/filepath.Match supports neither '**', '{}', nor the FNM_* flags). What / deferred items now implemented: - File.fnmatch? / File.fnmatch (pattern, path, flags=0) and the File::FNM_* constants (NOESCAPE=1, PATHNAME=2, DOTMATCH=4, CASEFOLD=8, EXTGLOB=16, SYSCASE=0). Full matcher: '*' (no '/' under PATHNAME), '**' (recursive '**/' segment under PATHNAME, else '*'), '?', '[set]' with ranges + negation + ']'-closes-empty-class semantics, '{a,b}' under EXTGLOB (nested/empty alts), '\'-escapes unless NOESCAPE, case fold, and the leading-'.' rule unless DOTMATCH. - Dir.glob / Dir.[] over the real filesystem: String or Array patterns, positional flags plus base:/sort:/flags: keywords, block form (yields, returns nil), '{a,b}' brace expansion, '**'/'**/' recursion (zero dirs, hidden dirs skipped without DOTMATCH), '?', '[set]', the "." synthetic entry MRI emits, trailing-'/' directory-only matches, and dedup+sort. Verification: - fnmatch matcher truth table (70+ rows) and Dir.glob over a known tree captured from ruby 4.0.5 (File.fnmatch? / Dir.glob); rbgo matches byte for byte. - New code at 100% coverage incl. error branches (CI-exact -coverpkg gate: only the pre-existing tolerated partials remain below 100%). - go test ./... green; gofmt + go vet clean. - ruby/spec ratchet: 12698 passing; FLOOR bumped 12553 -> 12668. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(conformance): bump ruby/spec FLOOR to 12776 2026-08-10 11:55 CEST After merging origin/main (IO descriptors work) the 16-way ruby/spec run reports 12806 passing on the merged tree; lock in N-30 = 12776 (was 12668 on this branch, 12553 on main). Verified in the foreground on the merged tree: ratchet OK, CI-exact -coverpkg gate clean (only pre-existing tolerated partials), go test ./... green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * conformance: set FLOOR 12650 (conservative) for fnmatch/glob — FS/case-sensitivity platform margin **Horodate : 2026-08-10 11:16 CEST** Dir.glob/File.fnmatch? specs are filesystem- and case-sensitivity-sensitive; this machine's ratchet was measured on macOS (case-insensitive FS by default), and the org CI runner is currently evicting every ratchet run (SIGTERM), so a clean Linux number is unavailable. Setting FLOOR to 12650 (measured 12806, ~156 cushion) keeps main's ratchet lane robust across platforms rather than the tight 12776 (30 buffer). Per-PR full `go test ./...` + independent verification remain the primary regression guard; the ratchet FLOOR is a secondary backstop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Horodate: 2026-08-10 09:55 CEST
Advances real-filesystem
File::Stat/File/Dirsupport toward MRI 4.0.5. All behaviour cross-checked againstruby 4.0.5and throughrbgoitself. Pure-Go (CGO=0),GOWORK=off, go 1.26.4.What's implemented
File::Stataccessor surface (new)setuid?setgid?sticky?world_readable?grpowned?readable_real?writable_real?executable_real?rdevblocksdev_majordev_minorrdev_majorrdev_minorbirthtime(raisesNotImplementedError, as MRI on unsupported platforms/filesystems).statFieldsnow carriesrdev/blocks(fromsyscall.Stat_ton unix;0on windows/wasm). Access checks were refactored around a sharedpermFor, so the effective-id (accessible) and real-id (accessibleReal) predicates share one owner/group/other decision. Device numbers decompose with the glibcgnu_dev_major/minorencoding.File.*class methods (new)Predicates delegating to a following stat:
pipe?socket?blockdev?chardev?setuid?setgid?sticky?owned?grpowned?world_readable?world_writable?identical?readable_real?writable_real?executable_real?; metadataatimectimebirthtime; and filesystem opslinksymlinkreadlinktruncaterealdirpathpath— each with MRI-faithful arity/type checks and error classes (Errno::EEXIST/EINVAL/ENOENT,SystemCallError).Dirinstances (new)rbgo's
Dirhad only class methods; this adds theDirObjvalue withDir.new/Dir.openand the full instance surface:readpostell(alias ofpos)seekpos=rewindpathto_path(aliaspath/to_path)filenocloseeacheach_child. Semantics match MRI: a closed handle raisesIOErroron every read-side method (path/to_pathkeep working);filenoraisesNotImplementedError(no dirfd in rbgo);Dir.openwith a block closes the handle on exit — even on a raised exception — and returns the block value; a missing path raises aSystemCallError; blocklesseach/each_childreturn anEnumerator.Deferred (noted precisely)
File.fnmatch?+File::FNM_*flags, andDirrecursive/brace glob (**/{a,b}/ char-class + FNM flags) — the**/PATHNAME/./edge semantics are intricate; left for a focused follow-up rather than risk a partial matcher.File::Stat#inspectand a real#birthtime(platform-specific: real on darwin/BSD,statx-gated on Linux).File.lchmod(unsupported on Linux; MRI raisesNotImplementedErrorthere).Verification
go test ./...exit 0 (no regressions).cmpFloat,rangeSize,newFiber,fiberResume,ioGetsParagraph,registerIOClassMethods,registerNetHTTP,classOf,ivarTable,registerSleep) — every function added or touched is 100%, including error branches.gofmt+go vet ./internal/vm/clean.FLOORbumped12352 → 12553(measured − 30).🤖 Generated with Claude Code