From c67c3dfea9d38616ee0345f2fc797878eee6bbb5 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 8 Apr 2026 21:03:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor(compiler):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=99=A8=E5=86=85=E9=83=A8=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E5=B8=B8=E9=87=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 internalConstants 数组用于存储内部常量 - 重构变量赋值类型检查逻辑,简化条件判断 - 添加 isInternalInterface 和 isInternalConstant 辅助方法 - 优化参数类型解析过程,统一对象类型处理 - 改进常量查找逻辑,优先检查内部常量 - 增强类继承关系检查,支持内部接口验证 - 在预处理器中添加循环依赖检测和解决机制 - 引入 TopSort 库处理文件依赖排序问题 - 优化反射类,添加内部接口识别功能 - 初始化内部常量列表,提升常量处理性能 - 添加测试用例验证类型命中功能 --- src/Php/CompilerBase.php | 39 ++++++++++------ src/Php/Preprocessor.php | 86 +++++++++++++++++++++++++++++++++--- src/Php/Reflection.php | 23 ++++++++++ src/Php/Translator.php | 5 ++- tests/aot/type_hits/003.phpt | 26 +++++++++++ 5 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 tests/aot/type_hits/003.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 3286c99a..930ce710 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -142,6 +142,7 @@ class CompilerBase extends \PhpAot\Core\Translator ]; protected array $localHeaders = []; protected array $internalFunctions = []; + protected array $internalConstants = []; /** * 存储所有函数、类方法的声明,key 是 符号名称,Value 是函数、类方法所在的文件名称 @@ -1437,12 +1438,8 @@ class CompilerBase extends \PhpAot\Core\Translator if (!$this->hasVar($var)) { $this->addLocalVar($var, $type); - } elseif ($this->getVarType($var) !== self::TYPE_VAR) { // var 可以作为任意类型进行赋值 - if ($this->isTypedObject($var) and $type !== self::TYPE_OBJECT - or $this->getVarType($var) === self::TYPE_ARRAY and ($type !== self::TYPE_ARRAY) - ) { - $this->fatalError($left, "Cannot re-assign variable `\${$var}` from " . $this->getVarType($var) . ' to ' . $type); - } + } elseif ($this->getVarType($var) !== self::TYPE_VAR and $this->isTypedObject($var) and $type !== self::TYPE_OBJECT) { + $this->fatalError($left, "Cannot re-assign variable `\${$var}` from " . $this->getVarType($var) . ' to ' . $type); } } elseif ($this->isPropertyFetch($left) and !$left->getAttribute('nativeProperty')) { return $this->parseAssignPropertyFetch($left, $right); @@ -1453,7 +1450,7 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($left, 'Cannot use [] for strings'); } } - if ($this->isScalar($right) or $this->isVarExpr($right)) { + if ($this->isVarExpr($left->var) and ($this->isVarExpr($right) or $this->isScalar($right))) { return $this->parseAssignArrayDim($left, $right); } } @@ -1510,6 +1507,16 @@ class CompilerBase extends \PhpAot\Core\Translator return Reflection::isInternalClass($name); } + protected function isInternalInterface(string $name): bool + { + return Reflection::isInternalInterface($name); + } + + protected function isInternalConstant(string $name): bool + { + return array_key_exists($name, $this->internalConstants); + } + protected function isAssignOpConcat(string $op): bool { return $op === '.='; @@ -2006,16 +2013,15 @@ class CompilerBase extends \PhpAot\Core\Translator if ($typeName === 'void' or $typeName === 'never') { $this->fatalError($param, 'Cannot use `void`/`never` as a parameter type.'); } elseif ($typeName === 'self') { - $this->addObject($var, $this->classDef->getNamespacedName(false)); - return self::TYPE_OBJECT; + $class = $this->classDef->getNamespacedName(false); } elseif (isset($this->zendTypeMap[$typeName])) { return $this->getTypeFromZendType($typeName); } else { $class = $this->getNamespacedClassName($typeName); - $this->addObject($var, $class); - $argInfo->class = $class; - return self::TYPE_OBJECT; } + $this->addObject($var, $class); + $argInfo->class = $class; + return self::TYPE_OBJECT; } protected function parseIncludes(): string @@ -3184,7 +3190,9 @@ class CompilerBase extends \PhpAot\Core\Translator $ce = $this->getClassEntryPtr($fullName); return 'php::constant(' . $ce . ', ' . $this->getLiteralString($ns[1]) . ')'; } - if (isset($this->useAliases[$name])) { + if ($this->isInternalConstant($name)) { + return 'php::constant(' . $this->getLiteralString($name) . ')'; + } elseif (isset($this->useAliases[$name])) { $name = $this->useAliases[$name]; } else { $fullName = $this->getNamespacedClassName($name); @@ -3272,6 +3280,9 @@ class CompilerBase extends \PhpAot\Core\Translator protected function isInheritedFrom(string $class, string $expected): bool { + if ($this->isInternalClass($class) and ($this->isInternalClass($expected) or $this->isInternalInterface($expected))) { + return $class === $expected or is_subclass_of($class, $expected); + } while (true) { if (strcasecmp($class, $expected) === 0) { return true; @@ -3301,7 +3312,7 @@ class CompilerBase extends \PhpAot\Core\Translator $object = $this->parseVariable($arg->value); if ($this->isTypedObject($object)) { $class = $this->getObjectType($object); - if (!$this->isInheritedFrom($class, $argInfo->class)) { + if ($class and $argInfo->class and !$this->isInheritedFrom($class, $argInfo->class)) { $this->fatalError($arg, "Argument `{$argInfo->name}` must be an instance of `{$argInfo->class}`, `{$class}` given"); } } diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index 3a908b48..dbc1f121 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -8,6 +8,8 @@ namespace PhpAot\Php; +use MJS\TopSort\CircularDependencyException; +use MJS\TopSort\ElementNotFoundException; use MJS\TopSort\Implementations\StringSort; use PhpAot\Php\Exception\SyntaxError; use PhpAot\Php\Exception\Unsupported; @@ -24,7 +26,9 @@ class Preprocessor extends CompilerBase public function sortFiles(array &$list): void { $sorter = new StringSort(); + $fileDeps = []; + // 构建依赖关系图 foreach ($this->symbolCallInFile as $file => $symbols) { $deps = []; foreach ($symbols as $symbol) { @@ -35,19 +39,89 @@ class Preprocessor extends CompilerBase } } } - $sorter->add($file, array_unique($deps)); + $deps = array_unique($deps); + $fileDeps[$file] = $deps; + $sorter->add($file, $deps); } - $sortedFiles = $sorter->sort(); + try { + // 尝试进行拓扑排序 + $sortedFiles = $sorter->sort(); + } catch (CircularDependencyException $e) { + // 检测到循环依赖,尝试打破循环 + $this->climate->yellow('Warning: Circular dependency detected, attempting to resolve...'); + $circularNodes = $e->getNodes(); + $this->climate->darkGray('Circular path: ' . implode(' -> ', $circularNodes)); + + // 使用打破循环后的依赖关系重新排序 + $sortedFiles = $this->resolveCircularDependencies($fileDeps, $circularNodes); + } + // 添加未参与依赖管理的文件(非 stub 文件且不在已排序列表中) foreach ($list as $file) { if (!$this->isStubFile($file) and !in_array($file, $sortedFiles)) { $sortedFiles[] = $file; } } + $list = $sortedFiles; } + /** + * 解决循环依赖问题 + * + * @param array $fileDeps 所有文件的依赖关系 + * @param array $circularNodes 循环依赖中的节点 + * @return array 排序后的文件列表 + * @throws ElementNotFoundException + */ + protected function resolveCircularDependencies(array $fileDeps, array $circularNodes): array + { + // 找出循环依赖中最少的边来打破循环 + // 策略:移除被依赖次数最少的文件的依赖关系 + $depCount = []; + foreach ($circularNodes as $node) { + $depCount[$node] = 0; + // 统计该节点在循环中被其他节点依赖的次数 + foreach ($circularNodes as $otherNode) { + if (isset($fileDeps[$otherNode]) && in_array($node, $fileDeps[$otherNode])) { + $depCount[$node]++; + } + } + } + + // 找到被依赖最少的节点,打破它的某个依赖 + asort($depCount); + $breakNode = key($depCount); + + $this->climate->darkGray("Breaking circular dependency at: {$breakNode}"); + + // 创建新的依赖关系,移除导致循环的依赖 + $resolvedDeps = $fileDeps; + if (isset($resolvedDeps[$breakNode])) { + // 移除该节点对循环中其他节点的依赖 + $resolvedDeps[$breakNode] = array_filter( + $resolvedDeps[$breakNode], + fn($dep) => !in_array($dep, $circularNodes) || $dep === $breakNode + ); + } + + // 使用修正后的依赖关系重新排序 + $sorter = new StringSort(); + foreach ($resolvedDeps as $file => $deps) { + $sorter->add($file, $deps); + } + + try { + return $sorter->sort(); + } catch (CircularDependencyException $e) { + // 如果仍然存在循环,递归处理 + $remainingCircular = $e->getNodes(); + $this->climate->yellow('Still has circular dependency, continuing to resolve...'); + return $this->resolveCircularDependencies($resolvedDeps, $remainingCircular); + } + } + public function getCppFile(string $file): string { $info = pathinfo($file); @@ -147,8 +221,9 @@ class Preprocessor extends CompilerBase } } - $depClasses = $nodeFinder->findInstanceOf($ast, Node\Expr\StaticCall::class); - $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\StaticPropertyFetch::class)); + $depClasses = []; +// $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\StaticCall::class)); +// $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\StaticPropertyFetch::class)); $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\ClassConstFetch::class)); $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\New_::class)); foreach ($depClasses as $call) { @@ -161,7 +236,8 @@ class Preprocessor extends CompilerBase } } // 依赖去重 - $this->symbolCallInFile[$this->file] = array_unique($this->symbolCallInFile[$this->file]); + $depClasses = array_unique($this->symbolCallInFile[$this->file]); + $this->symbolCallInFile[$this->file] = $depClasses; } protected function prepareNamespace(Node\Stmt\Namespace_ $node): void diff --git a/src/Php/Reflection.php b/src/Php/Reflection.php index 833c3213..e2b1a3a7 100644 --- a/src/Php/Reflection.php +++ b/src/Php/Reflection.php @@ -12,6 +12,7 @@ class Reflection { private static array $functions = []; private static array $classes = []; + private static array $interfaces = []; public static function isInternalClass(string $class): bool { @@ -36,6 +37,28 @@ class Reflection return isset($internalClasses[strtolower($class)]); } + public static function isInternalInterface(string $interface): bool + { + static $internalInterfaces = null; + + if ($internalInterfaces === null) { + $allInterfaces = get_declared_interfaces(); + $internalInterfaces = []; + foreach ($allInterfaces as $interfaceName) { + try { + $ref = new \ReflectionClass($interfaceName); + if ($ref->isInternal()) { + $internalInterfaces[strtolower($interfaceName)] = true; + } + } catch (\ReflectionException) { + continue; + } + } + } + + return isset($internalInterfaces[strtolower($interface)]); + } + public static function getFunction(string $fn): ?\ReflectionFunction { if (!isset(self::$functions[$fn])) { diff --git a/src/Php/Translator.php b/src/Php/Translator.php index e0ec0874..6c555d0f 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -51,10 +51,11 @@ class Translator extends Preprocessor $this->buildMode = $this->climate->arguments->get('mode'); $this->debugLine = intval($this->climate->arguments->get('debug-line')); $this->maxJob = intval($this->climate->arguments->get('job')); - $this->debugInfo = $this->climate->arguments->defined('debug-info'); + $this->debugInfo = $this->climate->arguments->defined('debug-info'); $this->noLiteralStrings = $this->climate->arguments->get('noLiteralStrings'); - $this->enableProfiler = $this->climate->arguments->defined('profile'); + $this->enableProfiler = $this->climate->arguments->defined('profile'); $this->internalFunctions = array_flip(get_defined_functions()['internal']); + $this->internalConstants = get_defined_constants(); if ($this->climate->arguments->defined('help')) { $this->showUsage(); exit(0); diff --git a/tests/aot/type_hits/003.phpt b/tests/aot/type_hits/003.phpt new file mode 100644 index 00000000..390cb6af --- /dev/null +++ b/tests/aot/type_hits/003.phpt @@ -0,0 +1,26 @@ +--TEST-- +type hits +--FILE-- + +--EXPECT-- +object(ArrayObject)#1 (1) { + ["storage":"ArrayObject":private]=> + array(2) { + ["name"]=> + string(4) "John" + ["age"]=> + int(20) + } +}