use crate::db::MirDatabase;
use mir_types::{ArrayKey, Atomic, Name, Type};
use std::sync::Arc;
pub(super) fn loop_guaranteed_to_execute(arr_ty: &Type) -> bool {
for atomic in &arr_ty.types {
match atomic {
Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. } => return true,
Atomic::TKeyedArray {
properties,
is_open: false,
..
} if !properties.is_empty() => return true,
_ => {}
}
}
false
}
pub(super) fn vars_stabilized(
prev: &rustc_hash::FxHashMap<Name, Arc<Type>>,
next: &rustc_hash::FxHashMap<Name, Arc<Type>>,
) -> bool {
if prev.len() != next.len() {
return false;
}
prev.iter().all(|(k, v)| {
next.get(k)
.map(|u| Arc::ptr_eq(u, v) || **u == **v)
.unwrap_or(false)
})
}
pub(super) fn widen_unstable(
pre_vars: &rustc_hash::FxHashMap<Name, Arc<Type>>,
current_vars: &mut rustc_hash::FxHashMap<Name, Arc<Type>>,
loop_guaranteed: bool,
) {
for (name, ty) in current_vars.iter_mut() {
if let Some(pre_ty) = pre_vars.get(name) {
if !Arc::ptr_eq(ty, pre_ty) && **ty != **pre_ty {
let mut merged = (**ty).clone();
merged.merge_with(pre_ty);
*ty = mir_codebase::definitions::wrap_var_type(merged);
}
} else if loop_guaranteed {
} else {
}
}
}
pub(crate) fn infer_foreach_types(arr_ty: &Type) -> (Type, Type) {
if arr_ty.is_mixed() {
return (Type::mixed(), Type::mixed());
}
for atomic in &arr_ty.types {
match atomic {
Atomic::TArray { key, value } | Atomic::TNonEmptyArray { key, value } => {
return (*key.clone(), *value.clone());
}
Atomic::TList { value } | Atomic::TNonEmptyList { value } => {
return (Type::single(Atomic::TInt), *value.clone());
}
Atomic::TKeyedArray { properties, .. } => {
let mut keys = Type::empty();
let mut values = Type::empty();
for (k, prop) in properties.iter() {
let key_atomic = match k {
ArrayKey::String(s) => Atomic::TLiteralString(s.clone()),
ArrayKey::Int(i) => Atomic::TLiteralInt(*i),
};
keys.merge_with(&Type::single(key_atomic));
values.merge_with(&prop.ty);
}
let keys = if keys.is_empty() { Type::mixed() } else { keys };
let values = if values.is_empty() {
Type::mixed()
} else {
values
};
return (keys, values);
}
Atomic::TString => {
return (Type::single(Atomic::TInt), Type::single(Atomic::TString));
}
_ => {}
}
}
(Type::mixed(), Type::mixed())
}
pub(crate) fn infer_foreach_types_with_db(db: &dyn MirDatabase, arr_ty: &Type) -> (Type, Type) {
infer_foreach_types_with_db_depth(db, arr_ty, 4)
}
fn infer_foreach_types_with_db_depth(
db: &dyn MirDatabase,
arr_ty: &Type,
depth: u8,
) -> (Type, Type) {
if depth == 0 || arr_ty.is_mixed() {
return (Type::mixed(), Type::mixed());
}
for atomic in &arr_ty.types {
if let Atomic::TNamedObject { fqcn, type_params } = atomic {
if let Some(kv) = resolve_iterator_item_types(db, fqcn, type_params, depth) {
return kv;
}
}
}
infer_foreach_types(arr_ty)
}
fn generator_item_types(type_params: &[Type]) -> (Type, Type) {
match type_params {
[] => (Type::mixed(), Type::mixed()),
[value] => (Type::mixed(), value.clone()),
[key, value, ..] => (key.clone(), value.clone()),
}
}
fn resolve_iterator_item_types(
db: &dyn MirDatabase,
fqcn: &Name,
type_params: &[Type],
depth: u8,
) -> Option<(Type, Type)> {
let bare = fqcn.as_ref().trim_start_matches('\\');
if bare.eq_ignore_ascii_case("Generator") {
return Some(generator_item_types(type_params));
}
if (bare.eq_ignore_ascii_case("Iterator")
|| bare.eq_ignore_ascii_case("IteratorAggregate")
|| bare.eq_ignore_ascii_case("Traversable"))
&& !type_params.is_empty()
{
return Some(generator_item_types(type_params));
}
let class = crate::db::find_class_like(db, crate::db::Fqcn::from_str(db, bare))?;
let class_tps = crate::db::class_template_params(db, bare).unwrap_or_default();
let bindings = crate::generic::build_class_bindings(&class_tps, type_params);
let annotated = class
.implements_type_args()
.iter()
.find_map(|(iface, args)| {
let iface_bare = iface.trim_start_matches('\\');
(iface_bare.eq_ignore_ascii_case("Iterator")
|| iface_bare.eq_ignore_ascii_case("IteratorAggregate"))
.then_some(args)
});
if let Some(args) = annotated {
if args.len() >= 2 {
let key = args[0].substitute_templates(&bindings);
let value = args[1].substitute_templates(&bindings);
return Some((key, value));
}
}
let implements = |name: &str| {
class
.interfaces()
.iter()
.any(|i| i.trim_start_matches('\\').eq_ignore_ascii_case(name))
};
let method_return_ty = |method: &str| -> Option<Type> {
let (_, def) =
crate::db::find_method_in_chain(db, crate::db::Fqcn::from_str(db, bare), method)?;
let ty = def.return_type.as_deref().cloned()?;
Some(ty.substitute_templates(&bindings))
};
if implements("IteratorAggregate") {
let ret_ty = method_return_ty("getiterator")?;
return Some(infer_foreach_types_with_db_depth(db, &ret_ty, depth - 1));
}
if implements("Iterator") {
let value = method_return_ty("current").unwrap_or_else(Type::mixed);
let key = method_return_ty("key").unwrap_or_else(Type::mixed);
return Some((key, value));
}
None
}