Skip to content

Commit 1f52d16

Browse files
committed
Add support for stubs to declare intersection type class properties
1 parent 790be97 commit 1f52d16

File tree

5 files changed

+78
-3
lines changed

5 files changed

+78
-3
lines changed

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: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,14 @@ public static function fromNode(Node $node): Type {
513513
}
514514
return new Type($types);
515515
}
516+
if ($node instanceof Node\IntersectionType) {
517+
$nestedTypeObjects = array_map(['Type', 'fromNode'], $node->types);
518+
$types = [];
519+
foreach ($nestedTypeObjects as $typeObject) {
520+
array_push($types, ...$typeObject->types);
521+
}
522+
return new Type($types, true);
523+
}
516524

517525
if ($node instanceof Node\NullableType) {
518526
return new Type(
@@ -574,7 +582,7 @@ public static function fromString(string $typeString): self {
574582
/**
575583
* @param SimpleType[] $types
576584
*/
577-
private function __construct(array $types) {
585+
private function __construct(array $types, public readonly bool $isIntersection = false) {
578586
$this->types = $types;
579587
}
580588

@@ -2136,7 +2144,11 @@ public function getDeclaration(iterable $allConstInfos): string {
21362144

21372145
$typeMaskCode = $this->type->toArginfoType()->toTypeMask();
21382146

2139-
$code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_UNION(property_{$propertyName}_type_list, $typeMaskCode);\n";
2147+
if ($this->type->isIntersection) {
2148+
$code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_INTERSECTION(property_{$propertyName}_type_list, $typeMaskCode);\n";
2149+
} else {
2150+
$code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_UNION(property_{$propertyName}_type_list, $typeMaskCode);\n";
2151+
}
21402152
$typeCode = "property_{$propertyName}_type";
21412153
} else {
21422154
$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
@@ -17,6 +17,7 @@ class _ZendTestClass implements _ZendTestInterface {
1717
public int $intProp = 123;
1818
public ?stdClass $classProp = null;
1919
public stdClass|Iterator|null $classUnionProp = null;
20+
public Traversable&Countable $classIntersectionProp;
2021
public readonly int $readonlyProp;
2122

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