Repository navigation
Pathname.new converts with File.path, not to_s, so a non-String is refused (fixes #93) - #156
Open
jiro-by-nori[bot] wants to merge 1 commit into
Open
jiro-by-nori[bot] wants to merge 1 commit into
jiro-by-nori[bot] wants to merge 1 commit into
Conversation
… is refused (issue #93) The prelude's `Pathname#initialize` assigned `path.to_s`, but CRuby's is `@path = File.path(path).dup`: a String is a path already, anything else converts through `#to_path` and then `#to_str`, a result that is still not a String raises `TypeError: Pathname.new requires a String, #to_path or #to_str`, and a path holding a null byte raises `ArgumentError: path name contains null byte`. Three wrong answers followed from the `to_s` coercion: `Pathname.new(123)` answered `#<Pathname:123>` where CRuby raises, an object with both `#to_path` and a different `#to_s` got the wrong path, and mutating the argument afterwards changed the Pathname CRuby would have copied. The constructor is now that conversion, with the `dup` and the null-byte check in CRuby's order. Which conversions exist is `rb_check_funcall`'s question, not `respond_to?`'s, so it goes through the prelude's `__conv_callable?`: a `#to_path` that only `method_missing` serves is called, a custom `respond_to_missing?` that denies the name is believed, and a `#to_path` that answers nil does not fall back to `#to_str`. `difftest/corpus/regressions/ pathname-new-requires-a-path.rb` pins all of it; every line of it disagrees with CRuby against the pre-change prelude. Validated: `make conformance` holds the baseline exactly (1287 ran, 1087 agree, 194 declined, 0 disagree), the regressions and tier3 tiers agree with the new case present, `make books` and `make metatheory` build with the booted heap regenerated, and `make gen-check` reports the generated sources fresh. Co-authored-by: Jiro <jiro@nori.ai>
jiro-by-nori
Bot
force-pushed
the
pathname-new-uses-file-path-conversion
branch
from
November 15, 2026 03:58
ec38cdc to
9112e2d
Compare
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.
Authored by Jiro, an autonomous coding agent, on behalf of @sam-xif — the code and this description are machine-written.
Fixes #93.
What changed
Pathname#initializein the prelude assignedpath.to_s. CRuby's constructor is@path = File.path(path).dup, so:String(subclasses included) is the path, unchanged;#to_path, then#to_str— never#to_s;rb_check_funcall's question, notrespond_to?'s, so the constructor asks the prelude's__conv_callable?(the same predicateString.try_convertuses): a#to_paththat onlymethod_missingserves is called, a customrespond_to_missing?that denies the name is believed, and a#to_paththat answers nil does not fall back to#to_str;String:TypeError: Pathname.new requires a String, #to_path or #to_str;#to_path:ArgumentError: path name contains null byte;duped, so mutating the argument afterwards leaves thePathnamealone.Three wrong answers follow from the
to_scoercion, all fixed here:Pathname.new(123)/nil/:sym/[1]/Object.newanswered aPathnamewhere CRuby raisesTypeError(a type-error-family exception the validator claims to exclude, so a wrong answer rather than a gate); an object with both#to_pathand a different#to_sgot the wrong path; and a mutated argument changed the path.ruby-lean/RubyCore/Generated/Prelude.leanandPreludeJson.leanare themake genoutput of that prelude edit.Test plan
New regression case
difftest/corpus/regressions/pathname-new-requires-a-path.rb(plus afixedsidecar) covering, against CRuby 4.0.5: the six rejection shapes (Integer,nil,true,Symbol,Array,Object.new),Kernel#Pathnameon a non-String,#to_pathpreferred over#to_str, a bad and a nil#to_pathnot falling back, the threemethod_missing/respond_to_missing?shapes, both null-byte paths, aPathnameand aStringsubclass as controls, and thedup.bin/ruby-lean --compare difftest/corpus/regressions/pathname-new-requires-a-path.rbAGREE— identical stdout, no exceptioninteger => #<Pathname:123>vs CRuby'sTypeError,to_path wins => "/from-to-s"vs"/from-to-path",argument mutated after => "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/before/after"vs"/before"), so the case pins the defect instead of passing triviallymake conformance+scripts/check-conformance.shCONFORMANCE OK, the recorded baseline held exactlydifftest run --tier regressions --sut leansorbet-hash-gate), 0 failures; the new case reconciles ashelddifftest replay corpus/tier3 --sut leanmake booksBuild completed successfully(the booted heap is regenerated from the new prelude)make metatheoryOK— theorems rest onpropext/Classical.choice/Quot.soundonly, and the booted heap satisfies the assumptionsmake gen-checkfreshscripts/check-not-weakened.pyCHECKS NOT WEAKENEDNot run locally, with the reason:
make lintand the Bundler-backed suites (desugar-unit,emitter-unit,difftest-test,soundness,book-checks). The machine this was written on has no C compiler, sobundle installcannot build the pinnedjsongem andmake depsfails before those targets start. The change adds no linted file —ruby-lean/prelude/anddifftest/corpus/are excluded from RuboCop — andlintpasses in CI. Thesoundnessandcomparatorhalves are covered by CI's proofs job.Related, deliberately not in this PR
Pathname#empty?answersString#empty?; a separate wrong answer, tracked in Pathname#empty? answers String#empty? instead of asking the filesystem, so it is a wrong answer #91.Filepath helpers still coerce withto_s(File path helpers coerce non-String paths with to_s instead of refusing, so the model answers where CRuby raises TypeError #57, PRsFilepath helpers convert arguments the way CRuby does, and raiseTypeErrorwhere CRuby does (fixes #57) #148/File path helpers runrb_get_path, notto_s, so a non-String path is refused (fixes #57) #153).