Merge remote-tracking branch 'origin/master' into fix-trait

pull/26/head
韩天峰 1 month ago
commit 33d35435ba
  1. 13
      phpunit/code/inheritance_error_const_final.php
  2. 13
      phpunit/code/inheritance_error_const_missing_type.php
  3. 8
      phpunit/code/variable-variable-arraydim.php
  4. 12
      phpunit/code/variable-variable-function-call.php
  5. 10
      phpunit/src/InheritanceErrorTest.php
  6. 14
      phpunit/src/VariableVariableTest.php
  7. 23
      src/CompilerBase.php
  8. 2
      src/Entity/ConstantDef.php
  9. 7
      src/Generator/CallArgumentGenerator.php
  10. 2
      src/Generator/TypeCheckGenerator.php
  11. 4
      src/Parser/ArrayExpressionTrait.php
  12. 13
      src/Parser/AssignOpTrait.php
  13. 9
      src/Parser/ClassConstantFetchTrait.php
  14. 71
      src/Preprocessor.php
  15. 10
      src/Resolver/ClassConstantValueTrait.php
  16. 2
      src/Resolver/NameResolutionTrait.php
  17. 38
      src/Translator.php
  18. 41
      src/gen_stub.php
  19. 42
      tests/compiler/class/interface-method-self-return.phpt
  20. 28
      tests/compiler/coalesce/assign-coalesce-undefined-var.phpt
  21. 51
      tests/compiler/const/class-const-override-variants.phpt
  22. 30
      tests/compiler/const/class-const-override.phpt
  23. 43
      tests/compiler/const/class-const-parent-cross-ns.phpt
  24. 62
      tests/compiler/namespace/interface-impl-param-type-cross-ns.phpt
  25. 56
      tests/compiler/namespace/interface-self-return-namespaced.phpt
  26. 80
      tests/compiler/ref/dynamic-return-reference-argument.phpt

@ -0,0 +1,13 @@
<?php
class FinalConstantParent
{
final public const VALUE = 1;
}
class FinalConstantChild extends FinalConstantParent
{
public const VALUE = 2;
}
function main() {}

@ -0,0 +1,13 @@
<?php
class TypedConstantParent
{
public const int VALUE = 1;
}
class UntypedConstantChild extends TypedConstantParent
{
public const VALUE = 1;
}
function main() {}

@ -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;
}

