use super::*;
pub(super) fn analysis_entries_for_requested_fields(
entries: &[Entry],
requested_fields: Option<&HashSet<String>>,
) -> Vec<Entry> {
let Some(requested_fields) = requested_fields else {
return entries.to_vec();
};
entries
.iter()
.filter(|entry| match entry {
Entry::Property(prop) => {
has_modifier(&prop.modifiers, Modifier::Local)
|| requested_fields.contains(&prop.name)
}
Entry::DynProperty(..)
| Entry::Predicate(..)
| Entry::Spread(_)
| Entry::ForGenerator(_)
| Entry::WhenGenerator(_)
| Entry::ClassDef(..)
| Entry::TypeAlias(..) => true,
Entry::Elem(_) => false,
})
.cloned()
.collect()
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) enum ImportUse {
Fields(HashSet<String>),
Whole,
}
pub(super) fn requested_fields_for_import(
uses: &HashMap<String, ImportUse>,
alias: &str,
) -> Option<HashSet<String>> {
match uses.get(alias) {
Some(ImportUse::Fields(fields)) => Some(fields.clone()),
Some(ImportUse::Whole) | None => None,
}
}
pub(super) fn glob_index_keys(
uses: &HashMap<String, ImportUse>,
alias: &str,
) -> Option<HashSet<String>> {
if uses.contains_key(&other_uses_key(alias)) {
return None;
}
requested_fields_for_import(uses, &index_reads_key(alias))
}
fn other_uses_key(alias: &str) -> String {
format!("{alias}\0*")
}
fn index_reads_key(alias: &str) -> String {
format!("{alias}\0[]")
}
pub(super) fn import_field_uses(entries: &[Entry]) -> HashMap<String, ImportUse> {
let mut uses = HashMap::default();
let shadows = HashSet::default();
collect_entry_import_field_uses(entries, &mut uses, &shadows);
uses
}
pub(super) fn collect_entry_import_field_uses(
entries: &[Entry],
uses: &mut HashMap<String, ImportUse>,
shadows: &HashSet<String>,
) {
let mut entry_shadows = shadows.clone();
entry_shadows.extend(declared_entry_roots(entries));
for entry in entries {
match entry {
Entry::Property(prop) => {
if let Some(ty) = &prop.type_ann {
collect_type_import_field_uses(ty, uses, &entry_shadows);
}
if let Some(expr) = &prop.value {
collect_expr_import_field_uses(expr, uses, &entry_shadows);
}
if let Some(body) = &prop.body {
collect_entry_import_field_uses(body, uses, &entry_shadows);
}
}
Entry::DynProperty(key, value) | Entry::Predicate(key, value) => {
collect_expr_import_field_uses(key, uses, &entry_shadows);
collect_expr_import_field_uses(value, uses, &entry_shadows);
}
Entry::ForGenerator(fgen) => {
collect_expr_import_field_uses(&fgen.collection, uses, &entry_shadows);
let mut body_shadows = entry_shadows.clone();
body_shadows.insert(fgen.val_var.clone());
if let Some(key_var) = &fgen.key_var {
body_shadows.insert(key_var.clone());
}
collect_entry_import_field_uses(&fgen.body, uses, &body_shadows);
}
Entry::WhenGenerator(wgen) => {
collect_expr_import_field_uses(&wgen.condition, uses, &entry_shadows);
collect_entry_import_field_uses(&wgen.body, uses, &entry_shadows);
if let Some(else_body) = &wgen.else_body {
collect_entry_import_field_uses(else_body, uses, &entry_shadows);
}
}
Entry::Spread(expr) | Entry::Elem(expr) => {
collect_expr_import_field_uses(expr, uses, &entry_shadows);
}
Entry::ClassDef(_, _, _, body) => {
collect_entry_import_field_uses(body, uses, &entry_shadows);
}
Entry::TypeAlias(_, ty) => collect_type_import_field_uses(ty, uses, &entry_shadows),
}
}
}
pub(super) fn collect_expr_import_field_uses(
expr: &Expr,
uses: &mut HashMap<String, ImportUse>,
shadows: &HashSet<String>,
) {
match expr {
Expr::Ident(name) => record_whole_import_use(uses, shadows, name),
Expr::Field(base, field) | Expr::NullSafeField(base, field) => {
if let Expr::Ident(name) = base.as_ref() {
record_field_import_use(uses, shadows, name, field);
} else {
collect_expr_import_field_uses(base, uses, shadows);
}
}
Expr::Index(base, index)
if matches!(base.as_ref(), Expr::Ident(_))
&& matches!(index.as_ref(), Expr::String(_)) =>
{
if let (Expr::Ident(name), Expr::String(key)) = (base.as_ref(), index.as_ref())
&& !shadows.contains(name)
{
uses.insert(name.clone(), ImportUse::Whole);
record_field_import_use(uses, shadows, &index_reads_key(name), key);
}
}
Expr::Index(base, index) | Expr::Binop(_, base, index) => {
collect_expr_import_field_uses(base, uses, shadows);
collect_expr_import_field_uses(index, uses, shadows);
}
Expr::New(type_name, entries, generic_params) => {
if let Some(type_name) = type_name {
if let Some((root, field)) = type_name.split_once('.') {
record_field_import_use(uses, shadows, root, field);
} else {
record_whole_import_use(uses, shadows, type_name);
}
}
for param in generic_params {
record_type_name_import_use(uses, shadows, param);
}
collect_entry_import_field_uses(entries, uses, shadows);
}
Expr::Call(callee, args) => {
if let Some(name) = object_method_import_receiver(callee.as_ref()) {
record_whole_import_use(uses, shadows, name);
} else {
collect_expr_import_field_uses(callee, uses, shadows);
}
for arg in args {
collect_expr_import_field_uses(arg, uses, shadows);
}
}
Expr::If(cond, then_expr, else_expr) => {
collect_expr_import_field_uses(cond, uses, shadows);
collect_expr_import_field_uses(then_expr, uses, shadows);
collect_expr_import_field_uses(else_expr, uses, shadows);
}
Expr::Let(name, value, body) => {
collect_expr_import_field_uses(value, uses, shadows);
let mut body_shadows = shadows.clone();
body_shadows.insert(name.clone());
collect_expr_import_field_uses(body, uses, &body_shadows);
}
Expr::Is(value, ty) | Expr::As(value, ty) => {
collect_expr_import_field_uses(value, uses, shadows);
collect_type_import_field_uses(ty, uses, shadows);
}
Expr::Lambda(params, value) => {
let mut body_shadows = shadows.clone();
body_shadows.extend(params.iter().cloned());
collect_expr_import_field_uses(value, uses, &body_shadows);
}
Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value, _)
| Expr::Read(value, _)
| Expr::ReadOrNull(value, _)
| Expr::ReadGlob(value, _) => collect_expr_import_field_uses(value, uses, shadows),
Expr::InferredNew(ty, entries) => {
collect_type_import_field_uses(ty, uses, shadows);
collect_entry_import_field_uses(entries, uses, shadows);
}
Expr::ObjectBody(entries) => collect_entry_import_field_uses(entries, uses, shadows),
Expr::StringInterpolation(parts) => {
for part in parts {
if let StringInterpPart::Expr(expr) = part {
collect_expr_import_field_uses(expr, uses, shadows);
}
}
}
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => {}
}
}
pub(super) fn object_method_import_receiver(expr: &Expr) -> Option<&str> {
let (base, method) = match expr {
Expr::Field(base, method) | Expr::NullSafeField(base, method) => (base.as_ref(), method),
_ => return None,
};
if !is_intrinsic_object_method(method) {
return None;
}
match base {
Expr::Ident(name) => Some(name),
_ => None,
}
}
pub(super) fn is_intrinsic_object_method(method: &str) -> bool {
matches!(
method,
"containsKey" | "toMap" | "toMapping" | "mapValues" | "filter" | "toList" | "toDynamic"
)
}
pub(super) fn collect_type_import_field_uses(
ty: &crate::parser::TypeExpr,
uses: &mut HashMap<String, ImportUse>,
shadows: &HashSet<String>,
) {
match ty {
crate::parser::TypeExpr::Named(name) => {
if string_literal_type_value(name).is_none() {
record_type_name_import_use(uses, shadows, name);
}
}
crate::parser::TypeExpr::Nullable(inner) => {
collect_type_import_field_uses(inner, uses, shadows);
}
crate::parser::TypeExpr::Union(types) => {
for ty in types {
collect_type_import_field_uses(ty, uses, shadows);
}
}
crate::parser::TypeExpr::Generic(name, params) => {
record_type_name_import_use(uses, shadows, name);
for param in params {
collect_type_import_field_uses(param, uses, shadows);
}
}
crate::parser::TypeExpr::Constrained(base, constraint) => {
for component in constrained_type_components(base) {
record_type_name_import_use(uses, shadows, component);
}
collect_expr_import_field_uses(constraint, uses, shadows);
}
}
}
pub(super) fn record_type_name_import_use(
uses: &mut HashMap<String, ImportUse>,
shadows: &HashSet<String>,
name: &str,
) {
if let Some((root, field)) = name.split_once('.') {
record_field_import_use(uses, shadows, root, field);
} else {
record_whole_import_use(uses, shadows, name);
}
}
pub(super) fn record_field_import_use(
uses: &mut HashMap<String, ImportUse>,
shadows: &HashSet<String>,
name: &str,
field: &str,
) {
if shadows.contains(name) {
return;
}
mark_other_use(uses, name);
match uses.entry(name.to_string()) {
std::collections::hash_map::Entry::Occupied(mut entry) => {
if let ImportUse::Fields(fields) = entry.get_mut() {
fields.insert(field.to_string());
}
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(ImportUse::Fields(HashSet::from_iter([field.to_string()])));
}
}
}
pub(super) fn record_whole_import_use(
uses: &mut HashMap<String, ImportUse>,
shadows: &HashSet<String>,
name: &str,
) {
if !shadows.contains(name) {
mark_other_use(uses, name);
uses.insert(name.to_string(), ImportUse::Whole);
}
}
fn mark_other_use(uses: &mut HashMap<String, ImportUse>, name: &str) {
if !name.contains('\0') {
uses.insert(other_uses_key(name), ImportUse::Whole);
}
}
pub(super) fn expand_requested_fields(
entries: &[Entry],
requested: &HashSet<String>,
inherited_builtins: &[&str],
) -> HashSet<String> {
let property_names: HashSet<String> = entries
.iter()
.filter_map(|entry| match entry {
Entry::Property(prop) if !has_modifier(&prop.modifiers, Modifier::Local) => {
Some(prop.name.clone())
}
_ => None,
})
.collect();
let mut module_aliases: TypeAliases = entries
.iter()
.filter_map(|entry| match entry {
Entry::TypeAlias(name, ty) => Some((name.as_str(), Some(ty))),
_ => None,
})
.collect();
for builtin in inherited_builtins {
module_aliases.entry(builtin).or_insert(None);
}
let definitions = Definitions {
types: entries
.iter()
.filter_map(|entry| match entry {
Entry::TypeAlias(name, _) | Entry::ClassDef(name, ..) => {
Some((name.as_str(), entry))
}
_ => None,
})
.collect(),
values: entries
.iter()
.filter_map(|entry| match entry {
Entry::Property(prop) => Some(prop.name.as_str()),
_ => None,
})
.collect(),
locals: entries
.iter()
.filter_map(|entry| match entry {
Entry::Property(prop) if has_modifier(&prop.modifiers, Modifier::Local) => {
Some((prop.name.as_str(), entry))
}
_ => None,
})
.collect(),
module_aliases: &module_aliases,
classes: ModuleClasses::new(entries),
};
let aliases = Some(&module_aliases);
let module_reading_definitions = if requested
.iter()
.any(|name| definitions.types.contains_key(name.as_str()))
{
module_dependent_members(entries)
} else {
indexmap::IndexSet::new()
};
let mut expanded = requested.clone();
let mut changed = true;
while changed {
changed = false;
for entry in entries {
let mut refs = Names::default();
let shadows = Names::default();
match entry {
Entry::Property(prop) if expanded.contains(&prop.name) => {
if let Some(ty) = &prop.type_ann {
collect_type_refs(ty, &mut refs, &shadows, aliases, Some(&definitions));
}
if let Some(expr) = &prop.value {
collect_expr_refs_in(
expr,
&mut refs,
&shadows,
aliases,
Some(&definitions),
);
collect_sibling_field_refs_expr(expr, &mut refs.values, true);
}
if let Some(body) = &prop.body {
collect_entry_refs_in(
body,
&mut refs,
&shadows,
aliases,
Some(&definitions),
);
collect_sibling_field_refs_entries(body, &mut refs.values);
}
}
Entry::ClassDef(name, ..) | Entry::TypeAlias(name, _)
if expanded.contains(name) && module_reading_definitions.contains(name) =>
{
refs.types.insert(name.clone());
}
_ => continue,
}
follow_definitions(&definitions, &mut refs, aliases);
for dep in refs.values {
if dep == DYNAMIC_SIBLING_REF {
for name in &property_names {
if expanded.insert(name.clone()) {
changed = true;
}
}
continue;
}
if property_names.contains(&dep) && expanded.insert(dep) {
changed = true;
}
}
}
}
expanded
}
fn follow_definitions(definitions: &Definitions, refs: &mut Names, aliases: Option<&TypeAliases>) {
let shadows = Names::default();
let types_as_values: Vec<String> = refs
.unbound_types
.iter()
.filter(|name| !definitions.types.contains_key(name.as_str()))
.cloned()
.collect();
refs.values.extend(types_as_values);
let mut pending: Vec<(String, bool, bool)> = refs
.types
.iter()
.map(|name| (name.clone(), false, true))
.chain(refs.values.iter().map(|name| (name.clone(), false, false)))
.collect();
let mut visited = HashSet::default();
while let Some((name, module_scope, is_type)) = pending.pop() {
let definition = definitions
.types
.get(name.as_str())
.filter(|_| is_type || !definitions.values.contains(name.as_str()));
let local = definitions.locals.get(name.as_str()).filter(|_| !is_type);
if (definition.is_none() && local.is_none())
|| !visited.insert((name, module_scope, is_type))
{
continue;
}
let mut definition_refs = Names::default();
if let Some(definition) = definition {
collect_entry_refs_unnarrowed(
std::slice::from_ref(*definition),
&mut definition_refs,
&shadows,
if module_scope {
Some(definitions.module_aliases)
} else {
aliases
},
None,
);
if let Entry::ClassDef(class, _, _, body) = definition {
let reads: HashSet<String> = definitions
.classes
.module_reads(class, body, &definition_refs.values)
.into_iter()
.cloned()
.collect();
definition_refs
.values
.retain(|root| !definitions.classes.is_property(root) || reads.contains(root));
}
collect_sibling_field_refs_entries(
std::slice::from_ref(*definition),
&mut definition_refs.values,
);
}
let mut local_refs = Names::default();
if let Some(local) = local {
collect_entry_refs_unnarrowed(
std::slice::from_ref(*local),
&mut local_refs,
&shadows,
Some(definitions.module_aliases),
None,
);
}
if let Some(Entry::Property(prop)) = local {
if let Some(ty) = &prop.type_ann {
collect_sibling_field_refs_type(ty, &mut local_refs.values);
}
if let Some(expr) = &prop.value {
collect_sibling_field_refs_expr(expr, &mut local_refs.values, true);
}
if let Some(body) = &prop.body {
collect_sibling_field_refs_entries(body, &mut local_refs.values);
}
}
for (deps, module_scope) in [(definition_refs, module_scope), (local_refs, true)] {
for dep in deps.unbound_types {
if !definitions.types.contains_key(dep.as_str()) && refs.values.insert(dep.clone())
{
pending.push((dep.clone(), module_scope, false));
}
refs.unbound_types.insert(dep);
}
for dep in deps.types {
refs.types.insert(dep.clone());
pending.push((dep, module_scope, true));
}
for dep in deps.values {
if dep != "this" {
refs.values.insert(dep.clone());
pending.push((dep, module_scope, false));
}
}
}
}
}
pub(super) fn collect_sibling_field_refs_entries(entries: &[Entry], refs: &mut HashSet<String>) {
for entry in entries {
match entry {
Entry::Property(prop) => {
if let Some(ty) = &prop.type_ann {
collect_sibling_field_refs_type(ty, refs);
}
if let Some(expr) = &prop.value {
collect_sibling_field_refs_expr(expr, refs, false);
}
if let Some(body) = &prop.body {
collect_sibling_field_refs_entries(body, refs);
}
}
Entry::DynProperty(key, value) | Entry::Predicate(key, value) => {
collect_sibling_field_refs_expr(key, refs, false);
collect_sibling_field_refs_expr(value, refs, false);
}
Entry::ForGenerator(fgen) => {
collect_sibling_field_refs_expr(&fgen.collection, refs, false);
collect_sibling_field_refs_entries(&fgen.body, refs);
}
Entry::WhenGenerator(wgen) => {
collect_sibling_field_refs_expr(&wgen.condition, refs, false);
collect_sibling_field_refs_entries(&wgen.body, refs);
if let Some(else_body) = &wgen.else_body {
collect_sibling_field_refs_entries(else_body, refs);
}
}
Entry::Spread(expr) | Entry::Elem(expr) => {
collect_sibling_field_refs_expr(expr, refs, false);
}
Entry::ClassDef(_, _, _, body) => collect_sibling_field_refs_entries(body, refs),
Entry::TypeAlias(_, ty) => collect_sibling_field_refs_type(ty, refs),
}
}
}
fn constraint_bound_names(base: &str) -> &'static [&'static str] {
if base.ends_with('?') {
return &["this"];
}
let base = base.trim_start_matches('*');
let base = base.split('<').next().unwrap_or(base).trim();
if BINDING_BUILTIN_TYPES.contains(&base) {
&["this", "length", "isEmpty"]
} else {
&["this"]
}
}
pub(super) const BINDING_BUILTIN_TYPES: &[&str] = &[
"String",
"List",
"Listing",
"Map",
"Mapping",
"Set",
"Collection",
];
type TypeAliases<'a> = HashMap<&'a str, Option<&'a crate::parser::TypeExpr>>;
#[derive(Clone, Default)]
struct Names {
values: HashSet<String>,
types: HashSet<String>,
unbound_types: HashSet<String>,
}
impl Names {
fn values(values: &HashSet<String>) -> Self {
Names {
values: values.clone(),
..Names::default()
}
}
fn extend(&mut self, other: Names) {
self.values.extend(other.values);
self.types.extend(other.types);
self.unbound_types.extend(other.unbound_types);
}
fn add_all_to(self, names: &mut HashSet<String>) {
names.extend(self.values);
names.extend(self.types);
}
}
struct Definitions<'a> {
types: HashMap<&'a str, &'a Entry>,
values: HashSet<&'a str>,
locals: HashMap<&'a str, &'a Entry>,
module_aliases: &'a TypeAliases<'a>,
classes: ModuleClasses<'a>,
}
impl Definitions<'_> {
fn unshadowed_values<'n>(
&self,
values: &'n HashSet<String>,
) -> impl Iterator<Item = &'n String> {
values.iter().filter(|name| {
self.types.contains_key(name.as_str()) && !self.values.contains(name.as_str())
})
}
}
fn collect_entries_type_decls<'e>(
entries: &'e [Entry],
aliases: &TypeAliases,
out: &mut HashSet<&'e str>,
) {
for entry in entries {
match entry {
Entry::TypeAlias(name, ty) => {
if aliases
.get(name.as_str())
.is_none_or(|outer| *outer != Some(ty))
{
out.insert(name);
}
}
Entry::ClassDef(name, ..) => {
out.insert(name);
}
_ => {}
}
}
}
fn mentioned_type_names<'t>(ty: &'t crate::parser::TypeExpr, out: &mut Vec<&'t str>) {
fn names_in<'s>(text: &'s str, out: &mut Vec<&'s str>) {
out.extend(
text.split(|c: char| !(c.is_alphanumeric() || c == '_'))
.filter(|name| !name.is_empty()),
);
}
match ty {
crate::parser::TypeExpr::Named(name) | crate::parser::TypeExpr::Constrained(name, _) => {
names_in(name, out)
}
crate::parser::TypeExpr::Nullable(inner) => mentioned_type_names(inner, out),
crate::parser::TypeExpr::Union(types) => {
for ty in types {
mentioned_type_names(ty, out);
}
}
crate::parser::TypeExpr::Generic(name, params) => {
names_in(name, out);
for param in params {
mentioned_type_names(param, out);
}
}
}
}
fn narrow_aliases<'a>(
aliases: &TypeAliases<'a>,
declared: &HashSet<&str>,
) -> Option<TypeAliases<'a>> {
if declared.is_empty() {
return None;
}
let affected = |start: &'a str| {
let mut seen = HashSet::default();
let mut pending = vec![start];
while let Some(name) = pending.pop() {
if declared.contains(name) {
return true;
}
if !seen.insert(name) {
continue;
}
if let Some(Some(ty)) = aliases.get(name) {
mentioned_type_names(ty, &mut pending);
}
}
false
};
let mut changed = false;
let kept: TypeAliases<'a> = aliases
.iter()
.filter_map(|(name, ty)| {
if ty.is_none() || !affected(name) {
return Some((*name, *ty));
}
changed = true;
BINDING_BUILTIN_TYPES
.contains(name)
.then_some((*name, None))
})
.collect();
changed.then_some(kept)
}
fn with_builtins_unresolved<'a>(
aliases: &TypeAliases<'a>,
entries: &'a [Entry],
declared: &HashSet<&str>,
) -> Option<TypeAliases<'a>> {
let redeclared: Vec<&str> = BINDING_BUILTIN_TYPES
.iter()
.copied()
.filter(|builtin| declared.contains(builtin) && aliases.get(builtin) != Some(&None))
.collect();
if redeclared.is_empty() {
return None;
}
let mut marked = aliases.clone();
for builtin in &redeclared {
marked.insert(builtin, None);
}
let binding_definition = |builtin: &str| {
if aliases.contains_key(builtin) {
return None;
}
let mut definitions = entries.iter().filter_map(|entry| match entry {
Entry::TypeAlias(name, ty) if name == builtin => Some(Some(ty)),
Entry::ClassDef(name, ..) if name == builtin => Some(None),
_ => None,
});
match (definitions.next(), definitions.next()) {
(Some(Some(ty)), None) if alias_binds_collection_names(ty, &marked) => Some(ty),
_ => None,
}
};
let kept: Vec<_> = redeclared
.iter()
.filter_map(|builtin| Some((*builtin, binding_definition(builtin)?)))
.collect();
for (builtin, ty) in kept {
marked.insert(builtin, Some(ty));
}
Some(marked)
}
fn alias_binds_collection_names(ty: &crate::parser::TypeExpr, aliases: &TypeAliases) -> bool {
match ty {
crate::parser::TypeExpr::Named(base)
| crate::parser::TypeExpr::Generic(base, _)
| crate::parser::TypeExpr::Constrained(base, _) => {
constraint_bound_names_resolving(base, Some(aliases)).contains(&"length")
}
crate::parser::TypeExpr::Nullable(_) | crate::parser::TypeExpr::Union(_) => false,
}
}
fn constraint_bound_names_resolving(
base: &str,
aliases: Option<&TypeAliases>,
) -> &'static [&'static str] {
let Some(aliases) = aliases else {
return constraint_bound_names(base);
};
let mut base = base;
let mut seen = HashSet::default();
loop {
if base.ends_with('?') {
return &["this"];
}
let name = base.trim_start_matches('*');
let name = name.split('<').next().unwrap_or(name).trim();
let Some(ty) = aliases.get(name) else {
return constraint_bound_names(base);
};
let Some(ty) = ty else {
return &["this"];
};
if !seen.insert(name) {
return &["this"];
}
base = match ty {
crate::parser::TypeExpr::Named(name) | crate::parser::TypeExpr::Generic(name, _) => {
name
}
crate::parser::TypeExpr::Constrained(base, _) => base,
crate::parser::TypeExpr::Nullable(_) | crate::parser::TypeExpr::Union(_) => {
return &["this"];
}
};
}
}
fn collect_sibling_field_refs_type(ty: &crate::parser::TypeExpr, refs: &mut HashSet<String>) {
match ty {
crate::parser::TypeExpr::Constrained(_, constraint) => {
collect_sibling_field_refs_expr(constraint, refs, false);
}
crate::parser::TypeExpr::Nullable(inner) => collect_sibling_field_refs_type(inner, refs),
crate::parser::TypeExpr::Union(types) | crate::parser::TypeExpr::Generic(_, types) => {
for ty in types {
collect_sibling_field_refs_type(ty, refs);
}
}
crate::parser::TypeExpr::Named(_) => {}
}
}
pub(super) fn collect_sibling_field_refs_expr(
expr: &Expr,
refs: &mut HashSet<String>,
include_this: bool,
) {
match expr {
Expr::Field(base, field) | Expr::NullSafeField(base, field) => {
if is_module_sibling_ref(base, include_this) {
refs.insert(field.clone());
}
collect_sibling_field_refs_expr(base, refs, include_this);
}
Expr::Index(base, index) => {
if is_module_sibling_ref(base, include_this) {
if let Expr::String(key) = index.as_ref() {
refs.insert(key.to_string());
} else {
refs.insert(DYNAMIC_SIBLING_REF.to_string());
}
}
collect_sibling_field_refs_expr(base, refs, include_this);
collect_sibling_field_refs_expr(index, refs, include_this);
}
Expr::Binop(_, left, right) => {
collect_sibling_field_refs_expr(left, refs, include_this);
collect_sibling_field_refs_expr(right, refs, include_this);
}
Expr::New(type_name, entries, _) => {
if let Some((root, rest)) = type_name.as_deref().and_then(|name| name.split_once('.'))
&& (root == "module" || (include_this && root == "this"))
{
let member = rest.split('.').next().unwrap_or(rest);
refs.insert(member.to_string());
}
collect_sibling_field_refs_entries(entries, refs);
}
Expr::InferredNew(_, entries) | Expr::ObjectBody(entries) => {
collect_sibling_field_refs_entries(entries, refs);
}
Expr::Call(callee, args) => {
collect_sibling_field_refs_expr(callee, refs, include_this);
for arg in args {
collect_sibling_field_refs_expr(arg, refs, include_this);
}
}
Expr::If(cond, then_expr, else_expr) => {
collect_sibling_field_refs_expr(cond, refs, include_this);
collect_sibling_field_refs_expr(then_expr, refs, include_this);
collect_sibling_field_refs_expr(else_expr, refs, include_this);
}
Expr::Let(_, value, body) => {
collect_sibling_field_refs_expr(value, refs, include_this);
collect_sibling_field_refs_expr(body, refs, include_this);
}
Expr::Is(value, ty) | Expr::As(value, ty) => {
collect_sibling_field_refs_expr(value, refs, include_this);
collect_sibling_field_refs_type(ty, refs);
}
Expr::Lambda(_, body) => collect_sibling_field_refs_expr(body, refs, include_this),
Expr::Unop(_, body)
| Expr::Throw(body)
| Expr::Trace(body, _)
| Expr::Read(body, _)
| Expr::ReadOrNull(body, _)
| Expr::ReadGlob(body, _) => {
collect_sibling_field_refs_expr(body, refs, include_this);
}
Expr::StringInterpolation(parts) => {
for part in parts {
if let StringInterpPart::Expr(expr) = part {
collect_sibling_field_refs_expr(expr, refs, include_this);
}
}
}
Expr::Ident(_)
| Expr::Import(..)
| Expr::ImportGlob(..)
| Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_) => {}
}
}
pub(super) fn property_reference_names(prop: &Property) -> HashSet<String> {
let mut refs = HashSet::default();
let shadows = HashSet::default();
if let Some(expr) = &prop.value {
collect_expr_refs(expr, &mut refs, &shadows);
collect_sibling_field_refs_expr(expr, &mut refs, true);
if has_modifier(&prop.modifiers, Modifier::Local) && is_module_sibling_ref(expr, true) {
refs.insert(DYNAMIC_SIBLING_REF.to_string());
}
}
if let Some(body) = &prop.body {
collect_entry_refs(body, &mut refs, &shadows);
collect_sibling_field_refs_entries(body, &mut refs);
}
refs
}
pub(super) fn is_module_sibling_ref(expr: &Expr, include_this: bool) -> bool {
matches!(expr, Expr::Ident(name) if name == "module" || (include_this && name == "this"))
}
pub(super) fn module_member_name(entry: &Entry) -> Option<&str> {
match entry {
Entry::Property(prop) => Some(&prop.name),
Entry::ClassDef(name, ..) | Entry::TypeAlias(name, _) => Some(name),
_ => None,
}
}
fn module_member_dependencies(entry: &Entry) -> HashSet<String> {
let mut refs = referenced_roots(std::slice::from_ref(entry));
match entry {
Entry::Property(prop) => {
refs.extend(property_reference_names(prop));
if let Some(ty) = &prop.type_ann {
collect_sibling_field_refs_type(ty, &mut refs);
}
}
Entry::ClassDef(_, _, parent, body) => {
let mut module_refs = HashSet::default();
collect_sibling_field_refs_entries(body, &mut module_refs);
collect_sibling_field_refs_entries(body, &mut refs);
if let Some((root, rest)) = parent.as_deref().and_then(|name| name.split_once('.'))
&& root == "module"
{
refs.insert(rest.split('.').next().unwrap_or(rest).to_string());
}
for entry in body.iter() {
if let Entry::Property(prop) = entry {
refs.remove(&prop.name);
}
}
refs.extend(module_refs);
}
Entry::TypeAlias(_, ty) => collect_sibling_field_refs_type(ty, &mut refs),
_ => {}
}
refs
}
type ClassInfo<'e> = (Option<&'e str>, &'e [Entry]);
#[derive(Clone, Default)]
struct ClassMap<'e> {
module: std::rc::Rc<HashMap<&'e str, ClassInfo<'e>>>,
nested: HashMap<&'e str, ClassInfo<'e>>,
}
impl<'e> ClassMap<'e> {
fn module(classes: HashMap<&'e str, ClassInfo<'e>>) -> Self {
ClassMap {
module: std::rc::Rc::new(classes),
nested: HashMap::default(),
}
}
fn get(&self, name: &str) -> Option<&ClassInfo<'e>> {
self.nested.get(name).or_else(|| self.module.get(name))
}
fn extend_with_body(&mut self, body: &'e [Entry]) {
self.nested
.extend(body.iter().filter_map(|entry| match entry {
Entry::ClassDef(name, _, parent, inner) => {
Some((name.as_str(), (parent.as_deref(), inner.as_slice())))
}
_ => None,
}));
}
}
fn ancestor_properties<'e>(classes: &ClassMap<'e>, parent: Option<&'e str>) -> HashSet<&'e str> {
let mut names = HashSet::default();
let mut seen = HashSet::default();
let mut next = parent.map(|parent| (parent, false));
while let Some((name, in_module)) = next {
let (qualified, name) = match name.strip_prefix("module.") {
Some(name) => (true, name),
None => (in_module, name),
};
let (in_module, info) = match classes.nested.get(name).filter(|_| !qualified) {
Some(info) => (false, Some(info)),
None => (true, classes.module.get(name)),
};
let Some((parent, body)) = info else {
break;
};
if !seen.insert((in_module, name)) {
break;
}
names.extend(body.iter().filter_map(|entry| match entry {
Entry::Property(prop) if !has_modifier(&prop.modifiers, Modifier::Local) => {
Some(prop.name.as_str())
}
_ => None,
}));
next = parent.map(|parent| (parent, in_module));
}
names
}
struct ModuleClasses<'a> {
properties: HashSet<&'a str>,
classes: ClassMap<'a>,
}
impl<'a> ModuleClasses<'a> {
fn new(entries: &'a [Entry]) -> Self {
Self {
properties: entries
.iter()
.filter_map(|entry| match entry {
Entry::Property(prop) if !has_modifier(&prop.modifiers, Modifier::Local) => {
Some(prop.name.as_str())
}
_ => None,
})
.collect(),
classes: ClassMap::module(
entries
.iter()
.filter_map(|entry| match entry {
Entry::ClassDef(name, _, parent, body) => {
Some((name.as_str(), (parent.as_deref(), body.as_slice())))
}
_ => None,
})
.collect(),
),
}
}
fn is_property(&self, name: &str) -> bool {
self.properties.contains(name)
}
fn module_reads<'r>(
&self,
class: &str,
body: &[Entry],
roots: &'r HashSet<String>,
) -> Vec<&'r String> {
let mut inherited = None;
let mut eager = None;
roots
.iter()
.filter(|root| {
if !self.is_property(root) {
return false;
}
let declared = body.iter().rposition(
|entry| matches!(entry, Entry::Property(prop) if prop.name == **root),
);
if let Some(declared) = declared {
return eager
.get_or_insert_with(|| EagerReads::new(body, &self.classes))
.upto(declared)
.contains(root.as_str());
}
if !inherited
.get_or_insert_with(|| self.inherited_properties(class))
.contains(root.as_str())
{
return true;
}
eager
.get_or_insert_with(|| EagerReads::new(body, &self.classes))
.all
.contains(root.as_str())
})
.collect()
}
fn inherited_properties(&self, class: &str) -> HashSet<&'a str> {
let parent = self.classes.get(class).and_then(|(parent, _)| *parent);
ancestor_properties(&self.classes, parent)
}
}
struct EagerReads<'e> {
body: &'e [Entry],
classes: ClassMap<'e>,
methods: HashSet<&'e str>,
stored: HashMap<&'e str, &'e str>,
all: HashSet<String>,
runs: HashSet<&'e str>,
}
impl<'e> EagerReads<'e> {
fn is_method(entry: &Entry) -> bool {
matches!(entry, Entry::Property(prop) if matches!(prop.value, Some(Expr::Lambda(..))))
}
fn is_local(entry: &Entry) -> bool {
matches!(entry, Entry::Property(prop) if has_modifier(&prop.modifiers, Modifier::Local))
}
fn new(body: &'e [Entry], scope: &ClassMap<'e>) -> Self {
let mut classes = scope.clone();
classes.extend_with_body(body);
let mut reads = EagerReads {
body,
classes,
methods: body
.iter()
.filter_map(|entry| match entry {
Entry::Property(prop) if Self::is_method(entry) => Some(prop.name.as_str()),
_ => None,
})
.collect(),
stored: stored_methods(body, Self::is_method),
all: HashSet::default(),
runs: HashSet::default(),
};
let defaults = body
.iter()
.filter(|entry| reads.is_default(entry))
.collect();
(reads.all, reads.runs) = reads.reads(defaults);
reads
}
fn is_default(&self, entry: &Entry) -> bool {
!Self::is_method(entry)
&& !matches!(entry, Entry::Property(prop) if self.stored.contains_key(prop.name.as_str()))
}
fn upto(&self, upto: usize) -> HashSet<String> {
let seeds = self
.body
.iter()
.enumerate()
.filter(|(index, entry)| match entry {
Entry::Property(prop) if Self::is_method(entry) => {
Self::is_local(entry) && self.runs.contains(prop.name.as_str())
}
_ => {
self.is_default(entry)
&& (*index <= upto
|| Self::is_local(entry)
|| matches!(entry, Entry::ClassDef(..)))
}
})
.map(|(_, entry)| entry)
.collect();
self.reads(seeds).0
}
fn reads(&self, seeds: Vec<&'e Entry>) -> (HashSet<String>, HashSet<&'e str>) {
let body = self.body;
let (nested, seeds): (Vec<&Entry>, Vec<&Entry>) = seeds
.into_iter()
.partition(|entry| matches!(entry, Entry::ClassDef(..)));
let mut refs = referenced_roots_of(&seeds);
for class in &nested {
if let Entry::ClassDef(_, _, _, class_body) = class {
refs.extend(class_default_reads(class_body, &self.classes));
}
}
let mut fields = HashSet::default();
let mut instance = InstanceReads {
members: HashSet::default(),
escapes: false,
};
for seed in &seeds {
let seed = std::slice::from_ref(*seed);
collect_field_names_entries(seed, &mut fields);
instance.entries(seed, 0);
}
let mut followed = HashSet::default();
let mut used: Vec<NestedClass<'e>> = Vec::new();
let mut used_keys: HashSet<*const Entry> = HashSet::default();
let mut followed_nested: HashSet<*const Entry> = HashSet::default();
loop {
let mut nested_reads = false;
for entry in body {
if let Entry::ClassDef(name, _, parent, class_body) = entry
&& refs.contains(name)
&& used_keys.insert(std::ptr::from_ref(entry))
{
used.push(NestedClass {
body: class_body,
parent: parent.as_deref(),
chain: vec![(body, None)],
});
}
}
let mut index = 0;
while index < used.len() {
let class = used[index].clone();
index += 1;
let scope = class.scope(&self.classes);
let shadow = class.shadow(&scope);
let stored = stored_methods(class.body, Self::is_method);
let mut called = fields.clone();
loop {
let calls_all = called.contains(DYNAMIC_SIBLING_REF);
let next: Vec<&Entry> = class
.body
.iter()
.filter(|entry| match entry {
Entry::Property(prop) if Self::is_method(entry) => {
(calls_all
|| called.contains(&prop.name)
|| stored.iter().any(|(property, method)| {
*method == prop.name && called.contains(*property)
}))
&& followed_nested.insert(std::ptr::from_ref(*entry))
}
_ => false,
})
.collect();
if next.is_empty() {
break;
}
for method in next {
let method = std::slice::from_ref(method);
let roots = referenced_roots(method);
called.extend(roots.iter().cloned());
collect_field_names_entries(method, &mut called);
collect_field_names_entries(method, &mut fields);
for root in &roots {
if let Some((entry, found)) = class.lookup(root)
&& used_keys.insert(std::ptr::from_ref(entry))
{
let found_scope = found.scope(&self.classes);
let enclosing = found.enclosing_names(&found_scope);
refs.extend(
class_default_reads(found.body, &found_scope)
.into_iter()
.filter(|root| !enclosing.contains(root.as_str())),
);
used.push(found);
}
}
refs.extend(
roots
.into_iter()
.filter(|root| !shadow.contains(root.as_str())),
);
nested_reads = true;
}
}
}
let calls_all = instance.escapes;
let read = |name: &str| refs.contains(name) || instance.members.contains(name);
let mut runs: HashSet<&str> = self
.methods
.iter()
.copied()
.filter(|method| calls_all || read(method))
.collect();
runs.extend(
self.stored
.iter()
.filter(|(property, _)| read(property))
.map(|(_, method)| *method),
);
let next: Vec<&Entry> = body
.iter()
.filter(|entry| match entry {
Entry::Property(prop) if Self::is_method(entry) => {
runs.contains(prop.name.as_str()) && followed.insert(prop.name.as_str())
}
_ => false,
})
.collect();
if next.is_empty() && !nested_reads {
return (refs, followed);
}
for method in next {
let method = std::slice::from_ref(method);
refs.extend(referenced_roots(method));
collect_field_names_entries(method, &mut fields);
instance.entries(method, 0);
}
}
}
}
#[derive(Clone)]
struct NestedClass<'e> {
body: &'e [Entry],
parent: Option<&'e str>,
chain: Vec<(&'e [Entry], Option<&'e str>)>,
}
impl<'e> NestedClass<'e> {
fn scope(&self, classes: &ClassMap<'e>) -> ClassMap<'e> {
let mut scope = classes.clone();
for (body, _) in &self.chain {
scope.extend_with_body(body);
}
scope
}
fn enclosing_names(&self, scope: &ClassMap<'e>) -> HashSet<&'e str> {
let mut names = HashSet::default();
for (body, parent) in self.chain.iter().skip(1) {
names.extend(instance_names(scope, *parent, body));
}
names
}
fn shadow(&self, scope: &ClassMap<'e>) -> HashSet<&'e str> {
let mut names = self.enclosing_names(scope);
names.extend(instance_names(scope, self.parent, self.body));
names
}
fn lookup(&self, name: &str) -> Option<(&'e Entry, NestedClass<'e>)> {
let mut chain = self.chain.clone();
chain.push((self.body, self.parent));
while let Some(&(body, _)) = chain.last() {
if let Some(entry) = body
.iter()
.find(|entry| matches!(entry, Entry::ClassDef(class, ..) if class == name))
&& let Entry::ClassDef(_, _, parent, inner) = entry
{
return Some((
entry,
NestedClass {
body: inner,
parent: parent.as_deref(),
chain,
},
));
}
chain.pop();
}
None
}
}
fn instance_names<'e>(
classes: &ClassMap<'e>,
parent: Option<&'e str>,
body: &'e [Entry],
) -> HashSet<&'e str> {
let mut names = ancestor_properties(classes, parent);
names.extend(body.iter().filter_map(|entry| match entry {
Entry::Property(prop) => Some(prop.name.as_str()),
_ => None,
}));
names
}
fn referenced_roots_of(entries: &[&Entry]) -> HashSet<String> {
let mut shadows = HashSet::default();
for entry in entries {
shadows.extend(declared_entry_roots(std::slice::from_ref(*entry)));
}
let mut refs = HashSet::default();
for entry in entries {
collect_entry_refs(std::slice::from_ref(*entry), &mut refs, &shadows);
}
refs
}
fn stored_methods(body: &[Entry], is_method: fn(&Entry) -> bool) -> HashMap<&str, &str> {
let methods: HashSet<&str> = body
.iter()
.filter_map(|entry| match entry {
Entry::Property(prop) if is_method(entry) => Some(prop.name.as_str()),
_ => None,
})
.collect();
body.iter()
.filter_map(|entry| match entry {
Entry::Property(prop) if !has_modifier(&prop.modifiers, Modifier::Local) => {
let method = match prop.value.as_ref()? {
Expr::Ident(name) => name,
Expr::Field(base, name) if matches!(&**base, Expr::Ident(this) if this == "this") => {
name
}
_ => return None,
};
methods
.contains(method.as_str())
.then_some((prop.name.as_str(), method.as_str()))
}
_ => None,
})
.collect()
}
fn class_default_reads<'e>(body: &'e [Entry], scope: &ClassMap<'e>) -> HashSet<String> {
let eager = EagerReads::new(body, scope);
eager
.all
.iter()
.filter(|root| {
body.iter()
.rposition(|entry| matches!(entry, Entry::Property(prop) if prop.name == **root))
.is_none_or(|declared| eager.upto(declared).contains(*root))
})
.cloned()
.collect()
}
fn collect_field_names_entries(entries: &[Entry], out: &mut HashSet<String>) {
for entry in entries {
match entry {
Entry::Property(prop) => {
if let Some(value) = &prop.value {
collect_field_names_expr(value, out);
}
if let Some(body) = &prop.body {
collect_field_names_entries(body, out);
}
}
Entry::DynProperty(key, value) | Entry::Predicate(key, value) => {
collect_field_names_expr(key, out);
collect_field_names_expr(value, out);
}
Entry::ForGenerator(fgen) => {
collect_field_names_expr(&fgen.collection, out);
collect_field_names_entries(&fgen.body, out);
}
Entry::WhenGenerator(wgen) => {
collect_field_names_expr(&wgen.condition, out);
collect_field_names_entries(&wgen.body, out);
if let Some(else_body) = &wgen.else_body {
collect_field_names_entries(else_body, out);
}
}
Entry::Spread(expr) | Entry::Elem(expr) => collect_field_names_expr(expr, out),
Entry::ClassDef(..) | Entry::TypeAlias(..) => {}
}
}
}
fn may_be_object(expr: &Expr) -> bool {
match expr {
Expr::Null | Expr::Bool(_) | Expr::Int(_) | Expr::Float(_) | Expr::String(_) => false,
Expr::Binop(_, left, right) => may_be_object(left) || may_be_object(right),
Expr::Unop(_, value) => may_be_object(value),
Expr::StringInterpolation(_) => false,
_ => true,
}
}
fn collect_field_names_expr(expr: &Expr, out: &mut HashSet<String>) {
match expr {
Expr::Field(base, name) | Expr::NullSafeField(base, name) => {
out.insert(name.clone());
collect_field_names_expr(base, out);
}
Expr::Index(base, index) => {
match index.as_ref() {
Expr::String(key) => {
out.insert(key.to_string());
}
_ => {
out.insert(DYNAMIC_SIBLING_REF.to_string());
}
}
collect_field_names_expr(base, out);
collect_field_names_expr(index, out);
}
Expr::New(_, entries, _) | Expr::ObjectBody(entries) | Expr::InferredNew(_, entries) => {
collect_field_names_entries(entries, out)
}
Expr::Binop(op, left, right) => {
if matches!(op, BinOp::Pipe) && may_be_object(left) {
out.insert(DYNAMIC_SIBLING_REF.to_string());
}
collect_field_names_expr(left, out);
collect_field_names_expr(right, out);
}
Expr::Call(callee, args) => {
if args.iter().any(may_be_object) {
out.insert(DYNAMIC_SIBLING_REF.to_string());
}
collect_field_names_expr(callee, out);
for arg in args {
collect_field_names_expr(arg, out);
}
}
Expr::If(cond, then_expr, else_expr) => {
collect_field_names_expr(cond, out);
collect_field_names_expr(then_expr, out);
collect_field_names_expr(else_expr, out);
}
Expr::Let(_, value, body) => {
collect_field_names_expr(value, out);
collect_field_names_expr(body, out);
}
Expr::Lambda(_, value) => collect_field_names_expr(value, out),
Expr::Is(value, _)
| Expr::As(value, _)
| Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value, _)
| Expr::Read(value, _)
| Expr::ReadOrNull(value, _)
| Expr::ReadGlob(value, _) => collect_field_names_expr(value, out),
Expr::StringInterpolation(parts) => {
for part in parts {
if let StringInterpPart::Expr(expr) = part {
collect_field_names_expr(expr, out);
}
}
}
Expr::Ident(_)
| Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => {}
}
}
pub(super) struct EvaluationPlan {
pub(super) order: Vec<usize>,
dependents: Vec<Vec<usize>>,
}
impl EvaluationPlan {
pub(super) fn affected_by(&self, failed: &HashSet<usize>) -> Vec<usize> {
let mut affected = vec![false; self.dependents.len()];
let mut stack: Vec<usize> = failed.iter().copied().collect();
while let Some(index) = stack.pop() {
if !std::mem::replace(&mut affected[index], true) {
stack.extend(&self.dependents[index]);
}
}
self.order
.iter()
.copied()
.filter(|&index| affected[index])
.collect()
}
}
pub(super) fn module_evaluation_plan(entries: &[Entry]) -> EvaluationPlan {
if entries.len() <= 1 {
return EvaluationPlan {
order: (0..entries.len()).collect(),
dependents: std::iter::repeat_with(Vec::new)
.take(entries.len())
.collect(),
};
}
let mut by_name: HashMap<&str, Vec<usize>> = HashMap::default();
for (index, entry) in entries.iter().enumerate() {
if let Some(name) = module_member_name(entry) {
by_name.entry(name).or_default().push(index);
}
}
let mut dynamic = vec![false; entries.len()];
let static_deps: Vec<Vec<usize>> = entries
.iter()
.enumerate()
.map(|(index, entry)| {
if module_member_name(entry).is_none() {
return Vec::new();
}
let refs = module_member_dependencies(entry);
dynamic[index] = refs.contains(DYNAMIC_SIBLING_REF);
let mut deps: Vec<usize> = refs
.iter()
.filter_map(|name| by_name.get(name.as_str()))
.flatten()
.copied()
.filter(|&dep| dep != index)
.collect();
deps.sort_unstable();
deps.dedup();
deps
})
.collect();
let reaches = |from: usize, to: usize| {
let mut seen = vec![false; entries.len()];
let mut stack = vec![from];
while let Some(i) = stack.pop() {
if i == to {
return true;
}
if !std::mem::replace(&mut seen[i], true) {
stack.extend(&static_deps[i]);
}
}
false
};
let deps: Vec<Vec<usize>> = (0..entries.len())
.map(|i| {
let mut deps = static_deps[i].clone();
if dynamic[i] {
deps.extend((0..entries.len()).filter(|&j| {
j != i
&& !dynamic[j]
&& module_member_name(&entries[j]).is_some()
&& !reaches(j, i)
}));
deps.sort_unstable();
deps.dedup();
}
deps
})
.collect();
fn visit(i: usize, deps: &[Vec<usize>], state: &mut [u8], order: &mut Vec<usize>) {
if state[i] != 0 {
return;
}
state[i] = 1;
for &j in &deps[i] {
visit(j, deps, state, order);
}
state[i] = 2;
order.push(i);
}
let mut state = vec![0u8; entries.len()];
let mut order = Vec::with_capacity(entries.len());
let is_property = |entry: &Entry| matches!(entry, Entry::Property(prop) if !has_modifier(&prop.modifiers, Modifier::Local));
for i in (0..entries.len()).filter(|&i| !is_property(&entries[i])) {
visit(i, &deps, &mut state, &mut order);
}
for i in 0..entries.len() {
visit(i, &deps, &mut state, &mut order);
}
let mut dependents = vec![Vec::new(); entries.len()];
for (index, deps) in deps.iter().enumerate() {
for &dep in deps {
dependents[dep].push(index);
}
}
EvaluationPlan { order, dependents }
}
pub(super) fn module_dependent_members(entries: &[Entry]) -> indexmap::IndexSet<String> {
fn qualified_member(name: &str, include_this: bool) -> Option<&str> {
let (root, rest) = name.split_once('.')?;
(root == "module" || (include_this && root == "this"))
.then(|| rest.split('.').next().unwrap_or(rest))
}
if !entries
.iter()
.any(|entry| matches!(entry, Entry::ClassDef(..)))
{
return indexmap::IndexSet::new();
}
let classes = ModuleClasses::new(entries);
let members: Vec<(&String, bool, HashSet<String>)> = entries
.iter()
.filter_map(|entry| match entry {
Entry::ClassDef(name, _, parent, body) => {
let mut refs = referenced_roots(body);
let reads_module = !classes.module_reads(name, body, &refs).is_empty();
if reads_module {
refs.insert("module".to_string());
}
collect_sibling_field_refs_entries(body, &mut refs);
if let Some(parent) = parent {
let mut parent_refs = Names::default();
collect_name_root(parent, &mut parent_refs, &Names::default());
parent_refs.add_all_to(&mut refs);
refs.extend(qualified_member(parent, false).map(str::to_string));
}
Some((name, true, refs))
}
Entry::TypeAlias(name, ty) => {
let mut refs = HashSet::default();
collect_type_names(ty, &mut refs);
collect_sibling_field_refs_type(ty, &mut refs);
if let Some(target) = type_alias_target(ty) {
refs.extend(qualified_member(target, true).map(str::to_string));
}
Some((name, false, refs))
}
Entry::Property(prop)
if has_modifier(&prop.modifiers, Modifier::Local)
|| matches!(prop.value, Some(Expr::Lambda(..))) =>
{
let value = prop.value.as_ref()?;
let mut refs = HashSet::default();
collect_expr_refs(value, &mut refs, &HashSet::default());
collect_sibling_field_refs_expr(value, &mut refs, true);
Some((&prop.name, false, refs))
}
_ => None,
})
.collect();
let mut dependent = HashSet::default();
loop {
let before = dependent.len();
for (name, is_class, refs) in &members {
if !dependent.contains(name.as_str())
&& ((refs.contains(DYNAMIC_SIBLING_REF) && !dependent.is_empty())
|| refs.iter().any(|root| {
(*is_class && root == "module") || dependent.contains(root.as_str())
}))
{
dependent.insert(name.to_string());
}
}
if dependent.len() == before {
break;
}
}
let tracked: Vec<&(&String, bool, HashSet<String>)> = members
.iter()
.filter(|(name, ..)| dependent.contains(name.as_str()))
.collect();
let index: HashMap<&str, usize> = tracked
.iter()
.enumerate()
.map(|(i, (name, ..))| (name.as_str(), i))
.collect();
let dynamic: Vec<bool> = tracked
.iter()
.map(|(_, _, refs)| refs.contains(DYNAMIC_SIBLING_REF))
.collect();
let static_deps: Vec<Vec<usize>> = tracked
.iter()
.enumerate()
.map(|(i, (_, _, refs))| {
let mut deps: Vec<usize> = refs
.iter()
.filter_map(|name| index.get(name.as_str()).copied())
.filter(|&j| j != i)
.collect();
deps.sort_unstable();
deps
})
.collect();
let reaches = |from: usize, to: usize| {
let mut seen = vec![false; tracked.len()];
let mut stack = vec![from];
while let Some(i) = stack.pop() {
if i == to {
return true;
}
if !std::mem::replace(&mut seen[i], true) {
stack.extend(&static_deps[i]);
}
}
false
};
let deps: Vec<Vec<usize>> = (0..tracked.len())
.map(|i| {
let mut deps = static_deps[i].clone();
if dynamic[i] {
deps.extend(
(0..tracked.len()).filter(|&j| j != i && !dynamic[j] && !reaches(j, i)),
);
deps.sort_unstable();
deps.dedup();
}
deps
})
.collect();
fn visit(i: usize, deps: &[Vec<usize>], state: &mut [u8], order: &mut Vec<usize>) {
if state[i] != 0 {
return;
}
state[i] = 1;
for &j in &deps[i] {
visit(j, deps, state, order);
}
state[i] = 2;
order.push(i);
}
let mut state = vec![0u8; tracked.len()];
let mut order = Vec::with_capacity(tracked.len());
for i in 0..tracked.len() {
visit(i, &deps, &mut state, &mut order);
}
order.into_iter().map(|i| tracked[i].0.clone()).collect()
}
pub(super) fn type_alias_target(ty: &crate::parser::TypeExpr) -> Option<&str> {
match ty {
crate::parser::TypeExpr::Named(target)
| crate::parser::TypeExpr::Constrained(target, _) => Some(target),
crate::parser::TypeExpr::Nullable(inner) => match inner.as_ref() {
crate::parser::TypeExpr::Named(target) => Some(target),
_ => None,
},
_ => None,
}
}
pub(super) fn referenced_roots(entries: &[Entry]) -> HashSet<String> {
let mut refs = HashSet::default();
let shadows = HashSet::default();
collect_entry_refs(entries, &mut refs, &shadows);
refs
}
pub(super) fn collect_entry_refs(
entries: &[Entry],
refs: &mut HashSet<String>,
shadows: &HashSet<String>,
) {
let mut collected = Names::default();
let shadows = Names::values(shadows);
collect_entry_refs_in(entries, &mut collected, &shadows, None, None);
collected.add_all_to(refs);
}
fn collect_entry_refs_in(
entries: &[Entry],
refs: &mut Names,
shadows: &Names,
aliases: Option<&TypeAliases>,
definitions: Option<&Definitions>,
) {
let no_aliases = TypeAliases::default();
let narrowed = {
let aliases = aliases.unwrap_or(&no_aliases);
let mut declared = HashSet::default();
collect_entries_type_decls(entries, aliases, &mut declared);
let narrowed = narrow_aliases(aliases, &declared);
with_builtins_unresolved(narrowed.as_ref().unwrap_or(aliases), entries, &declared)
.or(narrowed)
};
if let (Some(narrowed), Some(definitions)) = (&narrowed, definitions) {
let mut body_refs = Names::default();
collect_entry_refs_unnarrowed(
entries,
&mut body_refs,
shadows,
Some(narrowed),
Some(definitions),
);
follow_definitions(definitions, &mut body_refs, Some(narrowed));
let followed: HashSet<String> = definitions
.unshadowed_values(&body_refs.values)
.cloned()
.collect();
body_refs.values.retain(|name| !followed.contains(name));
body_refs
.types
.retain(|name| !definitions.types.contains_key(name.as_str()));
refs.extend(body_refs);
return;
}
collect_entry_refs_unnarrowed(
entries,
refs,
shadows,
narrowed.as_ref().or(aliases),
definitions,
);
}
fn collect_entry_refs_unnarrowed(
entries: &[Entry],
refs: &mut Names,
shadows: &Names,
aliases: Option<&TypeAliases>,
definitions: Option<&Definitions>,
) {
let mut entry_shadows = shadows.clone();
entry_shadows.extend(declared_entry_names(entries));
for entry in entries {
match entry {
Entry::Property(prop) => {
if let Some(ty) = &prop.type_ann {
collect_type_refs(ty, refs, &entry_shadows, aliases, definitions);
}
if let Some(expr) = &prop.value {
collect_expr_refs_in(expr, refs, &entry_shadows, aliases, definitions);
}
if let Some(body) = &prop.body {
collect_entry_refs_in(body, refs, &entry_shadows, aliases, definitions);
}
}
Entry::DynProperty(key, value) | Entry::Predicate(key, value) => {
collect_expr_refs_in(key, refs, &entry_shadows, aliases, definitions);
collect_expr_refs_in(value, refs, &entry_shadows, aliases, definitions);
}
Entry::ForGenerator(fgen) => {
collect_expr_refs_in(&fgen.collection, refs, &entry_shadows, aliases, definitions);
let mut body_shadows = entry_shadows.clone();
body_shadows.values.insert(fgen.val_var.clone());
if let Some(key_var) = &fgen.key_var {
body_shadows.values.insert(key_var.clone());
}
collect_entry_refs_in(&fgen.body, refs, &body_shadows, aliases, definitions);
}
Entry::WhenGenerator(wgen) => {
collect_expr_refs_in(&wgen.condition, refs, &entry_shadows, aliases, definitions);
collect_entry_refs_in(&wgen.body, refs, &entry_shadows, aliases, definitions);
if let Some(else_body) = &wgen.else_body {
collect_entry_refs_in(else_body, refs, &entry_shadows, aliases, definitions);
}
}
Entry::Spread(expr) | Entry::Elem(expr) => {
collect_expr_refs_in(expr, refs, &entry_shadows, aliases, definitions)
}
Entry::ClassDef(_, _, parent, body) => {
if let Some(parent) = parent {
collect_name_root(parent, refs, &entry_shadows);
}
collect_entry_refs_in(body, refs, &entry_shadows, aliases, definitions);
}
Entry::TypeAlias(_, ty) => {
collect_type_refs(ty, refs, &entry_shadows, aliases, definitions)
}
}
}
}
pub(super) fn with_listing_locals(expr: &Expr, locals: &[(String, Expr)]) -> Expr {
let mut expr = expr.clone();
let mut refs = HashSet::default();
collect_expr_refs(&expr, &mut refs, &HashSet::default());
for (name, value) in locals.iter().rev() {
if refs.remove(name) {
collect_expr_refs(value, &mut refs, &HashSet::default());
expr = Expr::Let(name.clone(), Box::new(value.clone()), Box::new(expr));
}
}
expr
}
pub(super) fn type_reference_names(ty: &crate::parser::TypeExpr) -> HashSet<String> {
let mut collected = Names::default();
collect_type_refs(ty, &mut collected, &Names::default(), None, None);
let mut refs = HashSet::default();
collected.add_all_to(&mut refs);
refs
}
fn collect_type_names(ty: &crate::parser::TypeExpr, refs: &mut HashSet<String>) {
refs.extend(type_reference_names(ty));
}
pub(super) fn collect_expr_refs(
expr: &Expr,
refs: &mut HashSet<String>,
shadows: &HashSet<String>,
) {
let mut collected = Names::default();
let shadows = Names::values(shadows);
collect_expr_refs_in(expr, &mut collected, &shadows, None, None);
collected.add_all_to(refs);
}
fn collect_expr_refs_in(
expr: &Expr,
refs: &mut Names,
shadows: &Names,
aliases: Option<&TypeAliases>,
definitions: Option<&Definitions>,
) {
match expr {
Expr::Ident(name) => {
if !shadows.values.contains(name) {
refs.values.insert(name.clone());
}
}
Expr::New(type_name, entries, generic_params) => {
if let Some(type_name) = type_name {
collect_name_root(type_name, refs, shadows);
}
for param in generic_params {
collect_name_root(param, refs, shadows);
}
collect_entry_refs_in(entries, refs, shadows, aliases, definitions);
}
Expr::Field(base, _) | Expr::NullSafeField(base, _) => {
collect_expr_refs_in(base, refs, shadows, aliases, definitions);
}
Expr::Index(base, index) | Expr::Binop(_, base, index) => {
collect_expr_refs_in(base, refs, shadows, aliases, definitions);
collect_expr_refs_in(index, refs, shadows, aliases, definitions);
}
Expr::Call(callee, args) => {
collect_expr_refs_in(callee, refs, shadows, aliases, definitions);
for arg in args {
collect_expr_refs_in(arg, refs, shadows, aliases, definitions);
}
}
Expr::If(cond, then_expr, else_expr) => {
collect_expr_refs_in(cond, refs, shadows, aliases, definitions);
collect_expr_refs_in(then_expr, refs, shadows, aliases, definitions);
collect_expr_refs_in(else_expr, refs, shadows, aliases, definitions);
}
Expr::Let(name, value, body) => {
collect_expr_refs_in(value, refs, shadows, aliases, definitions);
let mut body_shadows = shadows.clone();
body_shadows.values.insert(name.clone());
collect_expr_refs_in(body, refs, &body_shadows, aliases, definitions);
}
Expr::Is(value, ty) | Expr::As(value, ty) => {
collect_expr_refs_in(value, refs, shadows, aliases, definitions);
collect_type_refs(ty, refs, shadows, aliases, definitions);
}
Expr::Lambda(params, value) => {
let mut body_shadows = shadows.clone();
body_shadows.values.extend(params.iter().cloned());
collect_expr_refs_in(value, refs, &body_shadows, aliases, definitions);
}
Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value, _)
| Expr::Read(value, _)
| Expr::ReadOrNull(value, _)
| Expr::ReadGlob(value, _) => {
collect_expr_refs_in(value, refs, shadows, aliases, definitions)
}
Expr::InferredNew(ty, entries) => {
collect_type_refs(ty, refs, shadows, aliases, definitions);
collect_entry_refs_in(entries, refs, shadows, aliases, definitions);
}
Expr::ObjectBody(entries) => {
collect_entry_refs_in(entries, refs, shadows, aliases, definitions)
}
Expr::StringInterpolation(parts) => {
for part in parts {
if let StringInterpPart::Expr(expr) = part {
collect_expr_refs_in(expr, refs, shadows, aliases, definitions);
}
}
}
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => {}
}
}
fn collect_type_refs(
ty: &crate::parser::TypeExpr,
refs: &mut Names,
shadows: &Names,
aliases: Option<&TypeAliases>,
definitions: Option<&Definitions>,
) {
match ty {
crate::parser::TypeExpr::Named(name) => collect_name_root(name, refs, shadows),
crate::parser::TypeExpr::Nullable(inner) => {
collect_type_refs(inner, refs, shadows, aliases, definitions)
}
crate::parser::TypeExpr::Union(types) => {
for ty in types {
collect_type_refs(ty, refs, shadows, aliases, definitions);
}
}
crate::parser::TypeExpr::Generic(name, params) => {
collect_name_root(name, refs, shadows);
for param in params {
collect_type_refs(param, refs, shadows, aliases, definitions);
}
}
crate::parser::TypeExpr::Constrained(base, constraint) => {
for component in constrained_type_components(base) {
collect_name_root(component, refs, shadows);
}
let mut constraint_shadows = shadows.clone();
constraint_shadows.values.extend(
constraint_bound_names_resolving(base, aliases)
.iter()
.map(|name| name.to_string()),
);
collect_expr_refs_in(constraint, refs, &constraint_shadows, aliases, definitions);
}
}
}
fn collect_name_root(name: &str, refs: &mut Names, shadows: &Names) {
let root = name.split('.').next().unwrap_or(name);
if root.is_empty() {
return;
}
let qualified = root.len() < name.len();
if !shadows.types.contains(root) {
refs.types.insert(root.to_string());
if !qualified && !shadows.values.contains(root) {
refs.unbound_types.insert(root.to_string());
}
}
if qualified && !shadows.values.contains(root) {
refs.values.insert(root.to_string());
}
}
pub(super) fn constrained_type_components(name: &str) -> impl Iterator<Item = &str> {
name.trim_start_matches('*')
.trim_end_matches('?')
.split(['<', '>', ','])
.map(str::trim)
.filter(|component| !component.is_empty())
}
fn declared_entry_names(entries: &[Entry]) -> Names {
let mut names = Names::default();
for entry in entries {
match entry {
Entry::ClassDef(name, ..) => {
names.types.insert(name.clone());
names.values.insert(name.clone());
}
Entry::TypeAlias(name, _) => {
names.types.insert(name.clone());
}
Entry::Property(prop)
if has_modifier(&prop.modifiers, Modifier::Local)
|| matches!(prop.value, Some(Expr::Lambda(..))) =>
{
names.values.insert(prop.name.clone());
}
_ => {}
}
}
names
}
pub(super) fn declared_entry_roots(entries: &[Entry]) -> HashSet<String> {
entries
.iter()
.filter_map(|entry| match entry {
Entry::ClassDef(name, ..) | Entry::TypeAlias(name, _) => Some(name.clone()),
Entry::Property(prop)
if has_modifier(&prop.modifiers, Modifier::Local)
|| matches!(prop.value, Some(Expr::Lambda(..))) =>
{
Some(prop.name.clone())
}
_ => None,
})
.collect()
}
pub(super) fn collect_unshadowed_names(expr: &Expr, names: &mut HashSet<String>) {
match expr {
Expr::Ident(name) => {
names.insert(name.clone());
}
Expr::New(type_name, entries, generic_params) => {
if type_name.is_some() || !generic_params.is_empty() {
mark_type_use(names);
}
collect_unshadowed_entry_names(entries, names);
}
Expr::Field(base, _) | Expr::NullSafeField(base, _) => {
collect_unshadowed_names(base, names);
}
Expr::Index(base, index) | Expr::Binop(_, base, index) => {
collect_unshadowed_names(base, names);
collect_unshadowed_names(index, names);
}
Expr::Call(callee, args) => {
collect_unshadowed_names(callee, names);
for arg in args {
collect_unshadowed_names(arg, names);
}
}
Expr::If(cond, then_expr, else_expr) => {
collect_unshadowed_names(cond, names);
collect_unshadowed_names(then_expr, names);
collect_unshadowed_names(else_expr, names);
}
Expr::Let(_, value, body) => {
collect_unshadowed_names(value, names);
collect_unshadowed_names(body, names);
}
Expr::Is(value, ty) | Expr::As(value, ty) => {
collect_unshadowed_names(value, names);
collect_unshadowed_type_names(ty, names);
}
Expr::Lambda(_, value) => collect_unshadowed_names(value, names),
Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value, _)
| Expr::Read(value, _)
| Expr::ReadOrNull(value, _)
| Expr::ReadGlob(value, _) => collect_unshadowed_names(value, names),
Expr::InferredNew(ty, entries) => {
collect_unshadowed_type_names(ty, names);
collect_unshadowed_entry_names(entries, names);
}
Expr::ObjectBody(entries) => collect_unshadowed_entry_names(entries, names),
Expr::StringInterpolation(parts) => {
for part in parts {
if let StringInterpPart::Expr(expr) = part {
collect_unshadowed_names(expr, names);
}
}
}
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => {}
}
}
fn collect_unshadowed_entry_names(entries: &[Entry], names: &mut HashSet<String>) {
for entry in entries {
match entry {
Entry::Property(prop) => {
if let Some(ty) = &prop.type_ann {
collect_unshadowed_type_names(ty, names);
}
if let Some(expr) = &prop.value {
collect_unshadowed_names(expr, names);
}
if let Some(body) = &prop.body {
collect_unshadowed_entry_names(body, names);
}
}
Entry::DynProperty(key, value) | Entry::Predicate(key, value) => {
collect_unshadowed_names(key, names);
collect_unshadowed_names(value, names);
}
Entry::ForGenerator(fgen) => {
collect_unshadowed_names(&fgen.collection, names);
collect_unshadowed_entry_names(&fgen.body, names);
}
Entry::WhenGenerator(wgen) => {
collect_unshadowed_names(&wgen.condition, names);
collect_unshadowed_entry_names(&wgen.body, names);
if let Some(else_body) = &wgen.else_body {
collect_unshadowed_entry_names(else_body, names);
}
}
Entry::Spread(expr) | Entry::Elem(expr) => collect_unshadowed_names(expr, names),
Entry::ClassDef(_, _, parent, body) => {
if parent.is_some() {
mark_type_use(names);
}
collect_unshadowed_entry_names(body, names);
}
Entry::TypeAlias(_, ty) => collect_unshadowed_type_names(ty, names),
}
}
}
fn collect_unshadowed_type_names(ty: &crate::parser::TypeExpr, names: &mut HashSet<String>) {
mark_type_use(names);
if let crate::parser::TypeExpr::Constrained(_, constraint) = ty {
collect_unshadowed_names(constraint, names);
}
match ty {
crate::parser::TypeExpr::Nullable(inner) => collect_unshadowed_type_names(inner, names),
crate::parser::TypeExpr::Union(types) | crate::parser::TypeExpr::Generic(_, types) => {
for ty in types {
collect_unshadowed_type_names(ty, names);
}
}
_ => {}
}
}
pub(super) const NAMES_A_TYPE: &str = "\0type";
fn mark_type_use(names: &mut HashSet<String>) {
names.insert(NAMES_A_TYPE.to_string());
}
pub(super) fn entries_mention(entries: &[Entry], name: &str) -> bool {
entries.iter().any(|entry| entry_mentions(entry, name))
}
fn property_mentions(prop: &Property, name: &str) -> bool {
prop.type_ann
.as_ref()
.is_some_and(|ty| type_mentions(ty, name))
|| prop
.value
.as_ref()
.is_some_and(|expr| expr_mentions(expr, name))
|| prop
.body
.as_ref()
.is_some_and(|body| entries_mention(body, name))
}
fn entry_mentions(entry: &Entry, name: &str) -> bool {
match entry {
Entry::Property(prop) => property_mentions(prop, name),
Entry::DynProperty(key, value) | Entry::Predicate(key, value) => {
expr_mentions(key, name) || expr_mentions(value, name)
}
Entry::ForGenerator(fgen) => {
expr_mentions(&fgen.collection, name) || entries_mention(&fgen.body, name)
}
Entry::WhenGenerator(wgen) => {
expr_mentions(&wgen.condition, name)
|| entries_mention(&wgen.body, name)
|| wgen
.else_body
.as_ref()
.is_some_and(|body| entries_mention(body, name))
}
Entry::Spread(expr) | Entry::Elem(expr) => expr_mentions(expr, name),
Entry::ClassDef(_, _, parent, body) => {
parent
.as_deref()
.is_some_and(|parent| type_name_mentions(parent, name))
|| entries_mention(body, name)
}
Entry::TypeAlias(_, ty) => type_mentions(ty, name),
}
}
fn type_name_mentions(type_name: &str, name: &str) -> bool {
type_name
.split(|c: char| !(c.is_alphanumeric() || c == '_' || c == '$'))
.any(|token| token == name)
}
fn expr_mentions(expr: &Expr, name: &str) -> bool {
match expr {
Expr::Ident(ident) => ident == name,
Expr::New(type_name, entries, generic_params) => {
type_name
.iter()
.chain(generic_params)
.any(|type_name| type_name_mentions(type_name, name))
|| entries_mention(entries, name)
}
Expr::ObjectBody(entries) => entries_mention(entries, name),
Expr::InferredNew(ty, entries) => type_mentions(ty, name) || entries_mention(entries, name),
Expr::Field(base, _) | Expr::NullSafeField(base, _) => expr_mentions(base, name),
Expr::Index(base, index) | Expr::Binop(_, base, index) => {
expr_mentions(base, name) || expr_mentions(index, name)
}
Expr::Call(callee, args) => {
expr_mentions(callee, name) || args.iter().any(|arg| expr_mentions(arg, name))
}
Expr::If(cond, then_expr, else_expr) => {
expr_mentions(cond, name)
|| expr_mentions(then_expr, name)
|| expr_mentions(else_expr, name)
}
Expr::Let(_, value, body) => expr_mentions(value, name) || expr_mentions(body, name),
Expr::Is(value, ty) | Expr::As(value, ty) => {
expr_mentions(value, name) || type_mentions(ty, name)
}
Expr::Lambda(_, value) => expr_mentions(value, name),
Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value, _)
| Expr::Read(value, _)
| Expr::ReadOrNull(value, _)
| Expr::ReadGlob(value, _) => expr_mentions(value, name),
Expr::StringInterpolation(parts) => parts.iter().any(|part| match part {
StringInterpPart::Expr(expr) => expr_mentions(expr, name),
StringInterpPart::Literal(_) => false,
}),
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => false,
}
}
pub(super) fn mapping_entry_mentions_this(expr: &Expr) -> bool {
match expr {
Expr::Ident(name) => name == "this",
Expr::Field(base, _) | Expr::NullSafeField(base, _) => mapping_entry_mentions_this(base),
Expr::Index(base, index) | Expr::Binop(_, base, index) => {
mapping_entry_mentions_this(base) || mapping_entry_mentions_this(index)
}
Expr::Call(callee, args) => {
mapping_entry_mentions_this(callee) || args.iter().any(mapping_entry_mentions_this)
}
Expr::If(cond, then_expr, else_expr) => {
mapping_entry_mentions_this(cond)
|| mapping_entry_mentions_this(then_expr)
|| mapping_entry_mentions_this(else_expr)
}
Expr::Let(name, value, body) => {
mapping_entry_mentions_this(value)
|| (name != "this" && mapping_entry_mentions_this(body))
}
Expr::Lambda(params, body) => {
!params.iter().any(|param| param == "this") && mapping_entry_mentions_this(body)
}
Expr::New(..) | Expr::InferredNew(..) | Expr::ObjectBody(..) => false,
Expr::Is(value, _) | Expr::As(value, _) => mapping_entry_mentions_this(value),
Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value, _)
| Expr::Read(value, _)
| Expr::ReadOrNull(value, _)
| Expr::ReadGlob(value, _) => mapping_entry_mentions_this(value),
Expr::StringInterpolation(parts) => parts.iter().any(|part| match part {
StringInterpPart::Expr(expr) => mapping_entry_mentions_this(expr),
StringInterpPart::Literal(_) => false,
}),
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => false,
}
}
struct InstanceReads {
members: HashSet<String>,
escapes: bool,
}
impl InstanceReads {
fn reference_depth(expr: &Expr) -> Option<usize> {
match expr {
Expr::Ident(name) if name == "this" => Some(0),
Expr::Ident(name) if name == "outer" => Some(1),
Expr::Field(base, field) if field == "outer" => match Self::reference_depth(base)? {
0 => None,
depth => Some(depth + 1),
},
_ => None,
}
}
fn entries(&mut self, entries: &[Entry], depth: usize) {
for entry in entries {
match entry {
Entry::Property(prop) => {
if let Some(value) = &prop.value {
self.expr(value, depth);
}
if let Some(body) = &prop.body {
self.entries(body, depth + 1);
}
}
Entry::DynProperty(key, value) | Entry::Predicate(key, value) => {
self.expr(key, depth);
self.expr(value, depth);
}
Entry::Spread(expr) | Entry::Elem(expr) => self.expr(expr, depth),
Entry::ForGenerator(fgen) => {
self.expr(&fgen.collection, depth);
self.entries(&fgen.body, depth);
}
Entry::WhenGenerator(wgen) => {
self.expr(&wgen.condition, depth);
self.entries(&wgen.body, depth);
if let Some(else_body) = &wgen.else_body {
self.entries(else_body, depth);
}
}
Entry::ClassDef(..) | Entry::TypeAlias(..) => {}
}
}
}
fn expr(&mut self, expr: &Expr, depth: usize) {
let is_reference = |expr: &Expr| Self::reference_depth(expr) == Some(depth);
match expr {
_ if is_reference(expr) => self.escapes = true,
Expr::Field(base, field) | Expr::NullSafeField(base, field) if is_reference(base) => {
self.members.insert(field.clone());
}
Expr::Index(base, index) if is_reference(base) => match index.as_ref() {
Expr::String(key) => {
self.members.insert(key.to_string());
}
index => {
self.escapes = true;
self.expr(index, depth);
}
},
Expr::Field(base, _) | Expr::NullSafeField(base, _)
if Self::reference_depth(base).is_some() => {}
Expr::Ident(_) => {}
Expr::New(_, entries, _)
| Expr::ObjectBody(entries)
| Expr::InferredNew(_, entries) => self.entries(entries, depth + 1),
Expr::Field(base, _) | Expr::NullSafeField(base, _) => self.expr(base, depth),
Expr::Index(left, right) | Expr::Binop(_, left, right) => {
self.expr(left, depth);
self.expr(right, depth);
}
Expr::Call(callee, args) => {
self.expr(callee, depth);
for arg in args {
self.expr(arg, depth);
}
}
Expr::If(cond, then_expr, else_expr) => {
self.expr(cond, depth);
self.expr(then_expr, depth);
self.expr(else_expr, depth);
}
Expr::Let(_, value, body) => {
self.expr(value, depth);
self.expr(body, depth);
}
Expr::Is(value, _) | Expr::As(value, _) => self.expr(value, depth),
Expr::Lambda(_, value) => self.expr(value, depth),
Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value, _)
| Expr::Read(value, _)
| Expr::ReadOrNull(value, _)
| Expr::ReadGlob(value, _) => self.expr(value, depth),
Expr::StringInterpolation(parts) => {
for part in parts {
if let StringInterpPart::Expr(expr) = part {
self.expr(expr, depth);
}
}
}
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => {}
}
}
}
pub(super) fn type_mentions(ty: &crate::parser::TypeExpr, name: &str) -> bool {
match ty {
crate::parser::TypeExpr::Constrained(base, constraint) => {
type_name_mentions(base, name) || expr_mentions(constraint, name)
}
crate::parser::TypeExpr::Nullable(inner) => type_mentions(inner, name),
crate::parser::TypeExpr::Union(types) => types.iter().any(|ty| type_mentions(ty, name)),
crate::parser::TypeExpr::Generic(base, types) => {
type_name_mentions(base, name) || types.iter().any(|ty| type_mentions(ty, name))
}
crate::parser::TypeExpr::Named(type_name) => type_name_mentions(type_name, name),
}
}