Skip to content

Commit 37b4148

Browse files
MagicalTuxclaude
andcommitted
vm: restore $obj->$var dollar-prefix AST-delegation (fix 7742c3b)
7742c3b incorrectly removed the `name[0] == '$'` branches in emitObjectVarRead and emitObjectFuncCall, claiming they were dead code. They are NOT dead — the parser distinguishes two dynamic-property forms: $obj->{$x} parses to runObjectDynVar / runObjectDynFunc (curly braces) $obj->$x parses to runObjectVar / runObjectFunc with the literal dollar-prefixed token ("$x") as the name The first form is intercepted by IsObjectDynVarReadNode / IsObjectDynFuncNode earlier in the pipeline. The second form reaches emitObjectVarRead / emitObjectFuncCall with a name like "$x" that needs runtime variable lookup. Restore both AST-delegation branches with the correct rationale in the comments. Fixes regressions: access_modifiers/access_modifiers_011.phpt assign_obj_op_cache_slot.phpt Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 9fbccbf commit 37b4148

1 file changed

Lines changed: 38 additions & 7 deletions

File tree

core/vm/vmcompiler/emit_object.go

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,22 @@ func (e *emitter) emitObjectVarAssign(lhs objectVarNode, rhs phpv.Runnable, stmt
104104

105105
func (e *emitter) emitObjectVarRead(n objectVarNode) error {
106106
name := n.ObjectVarName()
107-
// Dynamic-name property reads ($obj->{$x}) parse to runObjectDynVar,
108-
// which IsObjectDynVarReadNode intercepts before this function is
109-
// reached. objectVarNode is only ever *runObjectVar, whose varName
110-
// is always a literal identifier — no $-prefix fallback needed.
107+
// `$obj->$x` (no curly braces) parses to runObjectVar with the
108+
// literal dollar-prefixed token as the name (e.g. "$x"). That form
109+
// needs runtime variable lookup, which the simple OP_OBJECT_GET
110+
// path doesn't do — AST-delegate via OpClassConst. Curly-brace
111+
// form `$obj->{$x}` parses to runObjectDynVar and is intercepted
112+
// by IsObjectDynVarReadNode earlier.
113+
if len(name) > 0 && name[0] == '$' {
114+
raw, ok := n.(phpv.Runnable)
115+
if !ok {
116+
return unsupportedf("property-read AST delegation: cannot retrieve raw Runnable")
117+
}
118+
idx := e.astIndex(raw)
119+
e.emit(vm.OpClassConst, idx, 0, 0)
120+
e.pushStack(1)
121+
return nil
122+
}
111123
if err := e.withSubexpr(func() error { return e.emitExpr(n.ObjectVarReceiver()) }); err != nil {
112124
return err
113125
}
@@ -146,9 +158,28 @@ func (e *emitter) emitObjectFuncCall(n objectFuncNode) error {
146158
}
147159
name := n.ObjectFuncName()
148160
args := n.ObjectFuncArgs()
149-
// Dynamic-name calls ($obj->{$x}(...)) parse to runObjectDynFunc and
150-
// are intercepted by IsObjectDynFuncNode before this function runs.
151-
// objectFuncNode is only ever *runObjectFunc with a literal name.
161+
// `$obj->$x(...)` (no curly braces) parses to runObjectFunc with
162+
// the literal dollar-prefixed token as the method name. That form
163+
// needs runtime variable lookup, which the native call path doesn't
164+
// do — AST-delegate via OpClassConst / OpTryFinally. Curly-brace
165+
// form `$obj->{$x}(...)` parses to runObjectDynFunc and is
166+
// intercepted by IsObjectDynFuncNode earlier.
167+
if len(name) > 0 && name[0] == '$' {
168+
raw, ok := n.(phpv.Runnable)
169+
if !ok {
170+
return unsupportedf("method-call AST delegation: cannot retrieve raw Runnable")
171+
}
172+
stmtCtx := e.stmtCtx
173+
idx := e.astIndex(raw)
174+
if stmtCtx {
175+
e.emit(vm.OpTryFinally, idx, 0, 0)
176+
} else {
177+
e.emit(vm.OpClassConst, idx, 0, 0)
178+
e.pushStack(1)
179+
}
180+
e.emit(vm.OpRefreshSlots, 0, 0, 0)
181+
return nil
182+
}
152183
// Special-args / writable-arg calls route through the by-exprs
153184
// opcode so ctx.Call sees raw arg expressions and binds by-ref
154185
// params correctly. Nullsafe is encoded via the C flag.

0 commit comments

Comments
 (0)