use super::*;
pub(super) fn timing(
p: &mut Producer<'_, '_>,
event: u32,
operation: Node<'_>,
scope: ResolutionScopeId,
cleanup: bool,
) {
let commit = if cleanup {
operation.end_byte()
} else {
match operation.kind() {
"for_statement" | "with_statement" | "except_clause" => operation
.child_by_field_name("body")
.or_else(|| named_children_iter(operation).find(|child| child.kind() == "block"))
.map_or(operation.end_byte(), |body| body.start_byte()),
"for_in_clause" => {
let mut cursor = operation.walk();
operation
.children_by_field_name("right", &mut cursor)
.last()
.map_or(operation.end_byte(), |iterable| iterable.end_byte())
}
"with_item" => operation.end_byte(),
"function_definition" | "class_definition" => operation
.parent()
.filter(|parent| parent.kind() == "decorated_definition")
.map_or(operation.end_byte(), |decorated| decorated.end_byte()),
"case_clause" => operation
.child_by_field_name("guard")
.or_else(|| operation.child_by_field_name("consequence"))
.map_or(operation.end_byte(), |body| body.start_byte()),
"parameters" | "lambda_parameters" => operation.start_byte(),
_ => operation.end_byte(),
}
};
p.facts
.binding_event_timings
.push(ResolutionBindingEventTimingFact {
event,
execution_scope: scope,
entry_byte: operation.start_byte(),
commit_byte: commit,
commit_phase: dense_ordinal(
p.facts.sites[p.facts.binding_events[event as usize].target.index()].start_byte,
"Python write phase",
),
});
}
pub(super) fn finish(
p: &mut Producer<'_, '_>,
scopes: &[Scope<'_>],
future_annotations: bool,
generic_annotation_scopes: &HashMap<ResolutionScopeId, ResolutionScopeId>,
generic_runtime_scopes: &HashSet<ResolutionScopeId>,
) {
let write_points = p
.facts
.binding_event_timings
.iter()
.filter_map(|timing| {
let event = p.facts.binding_events[timing.event as usize];
matches!(
event.kind,
ResolutionBindingEventKind::Assign
| ResolutionBindingEventKind::Class
| ResolutionBindingEventKind::Function
)
.then_some((event.target, timing.commit_byte))
})
.collect::<HashMap<_, _>>();
let module_end = scopes[0].node.end_byte();
let module_scope = p
.facts
.scopes
.iter()
.find(|scope| scope.kind == ResolutionScopeKind::CompilationUnit && scope.parent.is_none())
.expect("Python source module scope")
.id;
let mut function_annotations = if future_annotations {
p.facts
.python_annotations
.iter()
.filter(|annotation| annotation.mode != PythonNativeAnnotationMode::Eager)
.filter_map(|annotation| {
let slot = p.facts.type_slots[annotation.type_slot.index()];
let site = p.facts.sites[slot.site.index()];
let node = scopes[0]
.node
.descendant_for_byte_range(site.start_byte, site.end_byte)?;
let function = find_ancestor(node, |parent| {
parent.kind() == "function_definition"
&& parent
.child_by_field_name("body")
.is_some_and(|body| site.end_byte <= body.start_byte())
})?;
let mut annotation_node = node;
while let Some(parent) = annotation_node.parent() {
if parent == function {
break;
}
if parent.child_by_field_name("type") == Some(annotation_node)
|| parent.child_by_field_name("return_type") == Some(annotation_node)
{
break;
}
annotation_node = parent;
}
let mut current = Some(site.scope);
let mut route = module_scope;
let mut crosses_executable = false;
while let Some(scope) = current {
if route == module_scope {
if let Some(&runtime) = generic_annotation_scopes.get(&scope) {
route = runtime;
} else if generic_runtime_scopes.contains(&scope) {
route = scope;
}
}
let row = p.facts.scopes[scope.index()];
if row.kind == ResolutionScopeKind::Executable {
crosses_executable = true;
}
current = row.parent;
}
let unsupported = route != module_scope && crosses_executable;
Some((
annotation_node.start_byte(),
annotation_node.end_byte(),
if unsupported { module_scope } else { route },
unsupported,
))
})
.collect::<Vec<_>>()
} else {
Vec::new()
};
function_annotations.sort_unstable();
let mut function_ranges: Vec<(usize, usize, ResolutionScopeId, bool)> = Vec::new();
for (start, end, route, unsupported) in function_annotations {
if let Some(previous) = function_ranges.last_mut().filter(|previous| {
start <= previous.1 && route == previous.2 && unsupported == previous.3
}) {
previous.1 = previous.1.max(end);
} else {
function_ranges.push((start, end, route, unsupported));
}
}
let mut postponed = p
.facts
.python_annotations
.iter()
.filter(|annotation| annotation.mode != PythonNativeAnnotationMode::Eager)
.map(|annotation| {
let slot = p.facts.type_slots[annotation.type_slot.index()];
let site = p.facts.sites[slot.site.index()];
(site.start_byte, site.end_byte)
})
.collect::<Vec<_>>();
postponed.sort_unstable();
let mut ranges: Vec<(usize, usize)> = Vec::new();
for (start, end) in postponed {
if let Some(previous) = ranges.last_mut().filter(|previous| start <= previous.1) {
previous.1 = previous.1.max(end);
} else {
ranges.push((start, end));
}
}
let function_route = |site: ResolutionSiteFact| {
function_ranges
.partition_point(|range| range.0 <= site.start_byte)
.checked_sub(1)
.and_then(|index| {
(site.end_byte <= function_ranges[index].1)
.then_some((function_ranges[index].2, function_ranges[index].3))
})
};
let mut chain_routes = HashMap::default();
for root in &p.facts.root_references {
if let Some(prefix) = root.prefix_reference
&& let Some(route) = function_route(p.facts.sites[prefix.index()])
{
chain_routes.insert(root.reference, route);
}
}
let mut unsupported_annotations = Vec::new();
for identifier in &p.facts.identifiers {
if identifier.role != ResolutionIdentifierRole::Reference {
continue;
}
let site = &mut p.facts.sites[identifier.site.index()];
let function_annotation = chain_routes
.get(&identifier.site)
.copied()
.or_else(|| function_route(*site));
if let Some((route, _)) = function_annotation {
site.scope = route;
}
let site = *site;
if function_annotation.is_some_and(|(_, unsupported)| unsupported) {
unsupported_annotations.push(identifier.site);
}
let annotation = ranges
.partition_point(|range| range.0 <= site.start_byte)
.checked_sub(1)
.is_some_and(|index| site.end_byte <= ranges[index].1);
let deferred = annotation
|| find_scope_in_chain(
Some(site.scope),
|id| p.facts.scopes[id.index()].parent,
|id| {
matches!(
scopes[id.index()].node.kind(),
"function_definition" | "lambda" | "generator_expression"
)
},
)
.is_some();
p.facts
.binding_observations
.push(ResolutionBindingObservationFact {
reference: identifier.site,
execution_scope: site.scope,
point_byte: if annotation {
module_end
} else {
write_points
.get(&identifier.site)
.copied()
.unwrap_or(site.start_byte)
},
kind: if deferred {
ResolutionBindingObservationKind::Deferred
} else {
ResolutionBindingObservationKind::Immediate
},
});
}
for site in unsupported_annotations {
p.facts
.add_gap(site, ResolutionGapKind::UnsupportedScopeOrBinder);
}
let root_scope = p
.facts
.scopes
.iter()
.find(|scope| scope.kind == ResolutionScopeKind::CompilationUnit && scope.parent.is_none())
.expect("Python source module scope")
.id;
let mut routed = p
.facts
.root_references
.iter()
.map(|root| root.reference)
.collect::<HashSet<_>>();
let lexical = p
.facts
.identifiers
.iter()
.filter(|identifier| {
identifier.role == ResolutionIdentifierRole::Reference && identifier.qualifier.is_none()
})
.map(|identifier| identifier.site)
.collect::<HashSet<_>>();
for observation in &p.facts.binding_observations {
if lexical.contains(&observation.reference) && routed.insert(observation.reference) {
p.facts.root_references.push(ResolutionRootReferenceFact {
reference: observation.reference,
root_scope,
anchor: ResolutionRootImportAnchor::Lexical,
prefix_reference: None,
owner_continued: false,
});
}
}
let mut references = HashMap::default();
for identifier in &p.facts.identifiers {
if identifier.role == ResolutionIdentifierRole::Reference {
let site = p.facts.sites[identifier.site.index()];
references
.entry((site.start_byte, site.end_byte))
.or_insert(identifier.site);
}
}
for predicate in &mut p.facts.python_predicates {
let site = p.facts.sites[predicate.predicate.index()];
let node = scopes[0]
.node
.descendant_for_byte_range(site.start_byte, site.end_byte)
.expect("predicate AST node");
let operand = if predicate.kind == PythonNativePredicateKind::Attribute {
let attribute = node.child_by_field_name("attribute");
if !attribute.is_some_and(|field| &p.source[field.byte_range()] == "TYPE_CHECKING") {
predicate.kind = PythonNativePredicateKind::Unknown;
}
node.child_by_field_name("object").unwrap_or(node)
} else {
node
};
predicate.operand = references
.get(&(operand.start_byte(), operand.end_byte()))
.copied();
}
}
pub(super) fn predicate(p: &mut Producer<'_, '_>, node: Node<'_>, site: ResolutionSiteId) {
let kind = match node.kind() {
"identifier" => PythonNativePredicateKind::Name,
"attribute" => PythonNativePredicateKind::Attribute,
"true" => PythonNativePredicateKind::True,
"false" => PythonNativePredicateKind::False,
_ => PythonNativePredicateKind::Unknown,
};
p.facts.python_predicates.push(PythonNativePredicateFact {
predicate: site,
kind,
operand: None,
});
}