Resolve a class together with all inherited members from its parent chain. Walks up the `extends` chain via `class_loader`, collecting public and protected methods, properties, and constants from each ancestor. If a child already defines a member with the same name as a parent member, the child's version wins (even if the signatures differ). Private members are never inherited. When the child d
(
class: &ClassInfo,
class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>,
)
| 328 | /// |
| 329 | /// A depth limit of 20 prevents infinite loops from circular inheritance. |
| 330 | pub(crate) fn resolve_class_with_inheritance( |
| 331 | class: &ClassInfo, |
| 332 | class_loader: &dyn Fn(&str) -> Option<Arc<ClassInfo>>, |
| 333 | ) -> ClassInfo { |
| 334 | let mut merged = class.clone(); |
| 335 | |
| 336 | // Build dedup sets from the class's own members. These are passed |
| 337 | // through trait merging and the parent chain walk so that every |
| 338 | // addition is tracked in O(1) across all recursion levels. |
| 339 | let mut dedup = MergeDedup::from_class(&merged); |
| 340 | |
| 341 | // 1. Merge traits used by this class. |
| 342 | // PHP precedence: class methods > trait methods > inherited methods. |
| 343 | // Since `merged` already contains the class's own members, we only |
| 344 | // add trait members that don't collide with existing ones. |
| 345 | merge_traits_into( |
| 346 | &mut merged, |
| 347 | &class.used_traits, |
| 348 | &TraitContext { |
| 349 | use_generics: &class.use_generics, |
| 350 | precedences: &class.trait_precedences, |
| 351 | aliases: &class.trait_aliases, |
| 352 | }, |
| 353 | class_loader, |
| 354 | 0, |
| 355 | &mut dedup, |
| 356 | &class.fqn(), |
| 357 | ); |
| 358 | |
| 359 | // 2. Walk up the `extends` chain and merge parent members. |
| 360 | // |
| 361 | // `current` holds a reference to the class whose `parent_class`, |
| 362 | // `extends_generics`, `used_traits`, etc. we read at each level. |
| 363 | // For the first iteration this is the root `class` (a borrow — |
| 364 | // zero allocation). After that it becomes the `Arc<ClassInfo>` |
| 365 | // returned by `class_loader` (a cheap Arc move). |
| 366 | let mut current: ClassRef<'_> = ClassRef::Borrowed(class); |
| 367 | let mut depth = 0; |
| 368 | |
| 369 | // The substitution map accumulates as we walk the chain. |
| 370 | // It maps template parameter names → concrete types, and is |
| 371 | // re-computed at each level based on the `@extends` generics |
| 372 | // of the current class and the `@template` params of the parent. |
| 373 | let mut active_subs: HashMap<String, PhpType> = HashMap::new(); |
| 374 | |
| 375 | // Seed the initial substitution map from the root class's |
| 376 | // `@extends` generics. If the root class has |
| 377 | // `@extends Collection<int, Language>`, this will be applied |
| 378 | // when we load `Collection` as the first parent. |
| 379 | // |
| 380 | // We don't apply it yet — it's matched against the parent's |
| 381 | // template_params in the loop below. |
| 382 | |
| 383 | while let Some(ref parent_name) = current.parent_class { |
| 384 | depth += 1; |
| 385 | if depth > MAX_INHERITANCE_DEPTH { |
| 386 | break; |
| 387 | } |