重构:在预处理阶段获取所有类、函数、常量、属性定义,解决循环依赖的问题

pull/1/head
韩天峰 5 months ago
parent 90866a803b
commit d37c979031
  1. 8
      examples/extends/A.php
  2. 21
      examples/extends/B.php
  3. 8
      examples/extends/C.php
  4. 4
      examples/extends/Foo.php
  5. 32
      examples/extends/test.php
  6. 221
      src/Php/CompilerBase.php
  7. 6
      src/Php/Entity/ConstantDef.php
  8. 5
      src/Php/Entity/FunctionDef.php
  9. 1
      src/Php/Entity/PropertyDef.php
  10. 311
      src/Php/Preprocessor.php
  11. 158
      src/Php/Translator.php

@ -0,0 +1,8 @@
<?php
class A extends Foo
{
public function __construct()
{
echo "A::__construct()\n";
}
}

@ -0,0 +1,21 @@
<?php
class B extends A
{
public function __construct()
{
echo "B::__construct()\n";
parent::__construct();
}
public function foo()
{
$this->bar2();
}
function bar2()
{
var_dump(__METHOD__);
}
}

@ -0,0 +1,8 @@
<?php
class C extends B {
public function __construct()
{
echo "C::__construct()\n";
parent::__construct();
}
}

@ -0,0 +1,4 @@
<?php
class Foo extends ArrayObject {
}

