feat(preprocessor): 增强PHP预处理器功能支持接口成员和常量解析

- 实现接口方法、常量和继承列表的解析与存储
- 添加对抽象方法签名的完整解析支持
- 增加命名空间常量的解析和类型推断功能
- 实现接口实现验证和方法签名匹配检查
- 支持多重接口继承关系的解析处理
- 添加重复定义(类、接口、常量、方法)的检测机制
- 实现trait别名和修饰符的正确解析
- 增强依赖排序算法支持接口和trait依赖关系
- 完善方法重写签名验证逻辑
- 添加接口方法由trait提供的情况处理
pull/5/head
韩天峰 2 months ago
parent 1ad6b54a49
commit 3ca0d64a6a
  1. 1
      phpunit/code/compiler_api/hello_world.cc
  2. 2
      phpunit/code/compiler_api/ignored.php
  3. 2
      phpunit/code/compiler_api/kept.php
  4. 2
      phpunit/code/compiler_api/main.php
  5. 2
      phpunit/code/compiler_api/skipped_nested.php
  6. 20
      phpunit/code/interface_method_from_trait.php
  7. 11
      phpunit/code/interface_method_missing.php
  8. 15
      phpunit/code/interface_method_signature_mismatch.php
  9. 4
      phpunit/code/preprocessor/abstract_method_signature.php
  10. 4
      phpunit/code/preprocessor/class_constants.php
  11. 2
      phpunit/code/preprocessor/deps_class_implements.php
  12. 4
      phpunit/code/preprocessor/deps_class_uses_trait.php
  13. 2
      phpunit/code/preprocessor/deps_interface.php
  14. 2
      phpunit/code/preprocessor/deps_trait.php
  15. 5
      phpunit/code/preprocessor/duplicate_abstract_method.php
  16. 5
      phpunit/code/preprocessor/duplicate_class_constant.php
  17. 4
      phpunit/code/preprocessor/duplicate_namespaced_class_interface.php
  18. 7
      phpunit/code/preprocessor/interface_members.php
  19. 3
      phpunit/code/preprocessor/namespaced_const.php
  20. 10
      phpunit/code/preprocessor/trait_alias_modifier.php
  21. 15
      phpunit/src/CompilerBaseApiTest.php
  22. 19
      phpunit/src/Entity/InterfaceDefTest.php
  23. 15
      phpunit/src/InheritanceErrorTest.php
  24. 136
      phpunit/src/PreprocessorTest.php
  25. 26
      src/Php/CompilerBase.php
  26. 14
      src/Php/Entity/ClassDef.php
  27. 30
      src/Php/Entity/InterfaceDef.php
  28. 104
      src/Php/Preprocessor.php
  29. 84
      src/Php/Translator.php
  30. 0
      tests/aot/namespace/ns-const-01.phpt
  31. 28
      tests/aot/namespace/ns-const-02.phpt
  32. 24
      tests/aot/namespace/ns-const-03.phpt

@ -0,0 +1 @@
int main() { return 0; }

@ -0,0 +1,2 @@
<?php
function ignored() {}

@ -0,0 +1,2 @@
<?php
function kept() {}

@ -0,0 +1,2 @@
<?php
function main() {}

@ -0,0 +1,2 @@
<?php
function skipped() {}

@ -0,0 +1,20 @@
<?php
interface ContractTrait
{
public function run(int $id): string;
}
trait ContractTraitImpl
{
public function run(int $id): string
{
return (string) $id;
}
}
class ImplTrait implements ContractTrait
{
use ContractTraitImpl;
}
function main() {}

@ -0,0 +1,11 @@
<?php
interface ContractMissing
{
public function run(int $id): void;
}
class ImplMissing implements ContractMissing
{
}
function main() {}

@ -0,0 +1,15 @@
<?php
interface ContractMismatch
{
public function run(int $id): string;
}
class ImplMismatch implements ContractMismatch
{
public function run(string $id): string
{
return $id;
}
}
function main() {}

@ -0,0 +1,4 @@
<?php
abstract class PreprocessorAbstractSignature {
abstract public function load(int $id, ?string $name = null): self;
}

