Merge branch 'master' into fix-ref-value

pull/23/head
韩天峰 1 month ago
commit 191361eb38
  1. 8
      phpunit/code/variable-variable-arraydim.php
  2. 12
      phpunit/code/variable-variable-function-call.php
  3. 14
      phpunit/src/VariableVariableTest.php
  4. 8
      src/CompilerBase.php
  5. 2
      src/Generator/TypeCheckGenerator.php
  6. 13
      src/Parser/AssignOpTrait.php
  7. 2
      src/Resolver/NameResolutionTrait.php
  8. 5
      src/Translator.php
  9. 42
      tests/compiler/class/interface-method-self-return.phpt
  10. 28
      tests/compiler/coalesce/assign-coalesce-undefined-var.phpt
  11. 62
      tests/compiler/namespace/interface-impl-param-type-cross-ns.phpt
  12. 56
      tests/compiler/namespace/interface-self-return-namespaced.phpt

@ -0,0 +1,8 @@
<?php
function variable_variable_with_array_dim(): void
{
$foo = ['bar' => 'hello'];
${$foo['bar']} = 'world';
echo $hello;
}

@ -0,0 +1,12 @@
<?php
function variable_name(): string
{
return 'hello';
}
function variable_variable_with_function_call(): void
{
${variable_name()} = 'world';
echo $hello;
}

@ -0,0 +1,14 @@
<?php
class VariableVariableTest extends \BaseTest
{
public function testVariableVariableWithArrayDimThrowsUnsupportedError(): void
{
$this->exec('The `$$` syntax is not supported', 'variable-variable-arraydim.php');
}
public function testVariableVariableWithFunctionCallThrowsUnsupportedError(): void
{
$this->exec('The `$$` syntax is not supported', 'variable-variable-function-call.php');
}
}

