diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index d006e6bb..c92fad04 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -758,6 +758,9 @@ trait AssignOpTrait } elseif ($this->isPropertyFetch($expr->expr)) { $left = $this->parseIdentifier($expr->var); $rightExpr = $tmpVar . ' = ' . $this->emitDynamicPropertyFetchRef($expr->expr, $expr); + } elseif ($this->isStaticPropertyFetch($expr->expr)) { + $left = $this->parseIdentifier($expr->var); + $rightExpr = $tmpVar . ' = ' . $this->emitStaticPropertyFetchRef($expr->expr, $expr); } elseif ($this->isArrayDimFetch($expr->expr)) { $left = $this->parseIdentifier($expr->var); $array = $this->parseWritableIdentifier($expr->expr->var); diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index 6bb55ef9..4997c9a3 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -201,6 +201,36 @@ trait PropertyAccessTrait return $objectExpr . '.attrRef(' . $this->identifierToStr($expr->name) . ')'; } + /** + * Emit a reference (php::Ref) bound to a static property's underlying zval. + * + * `php::getStaticProperty(ce, offset)` returns a Variant that shares the + * static property's zval, so `.toReference()` yields a live reference whose + * writes propagate back to the static property. + */ + protected function emitStaticPropertyFetchRef(Expr\StaticPropertyFetch $expr, NodeAbstract $errorNode): string + { + if ($this->isIdExpr($expr->name)) { + $this->resolveNativeStaticPropertyFetch($expr); + $this->assertPropertySetVisibility($expr); + } + + $resolution = $this->resolveNativeStaticPropertyFetch($expr); + if ($resolution !== null) { + if ($resolution->class !== null) { + $classPtr = $this->getClassEntryPtr($resolution->class); + return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $resolution->expression . ').toReference()'; + } + if ($resolution->expression !== null) { + // Dynamic target, e.g. `self` resolved through the called class inside a trait. + return $resolution->expression . '.toReference()'; + } + } + + // Fully dynamic path: `static` keyword, dynamic class name, or dynamic property name. + return $this->parseDynamicStaticPropertyFetch($expr) . '.toReference()'; + } + protected function resolveNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): ?StaticPropertyFetchResolution { diff --git a/tests/compiler/static/static-prop-assign-ref-late-static-binding.phpt b/tests/compiler/static/static-prop-assign-ref-late-static-binding.phpt new file mode 100644 index 00000000..c2053e27 --- /dev/null +++ b/tests/compiler/static/static-prop-assign-ref-late-static-binding.phpt @@ -0,0 +1,34 @@ +--TEST-- +Assign by reference to late-static-bound property resolves to called class +--FILE-- + +--EXPECT-- +int(1) +int(99) +int(1) diff --git a/tests/compiler/static/static-prop-assign-ref-native.phpt b/tests/compiler/static/static-prop-assign-ref-native.phpt new file mode 100644 index 00000000..fb454e18 --- /dev/null +++ b/tests/compiler/static/static-prop-assign-ref-native.phpt @@ -0,0 +1,41 @@ +--TEST-- +Assign by reference to native typed static property (self / static / class name) +--FILE-- + +--EXPECT-- +int(123) +int(456) +int(456) +int(789) +int(789) +int(1000) diff --git a/tests/compiler/static/static-prop-assign-ref-parent.phpt b/tests/compiler/static/static-prop-assign-ref-parent.phpt new file mode 100644 index 00000000..c75c0c45 --- /dev/null +++ b/tests/compiler/static/static-prop-assign-ref-parent.phpt @@ -0,0 +1,36 @@ +--TEST-- +Assign by reference to parent static property (parent::$value) +--FILE-- + +--EXPECT-- +int(1) +int(1) +int(999) +int(999) +int(999) diff --git a/tests/compiler/static/static-prop-assign-ref.phpt b/tests/compiler/static/static-prop-assign-ref.phpt new file mode 100644 index 00000000..f31f5dba --- /dev/null +++ b/tests/compiler/static/static-prop-assign-ref.phpt @@ -0,0 +1,40 @@ +--TEST-- +Assign by reference to static property (self / static / class name) +--FILE-- + +--EXPECT-- +int(123) +int(456) +int(456) +int(789) +int(789) +int(1000)