@ -0,0 +1,4 @@
<?php
class PreprocessorClassConstants {
public const TEXT = 'value', ITEMS = [];
}

@ -0,0 +1,2 @@
<?php
class SortDependencyClass implements SortDependencyInterface {}

@ -0,0 +1,4 @@
<?php
class SortDependencyTraitUser {
use SortDependencyTrait;
}

@ -0,0 +1,2 @@
<?php
interface SortDependencyInterface {}

@ -0,0 +1,2 @@
<?php
trait SortDependencyTrait {}

@ -0,0 +1,5 @@
<?php
abstract class PreprocessorDuplicateAbstractMethod {
abstract public function load(): void;
abstract public function load(): void;
}

@ -0,0 +1,5 @@
<?php
class PreprocessorDuplicateClassConstant {
public const VALUE = 1;
public const VALUE = 2;
}

@ -0,0 +1,4 @@
<?php
namespace App;
interface Demo {}
class Demo {}

@ -0,0 +1,7 @@
<?php
interface ParentA {}
interface ParentB {}
interface Demo extends ParentA, ParentB {
public const VERSION = '1.0';
public function run(int $id, ?string $name = null): ParentA|ParentB;
}

@ -0,0 +1,3 @@
<?php
namespace App;
const VERSION = 100;

@ -0,0 +1,10 @@
<?php
trait AliasModifierTrait {
public function hello() {}
}
class AliasModifierUser {
use AliasModifierTrait {
hello as private;
}
}

@ -74,6 +74,11 @@ class CompilerBaseApiTest extends TestCase
return $m->invoke($this->compiler, ...$args);
}
private function fixturePath(string $file): string
{
return __DIR__ . '/../code/compiler_api/' . $file;
}
private function createProjectFile(string $yaml, string $filename = 'project.yml', string $baseDir = ''): string
{
$projectDir = $baseDir === '' ? $this->testDir : $this->testDir . '/' . trim($baseDir, '/');
@ -82,7 +87,7 @@ class CompilerBaseApiTest extends TestCase
}
$sourceFile = $projectDir . '/main.php';
file_put_contents($sourceFile, "<?php\nfunction main() {}\n");
copy($this->fixturePath('main.php'), $sourceFile);
$projectFile = $projectDir . '/' . $filename;
file_put_contents($projectFile, $yaml);
@ -363,9 +368,9 @@ ignore:
YAML);
$projectDir = dirname($projectFile);
mkdir($projectDir . '/skipped', 0777, true);
file_put_contents($projectDir . '/ignored.php', "<?php\nfunction ignored() {}\n");
file_put_contents($projectDir . '/skipped/nested.php', "<?php\nfunction skipped() {}\n");
file_put_contents($projectDir . '/kept.php', "<?php\nfunction kept() {}\n");
copy($this->fixturePath('ignored.php'), $projectDir . '/ignored.php');
copy($this->fixturePath('skipped_nested.php'), $projectDir . '/skipped/nested.php');
copy($this->fixturePath('kept.php'), $projectDir . '/kept.php');
$files = $this->invokeMethod('parseProjectYaml', $projectFile);
@ -452,7 +457,7 @@ YAML);
$logFile = $spaceDir . '/format.log';
$sourceFile = $spaceDir . '/hello world.cc';
file_put_contents($sourceFile, "int main() { return 0; }\n");
copy($this->fixturePath('hello_world.cc'), $sourceFile);
$this->createFakeClangFormat($binDir, $logFile);
putenv('PATH=' . $binDir . ':' . ($this->originalPath ?: ''));

@ -4,6 +4,7 @@ namespace PhpAot\Tests\Entity;
use PHPUnit\Framework\TestCase;
use PhpAot\Php\Entity\InterfaceDef;
use PhpAot\Php\Entity\MethodDef;
class InterfaceDefTest extends TestCase
{
@ -39,4 +40,22 @@ class InterfaceDefTest extends TestCase
$this->assertEquals('Parent', $iface->extends);
}
public function testTracksMethodsCaseInsensitively(): void
{
$iface = new InterfaceDef('Runnable');
$iface->addMethod(new MethodDef(0, 'run'));
$this->assertTrue($iface->hasMethod('run'));
$this->assertTrue($iface->hasMethod('RUN'));
$this->assertArrayHasKey('run', $iface->methods);
}
public function testTracksMultipleParentInterfaces(): void
{
$iface = new InterfaceDef('Child');
$iface->extendsList = ['ParentA', 'ParentB'];
$this->assertSame(['ParentA', 'ParentB'], $iface->extendsList);
}
}

