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.
201 lines
7.9 KiB
201 lines
7.9 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP.
|
|
*
|
|
* @link https://www.swoole.com/
|
|
* @contact service@swoole.com
|
|
*/
|
|
|
|
namespace TypePhp\Parser;
|
|
|
|
use TypePhp\Type;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\Node\Scalar\MagicConst;
|
|
use TypePhp\Generator\Symbol;
|
|
|
|
trait ConstantExpressionTrait
|
|
{
|
|
protected function parseConstFetch(Expr\ConstFetch $expr, bool $scalar = false): string
|
|
{
|
|
$pythonAttribute = $this->parsePythonModuleAttributeFetch($expr, $scalar);
|
|
if ($pythonAttribute !== null) {
|
|
return $pythonAttribute;
|
|
}
|
|
|
|
if ($expr->name->getType() != 'Name' and !($expr->name instanceof Node\Name\FullyQualified)) {
|
|
abort($expr);
|
|
}
|
|
$name = $this->parseIdentifier($expr->name);
|
|
$name = ltrim($name, '\\');
|
|
if (strcasecmp($name, 'null') === 0) {
|
|
return self::VALUE_NULL;
|
|
}
|
|
if (strcasecmp($name, 'true') === 0) {
|
|
return 'true';
|
|
}
|
|
if (strcasecmp($name, 'false') === 0) {
|
|
return 'false';
|
|
}
|
|
if ($this->isNameExpr($expr->name)) {
|
|
if (str_contains($name, '::')) {
|
|
$ns = explode('::', $name)[0];
|
|
$fullName = $this->getNamespacedClassName($ns[0]);
|
|
$ce = $this->getClassEntryPtr($fullName);
|
|
return Symbol::constant() . '(' . $ce . ', ' . $this->getLiteralString($ns[1]) . ')';
|
|
}
|
|
|
|
if (isset($this->useConstants[$name])) {
|
|
$name = $this->useConstants[$name];
|
|
} elseif ($expr->name->isUnqualified()) {
|
|
if ($this->namespace) {
|
|
$namespacedName = $this->namespace . '\\' . $name;
|
|
if ($this->hasConstant($namespacedName)) {
|
|
return $this->getConstant($namespacedName);
|
|
}
|
|
|
|
// PHP resolves an unqualified constant in a namespace at
|
|
// runtime: first Namespace\NAME, then the global NAME. AOT
|
|
// cannot select only the namespaced spelling because a
|
|
// define() call may execute before this fetch.
|
|
return Symbol::constant() . '('
|
|
. $this->getLiteralString($namespacedName)
|
|
. ', php::ConstantLookup::UnqualifiedInNamespace)';
|
|
}
|
|
// A class import with the same alias does not affect a bare
|
|
// constant fetch. Only `use const` participates here.
|
|
} elseif ($expr->name instanceof Node\Name\FullyQualified) {
|
|
// parseIdentifier() has already removed the leading slash.
|
|
} else {
|
|
$fullName = $this->getNamespacedClassName($name);
|
|
if ($fullName) {
|
|
$name = $fullName;
|
|
}
|
|
}
|
|
|
|
if ($this->hasConstant($name)) {
|
|
return $this->getConstant($name);
|
|
}
|
|
if ($name === 'PHP_EOL') {
|
|
return '"' . $this->escapeString(PHP_EOL) . '"';
|
|
}
|
|
if ($this->isInternalScalarConstant($name)) {
|
|
return $this->getInternalScalarConstantValue($name);
|
|
}
|
|
if ($this->isInternalConstant($name)) {
|
|
return Symbol::constant() . '(' . $this->getLiteralString($name) . ')';
|
|
}
|
|
|
|
return Symbol::constant() . '(' . $this->getLiteralString($name) . ')';
|
|
}
|
|
return Symbol::constant() . '("' . $this->escapeString($name) . '")';
|
|
}
|
|
|
|
protected function parseMagicConst(MagicConst $expr): string
|
|
{
|
|
$class = $this->classDef?->getNamespacedName(false)
|
|
?? (($this->namespace ? $this->namespace . '\\' : '') . $this->class);
|
|
$function = ($this->namespace ? $this->namespace . '\\' : '') . $this->function;
|
|
switch ($expr->getType()) {
|
|
case 'Scalar_MagicConst_Dir':
|
|
return '"' . $this->escapeString($this->dir) . '"';
|
|
case 'Scalar_MagicConst_File':
|
|
return '"' . $this->escapeString($this->file) . '"';
|
|
case 'Scalar_MagicConst_Line':
|
|
return (string) $expr->getStartLine();
|
|
case 'Scalar_MagicConst_Function':
|
|
return '"' . $this->escapeString($function) . '"';
|
|
case 'Scalar_MagicConst_Class':
|
|
if (!$this->classDef) {
|
|
$this->fatalError($expr, 'The magic constant `__CLASS__` is not allowed in global scope');
|
|
}
|
|
if ($this->classDef->trait) {
|
|
return Symbol::getCalledClass();
|
|
}
|
|
return '"' . $this->escapeString($class) . '"';
|
|
case 'Scalar_MagicConst_Trait':
|
|
if ($this->methodDef?->traitOrigin !== '') {
|
|
return '"' . $this->escapeString($this->methodDef->traitOrigin) . '"';
|
|
}
|
|
if (!$this->classDef or !$this->classDef->trait) {
|
|
$this->fatalError($expr, 'The magic constant `__TRAIT__` is not allowed in global scope');
|
|
}
|
|
return '"' . $this->escapeString($class) . '"';
|
|
case 'Scalar_MagicConst_Method':
|
|
return '"' . $this->escapeString($class) . '::' . $this->escapeString($this->method) . '"';
|
|
default:
|
|
abort($expr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected function detectConstType($expr): string
|
|
{
|
|
$name = $this->parseIdentifier($expr->name);
|
|
$name = ltrim($name, '\\');
|
|
|
|
// true and false are language constants and never participate in
|
|
// namespace fallback. Keep type resolution consistent with
|
|
// parseConstFetch(), which handles them before resolving the name.
|
|
if (strcasecmp($name, 'true') === 0 || strcasecmp($name, 'false') === 0) {
|
|
return Type::BOOL;
|
|
}
|
|
|
|
if (isset($this->useConstants[$name])) {
|
|
$name = $this->useConstants[$name];
|
|
} elseif ($expr->name->isUnqualified() && $this->namespace) {
|
|
$namespacedName = $this->namespace . '\\' . $name;
|
|
if ($this->hasConstant($namespacedName)) {
|
|
return $this->getConstantType($namespacedName);
|
|
}
|
|
|
|
// PHP checks Namespace\NAME before falling back to global NAME.
|
|
// A runtime define() can therefore shadow even an internal global
|
|
// constant, so its type cannot be inferred statically here.
|
|
return Type::VAR;
|
|
} elseif (!($expr->name instanceof Node\Name\FullyQualified)) {
|
|
$name = $this->getNamespacedClassName($name);
|
|
}
|
|
|
|
if ($this->hasConstant($name)) {
|
|
return $this->getConstantType($name);
|
|
}
|
|
if ($this->isInternalConstant($name)) {
|
|
return $this->getTypeFromZendType(gettype($this->internalConstants[$name]));
|
|
}
|
|
if ($name === 'NAN' or $name === 'INF') {
|
|
return Type::FLOAT;
|
|
}
|
|
return Type::VAR;
|
|
}
|
|
|
|
protected function isInternalScalarConstant(string $name): bool
|
|
{
|
|
return $this->isInternalConstant($name) && is_scalar($this->internalConstants[$name]);
|
|
}
|
|
|
|
protected function getInternalScalarConstantValue(string $name): string
|
|
{
|
|
$value = $this->internalConstants[$name];
|
|
if (is_int($value)) {
|
|
return $this->genIntegerLiteral($value);
|
|
}
|
|
if (is_float($value)) {
|
|
if (is_nan($value)) {
|
|
return self::VALUE_NAN;
|
|
}
|
|
if (is_infinite($value)) {
|
|
return $value > 0 ? self::VALUE_INF : '-' . self::VALUE_INF;
|
|
}
|
|
return $this->genCValue($value);
|
|
}
|
|
if (is_bool($value)) {
|
|
return $value ? 1 : 0;
|
|
}
|
|
if (is_string($value)) {
|
|
return $this->genCharPtr($value, true);
|
|
}
|
|
$this->error('Unsupported constant type: ' . gettype($value));
|
|
}
|
|
}
|
|
|