refactor(compiler): 优化编译器内部结构并添加常量支持

- 添加 internalConstants 数组用于存储内部常量
- 重构变量赋值类型检查逻辑,简化条件判断
- 添加 isInternalInterface 和 isInternalConstant 辅助方法
- 优化参数类型解析过程,统一对象类型处理
- 改进常量查找逻辑,优先检查内部常量
- 增强类继承关系检查,支持内部接口验证
- 在预处理器中添加循环依赖检测和解决机制
- 引入 TopSort 库处理文件依赖排序问题
- 优化反射类,添加内部接口识别功能
- 初始化内部常量列表,提升常量处理性能
- 添加测试用例验证类型命中功能
pull/1/head
韩天峰 5 months ago
parent 128b6b8a73
commit c67c3dfea9
  1. 39
      src/Php/CompilerBase.php
  2. 86
      src/Php/Preprocessor.php
  3. 23
      src/Php/Reflection.php
  4. 5
      src/Php/Translator.php
  5. 26
      tests/aot/type_hits/003.phpt

@ -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");
}
}

@ -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

@ -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])) {

@ -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);

@ -0,0 +1,26 @@
--TEST--
type hits
--FILE--
<?php
function foo(ArrayAccess $data) {
var_dump($data);
}
function main()
{
$arr = new ArrayObject();
$name = "John";
$arr["name"] = $name;
$arr["age"] = 20;
foo($arr);
}
?>
--EXPECT--
object(ArrayObject)#1 (1) {
["storage":"ArrayObject":private]=>
array(2) {
["name"]=>
string(4) "John"
["age"]=>
int(20)
}
}
Loading…
Cancel
Save