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