feat(compiler): 增强类型声明和对象赋值检查功能

- 添加了 objval() 函数支持 Class::class 常量语法
- 实现了方法调用和静态调用的返回类型推断
- 加强了对象类型赋值检查,要求使用 objval() 进行类型对齐
- 禁止字符串与数组之间的直接转换
- 添加了内部类常量值获取功能
- 更新了测试用例验证类型声明功能
pull/1/head
韩天峰 4 months ago
parent e62c953c71
commit f41d42e5bc
  1. 49
      src/Php/CompilerBase.php
  2. 6
      src/Php/Translator.php
  3. 3
      src/gen_stub.php
  4. 34
      tests/aot/type_decl/007.phpt

@ -1277,12 +1277,13 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->addObject($var, $rightClass);
} elseif ($this->isTypedObject($var)) {
$leftClass = $this->getObjectType($var);
// 对象的类不一致,不能互相赋值
// 对象的类不一致,不能互相赋值,必须使用 objval() 对齐类型
// 注意这里必须使用绝对相等比较,即使存在继承关系,类的方法也可能不一致
if ($leftClass !== $rightClass) {
$this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`");
}
} else {
$this->fatalError($left, "Cannot re-assign `\${$var}` to object of `{$rightClass}`");
// TODO 右值是一个类型对象,但左值是一个 var ,许可,但无法标记对象类型
}
} else {
if ($this->isFuncCallExpr($right) and $this->isNameExpr($right->name)) {
@ -1326,7 +1327,11 @@ class CompilerBase extends \PhpAot\Core\Translator
}
if (!$this->hasVar($var)) {
$this->addLocalVar($var, $type);
} elseif ($this->getVarType($var) !== self::TYPE_VAR and $this->isTypedObject($var) and $type !== self::TYPE_OBJECT) {
} elseif (($this->getVarType($var) !== self::TYPE_VAR and $this->isTypedObject($var) and $type !== self::TYPE_OBJECT)
// 禁止字符串与数组互相转换,其他类型如对象可以使用 __toString() 协议转为字符串,整数和浮点型也可以转为字符串
or ($this->getVarType($var) === self::TYPE_STR and $type === self::TYPE_ARRAY)
or ($this->getVarType($var) === self::TYPE_ARRAY and $type === self::TYPE_STR)
) {
$this->fatalError($left, "Cannot re-assign variable `\${$var}` from " . $this->getVarType($var) . ' to ' . $type);
}
}
@ -1491,8 +1496,40 @@ class CompilerBase extends \PhpAot\Core\Translator
}
if ($this->isFuncCallExpr($expr) and $this->isNameExpr($expr->name)) {
$fn = $this->parseIdentifier($expr->name);
if (count($expr->args) === 2 and $fn === 'objval' and $this->isScalarString($expr->args[1]->value)) {
return $this->getNamespacedClassName($this->parseIdentifier($expr->args[1]->value));
if (count($expr->args) === 2 and $fn === 'objval') {
$argClass = $expr->args[1]->value;
if ($this->isScalarString($argClass)) {
return $this->getNamespacedClassName($this->parseIdentifier($argClass));
} elseif ($this->isClassConstFetch($argClass)) {
if ($this->isNameExpr($argClass->class) and $this->isIdExpr($argClass->name) and $this->parseIdentifier($argClass->name) === 'class') {
return $this->getNamespacedClassName($this->parseIdentifier($argClass->class));
}
} else {
$this->fatalError($expr, 'The second parameter of objval() function only supports string literals or `ClassName::class` constant');
}
}
}
if ($this->isMethodCall($expr) and $this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) {
$object = $this->parseVariable($expr->var);
try {
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
if ($nativeFunc) {
return $this->getFunction($nativeFunc)->returnClass;
}
} catch (DynamicCall) {}
}
if ($this->isStaticCall($expr) and $this->isNameExpr($expr->class) and $this->isNamedMethod($expr->name)) {
$class = $this->parseIdentifier($expr->class);
if ($class === 'self') {
$class = $this->class;
} elseif ($class === 'static' or $class === 'parent') {
return '';
}
$class = $this->getNamespacedClassName($class);
$method = $this->parseIdentifier($expr->name);
$nativeFunc = $this->getNativeMethod($expr, $class, $method);
if ($nativeFunc) {
return $this->getFunction($nativeFunc)->returnClass;
}
}
return '';
@ -1526,7 +1563,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$returnClass = $this->getReturnClass();
if ($returnClass) {
if (!$objectClass) {
$this->fatalError($v, 'The return value must be an instance of the `' . $returnClass . '` class');
// TODO 返回值的类型无法确定,需要插入动态类型检测代码
} elseif (!$this->isInheritedFrom($objectClass, $returnClass)) {
$this->fatalError($v, 'The return type is `' . $returnClass . '`, cannot return an instance of `' . $objectClass . '`');
}

@ -567,6 +567,12 @@ class Translator extends Preprocessor
$constDef = $expr->getAttribute('nativeConst');
return $this->genCValue($constDef->valueExpr->value);
}
if ($this->isInternalClass($class)) {
$constName = $class . '::' . $name;
if (defined($constName)) {
return $this->genCValue(constant($constName));
}
}
throw new \Exception("Class constant `{$class}::{$name}` not found");
}

@ -2310,8 +2310,9 @@ class EvaluatedValue
}
} elseif ($expr->name->__toString() === 'class') {
return $class;
} else {
return getTranslator()->getClassConstValue($expr, $class, $expr->name->__toString());
}
goto _error;
} else {
$constName = $expr->name->__toString();
if (strtolower($constName) === "unknown") {

@ -0,0 +1,34 @@
--TEST--
Type Declarations
--FILE--
<?php
namespace TestApp\Console {
class Command {
public $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
}
namespace TestApp\Command {
use TestApp\Console\Command;
class Test {
public function callCommand(string $class): Command
{
$array = ['object' => new Command($class)];
return objval($array['object'], Command::class);
}
}
}
namespace {
function main() {
$o = new TestApp\Command\Test;
$o2 = $o->callCommand('TestApp\Console\Command');
var_dump($o2->name);
}
}
?>
--EXPECT--
string(23) "TestApp\Console\Command"
Loading…
Cancel
Save