Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4457dba

Browse files
authored
Add support for stubs to declare intersection type class properties (#8751)
1 parent 77bd39a commit 4457dba

File tree

6 files changed

+96
-13
lines changed

6 files changed

+96
-13
lines changed

‎Zend/tests/type_declarations/typed_properties_095.phpt‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Typed properties in internal classes
33
--EXTENSIONS--
44
zend_test
5+
spl
56
--FILE--
67
<?php
78

@@ -70,6 +71,8 @@ object(_ZendTestClass)#1 (3) {
7071
}
7172
["classUnionProp"]=>
7273
NULL
74+
["classIntersectionProp"]=>
75+
uninitialized(Traversable&Countable)
7376
["readonlyProp"]=>
7477
uninitialized(int)
7578
}
@@ -84,6 +87,8 @@ object(Test)#4 (3) {
8487
}
8588
["classUnionProp"]=>
8689
NULL
90+
["classIntersectionProp"]=>
91+
uninitialized(Traversable&Countable)
8792
["readonlyProp"]=>
8893
uninitialized(int)
8994
}

‎Zend/zend_types.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ typedef struct {
280280
#define ZEND_TYPE_INIT_UNION(ptr, extra_flags) \
281281
{ (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_UNION_BIT) | (extra_flags) }
282282

283+
#define ZEND_TYPE_INIT_INTERSECTION(ptr, extra_flags) \
284+
{ (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags) }
285+
283286
#define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \
284287
ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags)
285288

‎build/gen_stub.php‎

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -548,23 +548,26 @@ public function equals(SimpleType $other): bool {
548548
class Type {
549549
/** @var SimpleType[] */
550550
public $types;
551+
/** @var bool */
552+
public $isIntersection = false;
551553

552554
public static function fromNode(Node $node): Type {
553-
if ($node instanceof Node\UnionType) {
555+
if ($node instanceof Node\UnionType || $nodeinstanceofNode\IntersectionType) {
554556
$nestedTypeObjects = array_map(['Type', 'fromNode'], $node->types);
555557
$types = [];
556558
foreach ($nestedTypeObjects as $typeObject) {
557559
array_push($types, ...$typeObject->types);
558560
}
559-
return new Type($types);
561+
return new Type($types, ($nodeinstanceofNode\IntersectionType));
560562
}
561563

562564
if ($node instanceof Node\NullableType) {
563565
return new Type(
564566
[
565567
...Type::fromNode($node->type)->types,
566568
SimpleType::null(),
567-
]
569+
],
570+
false
568571
);
569572
}
570573

@@ -573,18 +576,20 @@ public static function fromNode(Node $node): Type {
573576
[
574577
SimpleType::fromString("Traversable"),
575578
ArrayType::createGenericArray(),
576-
]
579+
],
580+
false
577581
);
578582
}
579583

580-
return new Type([SimpleType::fromNode($node)]);
584+
return new Type([SimpleType::fromNode($node)], false);
581585
}
582586

