fix(compiler): 修复函数use导入及declare编码解析

修复 `use function` 导入时完整命名空间解析错误,存储完整FQN而非仅命名空间部分。
修复 `declare` 语句中字符串/整数类型值的解析,支持 `encoding="UTF-8"` 等声明。
新增相关功能的AOT测试覆盖。
pull/13/head
韩天峰 2 months ago
parent c94be9c6a9
commit bd1a4db9d4
  1. 9
      src/Php/CompilerBase.php
  2. 6
      src/Php/Translator.php
  3. 26
      tests/aot/array/destructure-function-return.phpt
  4. 13
      tests/aot/basic/declare-encoding-utf8.phpt
  5. 14
      tests/aot/basic/shell-exec.phpt
  6. 37
      tests/aot/closure/static-closure-use.phpt
  7. 38
      tests/aot/exception/throw-expression.phpt
  8. 20
      tests/aot/functions/print-expression-return.phpt
  9. 33
      tests/aot/namespace/group-use-mixed.phpt
  10. 29
      tests/aot/namespace/use-function-alias.phpt
  11. 38
      tests/aot/operator/cast-expression-side-effects.phpt
  12. 42
      tests/aot/static/static-vars-multiple-init.phpt
  13. 46
      tests/aot/stdlib/class-exists-class-constant.phpt
  14. 40
      tests/aot/stdlib/method-property-exists-dynamic.phpt

