Skip to content

fix(compiler): write inherited promoted properties into their declare… - #168

Merged
matyhtf merged 2 commits into
swoole:masterfrom
alwaysLinger:fix/promotion-inherited-slot-write
Oct 14, 2026
Merged

matyhtf merged 2 commits into
swoole:masterfrom
alwaysLinger:fix/promotion-inherited-slot-write

Conversation

@alwaysLinger

Copy link
Copy Markdown
Contributor

Observed failure

When a child class inherits from a parent whose constructor uses promoted properties, constructing the child silently misplaces the constructor argument: the declared private slot stays empty and the value is stored in a dynamically created property instead. ZendPHP stores the value in the declared private slot.

<?php
class ParentCls
{
    public function __construct(private string $name = "default") {}
}

class ChildCls extends ParentCls {}

function main(): void
{
    $c = new ChildCls("hello");
    var_dump($c);
}

ZendPHP 8.4:

object(ChildCls)#1 (1) {
  ["name":"ParentCls":private]=>
  string(5) "hello"
}

TypePHP master (a31a059):

object(ChildCls)#1 (2) {
  ["name":"ParentCls":private]=>
  string(0) ""
  ["name"]=>
  string(5) "hello"
}

The declared slot keeps its zero value, the constructor argument is silently moved into a dynamic property, and the object grows a property that does not exist in the class declaration — with no diagnostic.

Root cause

genPropertyPromotion() (src/Generator/PropertyPromotion.php) emitted the promoted assignment as a bare name-based write:

this_.setProperty("name", name);

Object::setProperty(const char*, …) resolves the property against the object's class and the current scope. For an inherited private promoted property the declared slot belongs to ParentCls and is only visible with ParentCls scope, so the lookup misses the slot and the write degrades to a dynamic property creation.

Every other property write path in the compiler already handles this with a scope-aware helper: emitDynamicPropertyWrite() (src/Parser/PropertyAccessTrait.php) emits typephp_write_property_scoped(object, name, value, scope) passing the declaring class entry.

Fix

genPropertyPromotion() now emits the same scoped write used by ordinary property assignments, passing the declaring class entry:

typephp_write_property_scoped(this_, "name", name,
    get_persistent_class(PersistentClassId{1}, get_str(7)));

For a top-level function context (no class scope, used by the FiberGenerator promotion path) the scope falls back to nullptr, preserving the previous behavior there. Native-class promotion is untouched (it writes C++ fields directly).

@matyhtf
matyhtf merged commit d277430 into swoole:master Oct 14, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants