use super::*;
use crate::imports::{python_import_identifier_nodes, python_import_syntaxes_from_node};
pub(super) fn produce(
p: &mut Producer<'_, '_>,
node: Node<'_>,
scope: ResolutionScopeId,
events: &HashMap<usize, u32>,
) {
let leaves = python_import_syntaxes_from_node(node, p.source);
if leaves.is_empty() || node.has_error() {
p.omitted_reference(node, scope);
}
let mut seen = HashSet::default();
for leaf in leaves {
let site = p.site(leaf.leaf, scope, ResolutionSiteKind::ImportDeclaration);
p.facts.add_gap(site, ResolutionGapKind::UnprovenActivation);
let module = python_import_identifier_nodes(leaf.module);
let relative_level = named_children_iter(leaf.module)
.find(|part| part.kind() == "import_prefix")
.map_or(0, |prefix| {
let mut cursor = prefix.walk();
prefix
.children(&mut cursor)
.map(|punctuation| match punctuation.kind() {
"." => 1,
"..." => 3,
_ => 0,
})
.sum()
});
let module_count = dense_ordinal(module.len(), "Python module segments");
let star = leaf.info.is_wildcard;
let form = if star {
PythonNativeImportForm::Star
} else if leaf.member.is_some() {
PythonNativeImportForm::From
} else {
PythonNativeImportForm::Module
};
let mut route = module.clone();
if let Some(member) = leaf.member {
route.extend(python_import_identifier_nodes(member));
}
let binding = leaf.alias.or(leaf.target);
let event = binding.and_then(|token| events.get(&token.id()).copied());
if !star && (route.is_empty() || event.is_none()) {
p.omitted_reference(leaf.leaf, scope);
continue;
}
let local_name = binding.map(|token| {
p.facts
.intern_name(&mut p.names, &p.source[token.byte_range()])
});
let names = route
.iter()
.map(|token| {
p.facts
.intern_name(&mut p.names, &p.source[token.byte_range()])
})
.collect::<Vec<_>>();
p.facts.import_routes.push(ResolutionImportRouteFact {
site,
root_scope: scope,
kind: if star {
ResolutionImportRouteKind::Star
} else {
ResolutionImportRouteKind::Named
},
bound_name: local_name,
});
for (ordinal, &name) in names.iter().enumerate() {
p.facts
.import_route_segments
.push(ResolutionImportRouteSegmentFact {
import_site: site,
ordinal: dense_ordinal(ordinal, "Python import segments"),
name,
});
}
p.facts.python_imports.push(PythonNativeImportFact {
import_site: site,
form,
relative_level,
module_segment_count: module_count,
binding_event: event,
});
let anchor = if relative_level == 0 {
ResolutionRootImportAnchor::Absolute
} else {
ResolutionRootImportAnchor::Lexical
};
p.facts.root_imports.push(ResolutionRootImportFact {
site,
root_scope: scope,
anchor,
position: Some(node.end_byte()),
});
p.facts
.root_import_kinds
.push(ResolutionRootImportKindFact {
import_site: site,
kind: if star {
ResolutionRootImportKind::Glob
} else {
ResolutionRootImportKind::Named
},
});
let bound_index = if form == PythonNativeImportForm::Module && leaf.alias.is_none() {
0
} else {
names.len().saturating_sub(1)
};
let prefix = if star {
names.as_slice()
} else {
&names[..bound_index]
};
for (position, &name) in prefix.iter().enumerate() {
p.facts
.root_import_segments
.push(ResolutionRootImportSegmentFact {
import_site: site,
position: dense_ordinal(position, "Python root route"),
name,
});
}
p.facts.import_selectors.push(ResolutionImportSelectorFact {
site,
kind: if star {
ResolutionImportSelectorKind::Star
} else {
ResolutionImportSelectorKind::Named
},
source_name: (!star).then(|| names[bound_index]),
local_name,
});
if star {
p.facts.add_route_gap(
site,
ResolutionRouteGap::UnknownBinder {
scope,
name: None,
namespace: Some(ResolutionNamespace::Value),
may_publish: p.facts.scopes[scope.index()].kind
== ResolutionScopeKind::CompilationUnit,
},
);
}
emit_positioned_root_prefixes(
p,
&route,
&names,
ResolutionScopeId::new(0),
anchor,
|p, &token, ordinal| {
if (ordinal >= module.len() && (star || ordinal != bound_index))
|| !seen.insert(token.id())
{
return None;
}
let reference = if binding.is_some_and(|binding| binding.id() == token.id()) {
p.facts.binding_events[event.expect("named import event") as usize].target
} else {
p.identifier(
token,
scope,
ResolutionSiteKind::ModuleReference,
ResolutionIdentifierRole::Reference,
ResolutionNamespace::Value,
)
};
if ordinal < module.len() {
p.facts
.python_import_module_operands
.push(PythonImportModuleOperandFact {
reference_site: reference,
import_site: site,
prefix_len: dense_ordinal(ordinal + 1, "Python module operand prefix"),
});
}
if !star && ordinal == bound_index {
p.facts
.add_gap(reference, ResolutionGapKind::UnprovenActivation);
p.facts
.root_import_demands
.push(ResolutionRootImportDemandFact {
import_site: site,
namespace: ResolutionNamespace::Value,
name: names[ordinal],
});
p.facts
.root_import_demand_targets
.push(ResolutionRootImportDemandTargetFact {
import_site: site,
namespace: ResolutionNamespace::Value,
name: names[ordinal],
target: ResolutionRootImportDemandTarget::NamedReference(reference),
});
}
Some(reference)
},
|p| &mut p.facts,
);
}
}
pub(super) fn finish(p: &mut Producer<'_, '_>) {
let bound_references = p
.facts
.root_import_demand_targets
.iter()
.filter_map(|target| match target.target {
ResolutionRootImportDemandTarget::NamedReference(reference) => Some(reference),
ResolutionRootImportDemandTarget::SameNameGlob => None,
})
.collect::<HashSet<_>>();
let module_operands = p
.facts
.python_import_module_operands
.iter()
.map(|operand| operand.reference_site)
.filter(|reference| !bound_references.contains(reference))
.collect::<HashSet<_>>();
let mut seen = HashSet::default();
let demands = p
.facts
.identifiers
.iter()
.filter(|id| {
id.role == ResolutionIdentifierRole::Reference
&& !module_operands.contains(&id.site)
&& id.qualifier.is_none()
&& id.namespace == ResolutionNamespace::Value
})
.map(|id| (id.namespace, id.name))
.filter(|&name| seen.insert(name))
.collect::<Vec<_>>();
let stars = p
.facts
.root_import_kinds
.iter()
.filter(|kind| kind.kind == ResolutionRootImportKind::Glob)
.map(|kind| kind.import_site)
.collect::<Vec<_>>();
p.facts.extend_same_name_glob_demands(&stars, &demands);
}