[ruby-core:126327] [Ruby Bug#22235] node_id/location for constant assignment failing while reading
Issue #22235 has been updated by Eregon (Benoit Daloze). To be complete, the other two "failing while reading" cases in the spec are: (`$` marks the beginning and end of the `source_range`) ```ruby "index operator assignments failing while reading" => <<-RUBY, value = nil $value[0] += 42$ RUBY "attribute operator assignments failing while reading" => <<-RUBY, value = nil $value.foo += 42$ RUBY ``` Those also currently return the write/assignment node, even though it fails while reading. We should probably be consistent there and return the "read node" (`value[0]`/`value.foo`). The trouble there is there isn't such a node in Prism: 1st example, no node with `(3,0)-(3,8)`: ``` $ ruby -rprism -e 'pp Prism.parse(" value = nil value[0] += 42 ").value' @ ProgramNode (location: (2,0)-(3,14)) ├── flags: ∅ ├── locals: [:value] └── statements: @ StatementsNode (location: (2,0)-(3,14)) ├── flags: ∅ └── body: (length: 2) ├── @ LocalVariableWriteNode (location: (2,0)-(2,11)) │ ├── flags: newline │ ├── name: :value │ ├── depth: 0 │ ├── name_loc: (2,0)-(2,5) = "value" │ ├── value: │ │ @ NilNode (location: (2,8)-(2,11)) │ │ └── flags: static_literal │ └── operator_loc: (2,6)-(2,7) = "=" └── @ IndexOperatorWriteNode (location: (3,0)-(3,14)) ├── flags: newline ├── receiver: │ @ LocalVariableReadNode (location: (3,0)-(3,5)) │ ├── flags: ∅ │ ├── name: :value │ └── depth: 0 ├── call_operator_loc: ∅ ├── opening_loc: (3,5)-(3,6) = "[" ├── arguments: │ @ ArgumentsNode (location: (3,6)-(3,7)) │ ├── flags: ∅ │ └── arguments: (length: 1) │ └── @ IntegerNode (location: (3,6)-(3,7)) │ ├── flags: static_literal, decimal │ └── value: 0 ├── closing_loc: (3,7)-(3,8) = "]" ├── block: ∅ ├── binary_operator: :+ ├── binary_operator_loc: (3,9)-(3,11) = "+=" └── value: @ IntegerNode (location: (3,12)-(3,14)) ├── flags: static_literal, decimal └── value: 42 ``` 2nd example, no node with `(3,0)-(3,9)`: ``` ruby -rprism -e 'pp Prism.parse(" value = nil value.foo += 42 ").value' @ ProgramNode (location: (2,0)-(3,15)) ├── flags: ∅ ├── locals: [:value] └── statements: @ StatementsNode (location: (2,0)-(3,15)) ├── flags: ∅ └── body: (length: 2) ├── @ LocalVariableWriteNode (location: (2,0)-(2,11)) │ ├── flags: newline │ ├── name: :value │ ├── depth: 0 │ ├── name_loc: (2,0)-(2,5) = "value" │ ├── value: │ │ @ NilNode (location: (2,8)-(2,11)) │ │ └── flags: static_literal │ └── operator_loc: (2,6)-(2,7) = "=" └── @ CallOperatorWriteNode (location: (3,0)-(3,15)) ├── flags: newline ├── receiver: │ @ LocalVariableReadNode (location: (3,0)-(3,5)) │ ├── flags: ∅ │ ├── name: :value │ └── depth: 0 ├── call_operator_loc: (3,5)-(3,6) = "." ├── message_loc: (3,6)-(3,9) = "foo" ├── read_name: :foo ├── write_name: :foo= ├── binary_operator: :+ ├── binary_operator_loc: (3,10)-(3,12) = "+=" └── value: @ IntegerNode (location: (3,13)-(3,15)) ├── flags: static_literal, decimal └── value: 42 ``` ---------------------------------------- Bug #22235: node_id/location for constant assignment failing while reading https://bugs.ruby-lang.org/issues/22235#change-118445 * Author: Eregon (Benoit Daloze) * Status: Open * Backport: 3.3: UNKNOWN, 3.4: UNKNOWN, 4.0: UNKNOWN ---------------------------------------- ```ruby namespace = Module.new begin namespace::NotDefined += 1 rescue => e pp e.backtrace_locations[0] pp e.backtrace_locations[0].source_range pp e.backtrace_locations[0].syntax_tree end ``` On CRuby master it gives: ``` $ ruby -v const_assignment_fail_while_reading.rb ruby 4.1.0dev (2026-08-08T08:04:35Z master e56f452e46) +PRISM [x86_64-linux] "const_assignment_fail_while_reading.rb:3:in '<main>'" #<Ruby::SourceRange /.../const_assignment_fail_while_reading.rb:(3,2)-(3,28)> @ ConstantPathOperatorWriteNode (location: (3,2)-(3,28)) ├── flags: newline ├── target: │ @ ConstantPathNode (location: (3,2)-(3,23)) │ ├── flags: ∅ │ ├── parent: │ │ @ LocalVariableReadNode (location: (3,2)-(3,11)) │ │ ├── flags: ∅ │ │ ├── name: :namespace │ │ └── depth: 0 │ ├── name: :NotDefined │ ├── delimiter_loc: (3,11)-(3,13) = "::" │ └── name_loc: (3,13)-(3,23) = "NotDefined" ├── binary_operator_loc: (3,24)-(3,26) = "+=" ├── value: │ @ IntegerNode (location: (3,27)-(3,28)) │ ├── flags: static_literal, decimal │ └── value: 1 └── binary_operator: :+ ``` So even though it's the read failing, it returns the `ConstantPathOperatorWriteNode` and the section is `namespace::NotDefined += 1`. I'm implementing `source_range` & `syntax_tree` on TruffleRuby and there the result is: ``` "const_assignment_fail_while_reading.rb:3:in 'Module#const_missing'" #<Ruby::SourceRange /.../const_assignment_fail_while_reading.rb:(3,2)-(3,23)> @ ConstantPathNode (location: (3,2)-(3,23)) ... ``` So this returns the `ConstantPathNode` and the section is `namespace::NotDefined`. I think TruffleRuby is correct here, especially if we want to differentiate failing while reading or writing. On CRuby it is currently impossible to differentiate because the same node is returned in both cases. (UPDATE: it's possible to differentiate via the exception, but that seems quite brittle. The point is `syntax_tree`&`source_range` should point to the relevant reading node) More information: ``` irb(main):001> namespace = Module.new irb(main):002> namespace::NotDefined += 1 irb(main):003> e=_ irb(main):005> l=e.backtrace_locations[0] irb(main):008> puts RubyVM::InstructionSequence.of(l).disasm == disasm: #<ISeq:<compiled>@(irb):2 (2,0)-(2,26)> 0000 getlocal_WC_1 namespace@0 ( 2)[Li] 0002 dup 0003 putobject true 0005 getconstant :NotDefined 0007 putobject_INT2FIX_1_ 0008 opt_plus <calldata!mid:+, argc:1, FCALL|ARGS_SIMPLE>[CcCr] 0010 swap 0011 topn 1 0013 swap 0014 setconstant :NotDefined 0016 leave irb(main):011> RubyVM::InstructionSequence.of(l).to_a => ["YARVInstructionSequence/SimpleDataFormat", 4, 1, 1, {arg_size: 0, local_size: 0, stack_max: 3, node_id: 6, source_hash: 614720579064667961, code_location: [2, 0, 2, 26], node_ids: [2, 5, 5, 5, 4, 5, 5, 5, 5, 5, 6], parser: :prism}, "<compiled>", "(irb)", nil, 2, :eval, [], {}, [], [2, :RUBY_EVENT_LINE, [:getlocal_WC_1, 3], [:dup], [:putobject, true], [:getconstant, :NotDefined], [:putobject_INT2FIX_1_], [:opt_plus, {mid: :+, flag: 20, orig_argc: 1}], [:swap], [:topn, 1], [:swap], [:setconstant, :NotDefined], [:leave]]] irb(main):013> l.syntax_tree => @ ConstantPathOperatorWriteNode (location: (1,0)-(1,26)) ... ``` The failing instruction must be `getconstant` (BTW, do we have any API to get the insn/insn index for an exception?). That's given `node_id` 5 which is the `ConstantPathOperatorWriteNode`. OK to fix the `node_id` of `getconstant` in such a case to point to the `ConstantPathNode`? -- https://bugs.ruby-lang.org/
participants (1)
-
Eregon (Benoit Daloze)