feat(compiler): 实现空值检查表达式解析功能

- 添加了对复杂嵌套表达式的 empty() 函数支持
- 实现了数组维度获取和属性访问的解析逻辑
- 添加了 SkipException 类用于跳过错误节点处理
- 优化了全局变量访问的错误处理机制
- 增强了类型转换和函数调用的参数处理
- 更新了测试用例验证空值检查功能正确性
- 修复了编译器在处理未定义类时的错误判断
pull/1/head
韩天峰 7 months ago
parent 4ce8c24765
commit 423c10fe47
  1. 1
      .gitignore
  2. 28
      bin/gen_stub.php
  3. 11
      examples/array_get.php
  4. 73
      src/Php/CompilerBase.php
  5. 8
      src/Php/SkipException.php
  6. 6
      src/Php/Translator.php
  7. 4
      src/cpp/php_aot_helper.h
  8. 0
      tests/aot/const-test.phpt
  9. 13
      tests/aot/empty-test.phpt
  10. 0
      tests/aot/for-test.phpt
  11. 0
      tests/aot/foreach-test.phpt
  12. 0
      tests/aot/list-test.phpt

1
.gitignore vendored

@ -1,6 +1,7 @@
/.idea
/.cproject
/.project
/.settings
/logs
/build
/vendor

@ -4377,20 +4377,20 @@ class FileInfo {
}
if ($stmt instanceof Stmt\Const_) {
foreach ($stmt->consts as $const) {
$this->constInfos[] = parseConstLike(
$prettyPrinter,
new ConstName($const->namespacedName, $const->name->toString()),
$const,
0,
null,
$stmt->getComments(),
$cond,
$this->isUndocumentable,
$this->getMinimumPhpVersionIdCompatibility(),
AttributeInfo::createFromGroups($stmt->attrGroups)
);
}
// foreach ($stmt->consts as $const) {
// $this->constInfos[] = parseConstLike(
// $prettyPrinter,
// new ConstName($const->namespacedName, $const->name->toString()),
// $const,
// 0,
// null,
// $stmt->getComments(),
// $cond,
// $this->isUndocumentable,
// $this->getMinimumPhpVersionIdCompatibility(),
// AttributeInfo::createFromGroups($stmt->attrGroups)
// );
// }
continue;
}

@ -9,7 +9,10 @@
//var_dump($str[99]);
//var_dump($str[-3]);
$o = new ArrayObject();
$o['hello'] = 'world';
var_dump($o['hello'], $o);
function main()
{
$arr = array(
array(2, 2)
);
var_dump(empty($arr[0][0][2][3]->prop[3]));
}

