From: "Eregon (Benoit Daloze) via ruby-core" Date: 2026-08-08T12:50:20+00:00 Subject: [ruby-core:126328] [Ruby Bug#22235] node_id/location for constant assignment failing while reading Issue #22235 has been updated by Eregon (Benoit Daloze). Status changed from Open to Rejected `NotDefined += 1` also doesn't have a Prism node for the reading part (`name_loc` covers it but that's not a node): ``` ruby -rprism -e 'pp Prism.parse(" NotDefined += 1 ").value' @ ProgramNode (location: (2,0)-(2,15)) ��������� flags: ��� ��������� locals: [] ��������� statements: @ StatementsNode (location: (2,0)-(2,15)) ��������� flags: ��� ��������� body: (length: 1) ��������� @ ConstantOperatorWriteNode (location: (2,0)-(2,15)) ��������� flags: newline ��������� name: :NotDefined ��������� name_loc: (2,0)-(2,10) = "NotDefined" ��������� binary_operator_loc: (2,11)-(2,13) = "+=" ��������� value: ��� @ IntegerNode (location: (2,14)-(2,15)) ��� ��������� flags: static_literal, decimal ��� ��������� value: 1 ��������� binary_operator: :+ ``` So in general there is no node for the reading part, and `ConstantPathOperatorWriteNode` seems like the exception there. So for consistency I now think it makes sense to use the full assignment/OperatorWriteNode when it fails, even for reading. And differentiating failing while reading or writing must be done with the exception then. It's a little bit unfortunate but I see no good solution, so closing this as rejected since the current state makes sense. Sorry for the noise. Although maybe this will help someone else realize why it is the way it is for `ConstantPathOperatorWriteNode`. ---------------------------------------- Bug #22235: node_id/location for constant assignment failing while reading https://bugs.ruby-lang.org/issues/22235#change-118446 * Author: Eregon (Benoit Daloze) * Status: Rejected * 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 '
'" # @ 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'" # @ 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: #@(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 [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}, "", "(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/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/