@ -1,34 +1,18 @@
<?php
class A
{
public function __construct()
{
echo "A::__construct()\n";
}
function foo_test($a, $b, $c) {
return 1;
}
class B extends A
function foo2(): void
{
public function __construct()
{
parent::__construct();
echo "B::__construct()\n";
}
public function foo()
{
var_dump(__METHOD__);
}
}
class C extends ArrayObject {
var_dump(__FUNCTION__);
return;
var_dump(__FUNCTION__);
}
function main()
{
var_dump(foo_test(1, 2, 3));
$o = new B;
$o->foo();
@ -37,4 +21,6 @@ function main()
$c->offsetSet(1, 2);
var_dump($c);
var_dump($c->foo());
foo2();
}

@ -764,11 +764,11 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php_get_prop(' . $funcId . ', ' . $this->getLiteralString($prop) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')';
}
protected function parseTypeDecl(?NodeAbstract $type, int $what): string
protected function parseTypeDecl(?NodeAbstract $type, int $what, string &$class): string
{
// 未定义类型,属性、参数默认是 var (mixed, any) ,返回值是 void
// 未定义类型视为 var (mixed, any)
if ($type === null) {
return $what === self::DECL_TYPE_OF_RETURN ? self::TYPE_VOID : self::TYPE_VAR;
return self::TYPE_VAR;
}
if ($type instanceof UnionType or $type instanceof NullableType) {
// 联合类型暂时不支持,使用 var 类型代替
@ -781,84 +781,29 @@ class CompilerBase extends \PhpAot\Core\Translator
} elseif (isset($this->zendTypeMap[$typeName])) {
return $this->getTypeFromZendType($typeName);
} else {
$class = $this->getNamespacedClassName($typeName);
return self::TYPE_OBJECT;
}
}
}
protected function parseFunctionDecl(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef
{
// .stub 存根定义 C++ Native 函数,必须设置返回值类型
if ($this->stubFile and !$v->returnType) {
// 以下魔术方法都不能声明返回值类型 __construct()/__destruct()/__clone()
if (($this->method and !in_array($this->method, ['__construct', '__destruct', '__clone'])) or !$this->method) {
$name = $this->class ? $this->class . '::' . $v->name : $v->name;
$this->fatalError($v, 'The return type of the function `' . $name . '` must be specified');
}
}
// 返回值不能是引用类型
if ($v->byRef) {
$this->fatalError($v, 'The return type of the function `' . $v->name . '` cannot be a reference type');
}
$fnName = $this->parseIdentifier($v->name);
$returnType = $this->parseTypeDecl($v->returnType, self::DECL_TYPE_OF_RETURN);
$functionDef = new FunctionDef($fnName, $returnType, $this->namespace, $this->stubFile);
// 函数返回值精确类型
if ($v->returnType != null and !($v->returnType instanceof UnionType or $v->returnType instanceof NullableType)) {
$functionDef->exactReturnType = true;
$functionDef->returnClass = $v->returnType;
}
$this->functionDef = $functionDef;
$this->parseParams($v->params, $functionDef);
// main 函数,返回值必须为 void 类型,参数必须为空或者 argc, argv 两个参数
if (!$this->class and !$this->namespace and $fnName === 'main') {
if (count($v->params) > 0) {
if (count($v->params) != 2) {
$this->fatalError($v, 'The parameters of the main function must be `(int $argc, array $argv)`.');
}
if ($returnType !== self::TYPE_VOID) {
$this->fatalError($v, 'main function must return void');
}
if (!$this->checkArgType($functionDef->argInfoList[0]->type, self::TYPE_INT)) {
$this->fatalError($v, 'The first parameter of the main function must be of type `int`.');
}
if (!$this->checkArgType($functionDef->argInfoList[1]->type, self::TYPE_ARRAY)) {
$this->fatalError($v, 'The second parameter of the main function must be of type `array`.');
}
}
}
return $functionDef;
}
/**
* @throws \Exception
*/
protected function parseFunction(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): string
{
$this->resetFunction();
$this->function = $this->parseIdentifier($v->name);
$name = $this->getFunctionName($v);
if ($this->hasFunction($name)) {
$this->functionDef = $this->getFunction($name);
} else {
$this->addFunction($name, $this->parseFunctionDecl($v));
if (isset($this->redoAfterDeclare[$name])) {
unset($this->redoAfterDeclare[$name]);
$this->climate->cyan('Received redo request, retrying...');
throw new Redo();
}
$this->function = $this->parseIdentifier($v->name);
if (!$this->hasFunction($name)) {
$this->fatalError($v, 'Function `' . $name . '` not found');
}
$this->functionDef = $this->getFunction($name);
// 类方法不要保存到 functions 中
if ($this->class) {
if ($this->methodDef) {
$this->functionDef->method = true;
$this->methodDef->functionDef = $this->functionDef;
}
if ($this->methodDef) {
$this->methodDef->functionDef = $this->functionDef;
} else {
$this->functionDefineInFile[$name] = $this->functionDef;
}
@ -884,7 +829,6 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->isReturnStmtInLastLine($v->stmts)) {
$stmts .= $this->genReturnCode();
}
$this->functionDef->completed = true;
} catch (Skip) {
$this->climate->cyan('Skip function ' . $name);
}
@ -980,6 +924,15 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->escapeVarName($expr->name);
}
protected function parseImplements(array $implements): array
{
$list = [];
foreach ($implements as $implement) {
$list[] = $this->getNamespacedClassName($implement);
}
return $list;
}
protected function parseIdentifier(NodeAbstract $expr): string
{
$type = $expr->getType();
@ -1023,95 +976,6 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
/**
* @param $params array<Node\Param>
*/
protected function parseParams(array $params, FunctionDef $functionDef): void
{
$list = [];
$functionDef->argCountRequired = count($params);
$defaultValueCount = 0;
$last = array_key_last($params);
foreach ($params as $i => $param) {
// .stub 存根定义 C++ Native 函数,必须设置函数的参数类型
if ($this->stubFile and !$param->type) {
throw new \RuntimeException('No type for ' . $this->parseIdentifier($param->var));
}
// 构造方法属性定义语法(Constructor Property Promotion)
if ($param->isPromoted()) {
if (!$this->classDef or !$this->methodDef or $this->methodDef->name !== '__construct') {
$this->fatalError($param, 'Promoted properties are not supported');
}
$name = $this->parseIdentifier($param->var);
if ($param->type instanceof NullableType) {
$type = $param->type->type;
$nullable = true;
} elseif ($param->type instanceof UnionType) {
$type = 'mixed';
$nullable = false;
} else {
$type = $param->type === null ? '' : $param->type;
$nullable = false;
}
$default = $this->parseParamDefaultValue($param->default);
$propertyDef = new PropertyDef($name, $param->flags, $type, $default, $nullable);
$this->classDef->properties[$name] = $propertyDef;
}
if ($param->variadic) {
if ($i !== $last) {
$this->fatalError($param, 'Variadic parameters must be the last parameter');
} elseif ($param->byRef) {
$this->fatalError($param, 'Variadic parameters cannot be passed by reference');
}
}
$name = $this->parseIdentifier($param->var);
if ($this->method and $name == 'this_') {
$this->fatalError($param, 'Cannot use `$this` as parameter of class method');
}
$argInfo = new ArgInfo();
$type = $this->parseParameterType($param, $argInfo, $name);
if ($param->variadic) {
$list[] = self::TYPE_ARRAY . ' ' . $name;
} else {
$list[] = $type . ' ' . $name;
}
$argInfo->name = $name;
$argInfo->type = $type;
$argInfo->byRef = $param->byRef;
$argInfo->variadic = $param->variadic;
$argInfo->property = $param->isPromoted();
if ($param->type and $param->type instanceof NullableType) {
$argInfo->nullable = true;
}
if ($param->default) {
if ($param->byRef) {
if ($this->isEmptyArray($param->default)) {
$argInfo->default = 'php::getEmptyArrayRef()';
$argInfo->defaultValue = null;
} elseif ($this->isNull($param->default)) {
$argInfo->default = 'nullptr';
$argInfo->defaultValue = null;
} else {
$this->fatalError($param, 'Only null and empty array can be used as default value for reference parameter');
}
} else {
$argInfo->default = $this->parseParamDefaultValue($param->default);
$argInfo->defaultValue = $param->default;
}
$defaultValueCount++;
} elseif ($param->variadic) {
// 变长参数可以视为空数组默认值
$defaultValueCount++;
$argInfo->default = '{}';
$argInfo->defaultValue = new Expr\Array_();
}
$functionDef->argInfoList[] = $argInfo;
}
$functionDef->params = implode(', ', $list);
$functionDef->argCountRequired -= $defaultValueCount;
}
protected function getComment(Node\Stmt $v, string $class): string
{
if ($class == 'Stmt_Expression') {
@ -1589,17 +1453,13 @@ class CompilerBase extends \PhpAot\Core\Translator
// 实际函数的返回值
$type = $this->detectExprType($v->expr);
$expr = $this->parseExpr($v->expr);
$returnType = $this->getReturnType();
// 匿名函数的返回值一定是 var
if (!$this->context->inClosure) {
// 函数定义时没有声明返回值,但函数体中有返回值,修改为实际的返回值类型
if ($this->getReturnType() === 'void') {
$this->resetReturnType($v, $type);
} elseif ($this->isNativeType($type) and $this->getReturnType() !== self::TYPE_VAR and $this->getReturnType() !== $type) {
// 返回值类型不一致,说明存在多种类型的返回值,修改为 var 表示 any
$this->resetReturnType($v, self::TYPE_VAR);
}
$returnType = $this->getReturnType();
if ($returnType === 'void') {
$this->fatalError($v, 'The return type is void, cannot return any value');
}
} else {
$returnType = self::TYPE_VAR;
}
@ -1908,10 +1768,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$nativeFunc = $this->findNativeMethod($expr, $object, $method);
if ($nativeFunc) {
$funcDef = $this->functions[$nativeFunc];
// 用户未定义类型,但函数已编译完成,通过分析代码获得了返回值类型
if ($funcDef->completed or $funcDef->exactReturnType) {
return $funcDef->returnType;
}
return $funcDef->returnType;
} elseif ($this->isTypedObject($object)) {
return $this->detectMethodCallReturnType($this->getObjectType($object), $method);
}
@ -4365,6 +4222,14 @@ class CompilerBase extends \PhpAot\Core\Translator
return $v->name->name . ':';
}
protected function parseModifiers(int $flags): int
{
if (!($flags & Modifiers::PRIVATE) and !($flags & Modifiers::PROTECTED)) {
$flags |= Modifiers::PUBLIC;
}
return $flags;
}
protected function parseConstDef(mixed $v2): string
{
foreach ($v2->consts as $const) {
@ -4567,8 +4432,10 @@ class CompilerBase extends \PhpAot\Core\Translator
$class = $this->context->objects[$object];
$nativeFunc = $this->getNativeMethod($expr, $class, $method);
// 存在 Native 类,但是没有找到方法,可能是动态调用
if (!$nativeFunc and $this->hasClass($class) and $this->getNativeMethod($expr, $class, '__call', false)) {
throw new DynamicCall();
if (!$nativeFunc) {
if ($this->hasClass($class) and $this->getNativeMethod($expr, $class, '__call', false)) {
throw new DynamicCall();
}
}
}
if ($classDef) {
@ -4619,14 +4486,20 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseParentMethodCall(Expr\StaticCall $expr): string
{
$methodStr = $this->classDef->name . '::' . $this->parseIdentifier($expr->name);
if (!$this->classDef->extends) {
$this->fatalError($expr, 'Cannot call parent method `' . $this->classDef->name . '::' . $this->parseIdentifier($expr->name) . '()` because class `' . $this->classDef->name . '` does not extend any class');
$this->fatalError($expr, 'Cannot call parent method `' . $methodStr . '()` because class `' . $this->classDef->name . '` does not extend any class');
}
if (!$this->isIdExpr($expr->name)) {
$this->fatalError($expr, 'Cannot call parent method `' . $methodStr . '()` because method name is not a literal');
}
$method = $this->identifierToStr($expr->name);
$parentClass = $this->classDef->extends;
$method = $this->parseIdentifier($expr->name);
// TODO 是否转为 native 调用
if (empty($expr->args)) {
return 'this_.callParentMethod(' . $method . ')';
return 'this_.call(' . $this->getMethodPtr($parentClass, $method) . ')';
}
return 'this_.callParentMethod(' . $method . ', ' . $this->parseCallArgs($expr->args) . ')';
return 'this_.call(' . $this->getMethodPtr($parentClass, $method) . ', ' . $this->parseCallArgs($expr->args) . ')';
}
protected function genDebugInfo(?NodeAbstract $stmt = null): string

@ -14,14 +14,14 @@ class ConstantDef
public string $type;
public int $flags;
public string $value;
public string $arrayExpr;
public string $arrayExpr = '';
public string $class = '';
public function __construct(string $name, int $flags, string $type, string $value, string $arrayExpr = '')
public function __construct(string $name, int $flags, string $type, string $value)
{
$this->name = $name;
$this->type = $type;
$this->flags = $flags;
$this->value = $value;
$this->arrayExpr = $arrayExpr;
}
}

@ -24,16 +24,13 @@ class FunctionDef
public string $namespace;
public bool $method = false;
public bool $stub = false;
public bool $completed = false;
public bool $exactReturnType = false;
public string $returnClass = '';
public function __construct(string $name, string $returnType, string $namespace, bool $stub)
public function __construct(string $name, string $returnType, string $namespace)
{
$this->name = $name;
$this->returnType = $returnType;
$this->namespace = $namespace;
$this->stub = $stub;
}
public function getNamespacedName(): string

@ -17,6 +17,7 @@ class PropertyDef
public int $flags;
public ?string $default = null;
public bool $nullable = false;
public string $class = '';
public function __construct(string $name, int $flags, string $type, ?string $default = null, bool $nullable = false)
{

@ -11,10 +11,17 @@ namespace PhpAot\Php;
use MJS\TopSort\CircularDependencyException;
use MJS\TopSort\ElementNotFoundException;
use MJS\TopSort\Implementations\StringSort;
use PhpAot\Php\Entity\ClassDef;
use PhpAot\Php\Entity\ConstantDef;
use PhpAot\Php\Entity\FunctionDef;
use PhpAot\Php\Entity\MethodDef;
use PhpAot\Php\Entity\PropertyDef;
use PhpAot\Php\Exception\SyntaxError;
use PhpAot\Php\Exception\Unsupported;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
use PhpParser\NodeAbstract;
use PhpParser\NodeFinder;
use PhpParser\NodeTraverser;
@ -161,6 +168,7 @@ class Preprocessor extends CompilerBase
$this->symbolCallInFile[$this->file] = [];
$this->resetFile();
$this->resetFunction();
$this->resetMethod();
$this->resetClass();
$this->resetNamespace();
@ -208,6 +216,10 @@ class Preprocessor extends CompilerBase
}
}
}
protected function findSymbolUsing(NodeAbstract $ast)
{
$nodeFinder = new NodeFinder();
$functionCalls = $nodeFinder->findInstanceOf($ast, Node\Expr\FuncCall::class);
@ -222,8 +234,8 @@ class Preprocessor extends CompilerBase
}
$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\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) {
@ -242,13 +254,18 @@ class Preprocessor extends CompilerBase
protected function prepareNamespace(Node\Stmt\Namespace_ $node): void
{
$this->resetClass();
$this->resetMethod();
$this->resetFunction();
$this->resetNamespace();
$this->namespace = $node->name ? $this->parseIdentifier($node->name) : '';
foreach ($node->stmts as $v2) {
$type2 = $v2->getType();
switch ($type2) {
case 'Stmt_Class':
case 'Stmt_Enum':
case 'Stmt_Trait':
$this->prepareClass($v2);
break;
case 'Stmt_Function':
@ -269,17 +286,153 @@ class Preprocessor extends CompilerBase
}
}
/**
* @param $params array<Node\Param>
*/
protected function parseParams(array $params, FunctionDef $functionDef): void
{
$list = [];
$functionDef->argCountRequired = count($params);
$defaultValueCount = 0;
$last = array_key_last($params);
foreach ($params as $i => $param) {
// .stub 存根定义 C++ Native 函数,必须设置函数的参数类型
if ($this->stubFile and !$param->type) {
throw new \RuntimeException('No type for ' . $this->parseIdentifier($param->var));
}
// 构造方法属性定义语法(Constructor Property Promotion)
if ($param->isPromoted()) {
if (!$this->classDef or !$this->methodDef or $this->methodDef->name !== '__construct') {
$this->fatalError($param, 'Promoted properties are not supported');
}
$name = $this->parseIdentifier($param->var);
if ($param->type instanceof NullableType) {
$type = $param->type->type;
$nullable = true;
} elseif ($param->type instanceof UnionType) {
$type = 'mixed';
$nullable = false;
} else {
$type = $param->type === null ? '' : $param->type;
$nullable = false;
}
$default = $this->parseParamDefaultValue($param->default);
$propertyDef = new PropertyDef($name, $param->flags, $type, $default, $nullable);
$this->classDef->properties[$name] = $propertyDef;
}
if ($param->variadic) {
if ($i !== $last) {
$this->fatalError($param, 'Variadic parameters must be the last parameter');
} elseif ($param->byRef) {
$this->fatalError($param, 'Variadic parameters cannot be passed by reference');
}
}
$name = $this->parseIdentifier($param->var);
if ($this->method and $name == 'this_') {
$this->fatalError($param, 'Cannot use `$this` as parameter of class method');
}
$argInfo = new ArgInfo();
$type = $this->parseParameterType($param, $argInfo, $name);
if ($param->variadic) {
$list[] = self::TYPE_ARRAY . ' ' . $name;
} else {
$list[] = $type . ' ' . $name;
}
$argInfo->name = $name;
$argInfo->type = $type;
$argInfo->byRef = $param->byRef;
$argInfo->variadic = $param->variadic;
$argInfo->property = $param->isPromoted();
if ($param->type and $param->type instanceof NullableType) {
$argInfo->nullable = true;
}
if ($param->default) {
if ($param->byRef) {
if ($this->isEmptyArray($param->default)) {
$argInfo->default = 'php::getEmptyArrayRef()';
$argInfo->defaultValue = null;
} elseif ($this->isNull($param->default)) {
$argInfo->default = 'nullptr';
$argInfo->defaultValue = null;
} else {
$this->fatalError($param, 'Only null and empty array can be used as default value for reference parameter');
}
} else {
$argInfo->default = $this->parseParamDefaultValue($param->default);
$argInfo->defaultValue = $param->default;
}
$defaultValueCount++;
} elseif ($param->variadic) {
// 变长参数可以视为空数组默认值
$defaultValueCount++;
$argInfo->default = '{}';
$argInfo->defaultValue = new Node\Expr\Array_();
}
$functionDef->argInfoList[] = $argInfo;
}
$functionDef->params = implode(', ', $list);
$functionDef->argCountRequired -= $defaultValueCount;
}
protected function parseFunctionDecl(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef
{
// .stub 存根定义 C++ Native 函数,必须设置返回值类型
if ($this->stubFile and !$v->returnType) {
// 以下魔术方法都不能声明返回值类型 __construct()/__destruct()/__clone()
if (($this->method and !in_array($this->method, ['__construct', '__destruct', '__clone'])) or !$this->method) {
$name = $this->class ? $this->class . '::' . $v->name : $v->name;
$this->fatalError($v, 'The return type of the function `' . $name . '` must be specified');
}
}
// 返回值不能是引用类型
if ($v->byRef) {
$this->fatalError($v, 'The return type of the function `' . $v->name . '` cannot be a reference type');
}
$fnName = $this->parseIdentifier($v->name);
$class = '';
$returnType = $this->parseTypeDecl($v->returnType, self::DECL_TYPE_OF_RETURN, $class);
$functionDef = new FunctionDef($fnName, $returnType, $this->namespace);
$functionDef->returnClass = $class;
$functionDef->stub = $this->stubFile;
$this->parseParams($v->params, $functionDef);
// main 函数,返回值必须为 void 类型,参数必须为空或者 argc, argv 两个参数
if (!$this->class and !$this->namespace and $fnName === 'main') {
if (count($v->params) > 0) {
if (count($v->params) != 2) {
$this->fatalError($v, 'The parameters of the main function must be `(int $argc, array $argv)`.');
}
if ($returnType !== self::TYPE_VOID) {
$this->fatalError($v, 'main function must return void');
}
if (!$this->checkArgType($functionDef->argInfoList[0]->type, self::TYPE_INT)) {
$this->fatalError($v, 'The first parameter of the main function must be of type `int`.');
}
if (!$this->checkArgType($functionDef->argInfoList[1]->type, self::TYPE_ARRAY)) {
$this->fatalError($v, 'The second parameter of the main function must be of type `array`.');
}
}
}
return $functionDef;
}
protected function prepareFunction(Node\Stmt\ClassMethod|Node\Stmt\Function_ $v): void
{
$this->resetFunction();
$this->function = $this->parseIdentifier($v->name);
$name = $this->getFunctionName($v);
if ($this->stubFile) {
$this->addFunction($name, $this->parseFunctionDecl($v));
} else {
$functionNameLower = strtolower($name);
if (isset($this->symbolDeclInFile[$functionNameLower])) {
$this->fatalError($v, "Duplicate function `{$functionNameLower}`");
}
$this->symbolDeclInFile[$functionNameLower] = $this->file;
if ($this->hasFunction($name)) {
$this->fatalError($v, "Duplicate function `{$name}`");
}
$functionDef = $this->parseFunctionDecl($v);
$this->addFunction($name, $functionDef);
if ($this->methodDef) {
$functionDef->method = true;
$this->methodDef->functionDef = $functionDef;
}
}
@ -298,6 +451,17 @@ class Preprocessor extends CompilerBase
$fullClassName = $this->getFullClassName();
$fullClassNameLower = strtolower($fullClassName);
if ($class instanceof Node\Stmt\Enum_) {
$flags = Modifiers::PUBLIC;
} else if (!$class instanceof Node\Stmt\Trait_) {
$flags = $class->flags;
} else {
$flags = Modifiers::PUBLIC;
}
$this->classDef = new ClassDef($this->class, $flags, $this->namespace);
$this->addClass($fullClassName, $this->classDef);
if (!empty($class->extends)) {
$this->parentClass = $this->getParentClass($class->extends);
$parentClassLower = strtolower($this->parentClass);
@ -308,8 +472,17 @@ class Preprocessor extends CompilerBase
if (!$this->isInternalClass($parentClassLower)) {
$this->symbolCallInFile[$this->file][] = $parentClassLower;
}
$this->classDef->extends = $this->parentClass;
// 是否继承了内置类
$this->classDef->inheritedFromInternalClass = $this->isInternalClass($parentClassLower);
}
if ($class instanceof Node\Stmt\Enum_) {
$this->classDef->enum = true;
}
if (!$class instanceof Node\Stmt\Trait_) {
$this->classDef->implements = $this->parseImplements($class->implements);
}
if (isset($this->symbolDeclInFile[$fullClassNameLower])) {
$this->fatalError($class, "Duplicate class `{$fullClassName}`");
}
@ -321,13 +494,17 @@ class Preprocessor extends CompilerBase
$type = $v->getType();
switch ($type) {
case 'Stmt_ClassConst':
$this->parseClassConstDef($v);
break;
case 'Stmt_Property':
$this->parseClassPropertyDef($v);
break;
case 'Stmt_Nop':
case 'Stmt_TraitUse':
case 'Stmt_EnumCase':
break;
case 'Stmt_ClassMethod':
$this->prepareMethod($v, $class);
$this->prepareClassMethod($v, $class);
break;
case 'Stmt_Expression':
$this->foundStrayCode($v);
@ -342,12 +519,118 @@ class Preprocessor extends CompilerBase
return $code;
}
protected function prepareMethod(Node\Stmt\ClassMethod $v, Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): void
protected function getMethodName(Node\Stmt\ClassMethod $v): string
{
$this->method = $v->name;
$abstract = $v->flags & Modifiers::ABSTRACT;
return $this->parseIdentifier($v->name);
}
/**
* 检查父类方法是否可以被重写,私有方法不能被重写
*/
protected function checkParentMethodCanBeOverridden(Node\Stmt\ClassMethod $v, string $name): void
{
$classDef = $this->classDef;
while (true) {
$extends = $classDef->extends;
if (!$extends) {
break;
}
// 父类是内置类
if ($classDef->inheritedFromInternalClass) {
if (Reflection::getClassMethodModifiers($extends, $name) & \ReflectionMethod::IS_PRIVATE) {
goto _error;
}
break;
}
$classDef = $this->getClass($extends);
if ($classDef->hasMethod($this->method)) {
$methodDef = $classDef->getMethod($this->method);
if ($methodDef->flags & Modifiers::PRIVATE) {
_error:
$this->fatalError($v,
'Cannot override private method `' .
$classDef->getNamespacedName(false) . '::' . $this->method . '()`');
}
}
}
}
protected function parseClassConstDef(Node\Stmt\ClassConst $v): void
{
$this->resetFunction();
$flags = $this->parseModifiers($v->flags);
$class = '';
if ($v->type) {
$type = $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_CONST, $class);
} else {
$type = null;
}
foreach ($v->consts as $const) {
if ($type === null) {
$type = match ($const->value->getType()) {
'Expr_Array' => self::TYPE_ARRAY,
'Scalar_String' => self::TYPE_STR,
default => self::TYPE_VAR,
};
}
$constName = $this->parseIdentifier($const->name);
$constValue = $this->parseIdentifier($const->value);
$constInfo = new ConstantDef($constName, $flags, $type, $constValue);
if ($this->context->beforeStmtLines) {
$arrayExpr = '';
if ($this->context->localVars) {
$arrayExpr .= $this->genScopeVarDecl();
}
$arrayExpr .= $this->parseBeforeStmtLines();
$constInfo->arrayExpr = $arrayExpr;
}
$constInfo->class = $class;
$this->classDef->constants[$constInfo->name] = $constInfo;
}
}
protected function parseClassPropertyDef(Node\Stmt\Property $v): void
{
$oriCtx = $this->context;
$this->context = $this->classDef->propertyContext;
$flags = $this->parseModifiers($v->flags);
$class = '';
$type = $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_PROPERTY, $class);
foreach ($v->props as $prop) {
$propDef = new PropertyDef($this->parseIdentifier($prop->name), $flags, $type);
if ($prop->default) {
$propDef->default = $this->parseIdentifier($prop->default);
if ($prop->default->getType() == 'Expr_Array') {
$propDef->type = self::TYPE_ARRAY;
}
}
$propDef->class = $class;
$this->classDef->properties[$propDef->name] = $propDef;
}
$this->context = $oriCtx;
}
protected function prepareClassMethod(Node\Stmt\ClassMethod $v, Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): void
{
$name = $this->getMethodName($v);
$this->method = $name;
$flags = $this->parseModifiers($v->flags);
$abstract = $flags & Modifiers::ABSTRACT;
if (!$abstract) {
$this->methodDef = new MethodDef($flags, $name);
if ($this->classDef->hasMethod($name)) {
$this->fatalError($v, "Duplicate method `{$this->method}`");
}
$this->prepareFunction($v) . PHP_EOL;
$this->checkRequiredArgNum($name, $this->methodDef, $v);
$this->classDef->addMethod($this->methodDef);
} else {
if (!($class->flags & Modifiers::ABSTRACT)) {
$this->fatalError($v, "Class {$this->class} cannot override non-abstract method {$v->name}");

@ -671,9 +671,10 @@ class Translator extends Preprocessor
$stmts = $traverser->traverse($ast);
$this->resetFile();
$this->resetFunction();
$this->resetClass();
$this->resetNamespace();
$this->resetClass();
$this->resetMethod();
$this->resetFunction();
$cppCode = '';
foreach ($stmts as $v) {
@ -798,11 +799,6 @@ class Translator extends Preprocessor
$this->classCeList = $sorter->sort();
}
protected function getMethodName(Node\Stmt\ClassMethod $v): string
{
return $this->parseIdentifier($v->name);
}
protected function getNativeMethodName(ClassDef $classDef, MethodDef $methodDef): string
{
return $this->getNativeName($methodDef->name, $classDef->namespace, $classDef->name);
@ -814,6 +810,9 @@ class Translator extends Preprocessor
$code = '';
$this->resetNamespace();
$this->resetClass();
$this->resetMethod();
$this->resetFunction();
$this->namespace = $ns;
$ns_end = '';
@ -876,45 +875,26 @@ class Translator extends Preprocessor
protected function parseClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string
{
$this->class = $this->parseIdentifier($class->name);
if ($class instanceof Node\Stmt\Enum_) {
$flags = Modifiers::PUBLIC;
$extends = '';
} else {
$flags = $class->flags;
$extends = $class->extends;
}
$fullName = $this->getFullClassName();
if ($this->hasClass($fullName)) {
$this->classDef = $this->getClass($fullName);
} else {
$this->classDef = new ClassDef($this->class, $flags, $this->namespace);
$this->addClass($fullName, $this->classDef);
}
if ($class instanceof Node\Stmt\Enum_) {
$this->classDef->enum = true;
if (!$this->hasClass($fullName)) {
$this->fatalError($class, "class {$fullName} not found");
}
$this->classDef = $this->getClass($fullName);
if ($extends) {
// 如果不是继承自内置类,需要检查父类是否存在,在预处理阶段只需检查了是否继承内置类
// 目前不允许继承自动态加载的自定义类
if ($this->classDef->extends and !$this->classDef->inheritedFromInternalClass) {
$parentClass = $this->getParentClass($class->extends);
if ($this->hasClass($parentClass)) {
$parent = $this->getClass($parentClass);
// 父类是 final 无法继承
if ($parent->flags & Modifiers::FINAL) {
$this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$parentClass}`");
}
$this->classDef->extends = $parentClass;
$this->classDef->inheritedFromInternalClass = false;
} else {
if (Reflection::isInternalClass($parentClass)) {
$this->classDef->extends = $parentClass;
$this->classDef->inheritedFromInternalClass = true;
} else {
$this->fatalError($class, "Class `{$this->class}` inherits from a non-existent class `{$parentClass}`");
}
$this->fatalError($class, "Class `{$this->class}` inherits from a non-existent class `{$parentClass}`");
}
}
$this->classDef->implements = $this->parseIdentifierList($class->implements);
$className = $this->classDef->getNamespacedName();
$this->classesDefineInFile[$className] = $this->classDef;
@ -925,17 +905,13 @@ class Translator extends Preprocessor
$type = $v->getType();
switch ($type) {
case 'Stmt_ClassConst':
$this->parseClassConstDef($v);
break;
case 'Stmt_Property':
$this->parsePropertyDef($v);
case 'Stmt_Nop':
case 'Stmt_EnumCase':
break;
case 'Stmt_ClassMethod':
$this->parseClassMethod($v, $methodCodes);
break;
case 'Stmt_EnumCase':
case 'Stmt_Nop':
break;
default:
abort($v);
}
@ -1129,118 +1105,22 @@ class Translator extends Preprocessor
return $code;
}
protected function parseClassConstDef(Node\Stmt\ClassConst $v): void
{
$this->resetFunction();
$flags = $this->parseModifiers($v->flags);
if ($v->type) {
$type = $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_CONST);
} else {
$type = null;
}
foreach ($v->consts as $const) {
if ($type === null) {
$type = match ($const->value->getType()) {
'Expr_Array' => self::TYPE_ARRAY,
'Scalar_String' => self::TYPE_STR,
default => self::TYPE_VAR,
};
}
$constName = $this->parseIdentifier($const->name);
$constValue = $this->parseIdentifier($const->value);
$arrayExpr = '';
if ($this->context->beforeStmtLines) {
if ($this->context->localVars) {
$arrayExpr .= $this->genScopeVarDecl();
}
$arrayExpr .= $this->parseBeforeStmtLines();
}
$constInfo = new ConstantDef($constName, $flags, $type, $constValue, $arrayExpr);
$this->classDef->constants[$constInfo->name] = $constInfo;
}
}
protected function parsePropertyDef(Node\Stmt\Property $v): void
{
$oriCtx = $this->context;
$this->context = $this->classDef->propertyContext;
$flags = $this->parseModifiers($v->flags);
$type = $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_PROPERTY);
foreach ($v->props as $prop) {
$propDef = new PropertyDef($this->parseIdentifier($prop->name), $flags, $type);
if ($prop->default) {
$propDef->default = $this->parseIdentifier($prop->default);
if ($prop->default->getType() == 'Expr_Array') {
$propDef->type = self::TYPE_ARRAY;
}
}
$this->classDef->properties[$propDef->name] = $propDef;
}
$this->context = $oriCtx;
}
protected function parseModifiers(int $flags): int
{
if (!($flags & Modifiers::PRIVATE) and !($flags & Modifiers::PROTECTED)) {
$flags |= Modifiers::PUBLIC;
}
return $flags;
}
protected function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void
{
$name = $this->getMethodName($v);
$this->method = $name;
$flags = $this->parseModifiers($v->flags);
$classDef = $this->classDef;
while (true) {
$extends = $classDef->extends;
if (!$extends) {
break;
}
// 父类是内置类
if ($classDef->inheritedFromInternalClass) {
if (Reflection::getClassMethodModifiers($extends, $name) & \ReflectionMethod::IS_PRIVATE) {
goto _error;
}
break;
}
$classDef = $this->getClass($extends);
if ($classDef->hasMethod($this->method)) {
$methodDef = $classDef->getMethod($this->method);
if ($methodDef->flags & Modifiers::PRIVATE) {
_error:
$this->fatalError($v,
'Cannot override private method `' .
$classDef->getNamespacedName(false) . '::' . $this->method . '()`');
}
}
}
if (!($flags & Modifiers::ABSTRACT)) {
$this->methodDef = new MethodDef($flags, $name);
$this->methodDef = $this->classDef->getMethod($name);
// 预处理阶段没有父类的信息,只能在实现阶段检查
$this->checkParentMethodCanBeOverridden($v, $name);
$methodCodes[$name] = $this->parseFunction($v);
$this->checkRequiredArgNum($name, $this->methodDef, $v);
$this->classDef->addMethod($this->methodDef);
}
$this->resetMethod();
}
protected function parseIdentifierList(array $implements): array
{
$list = [];
foreach ($implements as $implement) {
$list[] = $this->getNamespacedClassName($implement);
}
return $list;
}
protected function parseInterface(Node\Stmt\Interface_ $v): void
{
$name = $this->parseIdentifier($v->name);

Loading…
Cancel
Save