Skip to content

Commit 5ad9c0d

Browse files
committed
fix synthesis inside generic methods
Signed-off-by: Robert Landers <landers.robert@gmail.com>
1 parent b05e2dc commit 5ad9c0d

3 files changed

Lines changed: 86 additions & 2 deletions

File tree

Zend/zend_inheritance.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6651,6 +6651,81 @@ ZEND_API zend_class_entry *zend_get_defaults_monomorph(zend_class_entry *base)
66516651
ZEND_UNREACHABLE();
66526652
}
66536653

6654+
/* When `new C::<...>(...)` is compiled inside a generic function/class, the
6655+
* turbofish args may reference enclosing-scope T parameters by ref. The op_array
6656+
* side-table stores those refs verbatim — at synth time they must be resolved
6657+
* against the executing frame's bindings, or the monomorph would carry literal
6658+
* "Box<T>"-style args and its method arg_info would never resolve to a concrete
6659+
* type. Walks args[i]; for top-level T-refs, substitutes via the frame's
6660+
* function-level type_args (FUNCTION_LIKE) or the lexical class's monomorph
6661+
* descendant's generic_type_args (CLASS_LIKE). Out slots that didn't need
6662+
* substitution are copied verbatim so callers can pass out[] unconditionally.
6663+
* Returns false when any ref can't be resolved — caller should fall through to
6664+
* the existing synth-with-unresolved-args behavior so the diagnostic remains
6665+
* where it always was. */
6666+
static bool zend_resolve_synth_args_against_frame(
6667+
const zend_type *args, uint32_t arity, zend_type *out)
6668+
{
6669+
zend_execute_data *ex = EG(current_execute_data);
6670+
for (uint32_t i = 0; i < arity; i++) {
6671+
if (!ZEND_TYPE_HAS_TYPE_PARAMETER(args[i])) {
6672+
out[i] = args[i];
6673+
continue;
6674+
}
6675+
const zend_type_parameter_ref *ref = ZEND_TYPE_TYPE_PARAMETER(args[i]);
6676+
const zend_type *resolved = NULL;
6677+
if (ref->origin == ZEND_GENERIC_ORIGIN_FUNCTION_LIKE) {
6678+
if (ex && ZEND_USER_CODE(ex->func->type)
6679+
&& ex->type_args
6680+
&& ref->index < ex->type_args->count) {
6681+
resolved = zend_type_arg_entry_type(&ex->type_args->entries[ref->index]);
6682+
}
6683+
} else {
6684+
/* Walk from called scope up to the direct child of the lexical
6685+
* class — that's the monomorph carrying the binding (see the
6686+
* matching walk in zend_resolve_generic_type_param). */
6687+
if (ex) {
6688+
zend_class_entry *lexical = ex->func->common.scope;
6689+
zend_class_entry *cur = zend_get_called_scope(ex);
6690+
while (cur && cur->parent != lexical) {
6691+
cur = cur->parent;
6692+
}
6693+
if (cur && cur->generic_type_args
6694+
&& ref->index < cur->generic_type_args->count) {
6695+
resolved = zend_type_arg_entry_type(
6696+
&cur->generic_type_args->entries[ref->index]);
6697+
}
6698+
}
6699+
}
6700+
if (!resolved || !ZEND_TYPE_IS_SET(*resolved)) {
6701+
return false;
6702+
}
6703+
out[i] = *resolved;
6704+
}
6705+
return true;
6706+
}
6707+
6708+
/* Same as zend_synthesize_monomorph, but resolves TYPE_PARAMETER refs in args
6709+
* against the executing frame's T-tables first. Use this at runtime `new`
6710+
* sites where the args originate from a compile-time side-table that may
6711+
* reference enclosing-scope T's. Static callers (class-build extends/implements
6712+
* with already-resolved args) keep using zend_synthesize_monomorph directly. */
6713+
ZEND_API zend_class_entry *zend_synthesize_monomorph_resolved(
6714+
zend_class_entry *base, const zend_type *args, uint32_t arity)
6715+
{
6716+
if (arity == 0) {
6717+
return zend_synthesize_monomorph(base, args, arity);
6718+
}
6719+
zend_type resolved[ZEND_GENERIC_MAX_PARAMS];
6720+
if (!zend_resolve_synth_args_against_frame(args, arity, resolved)) {
6721+
/* Fall through with the unresolved refs — produces the
6722+
* pre-existing "Box<T>" monomorph + downstream TypeError that the
6723+
* caller's existing error path already handles. */
6724+
return zend_synthesize_monomorph(base, args, arity);
6725+
}
6726+
return zend_synthesize_monomorph(base, resolved, arity);
6727+
}
6728+
66546729
ZEND_API zend_class_entry *zend_synthesize_monomorph(
66556730
zend_class_entry *base, const zend_type *args, uint32_t arity)
66566731
{

Zend/zend_inheritance.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
7070
ZEND_API zend_class_entry *zend_synthesize_monomorph(
7171
zend_class_entry *base, const zend_type *args, uint32_t arity);
7272

73+
/* Same as zend_synthesize_monomorph, but first resolves any TYPE_PARAMETER refs
74+
* in args[] against the currently executing frame's bindings (function-level
75+
* via EX()->type_args; class-level via the lexical class's monomorph descendant).
76+
* Use at runtime `new` sites where the args originate from an op_array side-table
77+
* compiled inside a generic scope and may name enclosing-scope T's by ref.
78+
* Static-build callers with already-resolved args keep using the base. */
79+
ZEND_API zend_class_entry *zend_synthesize_monomorph_resolved(
80+
zend_class_entry *base, const zend_type *args, uint32_t arity);
81+
7382
/* For a bare generic class `base`, synthesize (or return the cached) monomorph
7483
* built from the parameters' declared defaults. Returns NULL and throws Error
7584
* if any parameter has no default. If `base` is itself a monomorph (no

Zend/zend_vm_def.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9115,7 +9115,7 @@ ZEND_VM_HANDLER(212, ZEND_VERIFY_GENERIC_ARGUMENTS, TMP|UNUSED, UNUSED)
91159115
if (!EG(exception) && args_box && ZEND_TYPE_HAS_NAMED_WITH_ARGS(*args_box)) {
91169116
const zend_type_named_with_args *nwa = ZEND_TYPE_NAMED_WITH_ARGS(*args_box);
91179117
if (ce->generic_parameters) {
9118-
zend_class_entry *mono = zend_synthesize_monomorph(ce, nwa->args, nwa->count);
9118+
zend_class_entry *mono = zend_synthesize_monomorph_resolved(ce, nwa->args, nwa->count);
91199119
if (mono && mono != ce) {
91209120
Z_OBJ_P(new_obj)->ce = mono;
91219121
if (mono->constructor && call->func == ce->constructor) {
@@ -9181,7 +9181,7 @@ ZEND_VM_HANDLER(213, ZEND_INSTALL_GENERIC_ARGS, TMP|UNUSED, UNUSED)
91819181
if (args_box && ZEND_TYPE_HAS_NAMED_WITH_ARGS(*args_box)) {
91829182
const zend_type_named_with_args *nwa = ZEND_TYPE_NAMED_WITH_ARGS(*args_box);
91839183
if (ce->generic_parameters) {
9184-
zend_class_entry *mono = zend_synthesize_monomorph(ce, nwa->args, nwa->count);
9184+
zend_class_entry *mono = zend_synthesize_monomorph_resolved(ce, nwa->args, nwa->count);
91859185
if (mono && mono != ce) {
91869186
Z_OBJ_P(new_obj)->ce = mono;
91879187
if (mono->constructor && call->func == ce->constructor) {

0 commit comments

Comments
 (0)