MCPcopy Create free account
hub / github.com/PHPantom-dev/phpantom_lsp / build_mixin_substitution_map

Function build_mixin_substitution_map

src/virtual_members/phpdoc.rs:955–1026  ·  view source on GitHub ↗

This mirrors [`crate::inheritance::build_substitution_map`] but is scoped to the virtual-member provider so it does not need to be public on the inheritance module.

(
    current: &ClassInfo,
    parent: &ClassInfo,
    active_subs: &HashMap<String, PhpType>,
)

Source from the content-addressed store, hash-verified

953/// scoped to the virtual-member provider so it does not need to be public
954/// on the inheritance module.
955fn build_mixin_substitution_map(
956 current: &ClassInfo,
957 parent: &ClassInfo,
958 active_subs: &HashMap<String, PhpType>,
959) -> HashMap<String, PhpType> {
960 if parent.template_params.is_empty() {
961 return active_subs.clone();
962 }
963
964 let parent_short = short_name(&parent.name);
965
966 // Find `@extends`/`@implements` generics matching this parent.
967 let type_args = current
968 .extends_generics
969 .iter()
970 .chain(current.implements_generics.iter())
971 .find(|(name, _)| {
972 let name_short = short_name(name);
973 name_short == parent_short
974 })
975 .map(|(_, args)| args);
976
977 let type_args = match type_args {
978 Some(args) => args,
979 None => return active_subs.clone(),
980 };
981
982 // Check whether the parent has any @mixin whose name is itself a
983 // template parameter (e.g. `@mixin TNode` on a class with
984 // `@template TNode`). When this is the case and a substitution
985 // still resolves to a raw template parameter name on the child
986 // class, we fall back to the template bound. This handles the
987 // PHPMD pattern where `AbstractNode<TNode>` has `@mixin TNode`
988 // and `ASTNode extends AbstractNode<TNode>` — without the
989 // fallback, `TNode` stays as an unresolvable class name.
990 //
991 // We do NOT apply this fallback when the mixin is a concrete
992 // class with template arguments (e.g. `@mixin Builder<TModel>`),
993 // because the template param may be resolved later by a concrete
994 // caller through the generic substitution chain.
995 let parent_has_template_param_mixin = parent.mixins.iter().any(|m| {
996 parent
997 .template_params
998 .iter()
999 .any(|t| t.as_str() == m.as_str())
1000 });
1001
1002 let mut map = HashMap::new();
1003 for (i, param_name) in parent.template_params.iter().enumerate() {
1004 if let Some(arg) = type_args.get(i) {
1005 let mut resolved = if active_subs.is_empty() {
1006 arg.clone()
1007 } else {
1008 arg.substitute(active_subs)
1009 };
1010
1011 // Fall back to the template bound only when the parent
1012 // uses the template param directly as a mixin name.

Callers 1

provideMethod · 0.85

Calls 11

cloneMethod · 0.80
iterMethod · 0.80
as_strMethod · 0.80
substituteMethod · 0.80
base_nameMethod · 0.80
short_nameFunction · 0.50
is_emptyMethod · 0.45
mapMethod · 0.45
findMethod · 0.45
getMethod · 0.45
insertMethod · 0.45

Tested by

no test coverage detected