parent
d73291e11c
commit
d1a9397f6c
10 changed files with 344 additions and 5 deletions
@ -0,0 +1,13 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use CrossFileTrait\ClassB; |
||||||
|
use CrossFileTrait\ClassC; |
||||||
|
|
||||||
|
function callCrossFileTraitMethods(): void |
||||||
|
{ |
||||||
|
$withMagic = new ClassB(); |
||||||
|
$withMagic->getAttribute('with-magic'); |
||||||
|
|
||||||
|
$withoutMagic = new ClassC(); |
||||||
|
$withoutMagic->getAttribute('without-magic'); |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace CrossFileTrait; |
||||||
|
|
||||||
|
class ClassB |
||||||
|
{ |
||||||
|
use AttributeLookup; |
||||||
|
|
||||||
|
public function __call(string $name, array $arguments): mixed |
||||||
|
{ |
||||||
|
return 'magic:' . $name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class ClassC |
||||||
|
{ |
||||||
|
use AttributeLookup; |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace CrossFileTrait; |
||||||
|
|
||||||
|
trait AttributeLookup |
||||||
|
{ |
||||||
|
public function getAttribute(string $class = 'default'): string |
||||||
|
{ |
||||||
|
return 'attribute:' . $class; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* This file is part of TypePHP(AOT). |
||||||
|
* |
||||||
|
* @link https://www.swoole.com/aot/ |
||||||
|
* @contact service@swoole.com |
||||||
|
*/ |
||||||
|
|
||||||
|
use TypePhp\CompilerTest; |
||||||
|
|
||||||
|
/** |
||||||
|
* @internal |
||||||
|
* @coversNothing |
||||||
|
*/ |
||||||
|
final class TraitCrossFileCallTest extends BaseTest |
||||||
|
{ |
||||||
|
public function testTraitMethodsAreResolvedBeforeTheConsumingClassFileIsConverted(): void |
||||||
|
{ |
||||||
|
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH); |
||||||
|
$directory = TYPEPHP_ROOT_PATH . '/phpunit/code/trait-cross-file-call/'; |
||||||
|
$caller = $directory . 'a-caller.php'; |
||||||
|
$classes = $directory . 'z-classes.php'; |
||||||
|
$trait = $directory . 'zz-trait.php'; |
||||||
|
|
||||||
|
// The caller is deliberately prepared and converted before the class |
||||||
|
// file. Native method resolution must depend on the complete |
||||||
|
// declaration graph, not source filenames or conversion order. |
||||||
|
$files = [$caller, $classes, $trait]; |
||||||
|
$compiler->addFiles($files); |
||||||
|
foreach ($files as $file) { |
||||||
|
$compiler->prepareFile($file); |
||||||
|
} |
||||||
|
|
||||||
|
$classDef = $compiler->getClassDef('CrossFileTrait\\ClassB'); |
||||||
|
self::assertNotNull($classDef); |
||||||
|
self::assertFalse($classDef->hasMethod('getAttribute')); |
||||||
|
|
||||||
|
// Trait expansion is an explicit declaration phase after all files |
||||||
|
// have been prepared and before any body is converted. |
||||||
|
$compiler->composeTraitDeclarations($files); |
||||||
|
self::assertTrue($classDef->hasMethod('getAttribute')); |
||||||
|
$argument = $classDef->getMethod('getAttribute')->functionDef->argInfoList[0]; |
||||||
|
self::assertTrue($argument->hasDefaultValue()); |
||||||
|
self::assertSame('', $argument->default); |
||||||
|
|
||||||
|
$generated = $compiler->convertFile($caller); |
||||||
|
self::assertNotNull($generated); |
||||||
|
$code = file_get_contents($generated); |
||||||
|
self::assertIsString($code); |
||||||
|
|
||||||
|
self::assertStringContainsString( |
||||||
|
'php_crossfiletrait__classb__getattribute(', |
||||||
|
$code, |
||||||
|
); |
||||||
|
self::assertStringContainsString( |
||||||
|
'php_crossfiletrait__classc__getattribute(', |
||||||
|
$code, |
||||||
|
); |
||||||
|
self::assertStringNotContainsString( |
||||||
|
'php_crossfiletrait__classb____call(', |
||||||
|
$code, |
||||||
|
); |
||||||
|
self::assertStringNotContainsString('.call(', $code); |
||||||
|
self::assertNotSame('', $argument->default); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
--TEST-- |
||||||
|
Trait methods take precedence over __call magic fallback |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
trait AttributeLookup |
||||||
|
{ |
||||||
|
public function getAttribute(string $class): string |
||||||
|
{ |
||||||
|
return 'attribute:' . $class; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TraitMethodConsumer |
||||||
|
{ |
||||||
|
use AttributeLookup; |
||||||
|
|
||||||
|
public function __call(string $name, array $arguments): mixed |
||||||
|
{ |
||||||
|
return 'magic:' . $name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main(): void |
||||||
|
{ |
||||||
|
$object = new TraitMethodConsumer(); |
||||||
|
var_dump($object->getAttribute('SomeClass')); |
||||||
|
var_dump($object->missingMethod()); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
string(19) "attribute:SomeClass" |
||||||
|
string(19) "magic:missingMethod" |
||||||
Loading…
Reference in new issue