diff --git a/src/CompilerBase.php b/src/CompilerBase.php index c896b1e1..4959a714 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.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(); diff --git a/src/Resolver/DeclarationSymbolTrait.php b/src/Resolver/DeclarationSymbolTrait.php new file mode 100644 index 00000000..9c9443fe --- /dev/null +++ b/src/Resolver/DeclarationSymbolTrait.php @@ -0,0 +1,123 @@ +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); + } + +} + diff --git a/src/TypeSystem/NativeTypeCompatibilityTrait.php b/src/TypeSystem/NativeTypeCompatibilityTrait.php new file mode 100644 index 00000000..70b97389 --- /dev/null +++ b/src/TypeSystem/NativeTypeCompatibilityTrait.php @@ -0,0 +1,216 @@ +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); + } + +} +