@ -168,6 +168,16 @@ class InheritanceErrorTest extends TestCase
$this->exec('must be compatible', 'inheritance_error_const_type.php');
}
public function testTypedConstantCannotBeOverriddenWithoutDeclaredType()
{
$this->exec('must be compatible', 'inheritance_error_const_missing_type.php');
}
public function testFinalConstantCannotBeOverridden()
{
$this->exec('Cannot override final constant', 'inheritance_error_const_final.php');
}
public function testConstantVisibilityMismatch()
{
$this->exec('must be compatible', 'inheritance_error_const_visibility.php');

@ -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);
@ -1066,6 +1072,21 @@ class CompilerBase implements PropertyAccessContext
return $this->wrapVoidExprAsNull($expr, $this->parseExpr($expr));
}
/**
* Snapshot a reference-returning call before a by-value container can retain
* its php::Ref. Assigning to an existing Var detaches the reference, unlike
* constructing a Variant directly from Ref. Keep the assignment inline so
* earlier arguments or array elements retain PHP's evaluation order.
*/
protected function materializeRefReturnAsValue(NodeAbstract $value, string $expr): string
{
if ($value instanceof Expr\CallLike && $this->resolveRefReturningCall($value) !== false) {
$tmpVar = $this->addTmpVar(Type::VAR);
return '(' . $tmpVar . ' = ' . $expr . ')';
}
return $expr;
}
protected function getObjectPropVarName(string $object, string $prop): string
{
return self::OBJECT_PROP . $object . self::NAMESPACE_SEPARATOR . $prop;
@ -1273,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)) {

@ -19,6 +19,8 @@ class ConstantDef
public string $arrayExpr = '';
public string $class = '';
public ?NodeAbstract $valueExpr = null;
/** Explicit declared type (e.g. `const int FOO`); null for inferred/untyped constants. */
public ?string $declaredType = null;
public function __construct(string $name, int $flags, string $type, string $value)
{

@ -586,6 +586,13 @@ trait CallArgumentGenerator
protected function materializeCallArgValue(NodeAbstract $value, string $expr): string
{
// A call that returns by reference yields a live php::Ref aliasing the
// callee's storage. When such a call feeds a by-value argument, PHP takes
// a value snapshot at evaluation time (left to right), so later mutations
// to the aliased storage must not be observable. The dynamic ArgList keeps
// references verbatim (Ctor::CopyRef), so we dereference into a temporary
// value at the point of the call.
$expr = $this->materializeRefReturnAsValue($value, $expr);
if (!$this->shouldMaterializeCallArg($value)) {
return $expr;
}

@ -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') {

@ -60,7 +60,7 @@ trait ArrayExpressionTrait
$this->indentLevel++;
foreach ($items as $item) {
$this->assertExprCanBeUsedAsValue($item->value, 'array value');
$value = $this->parseIdentifier($item->value);
$value = $this->materializeRefReturnAsValue($item->value, $this->parseIdentifier($item->value));
if ($item->key) {
$this->assertExprCanBeUsedAsValue($item->key, 'array key');
$key = $this->parseArrayKey($item->key);
@ -221,7 +221,7 @@ trait ArrayExpressionTrait
}
$value = $this->convertToRef($item->value);
} else {
$value = $this->parseIdentifier($item->value);
$value = $this->materializeRefReturnAsValue($item->value, $this->parseIdentifier($item->value));
}
if ($item->unpack) {
$this->context->beforeStmtLines[] = $this->getIndent() . $tmpVar . '.merge(' . $value . ');';

@ -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 . '))';
}

@ -31,6 +31,14 @@ trait ClassConstantFetchTrait
$self = true;
$class = $this->class;
}
} elseif ($class === 'parent') {
if (!$this->classDef || !$this->classDef->extends) {
$this->fatalError($expr, 'Cannot use "parent" outside a class or class does not extend any class');
}
// extends is already fully resolved. Keep the leading slash so the
// current namespace is not applied again below.
$class = '\\' . $this->classDef->extends;
$self = true;
}
$const = $this->escapeString($this->parseIdentifier($expr->name));
@ -98,4 +106,3 @@ trait ClassConstantFetchTrait
}
}

