Bug #22235
Updated by Eregon (Benoit Daloze) 2 months ago
```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.
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 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`?