Skip to content

Commit ee4e50d

Browse files
committed
Generics experimentation (squashed)
1 parent 1612a71 commit ee4e50d

File tree

69 files changed

+2295
-718
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2295
-718
lines changed

Zend/Optimizer/dfa_pass.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ static inline bool can_elide_list_type(
278278
ZEND_ASSERT(!is_intersection);
279279
return can_elide_list_type(script, op_array, use_info, *single_type);
280280
}
281-
if (ZEND_TYPE_HAS_NAME(*single_type)) {
282-
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type));
281+
if (ZEND_TYPE_HAS_PNR(*single_type)) {
282+
zend_string *lcname = zend_string_tolower(ZEND_TYPE_PNR_NAME(*single_type));
283283
zend_class_entry *ce = zend_optimizer_get_class_entry(script, op_array, lcname);
284284
zend_string_release(lcname);
285285
bool result = ce && safe_instanceof(use_info->ce, ce);

Zend/Optimizer/escape_analysis.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ static bool is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, i
164164
/* These flags will always cause an exception */
165165
ZEND_ACC_IMPLICIT_ABSTRACT_CLASS | ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
166166
| ZEND_ACC_INTERFACE | ZEND_ACC_TRAIT;
167-
if (ce && !ce->parent && !ce->create_object && !ce->constructor &&
167+
if (ce && !ce->num_parents && !ce->create_object && !ce->constructor &&
168168
!ce->destructor && !ce->__get && !ce->__set &&
169169
!(ce->ce_flags & forbidden_flags) &&
170170
(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
@@ -228,7 +228,7 @@ static bool is_local_def(zend_op_array *op_array, zend_ssa *ssa, int def, int va
228228
zend_class_entry *ce = zend_optimizer_get_class_entry_from_op1(
229229
script, op_array, opline);
230230
if (ce && !ce->create_object && !ce->constructor &&
231-
!ce->destructor && !ce->__get && !ce->__set && !ce->parent) {
231+
!ce->destructor && !ce->__get && !ce->__set && !ce->num_parents) {
232232
return 1;
233233
}
234234
break;

Zend/Optimizer/zend_inference.c

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,8 +2385,8 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen
23852385
if (pce) {
23862386
/* As we only have space to store one CE,
23872387
* we use a plain object type for class unions. */
2388-
if (ZEND_TYPE_HAS_NAME(type)) {
2389-
zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(type));
2388+
if (ZEND_TYPE_HAS_PNR(type)) {
2389+
zend_string *lcname = zend_string_tolower(ZEND_TYPE_PNR_NAME(type));
23902390
// TODO: Pass through op_array.
23912391
*pce = zend_optimizer_get_class_entry(script, NULL, lcname);
23922392
zend_string_release_ex(lcname, 0);
@@ -2470,7 +2470,7 @@ static const zend_property_info *zend_fetch_static_prop_info(const zend_script *
24702470
break;
24712471
case ZEND_FETCH_CLASS_PARENT:
24722472
if (op_array->scope && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
2473-
ce = op_array->scope->parent;
2473+
ce = op_array->scope->parents[0]->ce;
24742474
}
24752475
break;
24762476
}
@@ -3341,8 +3341,8 @@ static zend_always_inline zend_result _zend_update_type_info(
33413341
}
33423342
break;
33433343
case ZEND_FETCH_CLASS_PARENT:
3344-
if (op_array->scope && op_array->scope->parent && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
3345-
UPDATE_SSA_OBJ_TYPE(op_array->scope->parent, 0, ssa_op->result_def);
3344+
if (op_array->scope && op_array->scope->parents[0]->ce && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
3345+
UPDATE_SSA_OBJ_TYPE(op_array->scope->parents[0]->ce, 0, ssa_op->result_def);
33463346
} else {
33473347
UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
33483348
}
@@ -3992,17 +3992,6 @@ ZEND_API zend_result zend_update_type_info(
39923992
return _zend_update_type_info(op_array, ssa, script, NULL, opline, ssa_op, ssa_opcodes, optimization_level, 0);
39933993
}
39943994

3995-
static uint32_t get_class_entry_rank(zend_class_entry *ce) {
3996-
uint32_t rank = 0;
3997-
if (ce->ce_flags & ZEND_ACC_LINKED) {
3998-
while (ce->parent) {
3999-
rank++;
4000-
ce = ce->parent;
4001-
}
4002-
}
4003-
return rank;
4004-
}
4005-
40063995
/* Compute least common ancestor on class inheritance tree only */
40073996
static zend_class_entry *join_class_entries(
40083997
zend_class_entry *ce1, zend_class_entry *ce2, int *is_instanceof) {
@@ -4014,22 +4003,20 @@ static zend_class_entry *join_class_entries(
40144003
return NULL;
40154004
}
40164005

4017-
rank1 = get_class_entry_rank(ce1);
4018-
rank2 = get_class_entry_rank(ce2);
4019-
4020-
while (rank1 != rank2) {
4021-
if (rank1 > rank2) {
4022-
ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
4023-
rank1--;
4024-
} else {
4025-
ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
4026-
rank2--;
4027-
}
4006+
if (ce1->num_parents > ce2->num_parents) {
4007+
rank1 = ce1->num_parents - ce2->num_parents;
4008+
rank2 = 0;
4009+
} else {
4010+
rank1 = 0;
4011+
rank2 = ce2->num_parents - ce1->num_parents;
40284012
}
40294013

4014+
ce1 = rank1 > 0 ? ce1->parents[rank1-1]->ce : ce1;
4015+
ce2 = rank2 > 0 ? ce2->parents[rank2-1]->ce : ce2;
4016+
40304017
while (ce1 != ce2) {
4031-
ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
4032-
ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
4018+
ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) || !ce1->num_parents ? NULL : ce1->parents[0]->ce;
4019+
ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) || !ce2->num_parents ? NULL : ce2->parents[0]->ce;
40334020
}
40344021

40354022
if (ce1) {
@@ -5070,7 +5057,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
50705057
const zend_class_entry *ce = var_info->ce;
50715058

50725059
if (var_info->is_instanceof ||
5073-
!ce || ce->create_object || ce->__get || ce->__set || ce->parent) {
5060+
!ce || ce->create_object || ce->__get || ce->__set || ce->num_parents) {
50745061
return 1;
50755062
}
50765063

Zend/tests/bug77530.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ echo (2)::class;
77

88
?>
99
--EXPECTF--
10-
Fatal error: Illegal class name in %s on line %d
10+
Fatal error: Cannot use "::class" on int in %s on line %d

Zend/tests/generics/basic.phpt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Basic generic class declaration
3+
--FILE--
4+
<?php
5+
6+
interface I<T> {
7+
}
8+
9+
class C<T> {
10+
}
11+
12+
final class F<T> {
13+
}
14+
15+
trait T<P> {
16+
}
17+
18+
?>
19+
--EXPECT--
20+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Duplicate generic parameter name
3+
--FILE--
4+
<?php
5+
6+
class Test<T, T> {
7+
}
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Duplicate generic parameter T in %s on line %d
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
--TEST--
2+
Combining a generic parameter with other types
3+
--FILE--
4+
<?php
5+
6+
abstract class AbstractTest1<T> {
7+
public ?T $prop;
8+
public function method(?T $param) {
9+
var_dump($param);
10+
}
11+
}
12+
class ConcreteTest1 extends AbstractTest1<array> {}
13+
14+
$obj = new ConcreteTest1;
15+
$obj->method([]);
16+
$obj->method(null);
17+
try {
18+
$obj->method("string");
19+
} catch (TypeError $e) {
20+
echo $e->getMessage(), "\n";
21+
}
22+
23+
echo "\n";
24+
25+
abstract class AbstractTest2<T> {
26+
public T|int $prop;
27+
public function method(T|int $param) {
28+
var_dump($param);
29+
}
30+
}
31+
class ConcreteTest2 extends AbstractTest2<array> {}
32+
33+
$obj = new ConcreteTest2;
34+
$obj->method([]);
35+
$obj->method(42);
36+
$obj->method("42");
37+
38+
echo "\n";
39+
40+
abstract class AbstractTest3<T> {
41+
public T|stdClass $prop;
42+
public function method(T|stdClass $param) {
43+
var_dump($param);
44+
}
45+
}
46+
class ConcreteTest3 extends AbstractTest3<array> {}
47+
48+
$obj = new ConcreteTest3;
49+
$obj->method([]);
50+
$obj->method(new stdClass);
51+
try {
52+
$obj->method("string");
53+
} catch (TypeError $e) {
54+
echo $e->getMessage(), "\n";
55+
}
56+
57+
?>
58+
--EXPECTF--
59+
array(0) {
60+
}
61+
NULL
62+
AbstractTest1::method(): Argument #1 ($param) must be of type ?T (where T = array), string given, called in %s on line %d
63+
64+
array(0) {
65+
}
66+
int(42)
67+
int(42)
68+
69+
array(0) {
70+
}
71+
object(stdClass)#3 (0) {
72+
}
73+
AbstractTest3::method(): Argument #1 ($param) must be of type T|stdClass (where T = array), string given, called in %s on line %d
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
Bind direct parent parameter during inheritance
3+
--FILE--
4+
<?php
5+
6+
abstract class WithParam<T> {
7+
public T $prop;
8+
9+
public function method(T $param) {
10+
var_dump($param);
11+
}
12+
}
13+
14+
class ConcreteInt extends WithParam<int> {
15+
}
16+
17+
class ConcreteStdClass extends WithParam<stdClass> {
18+
}
19+
20+
class ConcreteSelf extends WithParam<self> {
21+
}
22+
23+
$obj = new ConcreteInt;
24+
$obj->method(42);
25+
$obj->prop = 42;
26+
var_dump($obj->prop);
27+
28+
try {
29+
$obj->method("string");
30+
} catch (TypeError $e) {
31+
echo $e->getMessage(), "\n";
32+
}
33+
try {
34+
$obj->prop = "string";
35+
} catch (TypeError $e) {
36+
echo $e->getMessage(), "\n";
37+
}
38+
39+
$obj = new ConcreteStdClass;
40+
$obj->method(new stdClass);
41+
//$obj->prop = new stdClass;
42+
try {
43+
$obj->method("string");
44+
} catch (TypeError $e) {
45+
echo $e->getMessage(), "\n";
46+
}
47+
48+
/* TODO: This broke -- "self" resolves to WithParam here.
49+
$obj = new ConcreteSelf;
50+
$obj->method($obj);
51+
try {
52+
$obj->method(new stdClass);
53+
} catch (TypeError $e) {
54+
echo $e->getMessage(), "\n";
55+
}
56+
*/
57+
58+
?>
59+
--EXPECTF--
60+
int(42)
61+
int(42)
62+
WithParam::method(): Argument #1 ($param) must be of type T (where T = int), string given, called in %s on line %d
63+
Cannot assign string to property WithParam::$prop of type T
64+
object(stdClass)#1 (0) {
65+
}
66+
WithParam::method(): Argument #1 ($param) must be of type T (where T = stdClass), string given, called in %s on line %d
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
--TEST--
2+
Bind multiple parent parameters during inheritance
3+
--FILE--
4+
<?php
5+
6+
abstract class WithParams<T1, T2> {
7+
public function method(T1 $param1, T2 $param2) {
8+
var_dump($param1);
9+
var_dump($param2);
10+
}
11+
}
12+
13+
abstract class WithSameParam<T> extends WithParams<T, T> {
14+
public function method2(T $param) {
15+
var_dump($param);
16+
}
17+
}
18+
19+
abstract class WithOneFixedParam<T> extends WithParams<int, T> {
20+
public function method2(T $param) {
21+
var_dump($param);
22+
}
23+
}
24+
25+
abstract class WithFirstNullableParam<T> extends WithParams<?T, T> {
26+
}
27+
28+
class ConcreteIntInt extends WithSameParam<int> {
29+
}
30+
31+
class ConcreteIntString extends WithOneFixedParam<string> {
32+
}
33+
34+
class ConcreteNullableIntInt extends WithFirstNullableParam<int> {
35+
}
36+
37+
$obj = new ConcreteIntInt;
38+
$obj->method(42, 42);
39+
$obj->method2(42);
40+
41+
try {
42+
$obj->method("string", "string");
43+
} catch (TypeError $e) {
44+
echo $e->getMessage(), "\n";
45+
}
46+
try {
47+
$obj->method2("string");
48+
} catch (TypeError $e) {
49+
echo $e->getMessage(), "\n";
50+
}
51+
52+
echo "\n";
53+
$obj = new ConcreteIntString;
54+
$obj->method(42, "string");
55+
$obj->method2("string");
56+
57+
try {
58+
$obj->method("string", 42);
59+
} catch (TypeError $e) {
60+
echo $e->getMessage(), "\n";
61+
}
62+
try {
63+
$obj->method2(42);
64+
} catch (TypeError $e) {
65+
echo $e->getMessage(), "\n";
66+
}
67+
68+
echo "\n";
69+
$obj = new ConcreteNullableIntInt;
70+
$obj->method(null, 42);
71+
72+
?>
73+
--EXPECTF--
74+
int(42)
75+
int(42)
76+
int(42)
77+
WithParams::method(): Argument #1 ($param1) must be of type T1 (where T1 = int), string given, called in %s on line %d
78+
WithSameParam::method2(): Argument #1 ($param) must be of type T (where T = int), string given, called in %s on line %d
79+
80+
int(42)
81+
string(6) "string"
82+
string(6) "string"
83+
WithParams::method(): Argument #1 ($param1) must be of type T1 (where T1 = int), string given, called in %s on line %d
84+
string(2) "42"
85+
86+
NULL
87+
int(42)

0 commit comments

Comments
 (0)