diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php index 9bbaf678..c60ea94c 100644 --- a/phpunit/bootstrap.php +++ b/phpunit/bootstrap.php @@ -1,9 +1,4 @@ addFiles([$testFile]); + $compiler->convert($testFile); + } catch (TestError $exception) { + $this->assertStringContainsString($expected, $exception->getMessage()); + return; + } + $this->fail(); + } + + public function testStaticVar() + { + $this->exec('Duplicate static variable', 'duplicate_01.php'); + } + + public function testFunction() + { + $this->exec('Duplicate function', 'duplicate_02.php'); + } + + public function testClass() + { + $this->exec('Duplicate class', 'duplicate_03.php'); + } +} diff --git a/phpunit/src/ErrorTest.php b/phpunit/src/ErrorTest.php deleted file mode 100644 index a0230087..00000000 --- a/phpunit/src/ErrorTest.php +++ /dev/null @@ -1,23 +0,0 @@ -addFiles([$file]); - $o->convert($file); - } catch (TestError $exception) { - $this->assertStringContainsString('Duplicate static variable', $exception->getMessage()); - return; - } - $this->fail(); - } -} diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index ccf0396e..831eb2b4 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -11,6 +11,7 @@ namespace PhpAot\Php; use League\CLImate\CLImate; use PhpAot\Php\Context\FunctionContext; use PhpAot\Php\Entity\ClassDef; +use PhpAot\Php\Entity\ConstantDef; use PhpAot\Php\Entity\FunctionDef; use PhpAot\Php\Entity\InterfaceDef; use PhpAot\Php\Entity\MethodDef; @@ -136,15 +137,7 @@ class CompilerBase extends \PhpAot\Core\Translator 'php_aot_helper.h', ]; protected array $localHeaders = []; - - /** - * 存储所有函数、类方法的定义,key 是 native name,命名空间需要转为 `_`,并且必须为小写 - * @var array - */ - protected array $nativeFunctions = []; protected array $internalFunctions = []; - protected array $nativeConstants = []; - /** * 存储所有函数、类方法的声明,key 是 符号名称,Value 是函数、类方法所在的文件名称 * @var array @@ -185,28 +178,28 @@ class CompilerBase extends \PhpAot\Core\Translator protected string $class = ''; protected string $parentClass = ''; protected string $interface = ''; - - /** - * key 类名,包含命名空间 - * @var array - */ - protected array $classes = []; - /** * @var array */ protected array $interfaces = []; - /** + * 存储所有函数、类方法的定义,key 是 native name,命名空间需要转为 `_`,并且必须为小写 * @var array */ protected array $functions = []; - + /** + * key 类名,包含命名空间 + * @var array + */ + protected array $classes = []; + /** + * @var array + */ + protected array $constants = []; /** * @var array */ protected array $classesDefineInFile = []; - /** * @var array */ @@ -837,10 +830,10 @@ class CompilerBase extends \PhpAot\Core\Translator $this->resetFunction(); $this->function = $this->parseIdentifier($v->name); $name = $this->getFunctionName($v); - if (!empty($this->nativeFunctions[$name])) { - $this->functionDef = $this->nativeFunctions[$name]; + if ($this->hasFunction($name)) { + $this->functionDef = $this->getFunction($name); } else { - $this->nativeFunctions[$name] = $this->parseFunctionDecl($v); + $this->addFunction($name, $this->parseFunctionDecl($v)); if (isset($this->redoAfterDeclare[$name])) { unset($this->redoAfterDeclare[$name]); $this->climate->cyan('Received redo request, retrying...'); @@ -856,7 +849,6 @@ class CompilerBase extends \PhpAot\Core\Translator $this->methodDef->functionDef = $this->functionDef; } } else { - $this->functions[$name] = $this->functionDef; $this->functionDefineInFile[$name] = $this->functionDef; } @@ -1568,10 +1560,10 @@ class CompilerBase extends \PhpAot\Core\Translator protected function addStaticVar(string $name, string $type): void { - $this->context->staticVars[$name] = $type; if ($this->hasStaticVar($name)) { $this->error('Duplicate static variable `$' . $name . '`'); } + $this->context->staticVars[$name] = $type; $this->addGlobalVar($this->getStaticVarName($name), $type); } @@ -1594,21 +1586,11 @@ class CompilerBase extends \PhpAot\Core\Translator $this->globalVars[$name] = $type; } - protected function addClass(string $name, ClassDef $classDef): void - { - $this->classes[$this->escapeClass($name)] = $classDef; - } - protected function addObject(string $name, string $class): void { $this->context->objects[$name] = $class; } - protected function addFunction(string $name, FunctionDef $functionDef): void - { - $this->functions[$this->escapeFunction($name)] = $functionDef; - } - protected function hasVar(string $name): bool { return $this->hasLocalVar($name) || $this->hasGlobalVar($name) || $this->hasStaticVar($name); @@ -1619,39 +1601,45 @@ class CompilerBase extends \PhpAot\Core\Translator return isset($this->context->localVars[$name]); } + protected function addFunction(string $name, FunctionDef $functionDef): void + { + $this->functions[$this->escapeFunction($name)] = $functionDef; + } /** * @param string $name 必须传入带有完整命名空间的类名,将会自动转义为 native name */ - protected function hasNativeClass(string $name): bool + protected function hasFunction(string $name): bool { - return array_key_exists($this->escapeClass($name), $this->classes); + return array_key_exists($this->escapeFunction($name), $this->functions); } - protected function getNativeClass(string $name): ClassDef + protected function getFunction(string $name): FunctionDef { - return $this->classes[$this->escapeClass($name)]; + return $this->functions[$this->escapeFunction($name)]; } - /** - * @param string $name 必须传入带有完整命名空间的类名,将会自动转义为 native name - */ - protected function hasNativeFunction(string $name): bool + protected function addClass(string $name, ClassDef $classDef): void + { + $this->classes[$this->escapeClass($name)] = $classDef; + } + + protected function getClass(string $name): ClassDef { - return array_key_exists($this->escapeFunction($name), $this->nativeFunctions); + return $this->classes[$this->escapeClass($name)]; } - protected function getNativeFunction(string $name): FunctionDef + protected function hasClass(string $name): bool { - return $this->nativeFunctions[$this->escapeFunction($name)]; + return array_key_exists($this->escapeClass($name), $this->classes); } - protected function checkNativeFunction(string $name): void + protected function checkFunction(string $name): void { // 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误 // 跳过,稍后再处理 if (isset($this->symbolDeclInFile[$name]) and $this->symbolDeclInFile[$name] === $this->file - and !$this->hasNativeFunction($name)) { + and !$this->hasFunction($name)) { $this->redoAfterDeclare[$name] = true; throw new Skip(); } @@ -1670,11 +1658,11 @@ class CompilerBase extends \PhpAot\Core\Translator protected function getNativeMethod(CallLike $expr, string $class, string $method, bool $checkArgs = true): string|false { - if (!$this->hasNativeClass($class)) { + if (!$this->hasClass($class)) { return false; } - $classDef = $this->getClassDef($class); + $classDef = $this->getClass($class); $methodDef = null; // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 while ($classDef) { @@ -1682,7 +1670,7 @@ class CompilerBase extends \PhpAot\Core\Translator if (!$classDef->extends) { return false; } - if (!$this->hasNativeClass($classDef->extends)) { + if (!$this->hasClass($classDef->extends)) { if ($classDef->inheritedFromInternalClass) { if (!Reflection::hasMethod($classDef->extends, $method) and !Reflection::hasMethod($classDef->extends, $method . '__call')) { $this->fatalError($expr, 'Class `' . $classDef->getNamespacedName() . '` inherits from a internal class, but the class `' . @@ -1694,7 +1682,7 @@ class CompilerBase extends \PhpAot\Core\Translator } return false; } - $classDef = $this->getClassDef($classDef->extends); + $classDef = $this->getClass($classDef->extends); } else { $methodDef = $classDef->getMethod($method); break; @@ -1714,13 +1702,13 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->getNativeName($method, $classDef->namespace, $classDef->name); } - protected function getNativeClassConst(NodeAbstract $expr, string $class, string $const): string|false + protected function getClassConst(NodeAbstract $expr, string $class, string $const): string|false { - if (!$this->hasNativeClass($class)) { + if (!$this->hasClass($class)) { return false; } - $classDef = $this->getClassDef($class); + $classDef = $this->getClass($class); $constDef = null; // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法 while ($classDef) { @@ -1728,10 +1716,10 @@ class CompilerBase extends \PhpAot\Core\Translator if (!$classDef->extends) { return false; } - if (!$this->hasNativeClass($classDef->extends)) { + if (!$this->hasClass($classDef->extends)) { return false; } - $classDef = $this->getClassDef($classDef->extends); + $classDef = $this->getClass($classDef->extends); } else { $constDef = $classDef->getConstant($const); break; @@ -1747,11 +1735,6 @@ class CompilerBase extends \PhpAot\Core\Translator } } - protected function getClassDef(string $name): ClassDef - { - return $this->classes[$this->escapeClass($name)]; - } - protected function resetReturnType(Node\Stmt\Return_ $node, string $type): void { $oriType = $this->functionDef->returnType; @@ -1816,8 +1799,8 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Expr_FuncCall': if ($this->isNameExpr($expr->name)) { $name = $this->parseIdentifier($expr->name); - if ($this->hasNativeFunction($name)) { - return $this->getNativeFunction($name)->returnType; + if ($this->hasFunction($name)) { + return $this->getFunction($name)->returnType; } return $this->detectFuncCallReturnType($name); } @@ -1828,7 +1811,7 @@ class CompilerBase extends \PhpAot\Core\Translator $method = $this->parseIdentifier($expr->name); $nativeFunc = $this->findNativeMethod($expr, $object, $method); if ($nativeFunc) { - $funcDef = $this->nativeFunctions[$nativeFunc]; + $funcDef = $this->functions[$nativeFunc]; // 此函数尚未完成解析,仅经过初步的预处理,无法获得准确的返回值类型 if ($funcDef->completed) { return $funcDef->returnType; @@ -2323,8 +2306,8 @@ class CompilerBase extends \PhpAot\Core\Translator if (str_contains($nativeFunc, '\\')) { $nativeFunc = $this->escapeNamespace($nativeFunc); } - $this->checkNativeFunction($nativeFunc); - if ($this->hasNativeFunction($nativeFunc)) { + $this->checkFunction($nativeFunc); + if ($this->hasFunction($nativeFunc)) { return $nativeFunc; } } @@ -2351,7 +2334,7 @@ class CompilerBase extends \PhpAot\Core\Translator $nativeFn = $this->findNativeFunction($name); if ($nativeFn) { $expr->setAttribute('nativeCall', $nativeFn); - $this->checkNativeCallArgs($expr, $this->getNativeFunction($nativeFn), $expr->args, $name); + $this->checkNativeCallArgs($expr, $this->getFunction($nativeFn), $expr->args, $name); try { return self::PREFIX . $nativeFn . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')'; } catch (PlaceHolder) { @@ -2387,7 +2370,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseNativeCallArgs(array $callArgs, string $nativeFunc): string { $argList = []; - $functionDef = $this->nativeFunctions[$nativeFunc]; + $functionDef = $this->functions[$nativeFunc]; $args = []; $hasNamedArg = false; // 对命名参数进行重排 @@ -2966,8 +2949,8 @@ class CompilerBase extends \PhpAot\Core\Translator $cePtr = 'php_get_called_ce(this_)'; } else { $className = $this->getNamespacedClassName($className); - if ($this->hasNativeClass($className)) { - $classDef = $this->getClassDef($className); + if ($this->hasClass($className)) { + $classDef = $this->getClass($className); if ($classDef->flags & Modifiers::ABSTRACT) { $this->fatalError($expr, "abstract class `{$className}` cannot be instantiated"); } @@ -3113,10 +3096,10 @@ class CompilerBase extends \PhpAot\Core\Translator protected function getArgInfo(Node $arg, string $funcName, int $index): ArgInfo { - if (!isset($this->nativeFunctions[$funcName])) { + if (!isset($this->functions[$funcName])) { $this->fatalError($arg, "Function `{$funcName}` is undefined, you must adjust the order of function definition"); } - $funcDef = $this->nativeFunctions[$funcName]; + $funcDef = $this->functions[$funcName]; if (!array_key_exists($index, $funcDef->argInfoList)) { $this->fatalError($arg, "Argument `{$index}` of function `{$funcName}` not found"); } @@ -3704,7 +3687,7 @@ class CompilerBase extends \PhpAot\Core\Translator { if ($this->isVarExpr($expr->var)) { $this->context->inAssignExpr = true; - $left = $this->parseIdentifier($expr->var); + $left = $this->parseIdentifier($expr->var); $this->context->inAssignExpr = false; if (!$this->hasVar($left)) { $this->addLocalVar($left, self::TYPE_REF); @@ -3727,9 +3710,8 @@ class CompilerBase extends \PhpAot\Core\Translator return $left . ' = ' . $object . '.attrRef(' . $prop . ')'; } - } else { - $this->fatalError($expr, 'Cannot assign reference to non-variable'); } + $this->fatalError($expr, 'Cannot assign reference to non-variable'); } protected function parseMethodCall(Node\Expr\MethodCall $expr): string @@ -3863,7 +3845,7 @@ class CompilerBase extends \PhpAot\Core\Translator if ($callScope) { $nativeFunc = $this->getNativeMethod($expr, $class, $method); // 存在 Native 类,但是没有找到方法,可能是动态调用 - if (!$nativeFunc and $this->hasNativeClass($class) and $this->getNativeMethod($expr, $class, '__callStatic', false)) { + if (!$nativeFunc and $this->hasClass($class) and $this->getNativeMethod($expr, $class, '__callStatic', false)) { $dynamicCall = true; } @@ -3922,11 +3904,11 @@ class CompilerBase extends \PhpAot\Core\Translator } $fullName = $this->getNamespacedClassName($class); - if (!$this->hasNativeClass($fullName)) { + if (!$this->hasClass($fullName)) { return null; } - $classDef = $this->getClassDef($fullName); + $classDef = $this->getClass($fullName); if ($classDef->hasProperty($prop)) { $propDef = $classDef->getProperty($prop); if ($propDef->isStatic()) { @@ -3947,8 +3929,8 @@ class CompilerBase extends \PhpAot\Core\Translator $scope = $this->class ? ltrim($namespace . '\\' . $class, '\\') : ''; while (true) { - if ($this->hasNativeClass($findClass)) { - $classDef = $this->getClassDef($findClass); + if ($this->hasClass($findClass)) { + $classDef = $this->getClass($findClass); if ($classDef->hasProperty($property)) { $propertyDef = $classDef->getProperty($property); if ($propertyDef->isPublic()) { @@ -4021,13 +4003,13 @@ class CompilerBase extends \PhpAot\Core\Translator return '"' . $this->escapeString($class) . '"'; } if (($self or $this->isNameExpr($expr->class)) and $this->isIdExpr($expr->name)) { - if ($this->hasNativeClass($class)) { - $classDef = $this->getClassDef($class); + if ($this->hasClass($class)) { + $classDef = $this->getClass($class); if ($classDef->enum) { $ce = $this->getClassEntryPtr($class); return 'php::getEnumCase(' . $ce . ', ' . $this->getLiteralString($const) . ')'; } - $nativeConst = $this->getNativeClassConst($expr, $class, $const); + $nativeConst = $this->getClassConst($expr, $class, $const); if ($nativeConst) { return $nativeConst; } @@ -4157,12 +4139,12 @@ class CompilerBase extends \PhpAot\Core\Translator $constInfo->type = $this->detectStrValueType($value); $constInfo->namespace = $this->namespace; $constInfo->name = $name; - $this->nativeConstants[$this->escapeNamespace($name)] = $constInfo; + $this->constants[$this->escapeNamespace($name)] = $constInfo; } protected function hasConstant(string $name): bool { - return isset($this->nativeConstants[$this->escapeNamespace($name)]); + return isset($this->constants[$this->escapeNamespace($name)]); } protected function getConstant(string $name): string @@ -4172,7 +4154,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function getConstantType(string $name): string { - return $this->nativeConstants[$this->escapeNamespace($name)]->type; + return $this->constants[$this->escapeNamespace($name)]->type; } protected function detectStrValueType(mixed $constant): string @@ -4314,7 +4296,7 @@ class CompilerBase extends \PhpAot\Core\Translator $class = $this->context->objects[$object]; $nativeFunc = $this->getNativeMethod($expr, $class, $method); // 存在 Native 类,但是没有找到方法,可能是动态调用 - if (!$nativeFunc and $this->hasNativeClass($class) and $this->getNativeMethod($expr, $class, '__call', false)) { + if (!$nativeFunc and $this->hasClass($class) and $this->getNativeMethod($expr, $class, '__call', false)) { throw new DynamicCall(); } } @@ -4329,8 +4311,8 @@ class CompilerBase extends \PhpAot\Core\Translator return false; } if ($nativeFunc) { - $this->checkNativeFunction($nativeFunc); - if ($this->hasNativeFunction($nativeFunc)) { + $this->checkFunction($nativeFunc); + if ($this->hasFunction($nativeFunc)) { return $nativeFunc; } } @@ -4460,7 +4442,7 @@ class CompilerBase extends \PhpAot\Core\Translator } if ($this->isCallExpr($expr->expr)) { $nativeCall = $expr->expr->getAttribute('nativeCall'); - if ($nativeCall and $this->nativeFunctions[$nativeCall]->returnType === self::TYPE_VOID) { + if ($nativeCall and $this->functions[$nativeCall]->returnType === self::TYPE_VOID) { return $beforeCode . PHP_EOL . $code . ';' . PHP_EOL . 'return ' . self::VALUE_NULL . ';'; } } diff --git a/src/Php/CompilerTest.php b/src/Php/CompilerTest.php index 488671ce..7adb484b 100644 --- a/src/Php/CompilerTest.php +++ b/src/Php/CompilerTest.php @@ -10,14 +10,10 @@ namespace PhpAot\Php; class CompilerTest extends Translator { - protected static ?self $instance = null; - - public static function getInstance(string $rootPath = ''): CompilerTest + public static function create(string $rootPath = ''): CompilerTest { - if (!self::$instance) { - self::$instance = new self($rootPath); - self::$instance->forTest = true; - } - return self::$instance; + $instance = new self($rootPath); + $instance->forTest = true; + return $instance; } } diff --git a/src/Php/Preprocessor.php b/src/Php/Preprocessor.php index 236588bf..b0221e11 100644 --- a/src/Php/Preprocessor.php +++ b/src/Php/Preprocessor.php @@ -195,7 +195,7 @@ class Preprocessor extends CompilerBase { $name = $this->getFunctionName($v); if ($this->stubFile) { - $this->nativeFunctions[$name] = $this->parseFunctionDecl($v); + $this->addFunction($name, $this->parseFunctionDecl($v)); } else { $this->symbolDeclInFile[strtolower($name)] = $this->file; } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 41a136d5..8331f51e 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -244,7 +244,7 @@ class Translator extends Preprocessor public function genExtension(string $file): void { if ($this->buildMode == 'bin') { - if (!isset($this->nativeFunctions['main'])) { + if (!$this->hasFunction('main')) { $this->climate->red('When the build mode is a binary executable file, the `main()` function must be defined'); exit(1); } @@ -437,7 +437,7 @@ class Translator extends Preprocessor $literalStringsCount = count($this->literalStrings); $code .= 'extern ' . self::TYPE_STR . ' ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL; - foreach ($this->nativeFunctions as $name => $func) { + foreach ($this->functions as $name => $func) { $code .= 'extern ' . $func->returnType . ' ' . self::PREFIX . $name . '('; $list = []; if ($func->method) { @@ -462,7 +462,7 @@ class Translator extends Preprocessor } $code .= PHP_EOL; - foreach ($this->nativeConstants as $name => $constant) { + foreach ($this->constants as $name => $constant) { $code .= 'extern ' . $constant->type . ' ' . $name . ';' . PHP_EOL; } $this->writeFile($file, $code); @@ -881,8 +881,8 @@ class Translator extends Preprocessor } $fullName = $this->getFullClassName(); - if ($this->hasNativeClass($fullName)) { - $this->classDef = $this->getClassDef($fullName); + if ($this->hasClass($fullName)) { + $this->classDef = $this->getClass($fullName); } else { $this->classDef = new ClassDef($this->class, $flags, $this->namespace); $this->addClass($fullName, $this->classDef); @@ -894,8 +894,8 @@ class Translator extends Preprocessor if ($extends) { $parentClass = $this->getParentClass($class->extends); - if ($this->hasNativeClass($parentClass)) { - $parent = $this->getClassDef($parentClass); + if ($this->hasClass($parentClass)) { + $parent = $this->getClass($parentClass); if ($parent->flags & Modifiers::FINAL) { $this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$parentClass}`"); } diff --git a/src/template/extension.cc.php b/src/template/extension.cc.php index 64f50b41..d48b5707 100644 --- a/src/template/extension.cc.php +++ b/src/template/extension.cc.php @@ -74,7 +74,7 @@ foreach ($this->literalStrings as $str => $index): // constants nativeConstants as $name => $const): +foreach ($this->constants as $name => $const): ?> type?> ; @@ -103,6 +103,9 @@ foreach ($this->functions as $functionDef): if ($this->buildMode === 'ext' and $functionDef->name === 'main') { continue; } + if ($functionDef->method) { + continue; + } $zif_name = $this->escapeZendFnName($functionDef->getNamespacedName()); ?> namespace): ?> @@ -132,7 +135,7 @@ foreach ($this->registerSymbols as $registerSymbolFn): void php_app_init() { // register constants nativeConstants as $name => $const): +foreach ($this->constants as $name => $const): ?> = value ?>; php::define("escapeString($const->name)?>", ); @@ -166,7 +169,7 @@ foreach ($this->globalVars as $name => $type) : php::unsetGlobal(""); nativeConstants as $name => $const): +foreach ($this->constants as $name => $const): if ($const->type !== Translator::TYPE_VAR) { continue; }