Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions phpunit/code/polymorphic-dispatch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

class BaseAnimal
{
public static function identify(): string
{
return 'base';
}
}

class DogAnimal extends BaseAnimal
{
public static function identify(): string
{
return 'dog';
}
}

function makeAnimal(): BaseAnimal
{
return new DogAnimal();
}

function getExactClass(): string
{
$dog = new DogAnimal();
return get_class($dog);
}

function getPolymorphicClass(): string
{
$animal = makeAnimal();
return get_class($animal);
}

function callExactStatic(): string
{
$dog = new DogAnimal();
return $dog::identify();
}

function callPolymorphicStatic(): string
{
$animal = makeAnimal();
return $animal::identify();
}
44 changes: 44 additions & 0 deletions phpunit/src/PolymorphicClassDispatchTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

use TypePhp\CompilerTest;

final class PolymorphicClassDispatchTest extends BaseTest
{
public function testPolymorphicClassIntrospectionAndStaticDispatch(): void
{
global $translator;

$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/polymorphic-dispatch.php';
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
$code = file_get_contents($compiler->convertFile($source));

self::assertIsString($code);

// Exact new object definition get_class() folds to compile-time literal string
self::assertMatchesRegularExpression(
'/php_getexactclass\(\) \{.*?tmp_var_\d+ = \(get_str\(\d+\)\);/s',
$code,
);

// Polymorphic get_class() must NOT fold to BaseAnimal; it must invoke runtime get_class
self::assertMatchesRegularExpression(
'/php_getpolymorphicclass\(\) \{.*?tmp_var_\d+ = \(php::fn::get_class\(animal\)\);/s',
$code,
);

// Exact new object definition static call devirtualizes to cached call
self::assertMatchesRegularExpression(
'/php_callexactstatic\(\) \{.*?typephp_call_cached\(get_str\(\d+\)/s',
$code,
);

// Polymorphic static call must NOT statically bind to BaseAnimal; it must dispatch dynamically
self::assertMatchesRegularExpression(
'/php_callpolymorphicstatic\(\) \{.*?php::callStaticMethod\([^)]+\)/s',
$code,
);
}
}
4 changes: 2 additions & 2 deletions src/Optimizer/FuncCallOptimizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -1118,8 +1118,8 @@ protected function genGetClassOptimized(string $n, Node\Expr\FuncCall $e, array
'Native classes do not support runtime class introspection; use `NativeClass::class`',
);
}
if ($this->isVarExpr($obj) && $this->isStableObject($obj->name)) {
return $this->getLiteralString($this->getObjectType($obj->name));
if ($this->isVarExpr($obj) && isset($this->context->exactObjects[$obj->name])) {
return $this->getLiteralString($this->context->exactObjects[$obj->name]);
}
return 'php::fn::get_class(' . $this->parseIdentifier($obj) . ')';
}
Expand Down
4 changes: 2 additions & 2 deletions src/Parser/MethodCallTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -1103,10 +1103,10 @@ protected function parseStaticCall(Expr\StaticCall $expr): string

if (!$this->isNameExpr($expr->class)) {
if ($this->isVarExpr($expr->class)
&& $this->isStableObject($class)
&& isset($this->context->exactObjects[$class])
&& $this->isIdExpr($expr->name)
) {
$class = $this->getObjectType($class);
$class = $this->context->exactObjects[$class];
goto _do_call;
}
$classTarget = $this->materializeDynamicStaticCallTarget($expr->class);
Expand Down
Loading