use std::sync::Arc;
use mir_types::Type;
use crate::body_analysis::InferredTypes;
use crate::db::{Fqcn, MirDatabase, ScopeKey, SourceFile};
const WIDEN_AFTER_ITERATIONS: u32 = 3;
pub(crate) fn should_widen(cycle: &salsa::Cycle) -> bool {
cycle.iteration() >= WIDEN_AFTER_ITERATIONS
}
pub(crate) fn widen_unstable<T: PartialEq>(
last: &[T],
value: &mut [T],
ty: impl Fn(&mut T) -> &mut Type,
) {
for entry in value.iter_mut() {
if !last.contains(entry) {
*ty(entry) = Type::mixed();
}
}
}
pub fn inferred_function_return_type_demand(db: &dyn MirDatabase, fqn: &str) -> Option<Arc<Type>> {
let sf = crate::db::function_loc(db, Fqcn::from_str(db, fqn))?.file();
function_return_type_in(db, sf, fqn)
}
pub fn inferred_function_return_type_from(
db: &dyn MirDatabase,
file: &str,
fqn: &str,
) -> Option<Arc<Type>> {
let sf = crate::db::function_loc_from(db, file, Fqcn::from_str(db, fqn))?.file();
function_return_type_in(db, sf, fqn)
}
fn function_return_type_in(db: &dyn MirDatabase, sf: SourceFile, fqn: &str) -> Option<Arc<Type>> {
let Some(inferred) = infer_scopes_matching(
db,
sf,
|key| matches!(key, ScopeKey::Function(name, _) if name.as_ref() == fqn),
None,
) else {
return crate::db::infer_file_return_types(db, sf)
.functions
.get(fqn)
.cloned();
};
inferred
.iter()
.flat_map(|types| types.functions.iter())
.rfind(|(name, _)| name.as_ref() == fqn)
.map(|(_, ty)| mir_codebase::definitions::wrap_var_type(ty.clone()))
}
fn infer_scopes_matching(
db: &dyn MirDatabase,
sf: SourceFile,
matches: impl Fn(&ScopeKey) -> bool,
method: Option<&str>,
) -> Option<Vec<Arc<InferredTypes>>> {
let method: Option<Arc<str>> = method.map(Arc::from);
let inferred: Vec<Arc<InferredTypes>> = crate::db::file_scopes(db, sf)
.iter()
.filter(|key| matches(key))
.map(|key| crate::db::infer_member(db, sf, key.clone(), method.clone()).clone())
.collect();
(!inferred.is_empty()).then_some(inferred)
}
pub fn inferred_method_return_type_demand(
db: &dyn MirDatabase,
fqcn: &str,
method_name_lower: &str,
) -> Option<Arc<Type>> {
let sf = crate::db::class_like_loc(db, Fqcn::from_str(db, fqcn))?.file();
let Some(inferred) = infer_scopes_matching(
db,
sf,
|key| matches!(key, ScopeKey::ClassLike(name, _) if name.as_ref() == fqcn),
Some(method_name_lower),
) else {
return crate::db::infer_file_return_types(db, sf)
.methods
.get(&(Arc::<str>::from(fqcn), Arc::<str>::from(method_name_lower)))
.cloned();
};
inferred
.iter()
.flat_map(|types| types.methods.iter())
.rfind(|(class, name, _)| {
class.as_ref() == fqcn && name.eq_ignore_ascii_case(method_name_lower)
})
.map(|(_, _, ty)| mir_codebase::definitions::wrap_var_type(ty.clone()))
}
pub fn inferred_property_type_demand(
db: &dyn MirDatabase,
fqcn: &str,
name: &str,
) -> Option<Arc<Type>> {
let sf = crate::db::class_like_loc(db, Fqcn::from_str(db, fqcn))?.file();
let declared_here = crate::db::file_scopes(db, sf)
.iter()
.any(|key| matches!(key, ScopeKey::ClassLike(class, _) if class.as_ref() == fqcn));
let inferred = declared_here
.then(|| {
infer_scopes_matching(
db,
sf,
|key| matches!(key, ScopeKey::ClassLike(..)),
Some("__construct"),
)
})
.flatten();
let Some(inferred) = inferred else {
return crate::db::infer_file_return_types(db, sf)
.properties
.get(&(Arc::<str>::from(fqcn), Arc::<str>::from(name)))
.cloned();
};
let mut merged: Option<Type> = None;
for (_, _, ty) in inferred
.iter()
.flat_map(|types| types.properties.iter())
.filter(|(class, prop, _)| class.as_ref() == fqcn && prop.as_ref() == name)
{
match &mut merged {
Some(existing) => existing.merge_with(ty),
None => merged = Some(ty.clone()),
}
}
merged.map(mir_codebase::definitions::wrap_var_type)
}