@ -783,24 +783,89 @@ class Preprocessor extends CompilerBase
'Scalar_String' => Type::STR,
default => Type::VAR,
};
// `::class` is a compile-time magic constant that always yields a string,
// so a constant declared as `X = self::class` (or `Foo::class`) must be
// typed as a string rather than a generic variant.
if ($type === Type::VAR
&& $const->value instanceof Node\Expr\ClassConstFetch
&& strtolower((string) $const->value->name) === 'class') {
$type = Type::STR;
}
// A constant whose value references another class constant
// (e.g. `X = ParentClass::Y` or `X = self::Y`) must take the referenced
// constant's type. This keeps override compatibility checks and the C++
// declaration correct, mirroring PHP where overriding an untyped constant
// with a value of any (compatible) type is allowed.
if ($type === Type::VAR
&& $const->value instanceof Node\Expr\ClassConstFetch
&& $const->value->class instanceof Node\Name) {
$refType = $this->resolveReferencedConstantType($const->value, $this->getFullClassName());
if ($refType !== null) {
$type = $refType;
}
}
}
$constName = $this->parseIdentifier($const->name);
if ($this->classDef->hasConstant($constName)) {
$this->fatalError($v, "Duplicate constant `{$constName}`");
}
$constInfo = $this->parseClassLikeConstant($const, $flags, $type, $class);
$constInfo = $this->parseClassLikeConstant($const, $flags, $type, $class, $declaredType);
$constInfo->class = $class;
$this->classDef->constants[$constInfo->name] = $constInfo;
}
}
private function parseClassLikeConstant(Node\Const_ $const, int $flags, string $type, string $class = ''): ConstantDef
/**
* Resolve the compile-time type of a class constant whose value is a
* `ClassConstFetch` referencing another constant (e.g. `X = ParentClass::Y`
* or `X = self::Y`). Returns the referenced constant's type, or null when
* the reference cannot be resolved yet (for instance when the referenced
* class has not been prepared). `::class` always resolves to a string.
*/
private function resolveReferencedConstantType(Node\Expr\ClassConstFetch $fetch, string $currentClass): ?string
{
$constName = $fetch->name->toString();
if (strcasecmp($constName, 'class') === 0) {
return Type::STR;
}
if (!($fetch->class instanceof Node\Name)) {
return null;
}
$className = $fetch->class->toString();
if (strcasecmp($className, 'self') === 0 || strcasecmp($className, 'static') === 0) {
$targetClass = $currentClass;
} elseif (strcasecmp($className, 'parent') === 0) {
$targetClass = $this->getParentClass($currentClass);
} else {
$targetClass = $this->getNamespacedClassName($className);
}
if ($targetClass === '' || !$this->hasClass($targetClass)) {
return null;
}
$def = $this->getClass($targetClass);
if (!$def->hasConstant($constName)) {
return null;
}
$refConst = $def->getConstant($constName);
// Follow the chain in case the referenced constant is itself an
// expression that resolves to another constant.
if ($refConst->type !== Type::VAR) {
return $refConst->type;
}
if ($refConst->valueExpr instanceof Node\Expr\ClassConstFetch) {
return $this->resolveReferencedConstantType($refConst->valueExpr, $targetClass);
}
return null;
}
private function parseClassLikeConstant(Node\Const_ $const, int $flags, string $type, string $class = '', ?string $declaredType = null): ConstantDef
{
$constName = $this->parseIdentifier($const->name);
$constValue = $this->parseIdentifier($const->value);
$constInfo = new ConstantDef($constName, $flags, $type, $constValue);
$constInfo->valueExpr = $const->value;
$constInfo->declaredType = $declaredType;
if ($this->context->beforeStmtLines) {
$arrayExpr = '';
@ -1005,7 +1070,7 @@ class Preprocessor extends CompilerBase
default => Type::VAR,
};
}
$constInfo = $this->parseClassLikeConstant($const, $this->parseModifiers($stmt->flags), $type, $class);
$constInfo = $this->parseClassLikeConstant($const, $this->parseModifiers($stmt->flags), $type, $class, $stmt->type ? $type : null);
$this->interfaceDef->constants[$constName] = $constInfo;
}
continue;

