fix(compiler): preserve runtime class dispatch and introspection for polymorphic objects - #110
Conversation
…polymorphic objects When an object variable has an upper-bound declared type (e.g. assigned from a function or method return), SSA marks it as a stable object. However, stableObjects does not guarantee the concrete runtime class: the instance may be a subclass at runtime, or null if nullable. 1. FuncCallOptimizer::genGetClassOptimized checked isStableObject($obj->name) and folded get_class($obj) to the declared type literal string. For $a = getAnimal() returning a Dog, get_class($a) was incorrectly folded to "Animal" at compile time. 2. MethodCallTrait::parseStaticCall checked isStableObject($class) and jumped to _do_call with the declared base class. For $a::who(), this statically invoked Base::who() instead of dispatching to Dog::who() on the runtime object. Require proven exact object instances (exactObjects, populated from direct new ConcreteClass() definitions) before folding get_class() or devirtualizing static calls on object variables. Non-exact variables fall back to runtime class resolution (php::fn::get_class() and php::callStaticMethod()), matching PHP semantics. Add unit test coverage verifying exact objects continue to fold/devirtualize while polymorphic variables retain dynamic runtime dispatch.
e1e645c to
2a8f442
Compare
matyhtf
left a comment
There was a problem hiding this comment.
Thanks for addressing the incorrect devirtualization. Public polymorphic get_class() and $object::method() dispatch work in the tested cases, but two runtime issues still need to be fixed.
- The new dynamic
$object::method()path loses the lexical class scope. A protected static call that is valid in PHP is treated as coming from global scope:
class Base {
protected static function identify(): string {
return static::class;
}
public static function exercise(): string {
$object = makeChild(); // declared Base, returns Child
return $object::identify();
}
}PHP returns the child class name, while the compiled program throws:
Call to protected method Base::identify() from global scope
The runtime dispatch must preserve both the runtime target class and the lexical callable scope. Emitting an unscoped php::callStaticMethod() is insufficient here.
- The PR description says nullable values now produce PHP's
TypeError, butget_class()on a nullable object still segfaults. The non-exact path emitsphp::fn::get_class(value), whose current fast path dereferencesobj.ce()without validating null. For example, a?Basereturn containing null causes SIGSEGV instead of a catchableTypeError. Please use a checked/runtime path for values that may be null, or make the helper enforce PHP's argument semantics.
The added PHPUnit test only checks generated code text, so neither runtime failure is covered. Please add behavioral PHPT coverage for a protected polymorphic static call and nullable get_class(), in addition to the public dispatch cases.
…time type checks for get_class - Use php::callScoped with getCallableScopeExpr() for dynamic static calls inside class methods to preserve caller visibility on protected methods - Fall back to runtime php::call for get_class on non-exact values so nullable objects throw TypeError instead of dereferencing null - Add behavioral PHPT tests for protected polymorphic static calls and nullable get_class - Update unit tests for scoped dispatch and runtime introspection
|
Updated. Dynamic static calls inside methods now use Details on the changes:
|
In TypePHP AOT compilation, function main(): void serves as the binary entrypoint. Top-level function calls outside a function are rejected by the preprocessor as stray code.
When an object comes from a function or method return (like
$a = getAnimal()), SSA marks it instableObjects. But that only tracks the upper-bound declared type, not the actual runtime subclass:get_class($a)was getting folded at compile time to the base class instead of returning the runtime subclass (and returned a string onnullinstead of throwingTypeError)$a::who()statically resolved to the base class, bypassing subclass overridesSwapped
isStableObjectwithisset($this->context->exactObjects[...])ingenGetClassOptimizedandparseStaticCall. If the class isn't proven exact vianew ConcreteClass(), it safely falls back to runtime dispatch (php::fn::get_classandphp::callStaticMethod).Added unit tests for both exact and polymorphic cases.