commit
33d35435ba
26 changed files with 653 additions and 31 deletions
@ -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; |
||||
} |
||||
@ -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'); |
||||
} |
||||
} |
||||
@ -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…
Reference in new issue