@ -116,6 +116,16 @@ trait ClassConstantValueTrait
if ($expr instanceof Node\Expr\ClassConstFetch && $expr->class instanceof Node\Name) {
$constName = $expr->name->toString();
$className = $expr->class->toString();
if (strcasecmp($constName, 'class') === 0) {
// `::class` is a compile-time magic constant that resolves to the
// fully qualified class name of the referenced class.
if (strcasecmp($className, 'self') === 0 || strcasecmp($className, 'static') === 0) {
$className = $class;
} elseif (strcasecmp($className, 'parent') === 0) {
$className = $this->getParentClass($class);
}
return ltrim($this->getNamespacedClassName($className, $this->getNamespaceOfClass($class)), '\\');
}
if (strcasecmp($className, 'self') === 0) {
$className = $class;
} elseif (strcasecmp($className, 'parent') === 0) {

@ -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');

@ -3305,6 +3305,7 @@ CODE;
return $arg->typeCheck;
}
$declaredClass = $arg->declaredClass ?: $arg->class;
return match ($arg->type) {
Type::INT => [['kind' => 'isInt']],
Type::FLOAT => [['kind' => 'isFloat']],
@ -3312,8 +3313,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,
};
@ -3540,10 +3541,37 @@ CODE;
if ($parentConst->flags & Modifiers::PRIVATE) {
continue;
}
if ($childConst->type !== $parentConst->type || $childConst->class !== $parentConst->class) {
if ($parentConst->flags & Modifiers::FINAL) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");
"Cannot override final constant `{$parentClass}::{$name}`");
}
// PHP only enforces type compatibility when the parent constant
// carries an explicit declared type. Overriding an untyped constant
// with a value of any type is permitted, so the type check is skipped
// in that case. Visibility is always enforced below.
if ($parentConst->declaredType !== null) {
if ($childConst->declaredType === null) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");
}
// An untyped child constant whose value is an expression (e.g.
// `X = ParentClass::Y`) is inferred as a variant. Resolve its real
// type from the referenced constant so the compatibility check uses
// the actual value type.
$childType = $childConst->type;
if ($childType === Type::VAR
&& $childConst->valueExpr instanceof Node\Expr\ClassConstFetch) {
$resolved = $this->resolveReferencedConstantType($childConst->valueExpr, $this->getFullClassName());
if ($resolved !== null) {
$childType = $resolved;
}
}
if ($childType !== $parentConst->type || $childConst->class !== $parentConst->class) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");
}
}
if ($this->getVisibilityRank($childConst->flags) < $this->getVisibilityRank($parentConst->flags)) {
$this->fatalError($classStmt,

@ -2354,18 +2354,29 @@ class EvaluatedValue
}
if ($expr instanceof Expr\ClassConstFetch) {
$constName = $expr->name->__toString();
if (strcasecmp($constName, 'class') === 0) {
// `::class` is a compile-time magic constant that resolves to the
// fully qualified class name of the referenced class.
$className = getClassConstFetchClassName($expr);
if (strcasecmp($className, 'self') === 0 || strcasecmp($className, 'static') === 0) {
return ClassInfo::$currentClass;
}
if (strcasecmp($className, 'parent') === 0) {
return getTranslator()->getParentClass(ClassInfo::$currentClass);
}
return ltrim($className, '\\');
}
$class = getClassConstFetchClassName($expr);
if ($class === 'self') {
$constName = ClassInfo::$currentClass . "::" . $expr->name->__toString();
$constName = ClassInfo::$currentClass . "::" . $constName;
if (isset($allConstInfos[$constName])) {
return formatConstValue($allConstInfos[$constName]->getValue($allConstInfos)->value);
} else {
return formatConstValue(getTranslator()->getClassConstValue($expr, ClassInfo::$currentClass, $expr->name->toString()));
return formatConstValue(getTranslator()->getClassConstValue($expr, ClassInfo::$currentClass, $constName));
}
} elseif ($expr->name->__toString() === 'class') {
return $class;
} else {
return formatConstValue(getTranslator()->getClassConstValue($expr, $class, $expr->name->__toString(), ClassInfo::$currentClass));
return formatConstValue(getTranslator()->getClassConstValue($expr, $class, $constName, ClassInfo::$currentClass));
}
} else {
$constName = $expr->name->__toString();
@ -2490,16 +2501,14 @@ class EvaluatedValue
// PHP single-quote to C double-quote string
if ($this->type->isString()) {
if (
$this->expr instanceof PhpParser\Node\Expr\ClassConstFetch
) {
if ($this->expr->class instanceof PhpParser\Node\Name\FullyQualified and
$this->expr->name instanceof PhpParser\Node\Identifier and
if ($this->expr instanceof PhpParser\Node\Expr\ClassConstFetch) {
if ($this->expr->name instanceof PhpParser\Node\Identifier and
$this->expr->name->__toString() === 'class') {
$expr = '"' . addcslashes($this->expr->class->name, '\\') . '"';
} else {
return $this->value;
// `::class` is a compile-time magic constant that resolves to the
// fully qualified class name (already stored in $this->value).
return '"' . addcslashes($this->value, '\\') . '"';
}
return $this->value;
} elseif ($this->expr instanceof Expr\ConstFetch) {
return getTranslator()->getConstValue($this->expr->name->toString());
} elseif (!($this->expr instanceof String_)) {
@ -2899,6 +2908,12 @@ class ConstInfo extends VariableLike
$code .= "\tzend_string *const_{$constName}_name = zend_string_init_interned(\"$constName\", sizeof(\"$constName\") - 1, true);\n";
$nameCode = "const_{$constName}_name";
// A child class may override a constant inherited from its parent. The
// runtime copies the parent's constants into the child, so re-declaring
// the constant would fail with "Cannot redefine class constant".
// Drop any inherited entry first so the child's value replaces it.
$code .= "\tzend_hash_del(&class_entry->constants_table, $nameCode);\n";
if ($this->exposedDocComment) {
$commentCode = "const_{$constName}_comment";
$escapedCommentInit = $this->exposedDocComment->getInitCode();

@ -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,51 @@
--TEST--
class const override variants (self::class, parent::class, references, multi-level)
--FILE--
<?php
declare(strict_types=1);
class Base
{
public const NAME = 'Base';
public const GREETING = 'hello';
public const VALUE = 42;
}
class Other
{
public const TAG = 'other';
}
class Mid extends Base
{
public const NAME = Base::GREETING; // 'hello'
public const SELF_NAME = self::class; // 'Mid'
public const PARENT_NAME = parent::class; // 'Base'
public const CROSS = Other::TAG; // 'other'
}
class Leaf extends Mid
{
public const VALUE = Mid::NAME; // 'hello' (overrides Base::VALUE int with a string)
public const LEAF_NAME = self::class; // 'Leaf'
public const GREET = Base::GREETING; // 'hello'
}
function main()
{
var_dump(Mid::NAME, Mid::SELF_NAME, Mid::PARENT_NAME, Mid::CROSS);
var_dump(Leaf::VALUE, Leaf::LEAF_NAME, Leaf::GREET);
var_dump(Base::VALUE, Leaf::VALUE);
}
?>
--EXPECT--
string(5) "hello"
string(3) "Mid"
string(4) "Base"
string(5) "other"
string(5) "hello"
string(4) "Leaf"
string(5) "hello"
int(42)
string(5) "hello"

@ -0,0 +1,30 @@
--TEST--
class const override referencing another constant
--FILE--
<?php
declare(strict_types=1);
abstract class ParentClass
{
public const A = 'A';
public const B = 'B';
}
class TestClass extends ParentClass
{
public const A = ParentClass::B;
public const B = 'bbb';
}
function main()
{
var_dump(ParentClass::A, ParentClass::B);
var_dump(TestClass::A, TestClass::B);
}
?>
--EXPECT--
string(1) "A"
string(1) "B"
string(1) "B"
string(3) "bbb"

@ -0,0 +1,43 @@
--TEST--
parent class constants resolve across namespaces
--FILE--
<?php
namespace Library {
class Base
{
public const TOKEN = 'base';
}
}
namespace Application {
class Sibling
{
}
class Child extends \Library\Base
{
public const PARENT_NAME = parent::class;
public const SIBLING_NAME = Sibling::class;
public static function dumpParent(): void
{
var_dump(parent::class, parent::TOKEN);
}
}
}
namespace {
function main(): void
{
var_dump(\Application\Child::PARENT_NAME);
var_dump(\Application\Child::SIBLING_NAME);
\Application\Child::dumpParent();
}
}
?>
--EXPECT--
string(12) "Library\Base"
string(19) "Application\Sibling"
string(12) "Library\Base"
string(4) "base"

@ -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)

@ -0,0 +1,80 @@
--TEST--
Reference-returning calls are copied by value when used as call arguments or array elements
--FILE--
<?php
function main()
{
$v1 = &test1();
var_dump($v1);
$v2 = &test1();
var_dump($v2);
var_dump($v1, $v2);
$v1 = 0;
var_dump($v1, $v2);
var_dump(test1(), test2());
var_dump([test1(), test2()]);
var_dump(['first' => test1(), test2()]);
var_dump(value_order('arg-left'), ref_order('arg-ref'));
var_dump([value_order('array-left'), ref_order('array-ref')]);
}
function &test1()
{
$callback = 'test2';
return $callback();
}
function &test2()
{
static $value = 0;
++$value;
return $value;
}
function value_order(string $label): string
{
echo "$label\n";
return $label;
}
function &ref_order(string $label)
{
static $value = 42;
echo "$label\n";
return $value;
}
?>
--EXPECT--
int(1)
int(2)
int(2)
int(2)
int(0)
int(0)
int(1)
int(2)
array(2) {
[0]=>
int(3)
[1]=>
int(4)
}
array(2) {
["first"]=>
int(5)
[0]=>
int(6)
}
arg-left
arg-ref
string(8) "arg-left"
int(42)
array-left
array-ref
array(2) {
[0]=>
string(10) "array-left"
[1]=>
int(42)
}
Loading…
Cancel
Save