pull/37/head
commit
edb726efa6
46 changed files with 1541 additions and 36 deletions
@ -0,0 +1,17 @@ |
||||
<?php |
||||
|
||||
class PrivateConstructorParent |
||||
{ |
||||
private function __construct() |
||||
{ |
||||
} |
||||
} |
||||
|
||||
class PrivateConstructorChild extends PrivateConstructorParent |
||||
{ |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new PrivateConstructorChild(); |
||||
} |
||||
@ -0,0 +1,6 @@ |
||||
<?php |
||||
|
||||
function main() |
||||
{ |
||||
new Closure(); |
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
<?php |
||||
|
||||
namespace ConstructorVisibility; |
||||
|
||||
class Hidden |
||||
{ |
||||
private function __construct() |
||||
{ |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new Hidden(); |
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
<?php |
||||
|
||||
class TestClass |
||||
{ |
||||
private function __construct(){} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass; |
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
<?php |
||||
|
||||
class TestClass |
||||
{ |
||||
protected function __construct(){} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass; |
||||
} |
||||
@ -0,0 +1,19 @@ |
||||
<?php |
||||
|
||||
class Base |
||||
{ |
||||
protected function __construct(){} |
||||
} |
||||
|
||||
class Other |
||||
{ |
||||
public static function make(): Base |
||||
{ |
||||
return new Base(); |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
Other::make(); |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
<?php |
||||
|
||||
interface GeneratorReturnContract |
||||
{ |
||||
public function values(): \Generator; |
||||
} |
||||
|
||||
class GeneratorReturnImplementation implements GeneratorReturnContract |
||||
{ |
||||
public function values(): iterable |
||||
{ |
||||
yield 1; |
||||
} |
||||
} |
||||
@ -0,0 +1,23 @@ |
||||
<?php |
||||
|
||||
interface IntersectionLeft |
||||
{ |
||||
} |
||||
|
||||
interface IntersectionRight |
||||
{ |
||||
} |
||||
|
||||
interface IntersectionReturnParent |
||||
{ |
||||
public function value(): IntersectionLeft&IntersectionRight; |
||||
} |
||||
|
||||
class IntersectionReturnChild implements IntersectionReturnParent |
||||
{ |
||||
public function value(): IntersectionLeft |
||||
{ |
||||
return new class implements IntersectionLeft { |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,13 @@ |
||||
<?php |
||||
|
||||
abstract class NeverReturnParent |
||||
{ |
||||
abstract public function stop(): never; |
||||
} |
||||
|
||||
abstract class NeverReturnChild extends NeverReturnParent |
||||
{ |
||||
public function stop(): void |
||||
{ |
||||
} |
||||
} |
||||
@ -0,0 +1,17 @@ |
||||
<?php |
||||
|
||||
class StaticReturnParent |
||||
{ |
||||
public function value(): static |
||||
{ |
||||
return $this; |
||||
} |
||||
} |
||||
|
||||
class StaticReturnChild extends StaticReturnParent |
||||
{ |
||||
public function value(): self |
||||
{ |
||||
return $this; |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
<?php |
||||
|
||||
interface UnionReturnParent |
||||
{ |
||||
public function value(): int|string; |
||||
} |
||||
|
||||
class UnionReturnChild implements UnionReturnParent |
||||
{ |
||||
public function value(): bool |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace NamespaceEndingComment; |
||||
|
||||
const VALUE = 42; |
||||
|
||||
function value(): int |
||||
{ |
||||
return VALUE; |
||||
} |
||||
|
||||
// unbracketed namespace trailing comment |
||||
@ -0,0 +1,39 @@ |
||||
<?php |
||||
|
||||
interface CovarianceLeft |
||||
{ |
||||
} |
||||
|
||||
interface CovarianceRight |
||||
{ |
||||
} |
||||
|
||||
class CovarianceBoth implements CovarianceLeft, CovarianceRight |
||||
{ |
||||
} |
||||
|
||||
interface IntersectionNarrowingContract |
||||
{ |
||||
public function intersection(): CovarianceLeft; |
||||
} |
||||
|
||||
class IntersectionNarrowingImpl implements IntersectionNarrowingContract |
||||
{ |
||||
public function intersection(): CovarianceLeft&CovarianceRight |
||||
{ |
||||
return new CovarianceBoth(); |
||||
} |
||||
} |
||||
|
||||
interface IntersectionContract |
||||
{ |
||||
public function concrete(): CovarianceLeft&CovarianceRight; |
||||
} |
||||
|
||||
class IntersectionImpl implements IntersectionContract |
||||
{ |
||||
public function concrete(): CovarianceBoth |
||||
{ |
||||
return new CovarianceBoth(); |
||||
} |
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
trait TraitA |
||||
{ |
||||
public function __construct() |
||||
{ |
||||
echo "A\n"; |
||||
} |
||||
} |
||||
|
||||
trait TraitB |
||||
{ |
||||
public function __construct() |
||||
{ |
||||
echo "B\n"; |
||||
} |
||||
} |
||||
|
||||
class TestClass |
||||
{ |
||||
use TraitA, TraitB; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass(); |
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
trait TestTrait |
||||
{ |
||||
private function __construct() |
||||
{ |
||||
echo "trait ctor\n"; |
||||
} |
||||
} |
||||
|
||||
class TestClass |
||||
{ |
||||
use TestTrait; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass(); |
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
trait TestTrait |
||||
{ |
||||
protected function __construct() |
||||
{ |
||||
echo "trait ctor\n"; |
||||
} |
||||
} |
||||
|
||||
class TestClass |
||||
{ |
||||
use TestTrait; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass(); |
||||
} |
||||
@ -0,0 +1,78 @@ |
||||
<?php |
||||
|
||||
use TypePhp\Exception\TestError; |
||||
|
||||
class ConstructorVisibilityTest extends BaseTest |
||||
{ |
||||
/** |
||||
* 编译期错误在转换阶段直接以 TestError 抛出,而在桩文件生成阶段 |
||||
* (gen_stub.php) 会被包成 RuntimeException,这里两者都要捕获。 |
||||
*/ |
||||
protected function exec(string $expected, string $file): void |
||||
{ |
||||
try { |
||||
$this->compile($file); |
||||
} catch (TestError|RuntimeException $exception) { |
||||
$this->assertStringContainsString($expected, $exception->getMessage()); |
||||
return; |
||||
} |
||||
$this->fail('Expected compile-time error was not thrown'); |
||||
} |
||||
|
||||
public function testPrivateConstructorCannotBeCalledFromOutside(): void |
||||
{ |
||||
// 私有构造器不能从类外部通过 `new` 调用 |
||||
$this->exec('Cannot call private TestClass::__construct()', 'constructor_visibility_private.php'); |
||||
} |
||||
|
||||
public function testProtectedConstructorCannotBeCalledFromGlobalScope(): void |
||||
{ |
||||
// 保护构造器不能从全局作用域调用 |
||||
$this->exec('Cannot call protected TestClass::__construct()', 'constructor_visibility_protected.php'); |
||||
} |
||||
|
||||
public function testProtectedConstructorCannotBeCalledFromNonSubclass(): void |
||||
{ |
||||
// 保护构造器不能从非子类的其它类内部调用 |
||||
$this->exec('Cannot call protected Base::__construct()', 'constructor_visibility_protected_foreign_class.php'); |
||||
} |
||||
|
||||
public function testInheritedPrivateConstructorCannotBeCalledFromChildScope(): void |
||||
{ |
||||
$this->exec( |
||||
'Cannot call private PrivateConstructorParent::__construct()', |
||||
'constructor_visibility_inherited_private.php' |
||||
); |
||||
} |
||||
|
||||
public function testInternalPrivateConstructorCannotBeCalled(): void |
||||
{ |
||||
$this->exec('Cannot call private Closure::__construct()', 'constructor_visibility_internal_private.php'); |
||||
} |
||||
|
||||
public function testNamespacedConstructorUsesPhpClassNameInDiagnostic(): void |
||||
{ |
||||
$this->exec( |
||||
'Cannot call private ConstructorVisibility\Hidden::__construct()', |
||||
'constructor_visibility_namespaced.php' |
||||
); |
||||
} |
||||
|
||||
public function testTraitPrivateConstructorCannotBeCalledFromGlobalScope(): void |
||||
{ |
||||
// trait 提供的私有构造器扁平化后等价于类的私有构造器 |
||||
$this->exec('Cannot call private TestClass::__construct()', 'trait_constructor_private.php'); |
||||
} |
||||
|
||||
public function testTraitProtectedConstructorCannotBeCalledFromGlobalScope(): void |
||||
{ |
||||
// trait 提供的保护构造器扁平化后等价于类的保护构造器 |
||||
$this->exec('Cannot call protected TestClass::__construct()', 'trait_constructor_protected.php'); |
||||
} |
||||
|
||||
public function testConflictingTraitConstructorMustBeResolved(): void |
||||
{ |
||||
// 两个 trait 各自声明 __construct 时必须显式解决冲突 |
||||
$this->exec('Trait `TraitB` method `__construct` already exists', 'trait_constructor_conflict.php'); |
||||
} |
||||
} |
||||
@ -0,0 +1,47 @@ |
||||
--TEST-- |
||||
Interface return type covariance with nullable interface and anonymous class |
||||
--FILE-- |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
interface TestInterface1 |
||||
{ |
||||
} |
||||
|
||||
interface TestInterface2 extends TestInterface1 |
||||
{ |
||||
} |
||||
|
||||
interface TestInterface3 |
||||
{ |
||||
public function test(): ?TestInterface1; |
||||
} |
||||
|
||||
class TestClass implements TestInterface3 |
||||
{ |
||||
// Covariant: ?TestInterface2 is a subtype of ?TestInterface1 because |
||||
// TestInterface2 extends TestInterface1. |
||||
public function test(): ?TestInterface2 |
||||
{ |
||||
return new class() implements TestInterface2 { |
||||
public function hello(): string { |
||||
return "anon"; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$test = new TestClass; |
||||
$result = $test->test(); |
||||
var_dump($result instanceof TestInterface1); |
||||
var_dump($result instanceof TestInterface2); |
||||
var_dump($result === null); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
bool(true) |
||||
bool(false) |
||||
@ -0,0 +1,30 @@ |
||||
--TEST-- |
||||
Typed parameter default value from an unresolvable (external) class constant |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class TypedDefault |
||||
{ |
||||
public function run( |
||||
int $value = \ArrayObject::ARRAY_AS_PROPS, |
||||
float $floatValue = \ArrayObject::ARRAY_AS_PROPS, |
||||
string $format = \DateTime::ATOM, |
||||
int $composite = 1 | \ArrayObject::ARRAY_AS_PROPS, |
||||
mixed $variant = \ArrayObject::STD_PROP_LIST, |
||||
) |
||||
{ |
||||
var_dump($value, $floatValue, $format, $composite, $variant); |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
(new TypedDefault)->run(); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(2) |
||||
float(2) |
||||
string(13) "Y-m-d\TH:i:sP" |
||||
int(3) |
||||
int(1) |
||||
@ -0,0 +1,23 @@ |
||||
--TEST-- |
||||
class constants with heredoc and nowdoc syntax |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class Test |
||||
{ |
||||
const VALUE1 = <<<ABC |
||||
quote " slash \\ nul \0 tab \t ?? |
||||
ABC; |
||||
const VALUE2 = <<<'DEF' |
||||
$value ?? "quoted" \n \path |
||||
DEF; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
var_dump(bin2hex(Test::VALUE1), bin2hex(Test::VALUE2)); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(60) "71756f7465202220736c617368205c206e756c2000207461622009203f3f" |
||||
string(54) "2476616c7565203f3f202271756f74656422205c6e205c70617468" |
||||
@ -0,0 +1,51 @@ |
||||
--TEST-- |
||||
global constants, property defaults and parameter defaults with heredoc/nowdoc syntax |
||||
--FILE-- |
||||
<?php |
||||
|
||||
const G_HEREDOC = <<<ABC |
||||
abc |
||||
ABC; |
||||
const G_NOWDOC = <<<'DEF' |
||||
def |
||||
DEF; |
||||
|
||||
class WithProp |
||||
{ |
||||
public string $p = <<<ABC |
||||
xyz |
||||
ABC; |
||||
} |
||||
|
||||
function with_default(string $x = <<<'ABC' |
||||
$value ?? "quoted" \n \path |
||||
ABC): string { |
||||
return $x; |
||||
} |
||||
|
||||
function with_binary_default(string $x = <<<ABC |
||||
A\0B |
||||
ABC): string { |
||||
return $x; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
var_dump(G_HEREDOC, G_NOWDOC); |
||||
$o = new WithProp(); |
||||
var_dump($o->p); |
||||
var_dump(bin2hex(with_default()), bin2hex(with_binary_default())); |
||||
|
||||
$default = (new ReflectionFunction('with_default'))->getParameters()[0]->getDefaultValue(); |
||||
$binaryDefault = (new ReflectionFunction('with_binary_default'))->getParameters()[0]->getDefaultValue(); |
||||
var_dump(bin2hex($default), bin2hex($binaryDefault)); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(3) "abc" |
||||
string(3) "def" |
||||
string(3) "xyz" |
||||
string(54) "2476616c7565203f3f202271756f74656422205c6e205c70617468" |
||||
string(6) "410042" |
||||
string(54) "2476616c7565203f3f202271756f74656422205c6e205c70617468" |
||||
string(6) "410042" |
||||
@ -0,0 +1,57 @@ |
||||
--TEST-- |
||||
generator re-yielding array elements via foreach with \Generator return type |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function main() |
||||
{ |
||||
$g = test([1, 2, 3]); |
||||
var_dump($g); |
||||
foreach ($g as $value) |
||||
{ |
||||
var_dump($value); |
||||
} |
||||
} |
||||
|
||||
function test(array $array): \Generator |
||||
{ |
||||
foreach ($array as $value) |
||||
{ |
||||
yield $value; |
||||
} |
||||
} |
||||
|
||||
// main(); |
||||
?> |
||||
--EXPECTF-- |
||||
object(FiberGenerator)#%d (9) { |
||||
["callback":"FiberGenerator":private]=> |
||||
object(Closure)#%d (2) { |
||||
["function"]=> |
||||
string(19) "stdClass::{closure}" |
||||
["this"]=> |
||||
object(stdClass)#%d (1) { |
||||
["box"]=> |
||||
resource(%d) of type (php::box) |
||||
} |
||||
} |
||||
["fiber":"FiberGenerator":private]=> |
||||
NULL |
||||
["current":"FiberGenerator":private]=> |
||||
NULL |
||||
["key":"FiberGenerator":private]=> |
||||
NULL |
||||
["valid":"FiberGenerator":private]=> |
||||
bool(false) |
||||
["state":"FiberGenerator":private]=> |
||||
int(0) |
||||
["yield_count":"FiberGenerator":private]=> |
||||
int(0) |
||||
["next_index":"FiberGenerator":private]=> |
||||
int(0) |
||||
["return_value":"FiberGenerator":private]=> |
||||
NULL |
||||
} |
||||
int(1) |
||||
int(2) |
||||
int(3) |
||||
@ -0,0 +1,53 @@ |
||||
--TEST-- |
||||
generator return type accepts \Generator for methods, nullable and union variants |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class Box |
||||
{ |
||||
public function gen(array $array): \Generator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value * 2; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function nullableGen(array $array): ?\Generator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value; |
||||
} |
||||
} |
||||
|
||||
function unionGen(array $array): \Generator|\Iterator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value; |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$b = new Box(); |
||||
foreach ($b->gen([1, 2, 3]) as $v) { |
||||
var_dump($v); |
||||
} |
||||
$g = nullableGen([4, 5]); |
||||
foreach ($g as $v) { |
||||
var_dump($v); |
||||
} |
||||
$u = unionGen([6, 7]); |
||||
foreach ($u as $v) { |
||||
var_dump($v); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(2) |
||||
int(4) |
||||
int(6) |
||||
int(4) |
||||
int(5) |
||||
int(6) |
||||
int(7) |
||||
@ -0,0 +1,96 @@ |
||||
--TEST-- |
||||
generator methods implementing interfaces with iterable, nullable and union return types |
||||
--FILE-- |
||||
<?php |
||||
interface GenInterface |
||||
{ |
||||
public function gen(array $array): \Generator; |
||||
} |
||||
|
||||
interface IterableInterface |
||||
{ |
||||
public function it(array $array): iterable; |
||||
|
||||
public function narrowed(array $array): iterable; |
||||
} |
||||
|
||||
interface NullableInterface |
||||
{ |
||||
public function nullable(array $array): ?\Generator; |
||||
} |
||||
|
||||
interface UnionInterface |
||||
{ |
||||
public function union(array $array): \Generator|\Iterator; |
||||
} |
||||
|
||||
class Box implements GenInterface, IterableInterface, NullableInterface, UnionInterface |
||||
{ |
||||
public function gen(array $array): \Generator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value * 2; |
||||
} |
||||
} |
||||
|
||||
public function it(array $array): iterable |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value; |
||||
} |
||||
} |
||||
|
||||
public function narrowed(array $array): \Generator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value; |
||||
} |
||||
} |
||||
|
||||
public function nullable(array $array): ?\Generator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value; |
||||
} |
||||
} |
||||
|
||||
public function union(array $array): \Generator|\Iterator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$box = new Box(); |
||||
foreach ($box->gen([1, 2, 3]) as $v) { |
||||
var_dump($v); |
||||
} |
||||
foreach ($box->it([4, 5]) as $v) { |
||||
var_dump($v); |
||||
} |
||||
foreach ($box->narrowed([10, 11]) as $v) { |
||||
var_dump($v); |
||||
} |
||||
foreach ($box->nullable([6, 7]) as $v) { |
||||
var_dump($v); |
||||
} |
||||
foreach ($box->union([8, 9]) as $v) { |
||||
var_dump($v); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(2) |
||||
int(4) |
||||
int(6) |
||||
int(4) |
||||
int(5) |
||||
int(10) |
||||
int(11) |
||||
int(6) |
||||
int(7) |
||||
int(8) |
||||
int(9) |
||||
@ -0,0 +1,38 @@ |
||||
--TEST-- |
||||
generator method implementing an interface that declares \Generator return type |
||||
--FILE-- |
||||
<?php |
||||
interface T |
||||
{ |
||||
public function test(array $array): \Generator; |
||||
} |
||||
|
||||
class TestClass implements T |
||||
{ |
||||
public function test(array $array): \Generator |
||||
{ |
||||
foreach ($array as $value) { |
||||
yield $value; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$test = new TestClass; |
||||
$g = $test->test([1, 2, 3]); |
||||
// TypePHP generators return a \FiberGenerator which implements Iterator |
||||
// but is NOT the Zend \Generator class. |
||||
var_dump($g instanceof \Generator); |
||||
var_dump($g instanceof \Iterator); |
||||
foreach ($g as $value) { |
||||
var_dump($value); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(false) |
||||
bool(true) |
||||
int(1) |
||||
int(2) |
||||
int(3) |
||||
@ -0,0 +1,22 @@ |
||||
--TEST-- |
||||
A namespace block ending with a comment must not be treated as stray code |
||||
--FILE-- |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
namespace Test { |
||||
/* named namespace trailing block comment */ |
||||
} |
||||
|
||||
namespace { |
||||
function main() |
||||
{ |
||||
var_dump('done'); |
||||
} |
||||
|
||||
// global namespace trailing line comment |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(4) "done" |
||||
@ -0,0 +1,26 @@ |
||||
--TEST-- |
||||
Constructor visibility - protected constructor accessible from subclass |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class Base |
||||
{ |
||||
protected function __construct(){} |
||||
} |
||||
|
||||
class Sub extends Base |
||||
{ |
||||
public static function make(): Base |
||||
{ |
||||
return new Base(); |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$obj = Sub::make(); |
||||
var_dump($obj instanceof Base); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
@ -0,0 +1,29 @@ |
||||
--TEST-- |
||||
array reference assignment: append and element assignment write back through reference |
||||
--FILE-- |
||||
<?php |
||||
function main() |
||||
{ |
||||
$arr1 = [1, 2, 3]; |
||||
$arr2 = [&$arr1[0]]; |
||||
$arr2[0] = 123; |
||||
$arr2[] = &$arr1[1]; |
||||
$arr2[1] = 456; |
||||
var_dump($arr1, $arr2); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(3) { |
||||
[0]=> |
||||
&int(123) |
||||
[1]=> |
||||
&int(456) |
||||
[2]=> |
||||
int(3) |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
&int(123) |
||||
[1]=> |
||||
&int(456) |
||||
} |
||||
@ -0,0 +1,51 @@ |
||||
--TEST-- |
||||
array reference assignment to element: $arr[$k] = &$v writes back through reference |
||||
--FILE-- |
||||
<?php |
||||
class RefSource |
||||
{ |
||||
public $value = 30; |
||||
public static $staticValue = 40; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$x = 10; |
||||
$y = 20; |
||||
$arr = [1, 2, 3]; |
||||
$arr[0] = &$x; // 覆盖已有元素为引用 |
||||
$arr[5] = &$y; // 新建元素为引用 |
||||
$x = 100; |
||||
$y = 200; |
||||
var_dump($arr[0], $arr[5]); // 100, 200 |
||||
|
||||
// 通过元素引用写回 |
||||
$arr[0] = 111; |
||||
$arr[5] = 222; |
||||
var_dump($x, $y); // 111, 222 |
||||
|
||||
// 嵌套:引用赋值到多维数组元素 |
||||
$z = 7; |
||||
$m = [[1], [2]]; |
||||
$m[0][0] = &$z; |
||||
$z = 77; |
||||
var_dump($m[0][0]); // 77 |
||||
|
||||
// 左侧数组追加/元素写入不可因右侧是属性引用而被重新按读取解析 |
||||
$source = new RefSource(); |
||||
$propertyRefs = []; |
||||
$propertyRefs[] = &$source->value; |
||||
$propertyRefs[2] = &RefSource::$staticValue; |
||||
$propertyRefs[0] = 333; |
||||
$propertyRefs[2] = 444; |
||||
var_dump($source->value, RefSource::$staticValue); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(100) |
||||
int(200) |
||||
int(111) |
||||
int(222) |
||||
int(77) |
||||
int(333) |
||||
int(444) |
||||
@ -0,0 +1,37 @@ |
||||
--TEST-- |
||||
dynamically typed array element assignment preserves references and ArrayAccess writes |
||||
--FILE-- |
||||
<?php |
||||
function writeElement(mixed $container, mixed $key, mixed $value): void |
||||
{ |
||||
$container[$key] = $value; |
||||
} |
||||
|
||||
function writeReferencedContainer(mixed &$container, mixed $value): void |
||||
{ |
||||
$container[0] = $value; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$referenced = 10; |
||||
$array = [&$referenced]; |
||||
writeElement($array, 0, 123); |
||||
var_dump($referenced, $array[0]); |
||||
|
||||
$referencedAgain = 20; |
||||
$arrayByReference = [&$referencedAgain]; |
||||
writeReferencedContainer($arrayByReference, 234); |
||||
var_dump($referencedAgain, $arrayByReference[0]); |
||||
|
||||
$object = new ArrayObject(); |
||||
writeElement($object, 'key', 456); |
||||
var_dump($object['key']); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(123) |
||||
int(123) |
||||
int(234) |
||||
int(234) |
||||
int(456) |
||||
@ -0,0 +1,27 @@ |
||||
--TEST-- |
||||
Trait __construct is used by the composing class |
||||
--FILE-- |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
trait TestTrait |
||||
{ |
||||
public function __construct() |
||||
{ |
||||
echo "trait ctor\n"; |
||||
} |
||||
} |
||||
|
||||
class TestClass |
||||
{ |
||||
use TestTrait; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass(); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
trait ctor |
||||
@ -0,0 +1,32 @@ |
||||
--TEST-- |
||||
Class __construct overrides the one provided by a trait |
||||
--FILE-- |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
trait TestTrait |
||||
{ |
||||
public function __construct() |
||||
{ |
||||
echo "trait ctor\n"; |
||||
} |
||||
} |
||||
|
||||
class TestClass |
||||
{ |
||||
use TestTrait; |
||||
|
||||
public function __construct() |
||||
{ |
||||
echo "class ctor\n"; |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass(); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
class ctor |
||||
@ -0,0 +1,37 @@ |
||||
--TEST-- |
||||
Trait protected __construct is accessible from a subclass |
||||
--FILE-- |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
trait TestTrait |
||||
{ |
||||
protected function __construct() |
||||
{ |
||||
echo "base ctor\n"; |
||||
} |
||||
} |
||||
|
||||
class BaseClass |
||||
{ |
||||
use TestTrait; |
||||
} |
||||
|
||||
class SubClass extends BaseClass |
||||
{ |
||||
public function __construct() |
||||
{ |
||||
new BaseClass(); |
||||
echo "sub ctor\n"; |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new SubClass(); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
base ctor |
||||
sub ctor |
||||
@ -0,0 +1,30 @@ |
||||
--TEST-- |
||||
Trait __construct with arguments and $this property access |
||||
--FILE-- |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
trait TestTrait |
||||
{ |
||||
private int $value = 0; |
||||
|
||||
public function __construct(int $value) |
||||
{ |
||||
$this->value = $value; |
||||
echo "value=" . $this->value . "\n"; |
||||
} |
||||
} |
||||
|
||||
class TestClass |
||||
{ |
||||
use TestTrait; |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
new TestClass(42); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
value=42 |
||||
@ -0,0 +1,120 @@ |
||||
--TEST-- |
||||
Return type covariance: union narrowing and object subtype |
||||
--FILE-- |
||||
<?php |
||||
|
||||
interface UnionReturnContract |
||||
{ |
||||
public function make(): int|string; |
||||
} |
||||
|
||||
class UnionReturnImpl implements UnionReturnContract |
||||
{ |
||||
// Covariant: narrowing a union return type (int|string -> int) is allowed. |
||||
public function make(): int |
||||
{ |
||||
return 42; |
||||
} |
||||
} |
||||
|
||||
class BaseType {} |
||||
class ChildType extends BaseType {} |
||||
|
||||
interface ObjectReturnContract |
||||
{ |
||||
public function build(): BaseType; |
||||
} |
||||
|
||||
class ObjectReturnImpl implements ObjectReturnContract |
||||
{ |
||||
// Covariant: returning a subtype (ChildType) for a BaseType return is allowed. |
||||
public function build(): ChildType |
||||
{ |
||||
return new ChildType(); |
||||
} |
||||
} |
||||
|
||||
class StaticBase |
||||
{ |
||||
public function copy(): ?self |
||||
{ |
||||
return $this; |
||||
} |
||||
} |
||||
|
||||
class StaticChild extends StaticBase |
||||
{ |
||||
public function copy(): ?static |
||||
{ |
||||
return $this; |
||||
} |
||||
} |
||||
|
||||
interface IterableContract |
||||
{ |
||||
public function values(): iterable; |
||||
} |
||||
|
||||
class IterableImpl implements IterableContract |
||||
{ |
||||
public function values(): array |
||||
{ |
||||
return [1, 2]; |
||||
} |
||||
} |
||||
|
||||
interface BoolContract |
||||
{ |
||||
public function enabled(): bool; |
||||
} |
||||
|
||||
class LiteralBoolImpl implements BoolContract |
||||
{ |
||||
public function enabled(): true |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
abstract class VoidContract |
||||
{ |
||||
abstract public function stop(): void; |
||||
} |
||||
|
||||
abstract class NeverImpl extends VoidContract |
||||
{ |
||||
public function stop(): never |
||||
{ |
||||
throw new RuntimeException('stop'); |
||||
} |
||||
} |
||||
|
||||
function main() |
||||
{ |
||||
$impl = new UnionReturnImpl(); |
||||
var_dump($impl->make()); |
||||
|
||||
$obj = new ObjectReturnImpl(); |
||||
$built = $obj->build(); |
||||
var_dump($built instanceof BaseType); |
||||
var_dump($built instanceof ChildType); |
||||
|
||||
$static = new StaticChild(); |
||||
var_dump($static->copy() instanceof StaticChild); |
||||
|
||||
var_dump((new IterableImpl())->values()); |
||||
var_dump((new LiteralBoolImpl())->enabled()); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(42) |
||||
bool(true) |
||||
bool(true) |
||||
bool(true) |
||||
array(2) { |
||||
[0]=> |
||||
int(1) |
||||
[1]=> |
||||
int(2) |
||||
} |
||||
bool(true) |
||||
Loading…
Reference in new issue