refactor(compiler): extract declaration symbol and type compatibility traits

- Move constant definition and namespace use handling to DeclarationSymbolTrait
- Extract native type compatibility logic into NativeTypeCompatibilityTrait
- Remove duplicated method implementations from CompilerBase
- Add trait usage in CompilerBase for better code organization
- Separate concerns between compiler base and specialized functionality
pull/17/head
韩天峰 2 months ago
parent d5860c70ad
commit 7fdf7213ce
  1. 311
      src/CompilerBase.php
  2. 123
      src/Resolver/DeclarationSymbolTrait.php
  3. 216
      src/TypeSystem/NativeTypeCompatibilityTrait.php

@ -59,6 +59,7 @@ use TypePhp\Platform\PlatformBase;
use TypePhp\Platform\PlatformFactory;
use TypePhp\Platform\Windows;
use TypePhp\Resolver\InstancePropertyFetchTarget;
use TypePhp\Resolver\DeclarationSymbolTrait;
use TypePhp\Resolver\PropertyAccessContext;
use TypePhp\Resolver\NativePropertyAccess;
use TypePhp\Resolver\PropertyAccessResult;
@ -68,6 +69,7 @@ use TypePhp\Resolver\PropertyWriteTarget;
use TypePhp\Resolver\StaticPropertyFetchResolution;
use TypePhp\Resolver\StaticPropertyFetchTarget;
use TypePhp\TypeSystem\CompositeTypeCheckerTrait;
use TypePhp\TypeSystem\NativeTypeCompatibilityTrait;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\ArrayItem;
@ -91,7 +93,9 @@ class CompilerBase implements PropertyAccessContext
{
public const string DEFAULT_PHP_VERSION = '8.5';
use CompositeTypeCheckerTrait;
use NativeTypeCompatibilityTrait;
use NativeBuildConfigurationTrait;
use DeclarationSymbolTrait;
use AstNodeType;
use FuncCallOptimizer;
use AnonClassGenerator;
@ -3582,206 +3586,6 @@ class CompilerBase implements PropertyAccessContext
return $funcDef->argInfoList[$index];
}
protected function getReturnType(): string
{
$type = $this->functionDef->returnType;
if ($type === self::TYPE_STREAM) {
return self::TYPE_VAR;
}
return $type;
}
protected function getReturnClass(): string
{
return $this->functionDef->returnClass;
}
protected function isInheritedFrom(string $class, string $expected): bool
{
// 继承关系判断的唯一入口。调用者不应直接使用 PHP 运行时反射函数判断普通项目类。
// 对 AOT 已扫描到的项目类/接口,必须走 classDef/interfaceDef 中的 extends/implements 图;
// 对 PHP 内置类/接口,可以使用 Zend 运行时反射,因为这部分属于目标 PHP 运行时的固定能力;
// 对动态类返回 true 表示“静态阶段无法否定”,后续必须保留运行时检查兜底。
$class = ltrim($class, '\\');
$expected = ltrim($expected, '\\');
if (strcasecmp($class, $expected) === 0) {
return true;
}
$internal = ($this->isInternalClass($expected) or $this->isInternalInterface($expected));
$isInterface = ($this->hasInterface($expected) or $this->isInternalInterface($expected));
if ($this->hasInterface($class)) {
if (!$isInterface) {
return false;
}
return $this->interfaceExtends($class, $expected);
}
if ($this->isInternalClass($class) or $this->isInternalInterface($class)) {
// 只允许内置类型之间使用 Zend 的继承关系。这里不是查询任意用户类,
// 因此不会把编译器进程加载过的外部库类混入项目静态类型系统。
if (!$internal) {
return false;
}
return is_subclass_of($class, $expected);
}
// 类不存在,说明这是一个动态类,跳过静态检查,需要运行时检查
if (!$this->hasClass($class)) {
return true;
}
$classDef = $this->getClass($class);
while (true) {
if ($isInterface) {
if ($classDef->implements and in_array($expected, $classDef->implements)) {
return true;
}
// Check transitive interface inheritance (e.g., Iterator extends Traversable)
foreach ($classDef->implements as $iface) {
$stack = [$iface];
while ($stack) {
$check = array_pop($stack);
if (strcasecmp($check, $expected) === 0) {
return true;
}
if (!$this->hasInterface($check)) {
if ($internal && $this->isInternalInterface($check) && is_subclass_of($check, $expected)) {
return true;
}
continue;
}
$interfaceDef = $this->getInterface($check);
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parentIface) {
$stack[] = $parentIface;
}
}
}
} else {
if (strcasecmp($class, $expected) === 0) {
return true;
}
if (!$this->hasClass($class)) {
// 原生类继承自一个内置类,例如: UserError extends Exception ,然后 $expected 预期是 Throwable
// 这种情况,需要使用 ZendVM 获取继承关系
if ($this->isInternalClass($class) and $internal) {
return $class === $expected or is_subclass_of($class, $expected);
}
return false;
}
}
if (!$classDef->extends) {
return false;
}
$class = $classDef->extends;
if ($this->isInternalClass($class)) {
// 项目类可以继承内置类。进入内置父类链后,后续关系交给 Zend 判断;
// 但 expected 也必须是内置类/接口,否则不能跨到外部用户类命名空间做运行时反射。
return $internal && is_subclass_of($class, $expected);
}
$classDef = $this->getClass($class);
}
}
private function interfaceExtends(string $interface, string $expected): bool
{
// 接口继承需要单独处理,因为 interfaceDef 没有 classDef 的父类链。
// 这里同样只遍历 AOT 已知接口图;遇到内置接口时,才允许使用 Zend 的 is_subclass_of()。
$stack = [$interface];
while ($stack) {
$check = array_pop($stack);
if (strcasecmp($check, $expected) === 0) {
return true;
}
if (!$this->hasInterface($check)) {
if ($this->isInternalInterface($check) && $this->isInternalInterface($expected) && is_subclass_of($check, $expected)) {
return true;
}
continue;
}
$interfaceDef = $this->getInterface($check);
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parentInterface) {
$stack[] = $parentInterface;
}
}
return false;
}
protected function getTypeConvertedArg(Node\Arg $arg, ArgInfo $argInfo): string
{
$type = $this->detectTypeOfExpr($arg->value);
$this->assertExprCanBeUsedAsValue($arg->value, 'function argument');
if (!empty($argInfo->typeCheck)) {
$this->checkCompositeTypeAssignment(
$arg,
$argInfo->typeCheck,
$argInfo->typeStr,
$arg->value,
'argument `$' . ($argInfo->phpName ?: $this->unescapeVarName($argInfo->name)) . '`'
);
}
if ($argInfo->byRef) {
if ($this->isReferenceWrapperCall($arg->value)) {
$inner = $this->unwrapReferenceWrapperCall($arg->value, $arg);
if ($this->isVarExpr($inner)) {
$arg->value = $inner;
} else {
$expr = $this->expandRefvalExpr($inner, $arg);
if ($expr !== null) {
return $expr;
}
$this->fatalError($arg, 'The refval function only accepts a variable, array element, or object property');
}
}
if ($this->isVarExpr($arg->value)) {
$var = $this->parseVariable($arg->value);
// 若参数是引用类型,可以传入未定义变量,将立即创建变量作为引用
if (!$this->hasLocalVar($var)) {
$this->addLocalVar($var, self::TYPE_VAR);
}
}
return $this->convertToRef($arg->value);
}
$expr = $this->parseOrderedArg($arg);
$expr = $this->materializeCallArgValue($arg->value, $expr);
$this->checkVarAssignExpr($arg, $argInfo->type, $type);
if ($argInfo->type === self::TYPE_VAR && $this->isVarExpr($arg->value)) {
$varName = $this->parseIdentifier($arg->value);
if ($this->isStdContainer($varName)) {
return $varName;
}
}
if ($argInfo->type === self::TYPE_OBJECT) {
$declaredClass = $argInfo->declaredClass ?: $argInfo->class;
if ($declaredClass !== '') {
$class = $this->detectDeclaredClassOfExpr($arg->value);
if ($class !== '') {
// native call 是性能热点,若静态阶段已经证明实参 is-a 声明类型,
// 就不要再生成 php::toObject($expr, target_ce) 做重复运行时检查。
// 如果无法证明,但右值是已知 concrete object,说明一定不兼容,直接编译期 fatal;
// 其他动态/外部库/any 场景保留 php::toObject() 作为运行时兜底。
if ($this->isObjectClassStaticallyAssignableTo($class, $declaredClass)) {
return $type === self::TYPE_OBJECT ? $expr : $this->convertObjectExpr($expr);
}
if ($this->isKnownConcreteObjectExpr($arg->value, $class)) {
$argName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name);
$this->fatalError($arg, "Argument `{$argName}` must be an instance of `{$declaredClass}`, `{$class}` given");
}
}
return $this->convertObjectExpr($expr, $this->getClassEntryPtr($declaredClass));
}
return $type === self::TYPE_OBJECT ? $expr : $this->convertObjectExpr($expr);
}
return $this->convertExprType($expr, $argInfo->type, $type);
}
protected function parseExit(Expr\Exit_ $node): string
{
if (!$node->expr) {
@ -4300,58 +4104,6 @@ class CompilerBase implements PropertyAccessContext
return $flags;
}
protected function parseConstDef(mixed $v2): void
{
foreach ($v2->consts as $const) {
$name = $this->parseIdentifier($const->name);
$value = $this->parseIdentifier($const->value);
if ($this->namespace) {
$name = $this->namespace . '\\' . $name;
}
$this->addConstant($name, $value);
}
}
protected function addConstant(string $name, string $value): void
{
$constInfo = new \stdClass();
$constInfo->value = $value;
$constInfo->type = $this->detectStrValueType($value);
$constInfo->namespace = $this->namespace;
$constInfo->name = $name;
$this->constants[$this->escapeConstVar($name)] = $constInfo;
}
protected function hasConstant(string $name): bool
{
return isset($this->constants[$this->escapeConstVar($name)]);
}
protected function getConstant(string $name): string
{
return $this->escapeConstVar($name);
}
protected function getConstantType(string $name): string
{
return $this->constants[$this->escapeConstVar($name)]->type;
}
protected function detectStrValueType(mixed $constant): string
{
if ($this->isIntStr($constant)) {
return self::TYPE_INT;
}
if ($this->isFloatStr($constant)) {
return self::TYPE_FLOAT;
}
if ($this->isBoolStr($constant)) {
return self::TYPE_BOOL;
}
return self::TYPE_VAR;
}
protected function setBuildDir(string $string): void
{
if (!is_dir($string)) {
@ -4391,61 +4143,6 @@ class CompilerBase implements PropertyAccessContext
return $phpCode;
}
protected function parseUse(Node\Stmt\Use_ $v2): string
{
$code = '';
foreach ($v2->uses as $use) {
$id = $this->parseIdentifier($use->name);
$type = $use->type !== Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $v2->type;
if ($type === Node\Stmt\Use_::TYPE_FUNCTION) {
$lastIndex = strrpos($id, '\\');
$fn = substr($id, $lastIndex + 1);
if ($use->alias) {
$this->useFunctions[$use->alias->toString()] = $id;
} else {
$this->useFunctions[$fn] = $id;
}
} elseif ($type === Node\Stmt\Use_::TYPE_CONSTANT) {
$lastIndex = strrpos($id, '\\');
$cn = substr($id, $lastIndex + 1);
$ns = substr($id, 0, $lastIndex);
$fullName = $ns . '\\' . $cn;
if ($use->alias) {
$this->useConstants[$use->alias->toString()] = $fullName;
} else {
$this->useConstants[$cn] = $fullName;
}
} else {
$idLower = strtolower($id);
if ($idLower === 'native_types') {
$this->nativeTypes = true;
} elseif ($idLower === 'decimal_types') {
$this->decimalTypes = true;
} elseif ($idLower === 'bigint_types') {
$this->bigintTypes = true;
} else {
$this->useNamespaces[] = $id;
if ($use->alias) {
$this->useAliases[$use->alias->toString()] = $id;
}
}
}
}
return $code;
}
protected function parseGroupUse(Node\Stmt\GroupUse $node): void
{
$prefix = $node->prefix;
$uses = [];
foreach ($node->uses as $use) {
$fullName = Node\Name::concat($prefix, $use->name);
$uses[] = new Node\UseItem($fullName, $use->alias, $use->type);
}
$syntheticUse = new Node\Stmt\Use_($uses, $node->type);
$this->parseUse($syntheticUse);
}
protected function parseErrorSuppress(Expr\ErrorSuppress $expr): string
{
$tmpVar = $this->genTmpVarName();

@ -0,0 +1,123 @@
<?php
/**
* This file is part of TypePHP.
*
* Maintains constant symbols and namespace use/group-use declarations.
*/
namespace TypePhp\Resolver;
use PhpParser\Node;
trait DeclarationSymbolTrait
{
protected function parseConstDef(mixed $v2): void
{
foreach ($v2->consts as $const) {
$name = $this->parseIdentifier($const->name);
$value = $this->parseIdentifier($const->value);
if ($this->namespace) {
$name = $this->namespace . '\\' . $name;
}
$this->addConstant($name, $value);
}
}
protected function addConstant(string $name, string $value): void
{
$constInfo = new \stdClass();
$constInfo->value = $value;
$constInfo->type = $this->detectStrValueType($value);
$constInfo->namespace = $this->namespace;
$constInfo->name = $name;
$this->constants[$this->escapeConstVar($name)] = $constInfo;
}
protected function hasConstant(string $name): bool
{
return isset($this->constants[$this->escapeConstVar($name)]);
}
protected function getConstant(string $name): string
{
return $this->escapeConstVar($name);
}
protected function getConstantType(string $name): string
{
return $this->constants[$this->escapeConstVar($name)]->type;
}
protected function detectStrValueType(mixed $constant): string
{
if ($this->isIntStr($constant)) {
return self::TYPE_INT;
}
if ($this->isFloatStr($constant)) {
return self::TYPE_FLOAT;
}
if ($this->isBoolStr($constant)) {
return self::TYPE_BOOL;
}
return self::TYPE_VAR;
}
protected function parseUse(Node\Stmt\Use_ $v2): string
{
$code = '';
foreach ($v2->uses as $use) {
$id = $this->parseIdentifier($use->name);
$type = $use->type !== Node\Stmt\Use_::TYPE_UNKNOWN ? $use->type : $v2->type;
if ($type === Node\Stmt\Use_::TYPE_FUNCTION) {
$lastIndex = strrpos($id, '\\');
$fn = substr($id, $lastIndex + 1);
if ($use->alias) {
$this->useFunctions[$use->alias->toString()] = $id;
} else {
$this->useFunctions[$fn] = $id;
}
} elseif ($type === Node\Stmt\Use_::TYPE_CONSTANT) {
$lastIndex = strrpos($id, '\\');
$cn = substr($id, $lastIndex + 1);
$ns = substr($id, 0, $lastIndex);
$fullName = $ns . '\\' . $cn;
if ($use->alias) {
$this->useConstants[$use->alias->toString()] = $fullName;
} else {
$this->useConstants[$cn] = $fullName;
}
} else {
$idLower = strtolower($id);
if ($idLower === 'native_types') {
$this->nativeTypes = true;
} elseif ($idLower === 'decimal_types') {
$this->decimalTypes = true;
} elseif ($idLower === 'bigint_types') {
$this->bigintTypes = true;
} else {
$this->useNamespaces[] = $id;
if ($use->alias) {
$this->useAliases[$use->alias->toString()] = $id;
}
}
}
}
return $code;
}
protected function parseGroupUse(Node\Stmt\GroupUse $node): void
{
$prefix = $node->prefix;
$uses = [];
foreach ($node->uses as $use) {
$fullName = Node\Name::concat($prefix, $use->name);
$uses[] = new Node\UseItem($fullName, $use->alias, $use->type);
}
$syntheticUse = new Node\Stmt\Use_($uses, $node->type);
$this->parseUse($syntheticUse);
}
}

@ -0,0 +1,216 @@
<?php
/**
* This file is part of TypePHP.
*
* Resolves native return types, inheritance compatibility, and converted call arguments.
*/
namespace TypePhp\TypeSystem;
use PhpParser\Node;
use TypePhp\ArgInfo;
trait NativeTypeCompatibilityTrait
{
protected function getReturnType(): string
{
$type = $this->functionDef->returnType;
if ($type === self::TYPE_STREAM) {
return self::TYPE_VAR;
}
return $type;
}
protected function getReturnClass(): string
{
return $this->functionDef->returnClass;
}
protected function isInheritedFrom(string $class, string $expected): bool
{
// 继承关系判断的唯一入口。调用者不应直接使用 PHP 运行时反射函数判断普通项目类。
// 对 AOT 已扫描到的项目类/接口,必须走 classDef/interfaceDef 中的 extends/implements 图;
// 对 PHP 内置类/接口,可以使用 Zend 运行时反射,因为这部分属于目标 PHP 运行时的固定能力;
// 对动态类返回 true 表示“静态阶段无法否定”,后续必须保留运行时检查兜底。
$class = ltrim($class, '\\');
$expected = ltrim($expected, '\\');
if (strcasecmp($class, $expected) === 0) {
return true;
}
$internal = ($this->isInternalClass($expected) or $this->isInternalInterface($expected));
$isInterface = ($this->hasInterface($expected) or $this->isInternalInterface($expected));
if ($this->hasInterface($class)) {
if (!$isInterface) {
return false;
}
return $this->interfaceExtends($class, $expected);
}
if ($this->isInternalClass($class) or $this->isInternalInterface($class)) {
// 只允许内置类型之间使用 Zend 的继承关系。这里不是查询任意用户类,
// 因此不会把编译器进程加载过的外部库类混入项目静态类型系统。
if (!$internal) {
return false;
}
return is_subclass_of($class, $expected);
}
// 类不存在,说明这是一个动态类,跳过静态检查,需要运行时检查
if (!$this->hasClass($class)) {
return true;
}
$classDef = $this->getClass($class);
while (true) {
if ($isInterface) {
if ($classDef->implements and in_array($expected, $classDef->implements)) {
return true;
}
// Check transitive interface inheritance (e.g., Iterator extends Traversable)
foreach ($classDef->implements as $iface) {
$stack = [$iface];
while ($stack) {
$check = array_pop($stack);
if (strcasecmp($check, $expected) === 0) {
return true;
}
if (!$this->hasInterface($check)) {
if ($internal && $this->isInternalInterface($check) && is_subclass_of($check, $expected)) {
return true;
}
continue;
}
$interfaceDef = $this->getInterface($check);
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parentIface) {
$stack[] = $parentIface;
}
}
}
} else {
if (strcasecmp($class, $expected) === 0) {
return true;
}
if (!$this->hasClass($class)) {
// 原生类继承自一个内置类,例如: UserError extends Exception ,然后 $expected 预期是 Throwable
// 这种情况,需要使用 ZendVM 获取继承关系
if ($this->isInternalClass($class) and $internal) {
return $class === $expected or is_subclass_of($class, $expected);
}
return false;
}
}
if (!$classDef->extends) {
return false;
}
$class = $classDef->extends;
if ($this->isInternalClass($class)) {
// 项目类可以继承内置类。进入内置父类链后,后续关系交给 Zend 判断;
// 但 expected 也必须是内置类/接口,否则不能跨到外部用户类命名空间做运行时反射。
return $internal && is_subclass_of($class, $expected);
}
$classDef = $this->getClass($class);
}
}
private function interfaceExtends(string $interface, string $expected): bool
{
// 接口继承需要单独处理,因为 interfaceDef 没有 classDef 的父类链。
// 这里同样只遍历 AOT 已知接口图;遇到内置接口时,才允许使用 Zend 的 is_subclass_of()。
$stack = [$interface];
while ($stack) {
$check = array_pop($stack);
if (strcasecmp($check, $expected) === 0) {
return true;
}
if (!$this->hasInterface($check)) {
if ($this->isInternalInterface($check) && $this->isInternalInterface($expected) && is_subclass_of($check, $expected)) {
return true;
}
continue;
}
$interfaceDef = $this->getInterface($check);
foreach ($interfaceDef->extendsList ?: ($interfaceDef->extends ? [$interfaceDef->extends] : []) as $parentInterface) {
$stack[] = $parentInterface;
}
}
return false;
}
protected function getTypeConvertedArg(Node\Arg $arg, ArgInfo $argInfo): string
{
$type = $this->detectTypeOfExpr($arg->value);
$this->assertExprCanBeUsedAsValue($arg->value, 'function argument');
if (!empty($argInfo->typeCheck)) {
$this->checkCompositeTypeAssignment(
$arg,
$argInfo->typeCheck,
$argInfo->typeStr,
$arg->value,
'argument `$' . ($argInfo->phpName ?: $this->unescapeVarName($argInfo->name)) . '`'
);
}
if ($argInfo->byRef) {
if ($this->isReferenceWrapperCall($arg->value)) {
$inner = $this->unwrapReferenceWrapperCall($arg->value, $arg);
if ($this->isVarExpr($inner)) {
$arg->value = $inner;
} else {
$expr = $this->expandRefvalExpr($inner, $arg);
if ($expr !== null) {
return $expr;
}
$this->fatalError($arg, 'The refval function only accepts a variable, array element, or object property');
}
}
if ($this->isVarExpr($arg->value)) {
$var = $this->parseVariable($arg->value);
// 若参数是引用类型,可以传入未定义变量,将立即创建变量作为引用
if (!$this->hasLocalVar($var)) {
$this->addLocalVar($var, self::TYPE_VAR);
}
}
return $this->convertToRef($arg->value);
}
$expr = $this->parseOrderedArg($arg);
$expr = $this->materializeCallArgValue($arg->value, $expr);
$this->checkVarAssignExpr($arg, $argInfo->type, $type);
if ($argInfo->type === self::TYPE_VAR && $this->isVarExpr($arg->value)) {
$varName = $this->parseIdentifier($arg->value);
if ($this->isStdContainer($varName)) {
return $varName;
}
}
if ($argInfo->type === self::TYPE_OBJECT) {
$declaredClass = $argInfo->declaredClass ?: $argInfo->class;
if ($declaredClass !== '') {
$class = $this->detectDeclaredClassOfExpr($arg->value);
if ($class !== '') {
// native call 是性能热点,若静态阶段已经证明实参 is-a 声明类型,
// 就不要再生成 php::toObject($expr, target_ce) 做重复运行时检查。
// 如果无法证明,但右值是已知 concrete object,说明一定不兼容,直接编译期 fatal;
// 其他动态/外部库/any 场景保留 php::toObject() 作为运行时兜底。
if ($this->isObjectClassStaticallyAssignableTo($class, $declaredClass)) {
return $type === self::TYPE_OBJECT ? $expr : $this->convertObjectExpr($expr);
}
if ($this->isKnownConcreteObjectExpr($arg->value, $class)) {
$argName = $argInfo->phpName ?: $this->unescapeVarName($argInfo->name);
$this->fatalError($arg, "Argument `{$argName}` must be an instance of `{$declaredClass}`, `{$class}` given");
}
}
return $this->convertObjectExpr($expr, $this->getClassEntryPtr($declaredClass));
}
return $type === self::TYPE_OBJECT ? $expr : $this->convertObjectExpr($expr);
}
return $this->convertExprType($expr, $argInfo->type, $type);
}
}
Loading…
Cancel
Save