Remove all remaining skips in newline_test.rb - #4223
Conversation
|
Actually it's possible to match lines from RubyVM exactly for all the files being tested, done with the 2 |
|
https://github.com/ruby/prism/actions/runs/34165671547/job/101876103431?pr=4223 fails, yet |
|
Claude found why it differs:
Wow, test-unit disabling |
* Older versions have known bugs in this area, like not emitting a :line event for `nil`, and there is no value to replicate them.
Fix two mismatches between prism's newline flags and RubyVM's line events in the Newlines visitor: * def, class, module, and singleton class nodes compile to their own ISeqs with independent line-event tracking, so reset the line table for them like blocks and lambdas already do. This matches one-line definitions like `def foo; bar; end`, where the bytecode emits two line events on the same line. The body of an endless method definition never emits newline events, so in that case mark every line as already seen instead. * Statements inside string interpolation do not emit line events, so mark every line as already seen while visiting embedded statements. Nested scopes (blocks, lambdas, defs, etc.) still reset the lines and emit events again. The remaining divergences are bytecode artifacts: for statements like `foo = [` or `foo =` where the value continues on the following lines, the line event is emitted on the line of the first sub-expression of the value instead of on the first line of the statement. Replace the two ad-hoc compensations in the test with a single count-based rule that moves or drops the newline flag accordingly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lines like `while (foo = bar)` result in two line events in the bytecode: parentheses make the inner expression a statement with its own line event, and the predicate of a while or until loop is compiled at the end of the loop, after the body, so that event is emitted again in addition to the one for the loop statement itself. This also mirrors runtime behavior, since the predicate line fires on each iteration. Match this in the Newlines visitor by marking the loop node itself when a prefix loop has a parenthesized predicate, and by visiting the predicate with a fresh set of lines so that its statements can mark lines that were already seen. This removes the corresponding compensation in newline_test.rb. The remaining compensation for assignments whose value continues on the following lines is kept: the line event is emitted on the line of the statement's first compiled instruction, which depends on constant folding (for example, an array of static literals compiles to a single instruction on the line of the literal, and string literals are only static under `# frozen_string_literal: true`). That is a property of the compiler rather than of the AST, so it does not belong in the newline flags. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Earlopain
left a comment
There was a problem hiding this comment.
Overall I'm ok with this change, nice cleanup to the skips, but I'm thinking this might overfit for the few special cases the test code actually contains. Would be fine though in my eyes, we can see how it actually goes.
…test.rb The line event for a statement is emitted where its first instruction is compiled, so make nodes whose first instruction comes from a sub-expression delegate their newline flag to that sub-expression: assignments to their value, calls to their receiver, and array, hash, and interpolated string literals to their first element. Static literals are the exception: they are compiled to a single instruction on the first line of the literal, so they do not delegate. The static literal flag captures the folding boundary exactly, including that string literals are only static under `# frozen_string_literal: true`, both in arrays and in the parts of heredocs. With this, prism's newline flags match RubyVM's line events exactly on every file in the test suite and newline_test.rb needs no compensation logic at all. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Actually, it's |
|
Right, maybe we should switch to https://github.com/ruby/test-unit-ruby-core in Prism so it's the same test harness used in CRuby CI and Prism CI? |
|
I'm not so familiar with it, but my understanding is that it just adds some extra assertions. I tested it and in the end |
Originally this was done in ruby@c23908f But it is no longer needed because ruby now emits these events regardless. Reported in https://bugs.ruby-lang.org/issues/14870 and fixed with ruby/ruby@48c8df9 In ruby/prism#4223 we ran into this because it caused a difference in tracepoint line events
Ah interesting I thought it doesn't use the So CRuby |
|
I'm looking at the last failure, it's a divergence between the parse.y and prism compilers since 3.4, in 3.3 they agreed. |
34b2ada to
9bf6cbc
Compare
…t.rb" Mirror the newlines.rb changes of that commit in MarkNewlinesVisitor: def, class, module, and singleton class nodes compile to their own ISeqs with independent line-event tracking, so reset the marked lines for them like blocks and lambdas already do. The body of an endless method definition and statements inside string interpolation never emit newline events, so mark every line as already seen while visiting them. Since location fields are not available in the Java nodes, an endless method definition is detected as a statements body which ends with the def node itself, which was verified to be equivalent to checking the location of the `=` operator on every def node in this repository. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hile/until predicates" Mirror the newlines.rb changes of that commit in MarkNewlinesVisitor: the predicate of a while/until loop is compiled at the end of the loop, after the body, so any statements it contains (from parentheses) emit their line events again even if the lines were already seen, in addition to the line event of the loop itself. Since the newline_flag! overrides in newlines.rb are no longer a mirror of the generated setNewLineFlag() methods, introduce a setNewLineFlag() helper in the visitor for the extra logic. There is no keyword location in the Java nodes, but a prefix while/until loop can be detected as one which is not a begin modifier loop and does not start with its statements, which was verified to be equivalent to checking the keyword location on every while/until node in this repository. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ll compensations in newline_test.rb" Mirror the newlines.rb changes of that commit in MarkNewlinesVisitor: the line event for a statement is emitted where its first instruction is compiled, so nodes whose first instruction comes from a sub-expression delegate their newline flag to that sub-expression: assignments to their value, calls to their receiver, and array, hash, and interpolated string literals to their first element. Static literals are the exception: they are compiled to a single instruction on the first line of the literal, so they do not delegate. The delegating newline_flag! overrides in newlines.rb dispatch to each other, so the corresponding cases in the setNewLineFlag() helper of the visitor recurse through the helper, including for the predicate of if/unless/while/until and the expression of a rescue modifier. PM_NODE_FLAG_STATIC_LITERAL is serialized together with the node-specific flags, so it is read from the flags for nodes which have them, and computed structurally for hash literals (whose nodes have no flags), following the same rules as the parser: an assoc node is a static literal if neither its key nor its value is an array, hash, or range and both are static literals themselves. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…:java_loader Add a check to the test:java_loader task (run by the build-java job in CI) that MarkNewlinesVisitor.java marks exactly the same lines as Prism::ParseResult#mark_newlines! (newlines.rb), for the newline_test.rb corpus and every fixture which parses successfully, so that both cannot get out of sync. The expected lines are generated by a new test:java_loader:newline_fixtures task, which runs in a subprocess with the default (full) serialization mode since newlines.rb needs location fields, before the existing clobber and recompile with PRISM_SERIALIZE_ONLY_SEMANTICS_FIELDS=1. The Java side runs in the existing task through Loader.load(), which runs MarkNewlinesVisitor, and the marked lines are collected from the loaded Java nodes directly in JRuby. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
afc1f7e to
2299cc9
Compare
|
This is now ready for review, summary (I also updated the PR description to the same):
|
|
For the 2 changes to logic to match CRuby:
So while I think it's debatable whether that's the best location for :line events, matching CRuby is best for compatibility and it turns out to not be too hard either. |
newline_test.rbnewline_test.rb, the logic to mark newlines matches exactly the same lines as CRuby 3.4+ (so 3.4, 4.0, master prism, master parse.y) for the files included innewline_test.rbFixes #4217