fix(compiler): 修复函数调用返回类型检测和命名空间解析问题

- 移除多余空行避免返回类型检测中断
- 添加绝对命名空间函数的特殊处理逻辑
- 修复函数调用解析中对完全限定名的支持
- 更新存根文件生成命令行参数传递机制
- 优化源码目录跟踪和头文件路径生成逻辑
pull/1/head
韩天峰 6 months ago
parent 08ee0328f4
commit a0d750eea0
  1. 7
      bin/gen_stub.php
  2. 51
      src/Php/CompilerBase.php
  3. 30
      src/Php/Translator.php
  4. 16
      tests/aot/fn-call-001.phpt

@ -80,7 +80,8 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly =
if (!$includeOnly) {
global $translator;
$stubFilenameWithoutExtension = $translator->getArgInfoStubFilename($stubFile);
$arginfoFile = $translator->getArgInfoHeaderFile($stubFilenameWithoutExtension);
$arginfoFile = $context->objectFile;
var_dump($arginfoFile);
$legacyFile = "{$stubFilenameWithoutExtension}_legacy_arginfo.h";
$stubCode = file_get_contents($stubFile);
@ -174,6 +175,7 @@ class Context {
public array $allConstInfos = [];
/** @var FileInfo[] */
public array $parsedFiles = [];
public string $objectFile = '';
}
class ArrayType extends SimpleType {
@ -6171,7 +6173,7 @@ function main()
$opt_index = 0;
$options = getopt(
"fh",
"fho:",
[
"force-regeneration", "parameter-stats", "help", "verify", "verify-manual", "replace-predefined-constants",
"generate-classsynopses", "replace-classsynopses", "generate-methodsynopses", "replace-methodsynopses",
@ -6192,6 +6194,7 @@ function main()
$generateOptimizerInfo = isset($options["generate-optimizer-info"]);
$context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]);
$context->objectFile = $options["o"] ?? '';
$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $verifyManual || $replacePredefinedConstants || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses;
if (isset($options["h"]) || isset($options["help"])) {

@ -1512,7 +1512,6 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->isNativeFunction($name)) {
return $this->nativeFunctions[$name]->returnType;
}
return $this->detectFuncCallReturnType($name);
case 'Expr_New':
return self::TYPE_OBJECT;
@ -1945,28 +1944,35 @@ class CompilerBase extends \PhpAot\Core\Translator
*/
protected function findNativeFunction(string $fname): string|false
{
$possibleFunctionNames = [$this->escapeName($fname)];
if (isset($this->useAliases[$fname])) {
$possibleFunctionNames[] = $this->escapeName($this->escapeNamespace($this->useAliases[$fname]));
}
if ($this->namespace) {
$possibleFunctionNames[] = $this->escapeNamespace($this->namespace) . self::NAMESPACE_SEPARATOR . $fname;
}
if (isset($this->useFunctions[$fname])) {
$possibleFunctionNames[] = $this->escapeNamespace($this->useFunctions[$fname]) . self::NAMESPACE_SEPARATOR . $fname;
}
// 复杂命名空间规则,组合命名空间
// 例子:use foo\bar; bar\fn();
foreach ($this->useNamespaces as $use) {
$ns1 = explode('\\', $use);
$ns2 = explode('\\', $fname);
if ($ns1[array_key_last($ns1)] === $ns2[array_key_first($ns2)]) {
$ns = array_merge($ns1, $ns2);
array_splice($ns, array_key_last($ns1) + 1);
$possibleFunctionNames[] = $this->escapeNamespace(implode('\\', $ns));
break;
// 绝对命名空间的函数
if ($fname[0] == '\\') {
$fname = ltrim($fname, '\\');
$possibleFunctionNames = [$this->escapeName($fname)];
} else {
$possibleFunctionNames = [$this->escapeName($fname)];
if (isset($this->useAliases[$fname])) {
$possibleFunctionNames[] = $this->escapeName($this->escapeNamespace($this->useAliases[$fname]));
}
if ($this->namespace) {
$possibleFunctionNames[] = $this->escapeNamespace($this->namespace) . self::NAMESPACE_SEPARATOR . $fname;
}
if (isset($this->useFunctions[$fname])) {
$possibleFunctionNames[] = $this->escapeNamespace($this->useFunctions[$fname]) . self::NAMESPACE_SEPARATOR . $fname;
}
// 复杂命名空间规则,组合命名空间
// 例子:use foo\bar; bar\fn();
foreach ($this->useNamespaces as $use) {
$ns1 = explode('\\', $use);
$ns2 = explode('\\', $fname);
if ($ns1[array_key_last($ns1)] === $ns2[array_key_first($ns2)]) {
$ns = array_merge($ns1, $ns2);
array_splice($ns, array_key_last($ns1) + 1);
$possibleFunctionNames[] = $this->escapeNamespace(implode('\\', $ns));
break;
}
}
}
foreach ($possibleFunctionNames as $name) {
// 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误
// 跳过,稍后再处理
@ -1992,12 +1998,11 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseFuncCall(Node\Expr\FuncCall $expr, bool $silent = false): string
{
$call = '';
$placeHolder = '';
if ($this->isVarExpr($expr->name)) {
$fn = $this->parseIdentifier($expr->name);
$placeHolder = $fn;
$name = '';
} elseif ($expr->name->getType() === 'Name') {
} elseif ($expr->name->getType() === 'Name' or $expr->name->getType() === 'Name_FullyQualified') {
$name = $this->parseIdentifier($expr->name);
if (in_array($name, $this->unsupportedFunctions)) {
$this->fatalError($expr, 'Unsupported function: `' . $name . '`');

@ -27,6 +27,7 @@ class Translator extends Preprocessor
{
use MagicMethodDetector;
protected string $targetName = 'app';
protected array $sourceDirs = [];
protected bool $verbose = false;
protected array $phpSrcFiles = [];
protected array $argInfoHeaderFiles = [];
@ -152,6 +153,7 @@ class Translator extends Preprocessor
$list = $this->getFilesFromDir($path);
$targetName = basename($path);
$this->setTargetName($targetName);
$this->sourceDirs[] = $path;
} else {
$ext = pathinfo($path, PATHINFO_EXTENSION);
if ($ext === 'yml') {
@ -160,6 +162,7 @@ class Translator extends Preprocessor
$list = [$path];
$targetName = FileScanner::getFileName($path);
$this->setTargetName($targetName);
$this->sourceDirs[] = dirname($path);
} else {
$this->error('Unsupported file type: ' . $path);
}
@ -340,9 +343,16 @@ class Translator extends Preprocessor
public function getArgInfoHeaderFile(string $stubFilenameWithoutExtension, bool $relative = false): string
{
$basename = self::PREFIX . basename($stubFilenameWithoutExtension);
$basename = $this->escapeFileName($basename);
$absPath = $this->getIncludeDir() . "/{$basename}_arginfo.h";
foreach ($this->sourceDirs as $srcDir) {
if (str_starts_with($stubFilenameWithoutExtension, $srcDir)) {
$filePath = ltrim($this->removeCommonPrefix($srcDir, $stubFilenameWithoutExtension), '/');
break;
}
}
$filename = self::PREFIX . str_replace('/', '_', $filePath);
$filename = $this->escapeFileName($filename);
$absPath = $this->getIncludeDir() . "/{$filename}_arginfo.h";
if ($relative) {
return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/');
}
@ -414,9 +424,11 @@ class Translator extends Preprocessor
}
if (is_file($realPath)) {
$list[] = $realPath;
$this->sourceDirs[] = basename($realPath);
} else {
$tmp = $this->getFilesFromDir($realPath);
$tmp = $this->getFilesFromDir($realPath);
$list = array_merge($list, $tmp);
$this->sourceDirs[] = $realPath;
}
}
} else {
@ -646,12 +658,14 @@ class Translator extends Preprocessor
protected function genStubFile(string $file): void
{
$genStubCmd = PHP_BINARY . ' ' . $this->rootPath . '/bin/gen_stub.php -f ' . $file;
$output = shell_exec($genStubCmd);
$this->climate->info('generate stub file: ' . $file);
$this->climate->comment($genStubCmd);
$stubFilenameWithoutExtension = str_replace(['.stub.php', '.php'], '', $file);
$headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true);
$genStubCmd = PHP_BINARY . ' ' . $this->rootPath . '/bin/gen_stub.php -f -o ' . $this->getIncludeDir() . '/' . $headerFile . ' ' . $file;
$output = shell_exec($genStubCmd);
$this->climate->info('generate stub file: ' . $file);
$this->climate->comment($genStubCmd);
if (!str_contains($output, 'Saved')) {
$this->error("failed to generate arginfo header file: `{$headerFile}`, output: {$output}");
}

@ -0,0 +1,16 @@
--TEST--
fn call 001
--FILE--
<?php
function main()
{
if (\class_exists('\\\\Event', false)) {
$className = '\\\\Event';
} else {
$className = '\Event';
}
var_dump($className);
}
?>
--EXPECT--
string(6) "\Event"
Loading…
Cancel
Save