Repository navigation
Accept separator, limit and keywords in ARGF line methods - #9784
Conversation
|
headius
left a comment
There was a problem hiding this comment.
Nice to see that this is a small change. Figures that it's functionality nobody has spec'ed and nobody has reported missing.
…numerators Without a block these methods returned an Enumerator that passed keyword arguments back as a positional Hash, so iterating it raised TypeError (foreach, each_line) or ignored them (each). IO#each also dropped its positional arguments entirely. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows checks out the spec fixtures with CRLF line endings, so these fail there like the block forms of the same specs, which are already tagged windows:. The separator-and-limit Enumerator spec for IO#each was tagged fails: until this PR and is tagged windows: for IO#each_line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ARGF.gets, readline, readlines, to_a, each and each_line counted the keyword Hash as a positional argument and only allowed one, so passing a separator and a limit, or a separator with chomp:, raised ArgumentError. ARGF#each and ARGF#to_a were not declared to take keywords, and the enumerators returned by each and each_line passed keywords back as a positional Hash, raising TypeError when iterated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When $stdin is not an IO (for example a StringIO), ARGF#each and ARGF#each_line called $stdin.each with no arguments, dropping the separator, limit and keywords. ARGF.readlines passed keywords to $stdin.readlines as a positional Hash. Both now forward the arguments and keywords, and each/each_line call each_line as MRI's argf_each_line does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows checks out the spec fixtures with CRLF line endings, so the chomp results keep a "\r" and these fail there, like the IO line specs that are already tagged windows:. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e3c2279 to
85ccd4a
Compare
Warning
Conflicts with #9785. Whichever merges second needs a rebase that keeps both sides of the
RubyArgsFile.readlinesforwarding and ofshared/gets.rb. See the rebase note for the verified resolution.Depends on #9783. This branch is stacked on
io-enumerator-keywordsand uses theRubyEnumerator.enumeratorize(context, object, method, args, keywords)overload it adds. Until #9783 merges, the diff here includes its commit. Only the last two commits are new (875ce40, 0011cfc).ARGF.gets,readline,readlines,to_a,eachandeach_linechecked their arity as 0..1 on the raw argument array, and the keyword Hash counted as one of the arguments. In CRuby they sharerb_io_getline, which takes a separator and a limit plus keywords ("02:"). So in JRuby:ARGF.gets("\n", 1)), or a separator and a limit withchomp:, raisedArgumentError, with or without a block.ARGF#eachandARGF#to_aweren't declaredkeywords = true, sochomp:was read as the limit and raisedTypeError.ARGF.each_lineandARGF.eachreturned an Enumerator that passed keywords back as a positional Hash, the same bug Pass keyword arguments to IO.foreach, IO#each and IO#each_line enumerators #9783 fixes forIO. It raisedTypeErrorwhen iterated.With
patha file containing"a\nb\n", each run asruby -e '...' path:ARGF.gets(chomp: true)"a""a""a"ARGF.gets("\n", 1)"a"ArgumentError (given 2, expected 0..1)"a"ARGF.gets("\n", 1, 2)ArgumentError (given 3, expected 0..2)ArgumentError (given 3, expected 0..1)ArgumentError (given 3, expected 0..2)ARGF.readlines(chomp: true)["a", "b"]["a", "b"]["a", "b"]ARGF.readlines("\n", 1)["a", "\n", "b", "\n"]ArgumentError["a", "\n", "b", "\n"]ARGF.to_a(chomp: true)["a", "b"]TypeError: no implicit conversion of Hash into Integer["a", "b"]ARGF.each_line(chomp: true) { |l| a << l }["a", "b"]["a", "b"]["a", "b"]ARGF.each(chomp: true) { |l| a << l }["a", "b"]TypeError["a", "b"]ARGF.each_line("\n", 1, chomp: true) { |l| a << l }["a", "", "b", ""]ArgumentError (given 3, expected 0..1)["a", "", "b", ""]ARGF.each_line(chomp: true).to_a["a", "b"]TypeError["a", "b"]ARGF.each(chomp: true).to_a["a", "b"]TypeError["a", "b"]ARGF.each("\n", 1).to_a["a", "\n", "b", "\n"]ArgumentError["a", "\n", "b", "\n"]"Before" is the same for unmodified
jruby-10.0and for #9783, since #9783 doesn't touch ARGF. "After" matches CRuby 4.1.0dev on every row.When
$stdinisn't an IOWhen ARGV is empty and
$stdinis replaced (by aStringIO, say), ARGF hands off to$stdin. There,eachandeach_linecalled$stdin.eachwith no arguments, andreadlinespassed keywords on as a positional Hash. MRI callseach_lineandreadlineswith the original arguments and keywords. Each row runs with no ARGV and$stdinset to an object that reads fromStringIO.new("a\nb\n")and records what it receives:ARGF.to_a["a\n", "b\n"]viareadlines()["a\n", "b\n"]viato_a()["a\n", "b\n"]viareadlines()ARGF.readlines(chomp: true)["a", "b"]viareadlines(chomp: true)["a", "b"]viareadlines({chomp: true})["a", "b"]viareadlines(chomp: true)ARGF.readlines("\n", 1, chomp: true)["a", "", "b", ""]viareadlines("\n", 1, chomp: true)ArgumentError (given 3, expected 0..1)["a", "", "b", ""]viareadlines("\n", 1, chomp: true)ARGF.each_line { |l| a << l }["a\n", "b\n"]viaeach_line()["a\n", "b\n"]viaeach()["a\n", "b\n"]viaeach_line()ARGF.each_line(chomp: true) { |l| a << l }["a", "b"]viaeach_line(chomp: true)["a\n", "b\n"]viaeach()(chomp ignored)["a", "b"]viaeach_line(chomp: true)ARGF.each("\n", 1) { |l| a << l }["a", "\n", "b", "\n"]viaeach_line("\n", 1)ArgumentError (given 2, expected 0..1)["a", "\n", "b", "\n"]viaeach_line("\n", 1)ARGF.each_line("\n", 1, chomp: true).to_a["a", "", "b", ""]viaeach_line("\n", 1, chomp: true)ArgumentError (given 3, expected 0..1)["a", "", "b", ""]viaeach_line("\n", 1, chomp: true)"Before" is again the same on
jruby-10.0and #9783.ARGF.getsandARGF.readlinewith a non-IO$stdinraise aClassCastExceptiononjruby-10.0. That's a separate crash, fixed in #9785.Changes
RubyArgsFile, first commit (875ce40):checkGetlineArityhelper allows 0..2 positional arguments and doesn't count the keyword Hash.gets,readlines,eachandeach_lineuse it, andreadlineandto_ago through them. The annotations are nowoptional = 2.Method#aritystays-1.eachandeach_linereadcallInfobefore building the Enumerator and pass the keywords flag to it. Their block path is now a sharedeachLineCommon.eachis nowkeywords = true. Before, it wasn't, so the generated invoker zeroedcallInfoand theeach_lineit called never saw keywords.to_acallsreadlines. In MRI they're the same C function (io.c:rb_define_method(rb_cARGF, "to_a", argf_readlines, -1)). The oldto_awas a copy ofreadlineswith two differences: it passedcallInfoas0, and for a non-IO$stdinit forwardedto_awhere MRI forwardsreadlines. The fast path inargf_getlinefor no arguments with the default$/depends only onargs.length, soARGF.to_awith no arguments still uses it.RubyArgsFile, second commit (0011cfc):$stdin,eachandeach_linecall$stdin.each_linewith the arguments and keywords, as MRI'sargf_each_linedoes. I also dropped a redundant secondnext_argvcall there, since the method has already called it.readlinesrestorescallInfobefore forwarding to$stdin.readlines. That answers the oldTODOthe same wayargf_getlinealready does forgets.ARGFSpecs::Stdinfixture that records the arguments and keywords it receives. Each shared file is used by two methods, so that's 28 runs.shared/each_line.rb(each,each_line): separator plus limit,chomp:, separator plus limit pluschomp:, both Enumerator forms, and non-IO$stdinwith and without arguments.shared/gets.rb(gets,readline):chomp:, separator plus limit, and separator pluschomp:.shared/readlines.rb(readlines,to_a):chomp:, separator plus limit, and non-IO$stdinwith and without arguments.$stdinexamples useARGF.class.newwith no arguments, which is stream mode. MRI only supports a non-IO$stdinin stream mode: with"-"in ARGV it callsGetOpenFileon$stdin.Testing
ARGF.gets(chomp: true)andARGF.readlines(chomp: true)on files.spec/ruby/core/{io,file,enumerator,argf,kernel}andlibrary/stringiowithmspec ci: the same 2 failures as unmodifiedjruby-10.0(Kernel#require ... c-extension file exists but loading fails, unrelated).Not in this PR
$stdinClassCastExceptioningetsandreadline, and the other ARGF methods with the same cast. Those are in Fix ClassCastException in ARGF.gets and readline when $stdin is not an IO #9785, which asks whether to keep them separate or fold them in here.🤖 Generated with Claude Code