@ -97,4 +97,19 @@ class InheritanceErrorTest extends TestCase
{
$this->exec('must be compatible', 'inheritance_error_prop_readonly.php');
}
public function testInterfaceMethodMissing()
{
$this->exec('must implement method', 'interface_method_missing.php');
}
public function testInterfaceMethodSignatureMismatch()
{
$this->exec('must be compatible', 'interface_method_signature_mismatch.php');
}
public function testInterfaceMethodProvidedByTrait()
{
$this->assertCompiles('interface_method_from_trait.php');
}
}

@ -5,8 +5,10 @@ namespace PhpAot\Tests;
use PHPUnit\Framework\TestCase;
use PhpAot\Php\CompilerTest;
use PhpAot\Php\ArgInfo;
use PhpAot\Php\Exception\TestError;
use PhpParser\Node;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Modifiers;
use PhpParser\ParserFactory;
class PreprocessorTest extends TestCase
@ -59,6 +61,13 @@ class PreprocessorTest extends TestCase
$prop->setValue($this->compiler, $value);
}
private function getProperty(string $name): mixed
{
$prop = $this->ref->getProperty($name);
$prop->setAccessible(true);
return $prop->getValue($this->compiler);
}
private function parseFunctionNode(string $code): Function_
{
$parser = (new ParserFactory())->createForHostVersion();
@ -235,6 +244,133 @@ class PreprocessorTest extends TestCase
$this->assertIsArray($files);
}
public function testPrepareFileParsesInterfaceMembersAndTypeChecks(): void
{
$file = __DIR__ . '/../code/preprocessor/interface_members.php';
$this->compiler->prepareFile($file);
$interfaces = $this->getProperty('interfaces');
$this->assertArrayHasKey('demo', $interfaces);
$iface = $interfaces['demo'];
$this->assertSame(['ParentA', 'ParentB'], $iface->extendsList);
$this->assertSame('ParentA', $iface->extends);
$this->assertArrayHasKey('VERSION', $iface->constants);
$this->assertTrue($iface->hasMethod('run'));
$functionDef = $iface->methods['run']->functionDef;
$this->assertTrue($functionDef->method);
$this->assertSame('php::Int', $functionDef->argInfoList[0]->type);
$this->assertNull($functionDef->argInfoList[0]->typeCheck);
$this->assertSame('php::Var', $functionDef->argInfoList[1]->type);
$this->assertNotEmpty($functionDef->argInfoList[1]->typeCheck);
$this->assertSame('php::Var', $functionDef->returnType);
$this->assertNotEmpty($functionDef->returnTypeCheck);
}
public function testPrepareFileRejectsNamespacedDuplicateClassAndInterface(): void
{
$this->expectException(TestError::class);
$this->expectExceptionMessage('Duplicate class `App\\Demo`');
$file = __DIR__ . '/../code/preprocessor/duplicate_namespaced_class_interface.php';
$this->compiler->prepareFile($file);
}
public function testPrepareFileParsesNamespacedConstants(): void
{
$file = __DIR__ . '/../code/preprocessor/namespaced_const.php';
$this->compiler->prepareFile($file);
$constants = $this->getProperty('constants');
$this->assertArrayHasKey('_const_var_App__VERSION', $constants);
$this->assertSame('App\\VERSION', $constants['_const_var_App__VERSION']->name);
}
public function testSortFilesUsesImplementsAndTraitDependencies(): void
{
$classFile = realpath(__DIR__ . '/../code/preprocessor/deps_class_implements.php');
$interfaceFile = realpath(__DIR__ . '/../code/preprocessor/deps_interface.php');
$traitUserFile = realpath(__DIR__ . '/../code/preprocessor/deps_class_uses_trait.php');
$traitFile = realpath(__DIR__ . '/../code/preprocessor/deps_trait.php');
$this->compiler->prepareFile($classFile);
$this->compiler->prepareFile($interfaceFile);
$this->compiler->prepareFile($traitUserFile);
$this->compiler->prepareFile($traitFile);
$files = [$classFile, $interfaceFile, $traitUserFile, $traitFile];
$this->compiler->sortFiles($files);
$this->assertLessThan(array_search($classFile, $files, true), array_search($interfaceFile, $files, true));
$this->assertLessThan(array_search($traitUserFile, $files, true), array_search($traitFile, $files, true));
}
public function testPrepareFileParsesTraitAliasModifierWithoutNewName(): void
{
$file = __DIR__ . '/../code/preprocessor/trait_alias_modifier.php';
$this->compiler->prepareFile($file);
$classes = $this->getProperty('classes');
$this->assertArrayHasKey('aliasmodifieruser', $classes);
$aliases = $classes['aliasmodifieruser']->traitAliases;
$this->assertArrayHasKey('aliasmodifiertrait::hello', $aliases);
$this->assertSame('hello', $aliases['aliasmodifiertrait::hello']['newName']);
$this->assertSame(Modifiers::PRIVATE, $aliases['aliasmodifiertrait::hello']['newModifier']);
}
public function testPrepareFileInfersEachClassConstantTypeIndependently(): void
{
$file = __DIR__ . '/../code/preprocessor/class_constants.php';
$this->compiler->prepareFile($file);
$classes = $this->getProperty('classes');
$this->assertArrayHasKey('preprocessorclassconstants', $classes);
$constants = $classes['preprocessorclassconstants']->constants;
$this->assertSame('php::Str', $constants['TEXT']->type);
$this->assertSame('php::Array', $constants['ITEMS']->type);
}
public function testPrepareFileRejectsDuplicateClassConstants(): void
{
$this->expectException(TestError::class);
$this->expectExceptionMessage('Duplicate constant `VALUE`');
$file = __DIR__ . '/../code/preprocessor/duplicate_class_constant.php';
$this->compiler->prepareFile($file);
}
public function testPrepareFileParsesAbstractMethodSignatures(): void
{
$file = __DIR__ . '/../code/preprocessor/abstract_method_signature.php';
$this->compiler->prepareFile($file);
$classes = $this->getProperty('classes');
$this->assertArrayHasKey('preprocessorabstractsignature', $classes);
$methodDef = $classes['preprocessorabstractsignature']->abstractMethodDefs['load'];
$functionDef = $methodDef->functionDef;
$this->assertTrue($functionDef->method);
$this->assertSame('php::Int', $functionDef->argInfoList[0]->type);
$this->assertSame('php::Var', $functionDef->argInfoList[1]->type);
$this->assertNotEmpty($functionDef->argInfoList[1]->typeCheck);
$this->assertSame('php::Object', $functionDef->returnType);
$this->assertSame('PreprocessorAbstractSignature', $functionDef->returnClass);
}
public function testPrepareFileRejectsDuplicateAbstractMethods(): void
{
$this->expectException(TestError::class);
$this->expectExceptionMessage('Duplicate method `load`');
$file = __DIR__ . '/../code/preprocessor/duplicate_abstract_method.php';
$this->compiler->prepareFile($file);
}
public function testIntersectionParamDeclFallsBackToVarWithRuntimeCheck(): void
{
$fn = $this->parseFunctionNode('<?php interface A {} interface B {} function demo(A&B $value): void {}');

@ -1224,7 +1224,11 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$list = [];
foreach ($implements as $implement) {
$list[] = $this->getNamespacedClassName($implement);
$interfaceName = $this->getNamespacedClassName($this->parseIdentifier($implement));
$list[] = $interfaceName;
if (!$this->isInternalInterface($interfaceName)) {
$this->symbolCallInFile[$this->file][] = strtolower($interfaceName);
}
}
return $list;
}
@ -1822,6 +1826,11 @@ class CompilerBase extends \PhpAot\Core\Translator
return array_key_exists($this->escapeClass($name), $this->interfaces);
}
protected function getInterface(string $name): InterfaceDef
{
return $this->interfaces[$this->escapeClass($name)];
}
protected function checkFunction(string $name): void
{
// 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误
@ -3578,7 +3587,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->isNameExpr($expr->name) and $this->hasConstant($name)) {
return $this->getConstant($name);
}
if ($this->namespace and $this->isNameExpr($expr->name) and !str_contains($name, '\\')) {
if ($this->namespace and $this->isNameExpr($expr->name) and !$expr->name instanceof Node\Name\FullyQualified) {
$nsName = $this->namespace . '\\' . $name;
if ($this->hasConstant($nsName)) {
return $this->getConstant($nsName);
@ -3725,12 +3734,19 @@ class CompilerBase extends \PhpAot\Core\Translator
}
// Check transitive interface inheritance (e.g., Iterator extends Traversable)
foreach ($classDef->implements as $iface) {
$check = $iface;
while ($check && $this->hasInterface($check)) {
$stack = [$iface];
while ($stack) {
$check = array_pop($stack);
if (strcasecmp($check, $expected) === 0) {
return true;
}
$check = $this->getInterface($check)->extends;
if (!$this->hasInterface($check)) {
continue;
}
$interfaceDef = $this->getInterface($check);
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parentIface) {
$stack[] = $parentIface;
}
}
if (is_subclass_of($iface, $expected)) {
return true;

@ -48,6 +48,12 @@ class ClassDef extends ClassLikeDef
* @var array<string, int>
*/
public array $abstractMethods = [];
/**
* Abstract method name (lowercase) => method definition
* @var array<string, MethodDef>
*/
public array $abstractMethodDefs = [];
public ?Trait_ $trait = null;
/**
@ -79,9 +85,13 @@ class ClassDef extends ClassLikeDef
$this->methods[strtolower($method->name)] = $method;
}
public function addAbstractMethod(string $name, int $flags): void
public function addAbstractMethod(string $name, int $flags, ?MethodDef $methodDef = null): void
{
$this->abstractMethods[strtolower($name)] = $flags;
$lower = strtolower($name);
$this->abstractMethods[$lower] = $flags;
if ($methodDef !== null) {
$this->abstractMethodDefs[$lower] = $methodDef;
}
}
public function hasMethod(string $method): bool

@ -10,8 +10,38 @@ namespace PhpAot\Php\Entity;
class InterfaceDef extends ClassLikeDef
{
/**
* @var array<string, MethodDef>
*/
public array $methods = [];
/**
* @var array<string, ConstantDef>
*/
public array $constants = [];
/**
* @var string[]
*/
public array $extendsList = [];
public function __construct(string $name, string $namespace = '')
{
parent::__construct($name, $namespace);
}
public function addMethod(MethodDef $method): void
{
$this->methods[strtolower($method->name)] = $method;
}
public function hasMethod(string $method): bool
{
return isset($this->methods[strtolower($method)]);
}
public function hasConstant(string $name): bool
{
return isset($this->constants[$name]);
}
}

@ -212,6 +212,7 @@ class Preprocessor extends CompilerBase
$this->parseGroupUse($v2);
break;
case 'Stmt_Const':
$this->parseConstDef($v2);
break;
case 'Stmt_Interface':
$this->parseInterface($v2);
@ -554,13 +555,10 @@ class Preprocessor extends CompilerBase
$flags = $this->parseModifiers($v->flags);
$class = '';
if ($v->type) {
$type = $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_CONST, $class);
} else {
$type = null;
}
$declaredType = $v->type ? $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_CONST, $class) : null;
foreach ($v->consts as $const) {
$type = $declaredType;
if ($type === null) {
$type = match ($const->value->getType()) {
'Expr_Array' => self::TYPE_ARRAY,
@ -569,6 +567,9 @@ class Preprocessor extends CompilerBase
};
}
$constName = $this->parseIdentifier($const->name);
if ($this->classDef->hasConstant($constName)) {
$this->fatalError($v, "Duplicate constant `{$constName}`");
}
$constValue = $this->parseIdentifier($const->value);
$constInfo = new ConstantDef($constName, $flags, $type, $constValue);
@ -654,9 +655,16 @@ class Preprocessor extends CompilerBase
$this->checkRequiredArgNum($name, $this->methodDef, $v);
$this->classDef->addMethod($this->methodDef);
} else {
if ($this->classDef->hasMethod($name) || $this->classDef->hasAbstractMethod($name)) {
$this->fatalError($v, "Duplicate method `{$this->method}`");
}
if (!$class instanceof Node\Stmt\Trait_ && isset($class->flags) && !($class->flags & Modifiers::ABSTRACT)) {
$this->fatalError($v, "Non-abstract class {$this->class} contains abstract method {$v->name}");
}
$this->methodDef = new MethodDef($flags, $name);
$this->methodDef->functionDef = $this->parseFunctionDecl($v);
$this->methodDef->functionDef->method = true;
$this->checkRequiredArgNum($name, $this->methodDef, $v);
if ($this->method === '__construct') {
foreach ($v->params as $param) {
if ($param->isPromoted()) {
@ -664,7 +672,7 @@ class Preprocessor extends CompilerBase
}
}
}
$this->classDef->addAbstractMethod($name, $flags);
$this->classDef->addAbstractMethod($name, $flags, $this->methodDef);
}
$fullClassName = $this->getFullClassName();
@ -716,12 +724,81 @@ class Preprocessor extends CompilerBase
protected function parseInterface(Node\Stmt\Interface_ $v): void
{
$this->resetClass();
$this->resetMethod();
$this->resetFunction();
$name = $this->parseIdentifier($v->name);
$this->interface = $name;
$this->interfaceDef = new InterfaceDef($name, $this->namespace);
$interfaceName = $this->interfaceDef->getNamespacedName();
$interfaceName = $this->interfaceDef->getNamespacedName(false);
$interfaceNameLower = strtolower($interfaceName);
foreach ($v->extends as $parent) {
$parentName = $this->getNamespacedClassName($this->parseIdentifier($parent));
$this->interfaceDef->extendsList[] = $parentName;
if ($this->interfaceDef->extends === '') {
$this->interfaceDef->extends = $parentName;
}
if (!$this->isInternalInterface($parentName)) {
$this->symbolCallInFile[$this->file][] = strtolower($parentName);
}
}
if (isset($this->symbolDeclInFile[$interfaceNameLower])) {
$this->fatalError($v, "Duplicate interface `{$interfaceName}`");
}
$this->symbolDeclInFile[$interfaceNameLower] = $this->file;
$this->interfaces[$this->escapeClass($interfaceName)] = $this->interfaceDef;
$this->interfacesDefineInFile[$interfaceName] = $this->interfaceDef;
foreach ($v->stmts as $stmt) {
if ($stmt instanceof Node\Stmt\ClassConst) {
foreach ($stmt->consts as $const) {
$constName = $this->parseIdentifier($const->name);
if ($this->interfaceDef->hasConstant($constName)) {
$this->fatalError($stmt, "Duplicate constant `{$constName}`");
}
$class = '';
$type = $stmt->type
? $this->parseTypeDecl($stmt->type, self::DECL_TYPE_OF_CONST, $class)
: match ($const->value->getType()) {
'Expr_Array' => self::TYPE_ARRAY,
'Scalar_String' => self::TYPE_STR,
default => self::TYPE_VAR,
};
$constInfo = new ConstantDef($constName, $this->parseModifiers($stmt->flags), $type, $this->parseIdentifier($const->value));
$constInfo->class = $class;
$constInfo->valueExpr = $const->value;
$this->interfaceDef->constants[$constName] = $constInfo;
}
continue;
}
if ($stmt instanceof Node\Stmt\ClassMethod) {
$methodName = $this->getMethodName($stmt);
if ($this->interfaceDef->hasMethod($methodName)) {
$this->fatalError($stmt, "Duplicate method `{$methodName}`");
}
$this->method = $methodName;
$methodDef = new MethodDef($this->parseModifiers($stmt->flags), $methodName);
$methodDef->functionDef = $this->parseFunctionDecl($stmt);
$methodDef->functionDef->method = true;
$this->interfaceDef->addMethod($methodDef);
$this->resetMethod();
$this->resetFunction();
continue;
}
if (!$stmt instanceof Node\Stmt\Nop) {
$this->fatalError($stmt, 'Unsupported interface statement: ' . $stmt->getType());
}
}
$this->resetMethod();
$this->resetFunction();
$this->interface = '';
$this->interfaceDef = null;
}
protected function parseTraitUseOptions(Node\Stmt\TraitUse $traitUse, array &$aliases, array &$ignored): void
@ -739,7 +816,7 @@ class Preprocessor extends CompilerBase
$traits[] = $adaptation->trait;
}
foreach ($traits as $trait) {
$traitName = $this->getNamespacedClassName($trait);
$traitName = $this->getNamespacedClassName($this->parseIdentifier($trait));
$methodName = $adaptation->method->toString();
/*
* 例如:
@ -747,7 +824,7 @@ class Preprocessor extends CompilerBase
* 这表示 TraitA::method() 会被重命名为 TraitA::newMethod()
*/
$aliases[$this->getFullMethodName($traitName, $methodName)] = [
'newName' => $adaptation->newName->toString(),
'newName' => $adaptation->newName ? $adaptation->newName->toString() : $methodName,
'newModifier' => $adaptation->newModifier ?: 0,
];
}
@ -763,7 +840,8 @@ class Preprocessor extends CompilerBase
* 这表示 TraitB::method() 将会被忽略,真正执行的是 TraitA::method()
*/
foreach ($adaptation->insteadof as $trait2) {
$ignored[$this->getFullMethodName($trait2, $methodName)] = true;
$traitName = $this->getNamespacedClassName($this->parseIdentifier($trait2));
$ignored[$this->getFullMethodName($traitName, $methodName)] = true;
}
}
}
@ -776,6 +854,12 @@ class Preprocessor extends CompilerBase
if ($v->adaptations) {
$this->parseTraitUseOptions($v, $aliases, $ignored);
}
foreach ($v->traits as $trait) {
$traitName = $this->getNamespacedClassName($this->parseIdentifier($trait));
if (!$this->isInternalClass($traitName)) {
$this->symbolCallInFile[$this->file][] = strtolower($traitName);
}
}
$this->classDef->traitAliases = array_merge($this->classDef->traitAliases, $aliases);
$this->classDef->traitIgnored = array_merge($this->classDef->traitIgnored, $ignored);
}

