use rustc_hash::{FxHashMap, FxHashSet};
use mir_codebase::definitions::{DeclaredParam, TemplateParam};
use mir_types::{atomic::ArrayKey, union::empty_type_params, Atomic, Name, Type};
use crate::db::MirDatabase;
use crate::subtype::is_subtype;
pub fn infer_template_bindings(
db: &dyn MirDatabase,
template_params: &[TemplateParam],
params: &[DeclaredParam],
arg_types: &[Type],
arg_names: &[Option<String>],
) -> (FxHashMap<Name, Type>, FxHashSet<Name>) {
let (mut bindings, unchecked) =
infer_arg_template_bindings(db, template_params, params, arg_types, arg_names);
for tp in template_params {
bindings
.entry(Name::from(tp.name.as_ref()))
.or_insert_with(|| {
tp.default
.as_deref()
.or(tp.bound.as_deref())
.cloned()
.unwrap_or_else(Type::mixed)
});
}
for _ in 0..template_params.len() {
let mut changed = false;
for tp in template_params {
let name = Name::from(tp.name.as_ref());
if let Some(current) = bindings.get(&name) {
let substituted = current.substitute_templates(&bindings);
if substituted != *current {
bindings.insert(name, substituted);
changed = true;
}
}
}
if !changed {
break;
}
}
(bindings, unchecked)
}
pub fn infer_arg_template_bindings(
db: &dyn MirDatabase,
template_params: &[TemplateParam],
params: &[DeclaredParam],
arg_types: &[Type],
arg_names: &[Option<String>],
) -> (FxHashMap<Name, Type>, FxHashSet<Name>) {
let mut bindings: FxHashMap<Name, Type> = FxHashMap::default();
let mut risky_fallback: FxHashMap<Name, Type> = FxHashMap::default();
let template_names: FxHashSet<Name> = template_params
.iter()
.map(|tp| Name::from(tp.name.as_ref()))
.collect();
for (param, arg_ty) in bind_args_to_params(params, arg_types, arg_names) {
if let Some(param_ty) = ¶m.ty {
if param.is_variadic {
let elem = variadic_element_type(param_ty);
infer_from_pair(
db,
elem,
arg_ty,
&template_names,
&mut bindings,
&mut risky_fallback,
);
} else {
infer_from_pair(
db,
param_ty,
arg_ty,
&template_names,
&mut bindings,
&mut risky_fallback,
);
}
}
}
let mut unchecked: FxHashSet<Name> = FxHashSet::default();
for (name, val) in risky_fallback {
if let std::collections::hash_map::Entry::Vacant(e) = bindings.entry(name) {
e.insert(val);
unchecked.insert(name);
}
}
(bindings, unchecked)
}
fn bind_args_to_params<'p, 'a>(
params: &'p [DeclaredParam],
arg_types: &'a [Type],
arg_names: &[Option<String>],
) -> Vec<(&'p DeclaredParam, &'a Type)> {
let variadic_index = params.iter().position(|p| p.is_variadic);
let max_positional = variadic_index.unwrap_or(params.len());
let mut used = vec![false; params.len()];
let mut out = Vec::with_capacity(arg_types.len());
let mut positional = 0usize;
for (i, arg_ty) in arg_types.iter().enumerate() {
if let Some(Some(name)) = arg_names.get(i) {
if let Some(pi) = params.iter().position(|p| p.name.as_ref() == name.as_str()) {
if !used[pi] {
used[pi] = true;
out.push((¶ms[pi], arg_ty));
}
} else if let Some(vi) = variadic_index {
out.push((¶ms[vi], arg_ty));
}
continue;
}
while positional < max_positional && used[positional] {
positional += 1;
}
let pi = if positional < max_positional {
Some(positional)
} else {
variadic_index
};
if let Some(pi) = pi {
if pi < max_positional {
used[pi] = true;
positional += 1;
}
out.push((¶ms[pi], arg_ty));
}
}
out
}
pub(crate) fn variadic_element_type(ty: &Type) -> &Type {
if ty.types.len() == 1 {
match &ty.types[0] {
Atomic::TArray { key, value } | Atomic::TNonEmptyArray { key, value }
if key_admits_int_variadic_index(key) =>
{
value
}
Atomic::TList { value } | Atomic::TNonEmptyList { value } => value,
_ => ty,
}
} else {
ty
}
}
pub(crate) fn key_admits_int_variadic_index(key: &Type) -> bool {
key.is_empty() || key.types.iter().any(|a| a.is_int())
}
pub fn check_template_bounds_with_inheritance<'a>(
db: &dyn MirDatabase,
bindings: &'a FxHashMap<Name, Type>,
template_params: &'a [TemplateParam],
unchecked: &FxHashSet<Name>,
receiver_fqcn: Option<&str>,
) -> Vec<(&'a Name, &'a Type, Type)> {
fn is_unresolved(ty: &Type, template_params: &[TemplateParam]) -> bool {
ty.types.iter().any(|a| match a {
Atomic::TTemplateParam { .. }
| Atomic::TSelf { .. }
| Atomic::TStaticObject { .. }
| Atomic::TParent { .. } => true,
Atomic::TNamedObject { fqcn, type_params } => {
(type_params.is_empty() && !fqcn.contains('\\') && {
let name = fqcn.as_str();
name.eq_ignore_ascii_case("self")
|| name.eq_ignore_ascii_case("static")
|| name.eq_ignore_ascii_case("parent")
|| template_params.iter().any(|tp| tp.name.as_ref() == name)
}) || type_params
.iter()
.any(|t| is_unresolved_shallow(t, template_params))
}
Atomic::TIntersection { parts } => {
parts.iter().any(|p| is_unresolved(p, template_params))
}
_ => false,
})
}
let mut violations = Vec::new();
let mut seen_names: FxHashSet<&Name> = FxHashSet::default();
for tp in template_params {
if !seen_names.insert(&tp.name) {
continue;
}
if unchecked.contains(&tp.name) {
continue;
}
if let Some(bound) = &tp.bound {
if let Some(inferred) = bindings.get(&tp.name) {
let resolved_bound =
resolve_static_in_bound(bound.substitute_templates(bindings), receiver_fqcn);
if !resolved_bound.is_mixed()
&& !inferred.is_mixed()
&& !is_unresolved(inferred, template_params)
&& !is_subtype(db, inferred, &resolved_bound)
{
violations.push((&tp.name, inferred, resolved_bound));
}
}
}
}
violations
}
fn resolve_static_in_bound(bound: Type, receiver_fqcn: Option<&str>) -> Type {
let Some(fqcn) = receiver_fqcn else {
return bound;
};
let from_docblock = bound.from_docblock;
let possibly_undefined = bound.possibly_undefined;
let types: Vec<Atomic> = bound
.types
.into_iter()
.map(|a| resolve_static_in_bound_atomic(a, fqcn))
.collect();
let mut result = Type::from_vec(types);
result.from_docblock = from_docblock;
result.possibly_undefined = possibly_undefined;
result
}
fn resolve_static_in_bound_atomic(a: Atomic, fqcn: &str) -> Atomic {
match a {
Atomic::TStaticObject { .. } => Atomic::TNamedObject {
fqcn: Name::from(fqcn),
type_params: empty_type_params(),
},
Atomic::TNamedObject {
fqcn: obj_fqcn,
type_params,
} => Atomic::TNamedObject {
fqcn: obj_fqcn,
type_params: type_params
.iter()
.cloned()
.map(|t| resolve_static_in_bound(t, Some(fqcn)))
.collect::<Vec<_>>()
.into(),
},
Atomic::TIntersection { parts } => Atomic::TIntersection {
parts: parts
.iter()
.cloned()
.map(|t| resolve_static_in_bound(t, Some(fqcn)))
.collect::<Vec<_>>()
.into(),
},
other => other,
}
}
fn is_unresolved_shallow(ty: &Type, template_params: &[TemplateParam]) -> bool {
ty.types.iter().any(|a| match a {
Atomic::TTemplateParam { .. }
| Atomic::TSelf { .. }
| Atomic::TStaticObject { .. }
| Atomic::TParent { .. } => true,
Atomic::TNamedObject { fqcn, type_params } => {
type_params.is_empty() && !fqcn.contains('\\') && {
let name = fqcn.as_str();
name.eq_ignore_ascii_case("self")
|| name.eq_ignore_ascii_case("static")
|| name.eq_ignore_ascii_case("parent")
|| template_params.iter().any(|tp| tp.name.as_ref() == name)
}
}
_ => false,
})
}
pub fn build_class_bindings(
class_template_params: &[TemplateParam],
receiver_type_params: &[Type],
) -> FxHashMap<Name, Type> {
let mut bindings: FxHashMap<Name, Type> = class_template_params
.iter()
.zip(receiver_type_params.iter())
.map(|(tp, ty)| (Name::from(tp.name.as_ref()), ty.clone()))
.collect();
for tp in class_template_params {
if let Some(default) = tp.default.as_deref() {
bindings
.entry(Name::from(tp.name.as_ref()))
.or_insert_with(|| default.clone());
}
}
bindings
}
enum TemplateResidual {
UseArg,
Filtered(Type),
FullyExplainedByAlternative(Type),
}
impl TemplateResidual {
fn bind_value<'a>(&'a self, arg_ty: &'a Type) -> &'a Type {
match self {
TemplateResidual::UseArg => arg_ty,
TemplateResidual::Filtered(t) | TemplateResidual::FullyExplainedByAlternative(t) => t,
}
}
fn is_risky(&self) -> bool {
matches!(self, TemplateResidual::FullyExplainedByAlternative(_))
}
}
fn compute_template_residual(
param_ty: &Type,
arg_ty: &Type,
template_names: &FxHashSet<Name>,
) -> TemplateResidual {
let mut has_template = false;
let mut has_template_class_string = false;
let mut concrete: Vec<&Atomic> = Vec::new();
for a in ¶m_ty.types {
if is_template_atomic(a, template_names) {
has_template = true;
} else if matches!(a, Atomic::TClassString(Some(n)) | Atomic::TInterfaceString(Some(n)) if template_names.contains(n))
{
has_template_class_string = true;
} else {
concrete.push(a);
}
}
if !has_template || (concrete.is_empty() && !has_template_class_string) {
return TemplateResidual::UseArg;
}
let mut residual = Type::empty();
residual.from_docblock = arg_ty.from_docblock;
residual.possibly_undefined = arg_ty.possibly_undefined;
let mut class_string_consumed = false;
let mut container_consumed = false;
for a in &arg_ty.types {
let consumed_by_class_string = has_template_class_string
&& matches!(a, Atomic::TClassString(_) | Atomic::TInterfaceString(_))
|| matches!(a, Atomic::TLiteralString(s) if has_template_class_string && literal_is_class_like(s));
if consumed_by_class_string {
class_string_consumed = true;
continue;
}
if let Some(c) = concrete.iter().find(|c| atomics_match_for_filter(c, a)) {
if atomic_carries_template(c, template_names) {
container_consumed = true;
}
continue;
}
residual.add_type(a.clone());
}
if residual.types.is_empty() {
if class_string_consumed || container_consumed {
return TemplateResidual::Filtered(residual);
}
return TemplateResidual::FullyExplainedByAlternative(arg_ty.clone());
}
if residual.types.len() == arg_ty.types.len() && !class_string_consumed {
return TemplateResidual::UseArg;
}
TemplateResidual::Filtered(residual)
}
fn literal_is_class_like(s: &str) -> bool {
let t = s.trim_start_matches('\\');
if t.is_empty() {
return false;
}
let shape_ok = t.split('\\').all(|seg| {
!seg.is_empty()
&& seg
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || !c.is_ascii())
&& !seg.chars().next().is_some_and(|c| c.is_ascii_digit())
});
shape_ok && (s.contains('\\') || t.chars().next().is_some_and(|c| c.is_ascii_uppercase()))
}
fn is_template_atomic(a: &Atomic, template_names: &FxHashSet<Name>) -> bool {
match a {
Atomic::TTemplateParam { .. } => true,
Atomic::TNamedObject { fqcn, type_params } => {
type_params.is_empty() && !fqcn.contains('\\') && template_names.contains(fqcn)
}
Atomic::TIntersection { parts } => parts.iter().any(|p| {
p.types
.iter()
.any(|pa| is_template_atomic(pa, template_names))
}),
_ => false,
}
}
fn atomic_carries_template(a: &Atomic, template_names: &FxHashSet<Name>) -> bool {
match a {
Atomic::TArray { key, value } | Atomic::TNonEmptyArray { key, value } => {
key.types
.iter()
.any(|t| is_template_atomic(t, template_names))
|| value
.types
.iter()
.any(|t| is_template_atomic(t, template_names))
}
Atomic::TList { value } | Atomic::TNonEmptyList { value } => value
.types
.iter()
.any(|t| is_template_atomic(t, template_names)),
Atomic::TClosure { data } => {
data.params.iter().any(|p| {
p.ty.as_ref().is_some_and(|t| {
t.to_union()
.types
.iter()
.any(|a| is_template_atomic(a, template_names))
})
}) || data
.return_type
.types
.iter()
.any(|t| is_template_atomic(t, template_names))
}
Atomic::TCallable {
params,
return_type,
} => {
params.iter().flat_map(|ps| ps.iter()).any(|p| {
p.ty.as_ref().is_some_and(|t| {
t.to_union()
.types
.iter()
.any(|a| is_template_atomic(a, template_names))
})
}) || return_type.as_ref().is_some_and(|r| {
r.types
.iter()
.any(|t| is_template_atomic(t, template_names))
})
}
_ => false,
}
}
fn atomics_match_for_filter(concrete: &Atomic, arg: &Atomic) -> bool {
matches!(
(concrete, arg),
(Atomic::TNull, Atomic::TNull)
| (Atomic::TBool, Atomic::TBool)
| (Atomic::TBool, Atomic::TTrue)
| (Atomic::TBool, Atomic::TFalse)
| (Atomic::TTrue, Atomic::TTrue)
| (Atomic::TFalse, Atomic::TFalse)
| (Atomic::TInt, Atomic::TInt)
| (Atomic::TFloat, Atomic::TFloat)
| (Atomic::TIntegralFloat, Atomic::TIntegralFloat)
| (Atomic::TFloat, Atomic::TIntegralFloat)
| (Atomic::TIntegralFloat, Atomic::TFloat)
| (Atomic::TString, Atomic::TString)
| (Atomic::TClosure { .. }, Atomic::TClosure { .. })
| (Atomic::TCallable { .. }, Atomic::TCallable { .. })
| (Atomic::TCallable { .. }, Atomic::TClosure { .. })
) || (concrete.is_array() && arg.is_array())
|| matches!(
(concrete, arg),
(Atomic::TNamedObject { fqcn: cf, .. }, Atomic::TNamedObject { fqcn: af, .. })
if cf == af
)
}
fn infer_from_pair(
db: &dyn MirDatabase,
param_ty: &Type,
arg_ty: &Type,
template_names: &FxHashSet<Name>,
bindings: &mut FxHashMap<Name, Type>,
risky_fallback: &mut FxHashMap<Name, Type>,
) {
let template_residual = compute_template_residual(param_ty, arg_ty, template_names);
for p_atomic in ¶m_ty.types {
match p_atomic {
Atomic::TTemplateParam { name, .. } if template_names.contains(name) => {
let bind = template_residual.bind_value(arg_ty);
if bind.types.is_empty() {
continue;
}
let target = if template_residual.is_risky() {
&mut *risky_fallback
} else {
&mut *bindings
};
target
.entry(*name)
.or_insert_with(Type::empty)
.merge_with(bind);
}
Atomic::TNonEmptyArray { key: pk, value: pv } => {
for a_atomic in &arg_ty.types {
match a_atomic {
Atomic::TArray { key: ak, value: av }
| Atomic::TNonEmptyArray { key: ak, value: av } => {
infer_from_pair(db, pk, ak, template_names, bindings, risky_fallback);
infer_from_pair(db, pv, av, template_names, bindings, risky_fallback);
}
Atomic::TList { value: av } | Atomic::TNonEmptyList { value: av } => {
infer_from_pair(
db,
pk,
&Type::single(Atomic::TInt),
template_names,
bindings,
risky_fallback,
);
infer_from_pair(db, pv, av, template_names, bindings, risky_fallback);
}
Atomic::TKeyedArray { properties, .. } => {
let mut key_union = Type::empty();
let mut val_union = Type::empty();
for (k, prop) in properties.iter() {
let key_atomic = match k {
ArrayKey::String(_) => Atomic::TString,
ArrayKey::Int(_) => Atomic::TInt,
};
key_union.add_type(key_atomic);
val_union.merge_with(&prop.ty);
}
if !key_union.types.is_empty() {
infer_from_pair(
db,
pk,
&key_union,
template_names,
bindings,
risky_fallback,
);
infer_from_pair(
db,
pv,
&val_union,
template_names,
bindings,
risky_fallback,
);
}
}
_ => {}
}
}
}
Atomic::TArray { key: pk, value: pv } => {
for a_atomic in &arg_ty.types {
match a_atomic {
Atomic::TArray { key: ak, value: av }
| Atomic::TNonEmptyArray { key: ak, value: av } => {
infer_from_pair(db, pk, ak, template_names, bindings, risky_fallback);
infer_from_pair(db, pv, av, template_names, bindings, risky_fallback);
}
Atomic::TList { value: av } | Atomic::TNonEmptyList { value: av } => {
infer_from_pair(
db,
pk,
&Type::single(Atomic::TInt),
template_names,
bindings,
risky_fallback,
);
infer_from_pair(db, pv, av, template_names, bindings, risky_fallback);
}
Atomic::TKeyedArray { properties, .. } => {
let mut key_union = Type::empty();
let mut val_union = Type::empty();
for (k, prop) in properties.iter() {
let key_atomic = match k {
ArrayKey::String(_) => Atomic::TString,
ArrayKey::Int(_) => Atomic::TInt,
};
key_union.add_type(key_atomic);
val_union.merge_with(&prop.ty);
}
if !key_union.types.is_empty() {
infer_from_pair(
db,
pk,
&key_union,
template_names,
bindings,
risky_fallback,
);
infer_from_pair(
db,
pv,
&val_union,
template_names,
bindings,
risky_fallback,
);
}
}
_ => {}
}
}
}
Atomic::TList { value: pv } | Atomic::TNonEmptyList { value: pv } => {
for a_atomic in &arg_ty.types {
match a_atomic {
Atomic::TList { value: av } | Atomic::TNonEmptyList { value: av } => {
infer_from_pair(db, pv, av, template_names, bindings, risky_fallback);
}
Atomic::TKeyedArray {
properties,
is_list: true,
..
} => {
let mut val_union = Type::empty();
for prop in properties.values() {
val_union.merge_with(&prop.ty);
}
if !val_union.types.is_empty() {
infer_from_pair(
db,
pv,
&val_union,
template_names,
bindings,
risky_fallback,
);
}
}
_ => {}
}
}
}
Atomic::TKeyedArray {
properties: p_props,
..
} => {
for a_atomic in &arg_ty.types {
if let Atomic::TKeyedArray {
properties: a_props,
..
} = a_atomic
{
for (key, p_prop) in p_props.iter() {
if let Some(a_prop) = a_props.get(key) {
infer_from_pair(
db,
&p_prop.ty,
&a_prop.ty,
template_names,
bindings,
risky_fallback,
);
}
}
}
}
}
Atomic::TNamedObject {
fqcn: pfqcn,
type_params: pp,
} => {
if pp.is_empty() && !pfqcn.contains('\\') && template_names.contains(pfqcn) {
let bind = template_residual.bind_value(arg_ty);
if bind.types.is_empty() {
continue; }
let target = if template_residual.is_risky() {
&mut *risky_fallback
} else {
&mut *bindings
};
target
.entry(*pfqcn)
.or_insert_with(Type::empty)
.merge_with(bind);
continue;
}
for a_atomic in &arg_ty.types {
if let Atomic::TNamedObject {
fqcn: afqcn,
type_params: ap,
} = a_atomic
{
if pfqcn == afqcn {
for (p_param, a_param) in pp.iter().zip(ap.iter()) {
infer_from_pair(
db,
p_param,
a_param,
template_names,
bindings,
risky_fallback,
);
}
} else if !pp.is_empty() {
infer_from_generic_ancestor(
db,
pfqcn.as_ref(),
pp,
afqcn.as_ref(),
ap,
template_names,
bindings,
risky_fallback,
);
}
}
}
}
Atomic::TClosure { data: p_data } => {
let (p_params, p_ret) = (&p_data.params, &p_data.return_type);
for a_atomic in &arg_ty.types {
match a_atomic {
Atomic::TClosure { data: a_data } => {
let (a_params, a_ret) = (&a_data.params, &a_data.return_type);
for (pp, ap) in p_params.iter().zip(a_params.iter()) {
if let (Some(pt), Some(at)) = (pp.ty.as_ref(), ap.ty.as_ref()) {
infer_from_pair(
db,
&pt.to_union(),
&at.to_union(),
template_names,
bindings,
risky_fallback,
);
}
}
infer_from_pair(
db,
p_ret,
a_ret,
template_names,
bindings,
risky_fallback,
);
}
Atomic::TCallable {
params: Some(a_params),
return_type: Some(a_ret),
} => {
for (pp, ap) in p_params.iter().zip(a_params.iter()) {
if let (Some(pt), Some(at)) = (pp.ty.as_ref(), ap.ty.as_ref()) {
infer_from_pair(
db,
&pt.to_union(),
&at.to_union(),
template_names,
bindings,
risky_fallback,
);
}
}
infer_from_pair(
db,
p_ret,
a_ret,
template_names,
bindings,
risky_fallback,
);
}
_ => {}
}
}
}
Atomic::TCallable {
params: Some(p_params),
return_type: Some(p_ret),
} => {
for a_atomic in &arg_ty.types {
match a_atomic {
Atomic::TCallable {
params: Some(a_params),
return_type: Some(a_ret),
} => {
for (pp, ap) in p_params.iter().zip(a_params.iter()) {
if let (Some(pt), Some(at)) = (pp.ty.as_ref(), ap.ty.as_ref()) {
infer_from_pair(
db,
&pt.to_union(),
&at.to_union(),
template_names,
bindings,
risky_fallback,
);
}
}
infer_from_pair(
db,
p_ret,
a_ret,
template_names,
bindings,
risky_fallback,
);
}
Atomic::TClosure { data: a_data } => {
let (a_params, a_ret) = (&a_data.params, &a_data.return_type);
for (pp, ap) in p_params.iter().zip(a_params.iter()) {
if let (Some(pt), Some(at)) = (pp.ty.as_ref(), ap.ty.as_ref()) {
infer_from_pair(
db,
&pt.to_union(),
&at.to_union(),
template_names,
bindings,
risky_fallback,
);
}
}
infer_from_pair(
db,
p_ret,
a_ret,
template_names,
bindings,
risky_fallback,
);
}
_ => {}
}
}
}
Atomic::TIntersection { parts } => {
let arg = template_residual.bind_value(arg_ty);
if arg.types.is_empty() {
continue;
}
if template_residual.is_risky() {
let mut scratch: FxHashMap<Name, Type> = FxHashMap::default();
for part in parts.iter() {
infer_from_pair(
db,
part,
arg,
template_names,
&mut scratch,
risky_fallback,
);
}
for (name, val) in scratch {
risky_fallback
.entry(name)
.or_insert_with(Type::empty)
.merge_with(&val);
}
} else {
for part in parts.iter() {
infer_from_pair(db, part, arg, template_names, bindings, risky_fallback);
}
}
}
Atomic::TClassString(Some(param_name)) if template_names.contains(param_name) => {
for a_atomic in &arg_ty.types {
let cls_ty = match a_atomic {
Atomic::TClassString(Some(arg_cls)) => {
Some(Type::single(Atomic::TNamedObject {
fqcn: *arg_cls,
type_params: empty_type_params(),
}))
}
Atomic::TClassString(None) => Some(Type::single(Atomic::TObject)),
Atomic::TLiteralString(s) if literal_is_class_like(s) => {
Some(Type::single(Atomic::TNamedObject {
fqcn: Name::new(s.trim_start_matches('\\')),
type_params: empty_type_params(),
}))
}
_ => None,
};
if let Some(cls_ty) = cls_ty {
let entry = bindings.entry(*param_name).or_insert_with(Type::empty);
entry.merge_with(&cls_ty);
}
}
}
Atomic::TInterfaceString(Some(param_name)) if template_names.contains(param_name) => {
for a_atomic in &arg_ty.types {
let cls_ty = match a_atomic {
Atomic::TInterfaceString(Some(arg_cls))
| Atomic::TClassString(Some(arg_cls)) => {
Some(Type::single(Atomic::TNamedObject {
fqcn: *arg_cls,
type_params: empty_type_params(),
}))
}
Atomic::TInterfaceString(None) | Atomic::TClassString(None) => {
Some(Type::single(Atomic::TObject))
}
Atomic::TLiteralString(s) if literal_is_class_like(s) => {
Some(Type::single(Atomic::TNamedObject {
fqcn: Name::new(s.trim_start_matches('\\')),
type_params: empty_type_params(),
}))
}
_ => None,
};
if let Some(cls_ty) = cls_ty {
let entry = bindings.entry(*param_name).or_insert_with(Type::empty);
entry.merge_with(&cls_ty);
}
}
}
_ => {}
}
}
}
#[allow(clippy::too_many_arguments)]
fn infer_from_generic_ancestor(
db: &dyn MirDatabase,
pfqcn: &str,
pp: &[Type],
afqcn: &str,
ap: &[Type],
template_names: &FxHashSet<Name>,
bindings: &mut FxHashMap<Name, Type>,
risky_fallback: &mut FxHashMap<Name, Type>,
) {
let pfqcn_tps = crate::db::class_template_params(db, pfqcn).unwrap_or_default();
if pfqcn_tps.is_empty() || !crate::db::extends_or_implements(db, afqcn, pfqcn) {
return;
}
let afqcn_tps = crate::db::class_template_params(db, afqcn).unwrap_or_default();
let own_bindings: FxHashMap<Name, Type> = afqcn_tps
.iter()
.zip(ap)
.map(|(tp, ty)| (tp.name, ty.clone()))
.collect();
let ancestor_bindings = crate::db::inherited_template_bindings(db, afqcn, &own_bindings);
for (p_param, tp) in pp.iter().zip(pfqcn_tps.iter()) {
let Some(resolved) = ancestor_bindings.get(&tp.name) else {
continue;
};
infer_from_pair(
db,
p_param,
resolved,
template_names,
bindings,
risky_fallback,
);
}
}