TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
276 lines
11 KiB
276 lines
11 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP.
|
|
*
|
|
* Resolves static and dynamic class constant fetches.
|
|
*/
|
|
|
|
namespace TypePhp\Parser;
|
|
|
|
use TypePhp\Type;
|
|
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\NodeAbstract;
|
|
use TypePhp\Generator\Symbol;
|
|
use TypePhp\Resolver\Reflection;
|
|
|
|
trait ClassConstantFetchTrait
|
|
{
|
|
/**
|
|
* Return true when a class constant fetch can be evaluated at the hoisted
|
|
* local declaration without changing PHP execution order. Only a literal
|
|
* ::class name, constants owned by a statically known TypePHP class and
|
|
* public scalar constants from an internal class/interface are eligible.
|
|
* ZendVM lookup, late static binding and dynamic operands stay at their
|
|
* original source position.
|
|
*/
|
|
protected function isHoistSafeClassConstFetch(Expr\ClassConstFetch $expr): bool
|
|
{
|
|
if (!$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) {
|
|
return false;
|
|
}
|
|
if ($this->resolvePythonModule($expr->class) !== null) {
|
|
return false;
|
|
}
|
|
|
|
$class = $this->parseIdentifier($expr->class);
|
|
if ($class === 'static') {
|
|
return false;
|
|
}
|
|
if ($class === 'self' || $class === 'this_') {
|
|
if (!$this->classDef) {
|
|
return false;
|
|
}
|
|
$class = '\\' . $this->getFullClassName();
|
|
} elseif ($class === 'parent') {
|
|
if (!$this->classDef || !$this->classDef->extends) {
|
|
return false;
|
|
}
|
|
$class = '\\' . $this->classDef->extends;
|
|
}
|
|
|
|
$class = $this->getNamespacedClassName($class);
|
|
$const = $this->parseIdentifier($expr->name);
|
|
if (strcasecmp($const, 'class') === 0) {
|
|
return true;
|
|
}
|
|
if ($this->hasClass($class)) {
|
|
if ($this->getClass($class)->enum) {
|
|
return false;
|
|
}
|
|
$nativeConst = $this->findNativeClassConst($expr, $class, $const);
|
|
return $nativeConst !== false
|
|
&& !$this->classConstantValueRequiresRuntimeCall($nativeConst);
|
|
}
|
|
|
|
return $this->getInternalScalarClassConstant($class, $const) !== null;
|
|
}
|
|
|
|
private function classConstantValueRequiresRuntimeCall(string $value): bool
|
|
{
|
|
// get_str() is a pure accessor for the module's immutable literal pool,
|
|
// so moving it into a hoisted local initializer preserves semantics.
|
|
$value = preg_replace('/\bget_str\(\d+\)/', '', $value);
|
|
return preg_match('/\b[A-Za-z_][A-Za-z0-9_:]*\s*\(/', $value ?? '') === 1;
|
|
}
|
|
|
|
/** @return array{mixed}|null */
|
|
private function getInternalScalarClassConstant(string $class, string $const): ?array
|
|
{
|
|
if (!$this->isInternalClass($class) && !$this->isInternalInterface($class)) {
|
|
return null;
|
|
}
|
|
|
|
$reflection = Reflection::getClass($class);
|
|
$constant = $reflection?->getReflectionConstant($const);
|
|
if ($constant === false || $constant === null || !$constant->isPublic()) {
|
|
return null;
|
|
}
|
|
|
|
$value = $constant->getValue();
|
|
return is_scalar($value) ? [$value] : null;
|
|
}
|
|
|
|
protected function parseClassConstFetch(Expr\ClassConstFetch $expr): string
|
|
{
|
|
if (!$this->isNameExpr($expr->class)) {
|
|
$this->assertNotNativeObjectDynamicClassTarget($expr->class, $expr);
|
|
}
|
|
$this->rejectPythonModuleClassConstantFetch($expr);
|
|
|
|
if (!$this->isIdExpr($expr->name)) {
|
|
return $this->parseDynamicClassConstNameFetch($expr);
|
|
}
|
|
|
|
if (!$this->isNameExpr($expr->class)) {
|
|
return $this->parseDynamicClassConstFetch($expr);
|
|
}
|
|
|
|
$class = $this->parseIdentifier($expr->class);
|
|
$self = false;
|
|
if ($class === 'self' or $class === 'this_') {
|
|
$self = true;
|
|
// Trait methods are compiled only after their AST is composed into
|
|
// the consuming class. During trait preprocessing, however, class
|
|
// constant initializers still belong to the trait itself. Resolve
|
|
// `self` lexically in both cases; rewriting it to `static` would
|
|
// incorrectly require a method scope for expressions such as
|
|
// `const B = [...self::A]`.
|
|
$class = '\\' . $this->getFullClassName();
|
|
} elseif ($class === 'parent') {
|
|
if (!$this->classDef || !$this->classDef->extends) {
|
|
$this->fatalError($expr, 'Cannot use "parent" outside a class or class does not extend any class');
|
|
}
|
|
// extends is already fully resolved. Keep the leading slash so the
|
|
// current namespace is not applied again below.
|
|
$class = '\\' . $this->classDef->extends;
|
|
$self = true;
|
|
}
|
|
|
|
$const = $this->escapeString($this->parseIdentifier($expr->name));
|
|
if ($class === 'static') {
|
|
if ($this->classDef?->nativeObject) {
|
|
$this->fatalError(
|
|
$expr,
|
|
'Native classes do not support late static binding; use `self::` or a concrete class name',
|
|
);
|
|
}
|
|
if (!$this->methodDef) {
|
|
$this->fatalError($expr, "The 'static' keyword can only be used as the class name in class methods");
|
|
}
|
|
if ($const === 'class') {
|
|
return Symbol::getCalledClass();
|
|
} else {
|
|
return Symbol::constant() . '(' . Symbol::getCalledCe() . ', ' . $this->getLiteralString($const) . ')';
|
|
}
|
|
}
|
|
|
|
if ($self or $this->isNameExpr($expr->class)) {
|
|
$class = $this->getNamespacedClassName($class);
|
|
}
|
|
if ($const === 'class') {
|
|
if ($self or $this->isNameExpr($expr->class)) {
|
|
return $this->getLiteralString($class);
|
|
}
|
|
}
|
|
if (($self or $this->isNameExpr($expr->class)) and $this->isIdExpr($expr->name)) {
|
|
if ($this->hasClass($class)) {
|
|
$classDef = $this->getClass($class);
|
|
if ($classDef->enum) {
|
|
$ce = $this->getClassEntryPtr($class);
|
|
return 'php::getEnumCase(' . $ce . ', ' . $this->getLiteralString($const) . ')';
|
|
}
|
|
$nativeConst = $this->findNativeClassConst($expr, $class, $const);
|
|
if ($nativeConst) {
|
|
return $nativeConst;
|
|
}
|
|
}
|
|
$internalConst = $this->getInternalScalarClassConstant($class, $const);
|
|
if ($internalConst !== null) {
|
|
return $this->genInternalScalarConstantValue($internalConst[0]);
|
|
}
|
|
$ce = $this->getClassEntryPtr($class);
|
|
return Symbol::constant() . '(' . $ce . ', ' . $this->getLiteralString($const) . ')';
|
|
}
|
|
$name = $class . '::' . $const;
|
|
$name = $this->getLiteralString($name);
|
|
return Symbol::constant() . '(' . $name . ')';
|
|
}
|
|
|
|
protected function parseDynamicClassConstFetch(Expr\ClassConstFetch $expr): string
|
|
{
|
|
$const = $this->escapeString($this->parseIdentifier($expr->name));
|
|
|
|
// PhpParser represents `$this::CONST` as a dynamic class target even
|
|
// though the receiver class is the class currently being compiled.
|
|
// TypePHP class constants are compile-time data, so do not route this
|
|
// common path through get_class(), string concatenation and ZendVM's
|
|
// global constant table.
|
|
if ($this->isVarExpr($expr->class)
|
|
&& $this->parseVariable($expr->class) === 'this_'
|
|
&& $this->classDef
|
|
&& $this->hasClass($this->getFullClassName())
|
|
) {
|
|
$nativeConst = $this->findNativeClassConst(
|
|
$expr,
|
|
$this->getFullClassName(),
|
|
$const,
|
|
);
|
|
if ($nativeConst !== false) {
|
|
return $nativeConst;
|
|
}
|
|
}
|
|
|
|
$target = $this->materializeDynamicClassConstTarget($expr->class);
|
|
|
|
if ($const === 'class') {
|
|
return 'php::fn::get_class(' . $target . ')';
|
|
}
|
|
|
|
$scope = $this->methodDef && $this->classDef
|
|
? $this->getClassEntryPtr($this->getFullClassName())
|
|
: 'nullptr';
|
|
return 'php::classConstant('
|
|
. $target . ', '
|
|
. $this->getLiteralString($const) . ', '
|
|
. $scope
|
|
. ')';
|
|
}
|
|
|
|
protected function parseDynamicClassConstNameFetch(Expr\ClassConstFetch $expr): string
|
|
{
|
|
$scope = $this->methodDef && $this->classDef
|
|
? $this->getClassEntryPtr($this->getFullClassName())
|
|
: 'nullptr';
|
|
|
|
if (!$this->isNameExpr($expr->class)) {
|
|
// PHP evaluates the class target before the dynamic constant name.
|
|
$target = $this->materializeDynamicClassConstOperand($expr->class, 'class constant target');
|
|
$name = $this->materializeDynamicClassConstOperand($expr->name, 'class constant name');
|
|
return 'php::classConstant(' . $target . ', ' . $name . ', ' . $scope . ')';
|
|
}
|
|
|
|
$class = $this->parseIdentifier($expr->class);
|
|
if ($class === 'static') {
|
|
if ($this->classDef?->nativeObject) {
|
|
$this->fatalError(
|
|
$expr,
|
|
'Native classes do not support late static binding; use `self::` or a concrete class name',
|
|
);
|
|
}
|
|
if (!$this->methodDef) {
|
|
$this->fatalError($expr, "The 'static' keyword can only be used as the class name in class methods");
|
|
}
|
|
$ce = Symbol::getCalledCe();
|
|
} elseif ($class === 'self' or $class === 'this_') {
|
|
$ce = $this->getClassEntryPtr($this->getFullClassName());
|
|
} elseif ($class === 'parent') {
|
|
if (!$this->classDef || !$this->classDef->extends) {
|
|
$this->fatalError($expr, 'Cannot use "parent" outside a class or class does not extend any class');
|
|
}
|
|
$ce = $this->getClassEntryPtr($this->classDef->extends);
|
|
} else {
|
|
$ce = $this->getClassEntryPtr($this->getNamespacedClassName($class));
|
|
}
|
|
|
|
$name = $this->materializeDynamicClassConstOperand($expr->name, 'class constant name');
|
|
return 'php::classConstant(' . $ce . ', ' . $name . ', ' . $scope . ')';
|
|
}
|
|
|
|
protected function materializeDynamicClassConstTarget(NodeAbstract $expr): string
|
|
{
|
|
return $this->materializeDynamicClassConstOperand($expr, 'class constant target');
|
|
}
|
|
|
|
protected function materializeDynamicClassConstOperand(NodeAbstract $expr, string $description): string
|
|
{
|
|
$this->assertExprCanBeUsedAsValue($expr, $description);
|
|
[$value, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr);
|
|
$tmpVar = $this->addTmpVar(Type::VAR);
|
|
$this->appendCapturedStmtLinesToContext($beforeStmts);
|
|
$this->context->beforeStmtLines[] = $tmpVar . ' = ' . $value . ';';
|
|
$this->appendCapturedStmtLinesToContext($afterStmts);
|
|
return $tmpVar;
|
|
}
|
|
|
|
}
|
|
|