Repository navigation
Conversation
The several pure path helpers modelled in the prelude returned wrong answers
for separator and dotfile edge cases, which is a silent disagreement with CRuby
rather than a refusal (AGENTS.md rule 3).
- File.dirname returned the text before the last '/' verbatim, so trailing and
repeated separators leaked into the result: dirname("/foo/") was "/foo"
(CRuby: "/") and dirname("foo//bar") was "foo/" (CRuby: "foo"). It now
mirrors MRI's rb_file_dirname_n for one level: trailing separators do not name
a component, and the leading root is preserved ("//foo" -> "//").
- File.extname scanned to index 0 without skipping the leading run of dots, so
extname("..") was "." (CRuby: ""). It now skips the dot run first.
- File.join inserted a bare '/', producing "a///b" for join("a/", "/b").
It now mirrors MRI's rb_file_join: collapse the inserted separator with a
leading '/' of the next part, and drop trailing '/' from the accumulator when
the next part is absolute, while preserving interior separators.
Each helper is verified byte-for-byte against CRuby 4.0.5 over an exhaustive
corpus (every dirname/extname/basename case above, plus all 2- and 3-part
combinations of a separator alphabet). A new difftest regression case,
difftest/corpus/regressions/file-path-ops.rb, guards them.
This was referenced Nov 7, 2026
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.
Summary
The pure
Filepath helpers modelled in the prelude (dirname/extname/join)returned wrong answers for separator and dotfile edge cases. A wrong answer
is the worst kind of defect here — a silent disagreement with CRuby rather than a
named
Unsupported— whichAGENTS.mdrule 3 ("The model declines; it does notapproximate") explicitly forbids. These helpers are the pure path operations that
issue #7 leaves in the prelude as Ruby string functions, and this restores their
CRuby fidelity.
Fixes, each derived from MRI's C source and verified byte-for-byte against
CRuby 4.0.5:
File.dirname("/foo/")"/foo""/""/"File.dirname("foo//bar")"foo/""foo""foo"File.dirname("foo//bar//baz")"foo//bar/""foo//bar""foo//bar"File.extname("..")"."""""File.join("a/", "/b")"a///b""a/b""a/b"File.join("a//", "/a")"a////a""a/a""a/a"File.join("a//", "a")"a///a""a//a""a//a"What changed
ruby-lean/prelude/prelude.rb(regenerated intoRubyCore/Generated/Prelude*.lean):File.dirnamenow mirrors MRI's non-DOSrb_file_dirname_nfor one level.The root of the bug was treating "text before the last
/" as the answer, whichkeeps trailing and repeated separators. It now uses
rb_enc_path_last_separatorsemantics — a trailing run of
/does not name a component — sodirname("/foo/")is"/"anddirname("foo//bar")is"foo". The leadingroot is preserved (
"//foo"keeps"//"), and"."is returned when the onlyseparator is the path's own start.
File.extnamenow skips a leading run of dots before finding the extension(MRI's
ruby_enc_find_extname), so.bashrc/./../...have no extensionwhile
".a.b"is".b".File.joinnow mirrors MRI'srb_file_join: it collapses the separator itinserts with a leading
/of the next part and drops trailing/from theaccumulator when the next part is absolute, so
join("a/", "/b")is"a/b".Interior separators are preserved (
join("a//", "a")is"a//a").Test plan
Validated locally with the full toolchain (Lean
v4.32.2, CRuby 4.0.5, thepinned Sorbet/uv environment), and by the repository's own checks:
dirname/extname/basename(with and without a suffix) edge case above plusevery 2-part and 3-part combination of the separator alphabet
["", "a", "/a", "a/", "/a/", "//a", "a//", "/", "//"], and ranbin/ruby-lean --compare. All casesAGREE. The same corpus run against theunmodified prelude
DIFFERs on 20+ lines, so the corpus distinguishes the fixfrom the defect.
difftest/corpus/regressions/file-path-ops.rb(+sidecar,
status: fixed) pins the fixed cases. Confirmed it reportsoutcome: regressedagainst the old prelude andheldagainst this one, andthat
uv run difftest run --tier regressions --sut leanis green.make check→make check: ALL CHECKS PASSED, including:make books— the model and every proof book build; the FastPowerCRuby == model == theoremcheck runs 72/72.make conformance— model vs CRuby overbootstraptest: 1287 ran, 0disagreement, baseline held.
make difftest-test— 147 passed.make gen-check— all four generated sources fresh.make docs— strict build clean.Follow-up
Non-String path arguments are still coerced with
to_s(soFile.dirname(123)answers
"."where CRuby raisesTypeError). Tracked separately in #57 — out ofscope here, which is strictly the separator/dotfile wrong answers.
Part of #7 (the pure path helpers it keeps in the prelude).
Authored by Jiro, an autonomous agent. I read the repository's
AGENTS.md,set up the full Lean + CRuby toolchain, derived the corrected semantics from
MRI's
file.c(rb_file_join,rb_file_dirname_n,rb_enc_path_last_separator)and
ruby_enc_find_extname, and verified every change against CRuby 4.0.5 beforeopening this PR.