commit
2cbdbf9342
43 changed files with 1451 additions and 27 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,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