parent
34e229e30b
commit
35f6110842
6 changed files with 239 additions and 0 deletions
@ -0,0 +1,29 @@ |
||||
<?php |
||||
|
||||
class ExactMagicHandler |
||||
{ |
||||
public function __call(string $name, array $arguments): mixed |
||||
{ |
||||
return [$name, $arguments]; |
||||
} |
||||
} |
||||
|
||||
class RuntimeMagicMethod extends ExactMagicHandler |
||||
{ |
||||
public function missing(): string |
||||
{ |
||||
return 'real'; |
||||
} |
||||
} |
||||
|
||||
function exactMagicCall(): mixed |
||||
{ |
||||
$handler = new ExactMagicHandler(); |
||||
return $handler->missing(1, named: 2); |
||||
} |
||||
|
||||
function runtimeMagicCall(ExactMagicHandler $handler): mixed |
||||
{ |
||||
return $handler->missing(); |
||||
} |
||||
|
||||
@ -0,0 +1,42 @@ |
||||
<?php |
||||
|
||||
use TypePhp\CompilerTest; |
||||
|
||||
final class MagicCallCodegenTest extends \BaseTest |
||||
{ |
||||
public function testOnlyExactReceiverBypassesZendMagicCallTrampoline(): void |
||||
{ |
||||
$code = $this->compileFixture(); |
||||
|
||||
$exactBody = $this->functionBody($code, 'php_exactmagiccall'); |
||||
self::assertStringContainsString('php_exactmagichandler____call(', $exactBody); |
||||
self::assertStringNotContainsString('.call(', $exactBody); |
||||
|
||||
$runtimeBody = $this->functionBody($code, 'php_runtimemagiccall'); |
||||
self::assertStringContainsString('.call(', $runtimeBody); |
||||
self::assertStringNotContainsString('php_exactmagichandler____call(', $runtimeBody); |
||||
} |
||||
|
||||
private function compileFixture(): string |
||||
{ |
||||
global $translator; |
||||
|
||||
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); |
||||
$translator = $compiler; |
||||
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/magic-call-codegen.php'; |
||||
$compiler->addFiles([$source]); |
||||
$compiler->prepareFile($source); |
||||
$generated = $compiler->convertFile($source); |
||||
$code = file_get_contents($generated); |
||||
|
||||
self::assertIsString($code); |
||||
return $code; |
||||
} |
||||
|
||||
private function functionBody(string $code, string $function): string |
||||
{ |
||||
$matched = preg_match('/php::Var ' . preg_quote($function, '/') . '\\([^)]*\\) \\{(?<body>.*?)\\n\\}/s', $code, $match); |
||||
self::assertSame(1, $matched, "generated body of {$function}() not found"); |
||||
return $match['body']; |
||||
} |
||||
} |
||||
@ -0,0 +1,69 @@ |
||||
--TEST-- |
||||
compiled __call fast path preserves arguments and dynamic subclass dispatch |
||||
--FILE-- |
||||
<?php |
||||
class DirectMagic |
||||
{ |
||||
public function __call(string $name, array $arguments): mixed |
||||
{ |
||||
return [$name, $arguments]; |
||||
} |
||||
} |
||||
|
||||
class InheritedMagic extends DirectMagic |
||||
{ |
||||
} |
||||
|
||||
class RuntimeMethod extends DirectMagic |
||||
{ |
||||
public function existing(): string |
||||
{ |
||||
return 'runtime-method'; |
||||
} |
||||
} |
||||
|
||||
function callFromDeclaredBase(DirectMagic $object): mixed |
||||
{ |
||||
return $object->existing(); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$direct = new DirectMagic(); |
||||
var_dump($direct->missing(1, second: 2)); |
||||
|
||||
$inherited = new InheritedMagic(); |
||||
$values = [3, 4]; |
||||
var_dump($inherited->packed(...$values, tail: 5)); |
||||
|
||||
// A declared base type is not an exact runtime type. The compiler must |
||||
// retain Zend dispatch so a subclass's real method wins over __call(). |
||||
var_dump(callFromDeclaredBase(new RuntimeMethod())); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(2) { |
||||
[0]=> |
||||
string(7) "missing" |
||||
[1]=> |
||||
array(2) { |
||||
[0]=> |
||||
int(1) |
||||
["second"]=> |
||||
int(2) |
||||
} |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
string(6) "packed" |
||||
[1]=> |
||||
array(3) { |
||||
[0]=> |
||||
int(3) |
||||
[1]=> |
||||
int(4) |
||||
["tail"]=> |
||||
int(5) |
||||
} |
||||
} |
||||
string(14) "runtime-method" |
||||
Loading…
Reference in new issue