@ -673,7 +673,11 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($v->stmts) {
$this->indentLevel++;
$stmts = $this->parseStmts($v->stmts);
try {
$stmts = $this->parseStmts($v->stmts);
} catch (SkipException $e) {
$stmts = '';
}
$this->indentLevel--;
} else {
$stmts = '';
@ -1663,6 +1667,12 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$var = $this->parseIdentifier($node->var);
if ($this->isVarExpr($node->var)) {
if ($var === 'GLOBALS') {
if ($node->dim === null) {
$this->fatalError($node, 'Cannot use [] for GLOBALS');
}
return 'php::global(' . $this->parseIdentifier($node->dim) . ')';
}
if (!$this->hasVar($var)) {
if ($write) {
$this->addLocalVar($var, self::TYPE_ARRAY);
@ -1670,12 +1680,6 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->fatalError($node->var, "The variable `{$node->var->name}` is undefined");
}
}
if ($var === 'GLOBALS') {
if ($node->dim === null) {
$this->fatalError($node, 'Cannot use [] for GLOBALS');
}
return 'php::global(' . $this->parseIdentifier($node->dim) . ')';
}
}
if ($node->dim === null) {
@ -1729,12 +1733,12 @@ class CompilerBase extends \PhpAot\Core\Translator
}
foreach ($possibleFunctionNames as $name) {
// 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误
// 跳过,稍后再处理
if (isset($this->functionDeclInFile[$name])
and $this->functionDeclInFile[$name] === $this->file
and !$this->isNativeFunction($name)) {
$this->redoAfterDeclare[$name] = true;
return $name;
throw new SkipException();
}
if ($this->isNativeFunction($name)) {
return $name;
@ -2695,12 +2699,37 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
protected function parseEmpty(mixed $expr): string
protected function parseEmpty(Node\Expr\Empty_ $expr): string
{
return 'php::empty(' . $this->parseExpr($expr->expr) . ')';
if ($this->isVarExpr($expr->expr)) {
return 'php::empty(' . $this->parseExpr($expr->expr) . ')';
}
$list = [];
$expr = $expr->expr;
while (true) {
if ($this->isArrayDimFetch($expr)) {
if ($expr->dim === null) {
$this->fatalError($expr, 'Cannot use [] for reading');
}
$dim = $this->parseIdentifier($expr->dim);
$list[] = '{php::ArrayDimFetch, ' . self::TYPE_VAR . '(' . $dim . ')}';
} elseif ($this->isPropertyFetch($expr)) {
$name = $this->identifierToStr($expr->name);
$list[] = '{php::PropertyFetch, ' . self::TYPE_VAR . '(' . $name . ')}';
} elseif ($this->isVarExpr($expr)) {
$var = $this->parseIdentifier($expr);
break;
} else {
$this->fatalError($expr, 'The empty() only supports variables, array fetch, and property read');
}
$expr = $expr->var;
}
$list = array_reverse($list);
return 'php::empty(' . $var . ', {' . implode(', ', $list) . '})';
}
protected function parseCastArray(mixed $expr): string
protected function parseCastArray(Node\Expr\Cast\Array_ $expr): string
{
return $this->convertArrayExpr($this->parseIdentifier($expr->expr));
}
@ -2819,14 +2848,16 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($require) {
$this->requireVar($node, $id);
}
return $id;
}
if ($id === 'self') {
$id = $this->class;
}
return '"' . $id . '"';
if ($this->isNameExpr($node) or $this->isIdExpr($node)) {
return '"' . $id . '"';
} else {
return $id;
}
}
protected function requireVar($node, string $var): void
@ -2864,13 +2895,15 @@ class CompilerBase extends \PhpAot\Core\Translator
$class = $this->parseIdentifier($expr->class);
$prop = $this->parseIdentifier($expr->name);
if ($class === 'self') {
$classDef = $this->classDef;
$class = $this->class;
$namespace = $this->namespace;
} else {
$classDef = $this->classes[$class];
$namespace = $classDef->namespace;
}
if (!$this->hasNativeClass($class)) {
return null;
}
$classDef = $this->classes[$class];
$namespace = $classDef->namespace;
if ($classDef->hasProperty($prop)) {
$propDef = $classDef->getProperty($prop);
if ($propDef->isStatic()) {

@ -0,0 +1,8 @@
<?php
namespace PhpAot\Php;
class SkipException extends \RuntimeException
{
}

@ -123,7 +123,7 @@ class Translator extends Preprocessor
exit(1);
}
if (in_array($name, Constants::CPP_RESERVED_NAMES)) {
$this->climate->red('The target name must not be a reserved keyword');
$this->climate->red('The target name [' . $name . '] must not be a reserved keyword');
exit(1);
}
$this->targetName = $name;
@ -270,6 +270,10 @@ class Translator extends Preprocessor
public function genFunctionDeclaration(string $file): void
{
$code = '#include <phpx.h>' . PHP_EOL;
$literalStringsCount = count($this->literalStrings);
$code .= 'extern ' . self::TYPE_STR . ' ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL;
/**
* @var FunctionDef $func
*/

@ -3,7 +3,7 @@
extern zend_class_entry *php_get_class(int class_id, const php::String &class_name);
extern zend_function *php_get_func(int func_id, const php::String &func_name);
static inline php::Variant CALL(int func_id, const php::String &func_name, const std::initializer_list<php::Variant> &args) {
static inline php::Variant CALL(int func_id, const php::String &func_name, const php::ArgList &args) {
return php::call(php_get_func(func_id, func_name), args);
}
@ -11,7 +11,7 @@ static inline php::Variant CALL(int func_id, const php::String &func_name) {
return php::call(php_get_func(func_id, func_name));
}
static inline php::Variant CALL_SILENT(int func_id, const php::String &func_name, const std::initializer_list<php::Variant> &args) {
static inline php::Variant CALL_SILENT(int func_id, const php::String &func_name, const php::ArgList &args) {
return php::silentCall(php_get_func(func_id, func_name), args);
}

@ -0,0 +1,13 @@
--TEST--
empty (linked expr)
--FILE--
<?php
$arr = array(
array(2, 2)
);
var_dump(empty($arr[0][1]));
var_dump(empty($arr[0][1][2][3]->prop[4]));
?>
--EXPECT--
bool(false)
bool(true)
Loading…
Cancel
Save