@ -1020,6 +1020,12 @@ class CompilerBase implements PropertyAccessContext
return ltrim($this->namespace . '\\' . $this->class, '\\');
}
protected function getFullClassLikeName(): string
{
$name = $this->class !== '' ? $this->class : $this->interface;
return ltrim($this->namespace . '\\' . $name, '\\');
}
protected function getFullMethodName(string $fullClassName, string $method): string
{
return strtolower($fullClassName . '::' . $method);
@ -1288,7 +1294,7 @@ class CompilerBase implements PropertyAccessContext
protected function parseVariable(Variable $expr): string
{
if (is_object($expr->name) and $this->isVarExpr($expr->name)) {
if (!is_string($expr->name)) {
$this->fatalError($expr, 'The `$$` syntax is not supported');
}
if ($this->isSuperGlobal($expr->name)) {

@ -104,7 +104,7 @@ trait TypeCheckGenerator
}
if ($name === 'self') {
$class = $this->getFullClassName();
$class = $this->getFullClassLikeName();
} elseif ($name === 'parent') {
$class = $this->classDef->extends ?? '';
} elseif ($name === 'static') {

@ -862,9 +862,17 @@ trait AssignOpTrait
protected function parseAssignOpCoalesce(Expr\AssignOp\Coalesce $expr): string
{
$this->checkLeftValue($expr->var);
// An undefined variable must exist before generating its isset check.
// Keep it as Variant so NULL remains distinguishable from native defaults.
$var = $this->isVarExpr($expr->var) ? $this->parseIdentifier($expr->var) : null;
if ($var !== null && !$this->hasVar($var)) {
$this->addLocalVar($var, Type::VAR);
}
$isset = $this->parseChainedExpr($expr->var, self::OP_ISSET);
$var = $this->parseWritableIdentifier($expr->var);
$var ??= $this->parseWritableIdentifier($expr->var);
$propertyWriteTarget = $this->preparePropertyWriteTarget($expr->var);
if ($propertyWriteTarget !== null) {
@ -878,9 +886,6 @@ trait AssignOpTrait
if ($this->isVarExpr($expr->expr) and !$this->hasVar($right)) {
$this->errorUndefinedVariable($expr->expr);
}
if ($this->isVarExpr($expr->var) and !$this->hasVar($var)) {
$this->addLocalVar($var, $this->getNormalAssignType($this->detectTypeOfExpr($expr->expr)));
}
return '(' . $isset . '?' . $var . ':(' . $var . ' = ' . $right . '))';
}

@ -166,7 +166,7 @@ trait NameResolutionTrait
return $this->getTypeFromZendType($typeNameLower);
} else {
if ($typeName === 'self') {
$class = $this->getFullClassName();
$class = $this->getFullClassLikeName();
} elseif ($typeName === 'parent') {
if (!$this->classDef) {
$this->fatalError($type, 'Cannot use "parent" type declaration outside a class');

@ -3242,6 +3242,7 @@ CODE;
return $arg->typeCheck;
}
$declaredClass = $arg->declaredClass ?: $arg->class;
return match ($arg->type) {
Type::INT => [['kind' => 'isInt']],
Type::FLOAT => [['kind' => 'isFloat']],
@ -3249,8 +3250,8 @@ CODE;
Type::STR => [['kind' => 'isString']],
Type::ARRAY => [['kind' => 'isArray']],
Type::RESOURCE => [['kind' => 'isResource']],
Type::OBJECT => $arg->class
? [['kind' => 'instanceof', 'class' => $arg->class]]
Type::OBJECT => $declaredClass
? [['kind' => 'instanceof', 'class' => $declaredClass]]
: [['kind' => 'isObject']],
default => null,
};

@ -0,0 +1,42 @@
--TEST--
interface method with `self` return type implemented by class (fluent interface), and namespace block containing comments
--FILE--
<?php
namespace {
interface TestInterface
{
public function get(): self;
}
class TestClass implements TestInterface
{
public int $value = 0;
public function get(): self
{
return $this;
}
public function setValue(int $value): self
{
$this->value = $value;
return $this;
}
}
function main()
{
$test = new TestClass;
// get() returns self, so the result still satisfies the interface
var_dump($test->get() instanceof TestInterface);
var_dump($test === $test->get());
// fluent chaining of self-returning methods
var_dump($test->get()->setValue(42)->value);
}
}
?>
--EXPECT--
bool(true)
bool(true)
int(42)

@ -0,0 +1,28 @@
--TEST--
assign coalesce on undefined variable
--FILE--
<?php
$a ??= 123;
var_dump($a);
$b ??= 'foo';
$b ??= 'bar';
var_dump($b);
$c ??= null;
var_dump(isset($c));
$c ??= 'after-null';
var_dump($c);
for ($i = 0; $i < 2; $i++) {
$d ??= printf("default\n");
}
var_dump($d);
?>
--EXPECT--
int(123)
string(3) "foo"
bool(false)
string(10) "after-null"
default
int(8)

@ -0,0 +1,62 @@
--TEST--
Cross-namespace interface implementation parameter compatibility is declaration-order independent
--FILE--
<?php
namespace A {
use B\I0;
use B\I1;
use B\I2;
abstract class Test implements I2
{
public function test(I1 $a): bool
{
return true;
}
public function testParent(I0 $a): bool
{
return true;
}
}
}
namespace B {
interface I0
{
}
interface I1 extends I0
{
}
interface I2
{
public function test(I1 $a): bool;
public function testParent(I1 $a): bool;
}
class Impl1 implements I1
{
}
}
namespace {
class Concrete extends \A\Test
{
}
function main()
{
$obj = new Concrete();
var_dump($obj->test(new \B\Impl1()));
var_dump($obj->testParent(new \B\Impl1()));
echo "done\n";
}
}
?>
--EXPECT--
bool(true)
bool(true)
done

@ -0,0 +1,56 @@
--TEST--
interface method `self` return type resolves to the interface's fully-qualified name inside a named namespace
--FILE--
<?php
namespace App {
interface Chainable
{
public function chain(): self;
public function maybe(bool $present): ?self;
public function combine(self $other): self;
}
// comment inside a named namespace block (Stmt_Nop)
class Widget implements Chainable
{
public array $log = [];
public function chain(): self
{
$this->log[] = 'chain';
return $this;
}
public function maybe(bool $present): ?self
{
return $present ? $this : null;
}
public function combine(Chainable $other): self
{
return $this;
}
}
}
namespace {
function main()
{
$w = new \App\Widget();
var_dump($w->chain()->chain() instanceof \App\Chainable);
var_dump(count($w->log));
var_dump($w->maybe(true) instanceof \App\Chainable);
var_dump($w->maybe(false));
var_dump($w->combine(new \App\Widget()) === $w);
}
}
?>
--EXPECT--
bool(true)
int(2)
bool(true)
NULL
bool(true)
Loading…
Cancel
Save