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::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 import_field_uses(entries: &[Entry]) -> HashMap<String, ImportUse> {
let mut uses = HashMap::new();
let shadows = HashSet::new();
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) => {
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(..) => {}
}
}
}
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) | 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) => 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;
}
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([field.to_string()])));
}
}
}
pub(super) fn record_whole_import_use(
uses: &mut HashMap<String, ImportUse>,
shadows: &HashSet<String>,
name: &str,
) {
if !shadows.contains(name) {
uses.insert(name.to_string(), ImportUse::Whole);
}
}
pub(super) fn expand_requested_fields(
entries: &[Entry],
requested: &HashSet<String>,
) -> 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 expanded = requested.clone();
let mut changed = true;
while changed {
changed = false;
for entry in entries {
let Entry::Property(prop) = entry else {
continue;
};
if !expanded.contains(&prop.name) {
continue;
}
let mut refs = HashSet::new();
let shadows = HashSet::new();
if let Some(ty) = &prop.type_ann {
collect_type_refs(ty, &mut refs, &shadows);
}
if let Some(expr) = &prop.value {
collect_expr_refs(expr, &mut refs, &shadows);
collect_sibling_field_refs_expr(expr, &mut refs, true);
}
if let Some(body) = &prop.body {
collect_entry_refs(body, &mut refs, &shadows);
collect_sibling_field_refs_entries(body, &mut refs);
}
for dep in refs {
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
}
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(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) => {
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(..) => {}
}
}
}
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.clone());
} 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(_, entries, _) | 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, _) | Expr::As(value, _) => {
collect_sibling_field_refs_expr(value, refs, include_this);
}
Expr::Lambda(_, body)
| Expr::Unop(_, body)
| Expr::Throw(body)
| Expr::Trace(body)
| Expr::Read(body)
| Expr::ReadOrNull(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::new();
let shadows = HashSet::new();
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 referenced_roots(entries: &[Entry]) -> HashSet<String> {
let mut refs = HashSet::new();
let shadows = HashSet::new();
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 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_refs(ty, refs, &entry_shadows);
}
if let Some(expr) = &prop.value {
collect_expr_refs(expr, refs, &entry_shadows);
}
if let Some(body) = &prop.body {
collect_entry_refs(body, refs, &entry_shadows);
}
}
Entry::DynProperty(key, value) => {
collect_expr_refs(key, refs, &entry_shadows);
collect_expr_refs(value, refs, &entry_shadows);
}
Entry::ForGenerator(fgen) => {
collect_expr_refs(&fgen.collection, refs, &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_refs(&fgen.body, refs, &body_shadows);
}
Entry::WhenGenerator(wgen) => {
collect_expr_refs(&wgen.condition, refs, &entry_shadows);
collect_entry_refs(&wgen.body, refs, &entry_shadows);
if let Some(else_body) = &wgen.else_body {
collect_entry_refs(else_body, refs, &entry_shadows);
}
}
Entry::Spread(expr) | Entry::Elem(expr) => {
collect_expr_refs(expr, refs, &entry_shadows)
}
Entry::ClassDef(_, _, parent, body) => {
if let Some(parent) = parent {
collect_name_root(parent, refs, &entry_shadows);
}
collect_entry_refs(body, refs, &entry_shadows);
}
Entry::TypeAlias(_, ty) => collect_type_refs(ty, refs, &entry_shadows),
}
}
}
pub(super) fn with_listing_locals(expr: &Expr, locals: &[(String, Expr)]) -> Expr {
let mut expr = expr.clone();
let mut refs = HashSet::new();
collect_expr_refs(&expr, &mut refs, &HashSet::new());
for (name, value) in locals.iter().rev() {
if refs.remove(name) {
collect_expr_refs(value, &mut refs, &HashSet::new());
expr = Expr::Let(name.clone(), Box::new(value.clone()), Box::new(expr));
}
}
expr
}
pub(super) fn collect_expr_refs(
expr: &Expr,
refs: &mut HashSet<String>,
shadows: &HashSet<String>,
) {
match expr {
Expr::Ident(name) => {
if !shadows.contains(name) {
refs.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(entries, refs, shadows);
}
Expr::Field(base, _) | Expr::NullSafeField(base, _) => {
collect_expr_refs(base, refs, shadows);
}
Expr::Index(base, index) | Expr::Binop(_, base, index) => {
collect_expr_refs(base, refs, shadows);
collect_expr_refs(index, refs, shadows);
}
Expr::Call(callee, args) => {
collect_expr_refs(callee, refs, shadows);
for arg in args {
collect_expr_refs(arg, refs, shadows);
}
}
Expr::If(cond, then_expr, else_expr) => {
collect_expr_refs(cond, refs, shadows);
collect_expr_refs(then_expr, refs, shadows);
collect_expr_refs(else_expr, refs, shadows);
}
Expr::Let(name, value, body) => {
collect_expr_refs(value, refs, shadows);
let mut body_shadows = shadows.clone();
body_shadows.insert(name.clone());
collect_expr_refs(body, refs, &body_shadows);
}
Expr::Is(value, ty) | Expr::As(value, ty) => {
collect_expr_refs(value, refs, shadows);
collect_type_refs(ty, refs, shadows);
}
Expr::Lambda(params, value) => {
let mut body_shadows = shadows.clone();
body_shadows.extend(params.iter().cloned());
collect_expr_refs(value, refs, &body_shadows);
}
Expr::Unop(_, value)
| Expr::Throw(value)
| Expr::Trace(value)
| Expr::Read(value)
| Expr::ReadOrNull(value) => collect_expr_refs(value, refs, shadows),
Expr::InferredNew(ty, entries) => {
collect_type_refs(ty, refs, shadows);
collect_entry_refs(entries, refs, shadows);
}
Expr::ObjectBody(entries) => collect_entry_refs(entries, refs, shadows),
Expr::StringInterpolation(parts) => {
for part in parts {
if let StringInterpPart::Expr(expr) = part {
collect_expr_refs(expr, refs, shadows);
}
}
}
Expr::Null
| Expr::Bool(_)
| Expr::Int(_)
| Expr::Float(_)
| Expr::String(_)
| Expr::Import(..)
| Expr::ImportGlob(..) => {}
}
}
pub(super) fn collect_type_refs(
ty: &crate::parser::TypeExpr,
refs: &mut HashSet<String>,
shadows: &HashSet<String>,
) {
match ty {
crate::parser::TypeExpr::Named(name) => collect_name_root(name, refs, shadows),
crate::parser::TypeExpr::Nullable(inner) => collect_type_refs(inner, refs, shadows),
crate::parser::TypeExpr::Union(types) => {
for ty in types {
collect_type_refs(ty, refs, shadows);
}
}
crate::parser::TypeExpr::Generic(name, params) => {
collect_name_root(name, refs, shadows);
for param in params {
collect_type_refs(param, refs, shadows);
}
}
crate::parser::TypeExpr::Constrained(base, constraint) => {
for component in constrained_type_components(base) {
collect_name_root(component, refs, shadows);
}
collect_expr_refs(constraint, refs, shadows);
}
}
}
pub(super) fn collect_name_root(name: &str, refs: &mut HashSet<String>, shadows: &HashSet<String>) {
if let Some(root) = name.split('.').next()
&& !root.is_empty()
&& !shadows.contains(root)
{
refs.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())
}
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()
}