@ -1864,14 +1864,17 @@ CODE;
protected function getRegisterClassFunctionCeList(ClassDef|InterfaceDef $classDef): array
{
$list = [];
if ($classDef instanceof InterfaceDef) {
foreach ($classDef->extendsList ?: ($classDef->extends ? [$classDef->extends] : []) as $parentInterface) {
$list[] = self::PREFIX . 'class_entry_' . $this->escapeCeName($parentInterface);
}
return $list;
}
$parentCe = $this->getParentClassCe($classDef);
if ($parentCe !== '') {
$list = [$parentCe];
}
// interface 没有 implements
if ($classDef instanceof InterfaceDef) {
return $list;
}
$implements = $this->getImplementCe($classDef);
return array_merge($list, $implements);
@ -2290,14 +2293,13 @@ CODE;
$sorter = new StringSort();
foreach ($this->interfaces as $interfaceDef) {
$parent = $interfaceDef->extends;
$ce = $this->getClassCe($interfaceDef);
$deps = [];
if ($parent) {
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parent) {
$tmpCe = self::PREFIX . 'class_entry_' . $this->escapeCeName($parent);
// 不存在的接口,说明可能是内置接口
$tmpCe = $this->getParentClassCe($interfaceDef);
if (!isset($this->interfaces[$parent])) {
if (!$this->hasInterface($parent)) {
$sorter->add($tmpCe);
}
$deps[] = $tmpCe;
@ -2735,6 +2737,9 @@ CODE;
break;
}
}
if (!$class instanceof Node\Stmt\Trait_) {
$this->checkInterfaceImplementations($class);
}
$code = $this->genNativeMethod($methodCodes);
$oriCtx = $this->context;
@ -3084,17 +3089,16 @@ CODE;
'Cannot override private method `' .
$extends . '::' . $name . '()`');
}
$parentFuncDef = $methodDef->functionDef;
$this->validateMethodOverrideSignature($v, $name, $childFuncDef, $methodDef, $extends);
$this->validateMethodOverrideSignature($v, $name, $this->methodDef, $methodDef, $extends);
break;
}
}
}
private function validateMethodOverrideSignature(
Node\Stmt\ClassMethod $v,
NodeAbstract $v,
string $methodName,
FunctionDef $childFuncDef,
MethodDef $childMethodDef,
MethodDef $parentMethodDef,
string $parentClass
): void {
@ -3107,12 +3111,13 @@ CODE;
// PHP allows widening visibility in overrides (e.g. protected -> public),
// but forbids narrowing it.
if ($this->getVisibilityRank($this->methodDef->flags) < $this->getVisibilityRank($parentMethodDef->flags)) {
if ($this->getVisibilityRank($childMethodDef->flags) < $this->getVisibilityRank($parentMethodDef->flags)) {
$error('visibility mismatch');
}
$childFuncDef = $childMethodDef->functionDef;
$parentFuncDef = $parentMethodDef->functionDef;
if (!$parentFuncDef) {
if (!$childFuncDef || !$parentFuncDef) {
return;
}
@ -3154,6 +3159,57 @@ CODE;
}
}
private function checkInterfaceImplementations(Node\Stmt\Class_|Node\Stmt\Enum_ $classStmt): void
{
$classDef = $this->classDef;
foreach ($classDef->implements as $interfaceName) {
$this->checkInterfaceImplementation($classStmt, $classDef, $interfaceName);
}
}
private function checkInterfaceImplementation(NodeAbstract $node, ClassDef $classDef, string $interfaceName): void
{
if ($this->isInternalInterface($interfaceName)) {
return;
}
if (!$this->hasInterface($interfaceName)) {
return;
}
$interfaceDef = $this->getInterface($interfaceName);
foreach ($interfaceDef->methods as $methodName => $interfaceMethodDef) {
$childMethodDef = $this->findClassMethodDef($classDef, $methodName);
if ($childMethodDef === null) {
$this->fatalError($node, "Class `{$classDef->getNamespacedName(false)}` must implement method `{$interfaceName}::{$interfaceMethodDef->name}()`");
}
$this->validateMethodOverrideSignature(
$node,
$interfaceMethodDef->name,
$childMethodDef,
$interfaceMethodDef,
$interfaceName
);
}
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parentInterface) {
$this->checkInterfaceImplementation($node, $classDef, $parentInterface);
}
}
private function findClassMethodDef(ClassDef $classDef, string $methodName): ?MethodDef
{
$current = $classDef;
while (true) {
if ($current->hasMethod($methodName)) {
return $current->getMethod($methodName);
}
if (!$current->extends || !$this->hasClass($current->extends)) {
return null;
}
$current = $this->getClass($current->extends);
}
}
private function getVisibilityRank(int $flags): int
{
if ($flags & Modifiers::PUBLIC) {

@ -0,0 +1,28 @@
--TEST--
preprocessor registers namespace constants
--FILE--
<?php
namespace Preprocessor\NsConst {
const VALUE = 123;
const LABEL = "ns-const";
function readLocal(): string {
return LABEL . ":" . VALUE;
}
}
namespace {
use const Preprocessor\NsConst\VALUE;
use function Preprocessor\NsConst\readLocal;
function main(): void {
var_dump(readLocal());
var_dump(\Preprocessor\NsConst\LABEL);
var_dump(VALUE);
}
}
?>
--EXPECT--
string(12) "ns-const:123"
string(8) "ns-const"
int(123)

@ -0,0 +1,24 @@
--TEST--
namespace relative qualified constants
--FILE--
<?php
namespace App\Sub {
const VALUE = 77;
}
namespace App {
function readRelative(): void {
var_dump(Sub\VALUE);
}
}
namespace {
function main(): void {
App\readRelative();
var_dump(\App\Sub\VALUE);
}
}
?>
--EXPECT--
int(77)
int(77)
Loading…
Cancel
Save