583587
public static function fromString(string $typeString): self {
584588
$typeString .= "|";
585589
$simpleTypes = [];
586590
$simpleTypeOffset = 0;
587591
$inArray = false;
592+
$isIntersection = false;
588593

589594
$typeStringLength = strlen($typeString);
590595
for ($i = 0; $i < $typeStringLength; $i++) {
@@ -604,7 +609,8 @@ public static function fromString(string $typeString): self {
604609
continue;
605610
}
606611

607-
if ($char === "|") {
612+
if ($char === "|" || $char === "&") {
613+
$isIntersection = ($char === "&");
608614
$simpleTypeName = trim(substr($typeString, $simpleTypeOffset, $i - $simpleTypeOffset));
609615

610616
$simpleTypes[] = SimpleType::fromString($simpleTypeName);
@@ -613,14 +619,15 @@ public static function fromString(string $typeString): self {
613619
}
614620
}
615621

616-
return new Type($simpleTypes);
622+
return new Type($simpleTypes, $isIntersection);
617623
}
618624

619625
/**
620626
* @param SimpleType[] $types
621627
*/
622-
private function __construct(array $types) {
628+
private function __construct(array $types, bool$isIntersection) {
623629
$this->types = $types;
630+
$this->isIntersection = $isIntersection;
624631
}
625632

626633
public function isScalar(): bool {
@@ -650,7 +657,8 @@ public function getWithoutNull(): Type {
650657
function(SimpleType $type) {
651658
return !$type->isNull();
652659
}
653-
)
660+
),
661+
false
654662
);
655663
}
656664

@@ -683,6 +691,7 @@ public function toOptimizerTypeMask(): string {
683691
$optimizerTypes = [];
684692

685693
foreach ($this->types as $type) {
694+
// TODO Support for toOptimizerMask for intersection
686695
$optimizerTypes[] = $type->toOptimizerTypeMask();
687696
}
688697

@@ -711,8 +720,9 @@ public function toOptimizerTypeMaskForArrayValue(): string {
711720

712721
public function getTypeForDoc(DOMDocument $doc): DOMElement {
713722
if (count($this->types) > 1) {
723+
$typeSort = $this->isIntersection ? "intersection" : "union";
714724
$typeElement = $doc->createElement('type');
715-
$typeElement->setAttribute("class", "union");
725+
$typeElement->setAttribute("class", $typeSort);
716726

717727
foreach ($this->types as $type) {
718728
$unionTypeElement = $doc->createElement('type', $type->name);
@@ -755,7 +765,8 @@ public function __toString() {
755765
return 'mixed';
756766
}
757767

758-
return implode('|', array_map(
768+
$char = $this->isIntersection ? '&' : '|';
769+
return implode($char, array_map(
759770
function ($type) { return $type->name; },
760771
$this->types)
761772
);
@@ -2237,7 +2248,11 @@ public function getDeclaration(iterable $allConstInfos): string {
22372248

22382249
$typeMaskCode = $this->type->toArginfoType()->toTypeMask();
22392250

2240-
$code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_UNION(property_{$propertyName}_type_list, $typeMaskCode);\n";
2251+
if ($this->type->isIntersection) {
2252+
$code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_INTERSECTION(property_{$propertyName}_type_list, $typeMaskCode);\n";
2253+
} else {
2254+
$code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_UNION(property_{$propertyName}_type_list, $typeMaskCode);\n";
2255+
}
22412256
$typeCode = "property_{$propertyName}_type";
22422257
} else {
22432258
$escapedClassName = $arginfoType->classTypes[0]->toEscapedName();

‎ext/zend_test/test.stub.php‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class _ZendTestClass implements _ZendTestInterface {
2121
public int $intProp = 123;
2222
public ?stdClass $classProp = null;
2323
public stdClass|Iterator|null $classUnionProp = null;
24+
public Traversable&Countable $classIntersectionProp;
2425
public readonly int $readonlyProp;
2526

2627
public static function is_object(): int {}

‎ext/zend_test/test_arginfo.h‎

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
Test that internal classes can register intersection types
3+
--EXTENSIONS--
4+
zend_test
5+
spl
6+
--FILE--
7+
<?php
8+
9+
class C implements Countable {
10+
public function count(): int {
11+
return 1;
12+
}
13+
}
14+
15+
class I extends EmptyIterator implements Countable {
16+
public function count(): int {
17+
return 1;
18+
}
19+
}
20+
21+
$o = new _ZendTestClass();
22+
23+
try {
24+
var_dump($o->classIntersectionProp);
25+
} catch (Error $e) {
26+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
27+
}
28+
try {
29+
$o->classIntersectionProp = new EmptyIterator();
30+
} catch (TypeError $e) {
31+
echo $e->getMessage(), PHP_EOL;
32+
}
33+
try {
34+
$o->classIntersectionProp = new C();
35+
} catch (TypeError $e) {
36+
echo $e->getMessage(), PHP_EOL;
37+
}
38+
$o->classIntersectionProp = new I();
39+
40+
?>
41+
==DONE==
42+
--EXPECT--
43+
Error: Typed property _ZendTestClass::$classIntersectionProp must not be accessed before initialization
44+
Cannot assign EmptyIterator to property _ZendTestClass::$classIntersectionProp of type Traversable&Countable
45+
Cannot assign C to property _ZendTestClass::$classIntersectionProp of type Traversable&Countable
46+
==DONE==

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /