Issue #22302 has been updated by Eregon (Benoit Daloze). It turns out the parse.y fix is easy and sensible, let's do that: https://github.com/ruby/ruby/pull/18736 ---------------------------------------- Bug #22302: Prism and parse.y disagree on the `:line` event for a bare `nil` method body https://bugs.ruby-lang.org/issues/22302#change-118895 * Author: Eregon (Benoit Daloze) * Status: Open * ruby -v: ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux] * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ## Summary The Prism compiler and the parse.y compiler produce a different set of `:line` trace points (line events) for a bare `nil` literal that is the value-producing body of a method. Prism emits a line event on the `nil`'s own line; parse.y does not. ```ruby def m nil end ``` - With `--parser=prism`, executing `m` (or setting a `TracePoint(:line)` / coverage / a debugger breakpoint) reports a line event on **line 2**. - With `--parser=parse.y`, there is **no** line event on line 2. ## Reproduction ### Compile-time line events ```ruby def line_events(iseq) events = iseq.trace_points.select { |_, event| event == :line }.map(&:first) iseq.each_child { |child| events.concat(line_events(child)) } events.sort end p line_events(RubyVM::InstructionSequence.compile("def m\n nil\nend\n")) ``` ``` $ ruby --parser=prism repro.rb [1, 2] $ ruby --parser=parse.y repro.rb [1] ``` ### Runtime `:line` events ```ruby seen = [] TracePoint.new(:line) { |tp| seen << tp.lineno if tp.path.end_with?("body.rb") }.enable do File.write("body.rb", "def m\n nil\nend\nm\n") load "./body.rb" end p seen.sort ``` ``` $ ruby --parser=prism rt.rb [1, 2, 4] $ ruby --parser=parse.y rt.rb [1, 4] ``` Under Prism a line event fires on line 2 (the `nil`) when `m` is called; under parse.y it does not. ## Instruction sequences The method body iseq differs only in the line attributed to the `putnil`, and whether it carries the line event (`[Li]`): ``` # --parser=parse.y 0000 putnil ( 1)[Ca] 0001 leave ( 3)[Re] # --parser=prism 0000 putnil ( 2)[LiCa] 0001 leave ( 3)[Re] ``` parse.y attributes the method's implicit-`nil` `putnil` to the definition line (1) with only the `RUBY_EVENT_CALL` flag, so no line event is emitted for line 2. Prism attributes the `putnil` to the `nil`'s own line (2) and emits a line event there. ## Scope of the difference The difference is specific to a bare `nil` in method (`def`/`defs`) tail position. Other cases behave the same under both parsers: - Other literals in the same position emit a line event under both parsers (`def m; true; end`, `def m; 1; end`, `def m; :a; end`, `def m; "s"; end`). - A bare `nil` at the top level, or as the body of a block or lambda, emits a line event under both parsers. - The failing shape also occurs when the tail `nil` is reached through the branch of a conditional, e.g. a method whose body is `if cond; nil; elsif ...; end`. The behavior is consistent across Ruby versions: parse.y has never emitted the line event for this case (checked back to 2.3), and Prism has emitted it since its compiler could be selected. This is a standing difference between the two compilers rather than a recent change; it only became visible by default when Prism became the default parser. ## Note on the cause This difference stems from void-expression elimination: parse.y treats the trailing `nil` as the method's implicit `nil` return and elides it (compiling a synthetic `putnil` with no line event), whereas Prism keeps it as an explicit value expression that carries a line event. It is not ideal that whether a `:line` event is emitted depends on whether an expression is eliminated as void, but that coupling is already the established behavior in both compilers — an unused bare literal in non-tail statement position (e.g. `1` or `:a` followed by another statement) likewise loses its line event under both parsers — so this is not unique to the case here, only the one place the two happen to disagree. ## Question Which behavior is intended — should a bare `nil` in method tail position emit a `:line` event or not? Currently the two parsers disagree, which affects `TracePoint(:line)`, line coverage, and debuggers depending on which parser compiled the code. ## Environment ``` ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [x86_64-linux] ``` Also reproduced on ruby 3.4.0 with `--parser=prism` / `--parser=parse.y`. (found in https://github.com/ruby/prism/pull/4223) -- https://bugs.ruby-lang.org/