fix:修复静态回调解析及拼接赋值等编译问题

pull/13/head
韩天峰 2 months ago
parent 2a75143036
commit bc799fda0a
  1. 47
      src/Php/CompilerBase.php
  2. 2
      src/Php/Parser/AssignOpTrait.php
  3. 38
      tests/aot/class/class-alias-class-constant.phpt
  4. 38
      tests/aot/closure/static-closure-use-ref-internal-callback.phpt
  5. 47
      tests/aot/closure/static-closure-use-ref-typed-callback.phpt
  6. 51
      tests/aot/dynamic_call/call-static-magic-forward.phpt
  7. 28
      tests/aot/functions/internal-union-return-type.phpt
  8. 20
      tests/aot/operator/concat-assign-null.phpt
  9. 78
      tests/aot/static/static-prop-inherited-isolated.phpt

@ -3686,6 +3686,13 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
$this->fatalError($arg, 'Cannot use positional argument after argument unpacking'); $this->fatalError($arg, 'Cannot use positional argument after argument unpacking');
} }
$byRef = $funcName && $this->isReferenceArgument($funcName, $className, $i); $byRef = $funcName && $this->isReferenceArgument($funcName, $className, $i);
if (($funcName === 'call_user_func' || $funcName === 'call_user_func_array') && $i === 0) {
$callback = $this->parseScopedCallbackArg($arg);
if ($callback !== null) {
$this->addPositionalCallArg($callback, $arrayArgsVar, $list_args);
continue;
}
}
if ($this->isVarExpr($arg->value)) { if ($this->isVarExpr($arg->value)) {
$name = $this->parseIdentifier($arg->value); $name = $this->parseIdentifier($arg->value);
if ($byRef) { if ($byRef) {
@ -3771,6 +3778,46 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
return $namedArgsVar !== null ? $callArgs . ', ' . $namedArgsVar . '.array()' : $callArgs; return $namedArgsVar !== null ? $callArgs . ', ' . $namedArgsVar . '.array()' : $callArgs;
} }
protected function parseScopedCallbackArg(Node\Arg $arg): ?string
{
$value = $arg->value;
if (!$value instanceof Expr\Array_ || count($value->items) < 2 || !$this->methodDef) {
return null;
}
$first = $value->items[0];
if (!$first instanceof ArrayItem || $first->key !== null || $first->unpack) {
return null;
}
if (!$first->value instanceof Node\Scalar\String_) {
return null;
}
$scope = strtolower($first->value->value);
$classExpr = match ($scope) {
'static' => ($this->methodDef->flags & Modifiers::STATIC)
? $this->getLiteralString($this->getFullClassName())
: Symbol::getCalledClass(),
'self' => $this->getLiteralString($this->getFullClassName()),
'parent' => $this->classDef->extends ? $this->getLiteralString($this->classDef->extends) : null,
default => null,
};
if ($classExpr === null) {
return null;
}
$items = [$classExpr];
foreach (array_slice($value->items, 1) as $item) {
if (!$item instanceof ArrayItem || $item->key !== null || $item->unpack) {
return null;
}
$this->assertExprCanBeUsedAsValue($item->value, 'callback array item');
$items[] = $this->parseIdentifier($item->value);
}
return $this->genArray($items);
}
protected function ensureCallArrayArgs(?string &$arrayArgsVar, array &$listArgs): string protected function ensureCallArrayArgs(?string &$arrayArgsVar, array &$listArgs): string
{ {
if ($arrayArgsVar === null) { if ($arrayArgsVar === null) {

@ -362,7 +362,7 @@ trait AssignOpTrait
if ($this->isArrayVar($node->var)) { if ($this->isArrayVar($node->var)) {
$this->fatalError($node->var, 'Cannot concat string to array'); $this->fatalError($node->var, 'Cannot concat string to array');
} }
return $var . '.append(' . $rightExprStr . ')'; return $var . ' = php::concat(' . $var . ', ' . $rightExprStr . ')';
} }
if ($this->isAssignOpPow($op)) { if ($this->isAssignOpPow($op)) {
$powExpr = 'php::fn::pow(' . $var . ', ' . $rightExprStr . ')'; $powExpr = 'php::fn::pow(' . $var . ', ' . $rightExprStr . ')';

@ -0,0 +1,38 @@
--TEST--
class_alias with ::class constant
--FILE--
<?php
class AliasOriginal
{
public static function name(): string
{
return static::class;
}
public static function ok(): string
{
return 'ok';
}
public function value(): string
{
return self::class;
}
}
function main()
{
class_alias(AliasOriginal::class, 'AliasCopy');
$obj = new AliasCopy();
var_dump($obj instanceof AliasOriginal);
var_dump($obj instanceof AliasCopy);
var_dump($obj->value());
var_dump(AliasCopy::ok());
}
?>
--EXPECT--
bool(true)
bool(true)
string(13) "AliasOriginal"
string(2) "ok"

@ -0,0 +1,38 @@
--TEST--
static closure use by reference through internal callback
--FILE--
<?php
function main(): void
{
$seen = [];
$values = [1, 2, 3];
$result = array_map(
static function (int $value) use (&$seen): int {
$seen[] = $value;
return $value * 10;
},
$values
);
var_dump($result);
var_dump($seen);
}
?>
--EXPECT--
array(3) {
[0]=>
int(10)
[1]=>
int(20)
[2]=>
int(30)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}

@ -0,0 +1,47 @@
--TEST--
static closure use by reference through typed Closure parameter
--FILE--
<?php
function apply_items(iterable $items, Closure $callback): array
{
$out = [];
foreach ($items as $item) {
$out[] = $callback($item);
}
return $out;
}
function main(): void
{
$seen = [];
$prefix = 'v';
$result = apply_items(
[1, 2, 3],
static function (int $value) use (&$seen, $prefix): string {
$seen[] = $value;
return $prefix . ($value * 10);
}
);
var_dump($result);
var_dump($seen);
}
?>
--EXPECT--
array(3) {
[0]=>
string(3) "v10"
[1]=>
string(3) "v20"
[2]=>
string(3) "v30"
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}

@ -0,0 +1,51 @@
--TEST--
__callStatic forwards to static method through call_user_func_array
--FILE--
<?php
class StaticForwarder
{
public static function __callStatic($name, $arguments): bool
{
if ('all' === substr($name, 0, 3)) {
$method = lcfirst(substr($name, 3));
$args = $arguments;
foreach ($arguments[0] as $entry) {
$args[0] = $entry;
if (!call_user_func_array(['static', $method], $args)) {
return false;
}
}
return true;
}
if ('nullOr' === substr($name, 0, 6)) {
if ($arguments[0] !== null) {
$method = lcfirst(substr($name, 6));
return call_user_func_array(['static', $method], $arguments);
}
return true;
}
return false;
}
public static function lengthBetween($value, int $min, int $max): bool
{
$len = strlen($value);
return $len >= $min && $len <= $max;
}
}
function main()
{
var_dump(StaticForwarder::__callStatic('allLengthBetween', [['aa', 'bbbb'], 2, 4]));
var_dump(StaticForwarder::__callStatic('allLengthBetween', [['a', 'bbbb'], 2, 4]));
var_dump(StaticForwarder::__callStatic('nullOrLengthBetween', [null, 2, 4]));
var_dump(StaticForwarder::__callStatic('nullOrLengthBetween', ['abc', 2, 4]));
}
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(true)

@ -0,0 +1,28 @@
--TEST--
internal function with union return type does not crash type detection
--FILE--
<?php
function main()
{
$funcs = get_extension_funcs('date');
var_dump(is_array($funcs));
if (get_extension_funcs('extension_does_not_exist')) {
echo "unexpected\n";
} else {
echo "false branch\n";
}
$count = count(get_extension_funcs('date') ?: []);
var_dump($count > 0);
$tz = new DateTimeZone('UTC');
$transitions = $tz->getTransitions(0, 1);
var_dump(is_array($transitions));
}
?>
--EXPECT--
bool(true)
false branch
bool(true)
bool(true)

@ -0,0 +1,20 @@
--TEST--
concat assignment treats null as empty string
--FILE--
<?php
function main(): void
{
$s = null;
$s .= 'a';
$s .= 123;
var_dump($s);
$items = [];
$items['x'] = null;
$items['x'] .= 'b';
var_dump($items['x']);
}
?>
--EXPECT--
string(4) "a123"
string(1) "b"

@ -0,0 +1,78 @@
--TEST--
inherited static property accessed through static:: remains isolated per class
--FILE--
<?php
class StaticOptionBase
{
protected static $enabled = true;
protected static $events = [];
public static function setEnabled(bool $enabled): void
{
static::$enabled = $enabled;
static::$events[] = static::class . ':' . ($enabled ? 'on' : 'off');
}
public static function state(): array
{
return [static::class, static::$enabled, static::$events];
}
}
class StaticOptionChildA extends StaticOptionBase
{
protected static $enabled = false;
protected static $events = [];
}
class StaticOptionChildB extends StaticOptionBase
{
protected static $enabled = true;
protected static $events = [];
}
function main()
{
StaticOptionChildA::setEnabled(true);
StaticOptionChildB::setEnabled(false);
StaticOptionChildA::setEnabled(false);
var_dump(StaticOptionBase::state());
var_dump(StaticOptionChildA::state());
var_dump(StaticOptionChildB::state());
}
?>
--EXPECT--
array(3) {
[0]=>
string(16) "StaticOptionBase"
[1]=>
bool(true)
[2]=>
array(0) {
}
}
array(3) {
[0]=>
string(18) "StaticOptionChildA"
[1]=>
bool(false)
[2]=>
array(2) {
[0]=>
string(21) "StaticOptionChildA:on"
[1]=>
string(22) "StaticOptionChildA:off"
}
}
array(3) {
[0]=>
string(18) "StaticOptionChildB"
[1]=>
bool(false)
[2]=>
array(1) {
[0]=>
string(22) "StaticOptionChildB:off"
}
}
Loading…
Cancel
Save