Skip to content

Commit 5e28858

Browse files
committed
handle inference for scalar values
Signed-off-by: Robert Landers <landers.robert@gmail.com>
1 parent 11e7493 commit 5e28858

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

Zend/tests/generics/reification/composite_return_reification.phpt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ function par<T>(T|Other $x): string { return $x::class; }
2929
var_dump(par::<Foo>(new Foo));
3030
try { par::<Foo>(42); } catch (TypeError $e) { echo $e->getMessage(), "\n"; }
3131

32+
// Parameters fold/flatten through the same path as returns: T=Other folds the
33+
// duplicate, and a union binding flattens. The reified param type is observable.
34+
par::<Other>(new Other);
35+
var_dump((string) (new ReflectionFunction('par<Other>'))->getParameters()[0]->getType());
36+
par::<Foo|Other>(new Foo);
37+
var_dump((string) (new ReflectionFunction('par<Foo|Other>'))->getParameters()[0]->getType());
38+
3239
// Intersection return: T bound to an interface, T&B reifies to A&B.
3340
function inter<T: A>(mixed $x): T&B { return $x; }
3441
var_dump(inter::<A>(new AB)::class);
@@ -51,6 +58,8 @@ string(5) "Other"
5158
two(): Return value must be of type Foo|Other, int returned
5259
string(3) "Foo"
5360
par(): Argument #1 ($x) must be of type Foo|Other, int given, called in %s on line %d
61+
string(5) "Other"
62+
string(9) "Foo|Other"
5463
string(2) "AB"
5564
inter(): Return value must be of type A&B, Foo returned
5665
string(2) "AB"

Zend/zend_compile.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,30 @@ ZEND_API zend_type_arg_table *zend_build_generic_call_type_args(
11651165
zend_string *inferred_name = zend_string_copy(arg_ce->name);
11661166
table->entries[ref->index].owned_type =
11671167
(zend_type) ZEND_TYPE_INIT_CLASS(inferred_name, 0, 0);
1168+
} else {
1169+
/* Scalar/array values infer to their builtin type. Map the
1170+
* zval type to a type code, synthesise a mask owned_type, and
1171+
* take its canonical name ("int", "string", ...) so reflection
1172+
* and a reified `: T` return enforce the inferred type instead
1173+
* of collapsing to the default. IS_TRUE/IS_FALSE both map to
1174+
* bool; IS_NULL and other non-reifiable kinds (resources) are
1175+
* left for the declared default. */
1176+
uint32_t type_mask = 0;
1177+
switch (Z_TYPE_P(arg)) {
1178+
case IS_LONG: type_mask = MAY_BE_LONG; break;
1179+
case IS_DOUBLE: type_mask = MAY_BE_DOUBLE; break;
1180+
case IS_STRING: type_mask = MAY_BE_STRING; break;
1181+
case IS_ARRAY: type_mask = MAY_BE_ARRAY; break;
1182+
case IS_TRUE:
1183+
case IS_FALSE: type_mask = MAY_BE_BOOL; break;
1184+
default: break;
1185+
}
1186+
if (type_mask) {
1187+
zend_type inferred = (zend_type) ZEND_TYPE_INIT_MASK(type_mask);
1188+
table->entries[ref->index].name =
1189+
zend_type_arg_canonical_name(inferred);
1190+
table->entries[ref->index].owned_type = inferred;
1191+
}
11681192
}
11691193
} ZEND_HASH_FOREACH_END();
11701194
}

0 commit comments

Comments
 (0)