Build a substitution map for a parent class based on the child's `@extends` generics and the parent's `@template` parameters. If the child declares `@extends Collection ` and the parent `Collection` has `@template TKey` and `@template TValue`, the returned map is `{TKey => int, TValue => Language}`. When `active_subs` is non-empty (from a higher-level ancestor), the type arguments
(
current: &ClassInfo,
parent: &ClassInfo,
active_subs: &HashMap<String, PhpType>,
)
| 1147 | /// `@extends A<T>` gets the active substitution `{T => Foo}` applied, |
| 1148 | /// yielding `{U => Foo}`. |
| 1149 | fn build_substitution_map( |
| 1150 | current: &ClassInfo, |
| 1151 | parent: &ClassInfo, |
| 1152 | active_subs: &HashMap<String, PhpType>, |
| 1153 | ) -> HashMap<String, PhpType> { |
| 1154 | if parent.template_params.is_empty() { |
| 1155 | return active_subs.clone(); |
| 1156 | } |
| 1157 | |
| 1158 | let parent_short = short_name(&parent.name); |
| 1159 | |
| 1160 | // Search `current.extends_generics` for an entry matching this parent. |
| 1161 | // Also check `implements_generics` for interface inheritance. |
| 1162 | let type_args = current |
| 1163 | .extends_generics |
| 1164 | .iter() |
| 1165 | .chain(current.implements_generics.iter()) |
| 1166 | .find(|(name, _)| { |
| 1167 | let name_short = short_name(name); |
| 1168 | name_short == parent_short |
| 1169 | }) |
| 1170 | .map(|(_, args)| args); |
| 1171 | |
| 1172 | let type_args = match type_args { |
| 1173 | Some(args) => args, |
| 1174 | None => { |
| 1175 | // No @extends/@implements generics for this parent. |
| 1176 | // Carry forward any active substitutions — they may still |
| 1177 | // apply if the parent's methods reference template params |
| 1178 | // from a grandchild. |
| 1179 | return active_subs.clone(); |
| 1180 | } |
| 1181 | }; |
| 1182 | |
| 1183 | let mut map = HashMap::new(); |
| 1184 | |
| 1185 | for (i, param_name) in parent.template_params.iter().enumerate() { |
| 1186 | if let Some(arg) = type_args.get(i) { |
| 1187 | // Apply any active substitutions to the type argument. |
| 1188 | // This handles chaining: if arg is "T" and active_subs has |
| 1189 | // {T => Foo}, the result is {param_name => Foo}. |
| 1190 | let resolved = if active_subs.is_empty() { |
| 1191 | arg.clone() |
| 1192 | } else { |
| 1193 | arg.substitute(active_subs) |
| 1194 | }; |
| 1195 | map.insert(param_name.to_string(), resolved); |
| 1196 | } |
| 1197 | } |
| 1198 | |
| 1199 | map |
| 1200 | } |
| 1201 | |
| 1202 | /// Apply generic type substitution to a method's return type and parameter |
| 1203 | /// type hints. |