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 {
!arr_ty.types.is_empty()
&& arr_ty.types.iter().all(|atomic| {
matches!(
atomic,
Atomic::TNonEmptyArray { .. } | Atomic::TNonEmptyList { .. }
) || matches!(
atomic,
Atomic::TKeyedArray { properties, is_open: false, .. } if !properties.is_empty()
)
})
}
pub(super) fn promote_new_loop_vars_when_guaranteed(
pre: &crate::flow_state::FlowState,
post: &mut crate::flow_state::FlowState,
) {
let new_names: Vec<Name> = post
.vars
.keys()
.filter(|n| !pre.vars.contains_key(*n))
.copied()
.collect();
let post_vars = Arc::make_mut(&mut post.vars);
for name in &new_names {
if let Some(ty) = post_vars.get_mut(name) {
if ty.possibly_undefined {
let mut stripped = (**ty).clone();
stripped.possibly_undefined = false;
*ty = mir_codebase::definitions::wrap_var_type(stripped);
}
}
}
let assigned = Arc::make_mut(&mut post.assigned_vars);
let possibly = Arc::make_mut(&mut post.possibly_assigned_vars);
for name in &new_names {
if possibly.contains(name) {
possibly.remove(name);
assigned.insert(*name);
}
}
}
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 rebind_static_self(ty: Type, fqcn: &str, own_type_params: &[Type]) -> Type {
let mut out = Type::empty();
out.from_docblock = ty.from_docblock;
out.possibly_undefined = ty.possibly_undefined;
for atomic in ty.types {
let rebound = match atomic {
Atomic::TStaticObject { .. } | Atomic::TSelf { .. } => Atomic::TNamedObject {
fqcn: Name::from(fqcn),
type_params: mir_types::union::vec_to_type_params(own_type_params.to_vec()),
},
other => other,
};
out.add_type(rebound);
}
out
}
pub(crate) 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 own_bindings = crate::generic::build_class_bindings(&class_tps, type_params);
let mut annotation_bindings = own_bindings.clone();
for (k, v) in crate::db::inherited_template_bindings(db, bare, &annotation_bindings) {
annotation_bindings.entry(k).or_insert(v);
}
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(&annotation_bindings);
let value = args[1].substitute_templates(&annotation_bindings);
return Some((key, value));
}
}
let implements = |name: &str| crate::db::extends_or_implements(db, bare, name);
let method_return_ty = |method: &str| -> Option<Type> {
let (owner, def) =
crate::db::find_method_in_chain(db, crate::db::Fqcn::from_str(db, bare), method)?;
let ty = def.return_type.as_deref().cloned()?;
let mut bindings = own_bindings.clone();
let inherited = crate::db::inherited_template_bindings(db, bare, &own_bindings);
if owner.as_ref() == bare {
for (k, v) in inherited {
bindings.entry(k).or_insert(v);
}
} else {
bindings.extend(inherited);
}
Some(ty.substitute_templates(&bindings))
};
if implements("IteratorAggregate") {
let ret_ty = method_return_ty("getiterator")?;
let returns_self_or_static = ret_ty
.types
.iter()
.any(|a| matches!(a, Atomic::TStaticObject { .. } | Atomic::TSelf { .. }));
if returns_self_or_static && 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));
}
let ret_ty = rebind_static_self(ret_ty, bare, type_params);
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
}