@ -1071,7 +1071,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return ltrim($funcName, '\\');
}
if (isset($this->useFunctions[$funcName])) {
return $this->useFunctions[$funcName] . '\\' . $funcName;
return $this->useFunctions[$funcName];
}
return $funcName;
}
@ -3082,7 +3082,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$possibleFunctionNames[] = $this->escapeNamespace($this->namespace) . self::NAMESPACE_SEPARATOR . $this->escapeName($funcName);
}
if (isset($this->useFunctions[$funcName])) {
$possibleFunctionNames[] = $this->escapeNamespace($this->useFunctions[$funcName]) . self::NAMESPACE_SEPARATOR . $this->escapeName($funcName);
$possibleFunctionNames[] = $this->escapeNamespace($this->useFunctions[$funcName]);
}
// 复杂命名空间规则,组合命名空间
// 例子:use foo\bar; bar\fn();
@ -6705,11 +6705,10 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
if ($type === Node\Stmt\Use_::TYPE_FUNCTION) {
$lastIndex = strrpos($id, '\\');
$fn = substr($id, $lastIndex + 1);
$ns = substr($id, 0, $lastIndex);
if ($use->alias) {
$this->useFunctions[$use->alias->toString()] = $ns;
$this->useFunctions[$use->alias->toString()] = $id;
} else {
$this->useFunctions[$fn] = $ns;
$this->useFunctions[$fn] = $id;
}
} elseif ($type === Node\Stmt\Use_::TYPE_CONSTANT) {
$lastIndex = strrpos($id, '\\');

@ -2433,7 +2433,11 @@ CODE;
$declares = $v->declares;
foreach ($declares as $declare) {
$key = $this->parseIdentifier($declare->key);
$value = $this->parseIdentifier($declare->value);
$value = match (true) {
$declare->value instanceof Node\Scalar\String_ => $declare->value->value,
$declare->value instanceof Node\Scalar\Int_ => (string) $declare->value->value,
default => $this->parseIdentifier($declare->value),
};
if ($key === 'ticks') {
$this->fatalError($v, 'declare(ticks=1) is not supported');
} elseif ($key === 'encoding') {

@ -0,0 +1,26 @@
--TEST--
array destructuring from function return values
--FILE--
<?php
function make_pair(string $name): array
{
echo "make:$name\n";
return [$name, strtoupper($name)];
}
function main(): void
{
[$source, $upper] = make_pair('alpha');
var_dump($source);
var_dump($upper);
[$left, [$middle, $right]] = ['left', ['middle', 'right']];
var_dump($left . ':' . $middle . ':' . $right);
}
?>
--EXPECT--
make:alpha
string(5) "alpha"
string(5) "ALPHA"
string(17) "left:middle:right"

@ -0,0 +1,13 @@
--TEST--
declare encoding UTF-8 is accepted
--FILE--
<?php
declare(encoding="UTF-8");
function main(): void
{
echo "encoding-ok\n";
}
?>
--EXPECT--
encoding-ok

@ -0,0 +1,14 @@
--TEST--
shell execution operator returns command output
--FILE--
<?php
function main(): void
{
$name = 'aot';
$out = `printf "hello-%s" $name`;
var_dump($out);
}
?>
--EXPECT--
string(9) "hello-aot"

@ -0,0 +1,37 @@
--TEST--
static closure captures values and references through use
--FILE--
<?php
function main(): void
{
$base = 10;
$log = [];
$copy = static function (int $value) use ($base): int {
return $base + $value;
};
$push = static function (string $label) use (&$log): int {
$log[] = $label;
return count($log);
};
$base = 99;
var_dump($copy(5));
var_dump($push('first'));
var_dump($push('second'));
var_dump($log);
}
?>
--EXPECT--
int(15)
int(1)
int(2)
array(2) {
[0]=>
string(5) "first"
[1]=>
string(6) "second"
}

@ -0,0 +1,38 @@
--TEST--
throw expression in coalesce and ternary
--FILE--
<?php
function require_value(?string $value): string
{
return $value ?? throw new InvalidArgumentException('missing');
}
function pick_value(bool $ok): string
{
return $ok ? 'ok' : throw new RuntimeException('bad');
}
function main(): void
{
var_dump(require_value('present'));
var_dump(pick_value(true));
try {
require_value(null);
} catch (Throwable $e) {
echo get_class($e) . ':' . $e->getMessage() . "\n";
}
try {
pick_value(false);
} catch (Throwable $e) {
echo get_class($e) . ':' . $e->getMessage() . "\n";
}
}
?>
--EXPECT--
string(7) "present"
string(2) "ok"
InvalidArgumentException:missing
RuntimeException:bad

@ -0,0 +1,20 @@
--TEST--
print expression returns 1 and can be composed
--FILE--
<?php
function main(): void
{
$ret = print "hello\n";
var_dump($ret);
if (print "cond\n") {
echo "branch\n";
}
}
?>
--EXPECT--
hello
int(1)
cond
branch

@ -0,0 +1,33 @@
--TEST--
Mixed group use imports for class, function, and constant
--FILE--
<?php
namespace MixedGroup\Lib {
class Formatter {
public static function wrap(string $value): string
{
return '[' . $value . ']';
}
}
function label(string $value): string
{
return 'label:' . $value;
}
const DEFAULT_VALUE = 'mixed';
}
namespace {
use MixedGroup\Lib\{Formatter, function label, const DEFAULT_VALUE};
function main(): void
{
var_dump(Formatter::wrap(DEFAULT_VALUE));
var_dump(label(DEFAULT_VALUE));
}
}
?>
--EXPECT--
string(7) "[mixed]"
string(11) "label:mixed"

@ -0,0 +1,29 @@
--TEST--
use function alias resolves imported function name
--FILE--
<?php
namespace FunctionAlias\Lib {
function normalize(string $value): string
{
return strtolower(trim($value));
}
}
namespace FunctionAlias\App {
use function FunctionAlias\Lib\normalize as clean_name;
function run_alias(): void
{
var_dump(clean_name(' AOT '));
}
}
namespace {
function main(): void
{
FunctionAlias\App\run_alias();
}
}
?>
--EXPECT--
string(3) "aot"

@ -0,0 +1,38 @@
--TEST--
cast operands are evaluated once
--FILE--
<?php
function make_value(string $tag, mixed $value): mixed
{
echo "make:$tag\n";
return $value;
}
function main(): void
{
var_dump((int) make_value('int', '42'));
var_dump((float) make_value('float', '2.5'));
var_dump((string) make_value('string', 123));
var_dump((bool) make_value('bool', []));
$object = (object) make_value('object', ['name' => 'aot']);
var_dump($object->name);
$array = (array) make_value('array', $object);
var_dump($array['name']);
}
?>
--EXPECT--
make:int
int(42)
make:float
float(2.5)
make:string
string(3) "123"
make:bool
bool(false)
make:object
string(3) "aot"
make:array
string(3) "aot"

@ -0,0 +1,42 @@
--TEST--
multiple static variables initialize independently once
--FILE--
<?php
function init_static(string $name, int $value): int
{
echo "init:$name:$value\n";
return $value;
}
function next_pair(int $seed): void
{
static $a = init_static('a', 10), $b = init_static('b', 20);
$a += $seed;
$b += $seed * 2;
var_dump([$a, $b]);
}
function main(): void
{
next_pair(1);
next_pair(2);
}
?>
--EXPECT--
init:a:10
init:b:20
array(2) {
[0]=>
int(11)
[1]=>
int(22)
}
array(2) {
[0]=>
int(13)
[1]=>
int(26)
}

@ -0,0 +1,46 @@
--TEST--
class/interface/trait/enum exists with ::class names
--FILE--
<?php
namespace ExistsNames {
class ExistingClass {}
interface ExistingInterface {}
trait ExistingTrait {}
enum ExistingEnum { case One; }
function pick_name(array $names, string $key): string
{
echo "pick:$key\n";
return $names[$key];
}
}
namespace {
function main(): void
{
$names = [
'class' => ExistsNames\ExistingClass::class,
'interface' => ExistsNames\ExistingInterface::class,
'trait' => ExistsNames\ExistingTrait::class,
'enum' => ExistsNames\ExistingEnum::class,
];
var_dump(class_exists(ExistsNames\pick_name($names, 'class')));
var_dump(interface_exists(ExistsNames\pick_name($names, 'interface')));
var_dump(trait_exists(ExistsNames\pick_name($names, 'trait')));
var_dump(enum_exists(ExistsNames\pick_name($names, 'enum')));
var_dump(class_exists('ExistsNames\\MissingClass', false));
}
}
?>
--EXPECT--
pick:class
bool(true)
pick:interface
bool(true)
pick:trait
bool(true)
pick:enum
bool(true)
bool(false)

@ -0,0 +1,40 @@
--TEST--
method_exists and property_exists with dynamic names
--FILE--
<?php
class ExistsDynamicTarget
{
public int $count = 1;
private string $secret = 'x';
public function run(): string
{
return 'run';
}
}
function choose_name(string $kind): string
{
echo "choose:$kind\n";
return $kind === 'method' ? 'run' : 'secret';
}
function main(): void
{
$object = new ExistsDynamicTarget();
$class = ExistsDynamicTarget::class;
var_dump(method_exists($object, choose_name('method')));
var_dump(method_exists($class, 'missing'));
var_dump(property_exists($object, choose_name('property')));
var_dump(property_exists($class, 'count'));
}
?>
--EXPECT--
choose:method
bool(true)
bool(false)
choose:property
bool(true)
bool(true)
Loading…
Cancel
Save