@@ -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+
66546729ZEND_API zend_class_entry * zend_synthesize_monomorph (
66556730 zend_class_entry * base , const zend_type * args , uint32_t arity )
66566731{
0 commit comments