Rename semantic model methods to use current_* prefix (#6347)

## Summary

This PR attempts to draw a clearer divide between "methods that take
(e.g.) an expression or statement as input" and "methods that rely on
the _current_ expression or statement" in the semantic model, by
renaming methods like `stmt()` to `current_statement()`.

This had led to confusion in the past. For example, prior to this PR, we
had `scope()` (which returns the current scope), and `parent_scope`,
which returns the parent _of a scope that's passed in_. Now, the API is
clearer: `current_scope` returns the current scope, and `parent_scope`
takes a scope as argument and returns its parent.

Per above, I also changed `stmt` to `statement` and `expr` to
`expression`.
This commit is contained in:
Charlie Marsh 2023-08-07 10:44:49 -04:00 committed by GitHub
parent b763973357
commit bae87fa016
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 331 additions and 290 deletions

View file

@ -16,7 +16,7 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
}) })
| Stmt::AsyncFor(ast::StmtAsyncFor { | Stmt::AsyncFor(ast::StmtAsyncFor {
target, iter, body, .. target, iter, body, ..
}) = &checker.semantic.stmt() }) = &checker.semantic.current_statement()
{ {
if checker.enabled(Rule::UnusedLoopControlVariable) { if checker.enabled(Rule::UnusedLoopControlVariable) {
flake8_bugbear::rules::unused_loop_control_variable(checker, target, body); flake8_bugbear::rules::unused_loop_control_variable(checker, target, body);

View file

@ -112,7 +112,11 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// If the bindings are in different forks, abort. // If the bindings are in different forks, abort.
if shadowed.source.map_or(true, |left| { if shadowed.source.map_or(true, |left| {
binding.source.map_or(true, |right| { binding.source.map_or(true, |right| {
branch_detection::different_forks(left, right, &checker.semantic.stmts) branch_detection::different_forks(
left,
right,
&checker.semantic.statements,
)
}) })
}) { }) {
continue; continue;
@ -172,7 +176,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
if shadowed.kind.is_function_definition() if shadowed.kind.is_function_definition()
&& visibility::is_overload( && visibility::is_overload(
cast::decorator_list( cast::decorator_list(
checker.semantic.stmts[shadowed.source.unwrap()], checker.semantic.statements[shadowed.source.unwrap()],
), ),
&checker.semantic, &checker.semantic,
) )
@ -195,7 +199,11 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// If the bindings are in different forks, abort. // If the bindings are in different forks, abort.
if shadowed.source.map_or(true, |left| { if shadowed.source.map_or(true, |left| {
binding.source.map_or(true, |right| { binding.source.map_or(true, |right| {
branch_detection::different_forks(left, right, &checker.semantic.stmts) branch_detection::different_forks(
left,
right,
&checker.semantic.statements,
)
}) })
}) { }) {
continue; continue;

View file

@ -84,7 +84,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
// traverse nested unions. // traverse nested unions.
let is_unchecked_union = checker let is_unchecked_union = checker
.semantic .semantic
.expr_grandparent() .current_expression_grandparent()
.and_then(Expr::as_subscript_expr) .and_then(Expr::as_subscript_expr)
.map_or(true, |parent| { .map_or(true, |parent| {
!checker.semantic.match_typing_expr(&parent.value, "Union") !checker.semantic.match_typing_expr(&parent.value, "Union")
@ -206,11 +206,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
} }
ExprContext::Store => { ExprContext::Store => {
if checker.enabled(Rule::NonLowercaseVariableInFunction) { if checker.enabled(Rule::NonLowercaseVariableInFunction) {
if checker.semantic.scope().kind.is_any_function() { if checker.semantic.current_scope().kind.is_any_function() {
// Ignore globals. // Ignore globals.
if !checker.semantic.scope().get(id).is_some_and(|binding_id| { if !checker
.semantic
.current_scope()
.get(id)
.is_some_and(|binding_id| {
checker.semantic.binding(binding_id).is_global() checker.semantic.binding(binding_id).is_global()
}) { })
{
pep8_naming::rules::non_lowercase_variable_in_function( pep8_naming::rules::non_lowercase_variable_in_function(
checker, expr, id, checker, expr, id,
); );
@ -219,7 +224,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
} }
if checker.enabled(Rule::MixedCaseVariableInClassScope) { if checker.enabled(Rule::MixedCaseVariableInClassScope) {
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) = if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
&checker.semantic.scope().kind &checker.semantic.current_scope().kind
{ {
pep8_naming::rules::mixed_case_variable_in_class_scope( pep8_naming::rules::mixed_case_variable_in_class_scope(
checker, checker,
@ -230,7 +235,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
} }
} }
if checker.enabled(Rule::MixedCaseVariableInGlobalScope) { if checker.enabled(Rule::MixedCaseVariableInGlobalScope) {
if matches!(checker.semantic.scope().kind, ScopeKind::Module) { if matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
pep8_naming::rules::mixed_case_variable_in_global_scope( pep8_naming::rules::mixed_case_variable_in_global_scope(
checker, expr, id, checker, expr, id,
); );
@ -243,7 +248,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind { if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
if checker.enabled(Rule::BuiltinAttributeShadowing) { if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_attribute_shadowing( flake8_builtins::rules::builtin_attribute_shadowing(
checker, class_def, id, *range, checker, class_def, id, *range,
@ -668,7 +673,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_comprehensions::rules::unnecessary_map( flake8_comprehensions::rules::unnecessary_map(
checker, checker,
expr, expr,
checker.semantic.expr_parent(), checker.semantic.current_expression_parent(),
func, func,
args, args,
); );
@ -1082,7 +1087,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
// Avoid duplicate checks if the parent is an `|` since these rules // Avoid duplicate checks if the parent is an `|` since these rules
// traverse nested unions. // traverse nested unions.
let is_unchecked_union = !matches!( let is_unchecked_union = !matches!(
checker.semantic.expr_parent(), checker.semantic.current_expression_parent(),
Some(Expr::BinOp(ast::ExprBinOp { Some(Expr::BinOp(ast::ExprBinOp {
op: Operator::BitOr, op: Operator::BitOr,
.. ..

View file

@ -53,7 +53,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::BreakOutsideLoop) { if checker.enabled(Rule::BreakOutsideLoop) {
if let Some(diagnostic) = pyflakes::rules::break_outside_loop( if let Some(diagnostic) = pyflakes::rules::break_outside_loop(
stmt, stmt,
&mut checker.semantic.parents().skip(1), &mut checker.semantic.current_statements().skip(1),
) { ) {
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -63,7 +63,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ContinueOutsideLoop) { if checker.enabled(Rule::ContinueOutsideLoop) {
if let Some(diagnostic) = pyflakes::rules::continue_outside_loop( if let Some(diagnostic) = pyflakes::rules::continue_outside_loop(
stmt, stmt,
&mut checker.semantic.parents().skip(1), &mut checker.semantic.current_statements().skip(1),
) { ) {
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
@ -113,7 +113,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) = if let Some(diagnostic) =
pep8_naming::rules::invalid_first_argument_name_for_class_method( pep8_naming::rules::invalid_first_argument_name_for_class_method(
checker, checker,
checker.semantic.scope(), checker.semantic.current_scope(),
name, name,
decorator_list, decorator_list,
parameters, parameters,
@ -125,7 +125,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) { if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method( if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
checker, checker,
checker.semantic.scope(), checker.semantic.current_scope(),
name, name,
decorator_list, decorator_list,
parameters, parameters,
@ -193,7 +193,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
} }
if checker.enabled(Rule::DunderFunctionName) { if checker.enabled(Rule::DunderFunctionName) {
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name( if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
checker.semantic.scope(), checker.semantic.current_scope(),
stmt, stmt,
name, name,
&checker.settings.pep8_naming.ignore_names, &checker.settings.pep8_naming.ignore_names,
@ -348,7 +348,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::YieldInForLoop) { if checker.enabled(Rule::YieldInForLoop) {
pyupgrade::rules::yield_in_for_loop(checker, stmt); pyupgrade::rules::yield_in_for_loop(checker, stmt);
} }
if let ScopeKind::Class(class_def) = checker.semantic.scope().kind { if let ScopeKind::Class(class_def) = checker.semantic.current_scope().kind {
if checker.enabled(Rule::BuiltinAttributeShadowing) { if checker.enabled(Rule::BuiltinAttributeShadowing) {
flake8_builtins::rules::builtin_method_shadowing( flake8_builtins::rules::builtin_method_shadowing(
checker, checker,
@ -766,7 +766,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
} }
} else if &alias.name == "*" { } else if &alias.name == "*" {
if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) { if checker.enabled(Rule::UndefinedLocalWithNestedImportStarUsage) {
if !matches!(checker.semantic.scope().kind, ScopeKind::Module) { if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage { pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
name: helpers::format_import_from(level, module), name: helpers::format_import_from(level, module),
@ -982,7 +982,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
flake8_simplify::rules::nested_if_statements( flake8_simplify::rules::nested_if_statements(
checker, checker,
if_, if_,
checker.semantic.stmt_parent(), checker.semantic.current_statement_parent(),
); );
} }
if checker.enabled(Rule::IfWithSameArms) { if checker.enabled(Rule::IfWithSameArms) {
@ -1004,7 +1004,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
tryceratops::rules::type_check_without_type_error( tryceratops::rules::type_check_without_type_error(
checker, checker,
if_, if_,
checker.semantic.stmt_parent(), checker.semantic.current_statement_parent(),
); );
} }
if checker.enabled(Rule::OutdatedVersionBlock) { if checker.enabled(Rule::OutdatedVersionBlock) {
@ -1110,7 +1110,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker, checker,
stmt, stmt,
body, body,
checker.semantic.stmt_parent(), checker.semantic.current_statement_parent(),
); );
} }
if checker.enabled(Rule::RedefinedLoopName) { if checker.enabled(Rule::RedefinedLoopName) {
@ -1338,7 +1338,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
// Ignore assignments in function bodies; those are covered by other rules. // Ignore assignments in function bodies; those are covered by other rules.
if !checker if !checker
.semantic .semantic
.scopes() .current_scopes()
.any(|scope| scope.kind.is_any_function()) .any(|scope| scope.kind.is_any_function())
{ {
if checker.enabled(Rule::UnprefixedTypeParam) { if checker.enabled(Rule::UnprefixedTypeParam) {
@ -1403,7 +1403,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
// Ignore assignments in function bodies; those are covered by other rules. // Ignore assignments in function bodies; those are covered by other rules.
if !checker if !checker
.semantic .semantic
.scopes() .current_scopes()
.any(|scope| scope.kind.is_any_function()) .any(|scope| scope.kind.is_any_function())
{ {
flake8_pyi::rules::annotated_assignment_default_in_stub( flake8_pyi::rules::annotated_assignment_default_in_stub(

View file

@ -182,7 +182,7 @@ impl<'a> Checker<'a> {
} }
// Find the quote character used to start the containing f-string. // Find the quote character used to start the containing f-string.
let expr = model.expr()?; let expr = model.current_expression()?;
let string_range = self.indexer.f_string_range(expr.start())?; let string_range = self.indexer.f_string_range(expr.start())?;
let trailing_quote = trailing_quote(self.locator.slice(string_range))?; let trailing_quote = trailing_quote(self.locator.slice(string_range))?;
@ -202,7 +202,7 @@ impl<'a> Checker<'a> {
/// thus be applied whenever we delete a statement, but can otherwise be omitted. /// thus be applied whenever we delete a statement, but can otherwise be omitted.
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel { pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
parent parent
.and_then(|stmt| self.semantic.stmts.node_id(stmt)) .and_then(|stmt| self.semantic.statements.node_id(stmt))
.map_or(IsolationLevel::default(), |node_id| { .map_or(IsolationLevel::default(), |node_id| {
IsolationLevel::Group(node_id.into()) IsolationLevel::Group(node_id.into())
}) })
@ -264,7 +264,7 @@ where
{ {
fn visit_stmt(&mut self, stmt: &'b Stmt) { fn visit_stmt(&mut self, stmt: &'b Stmt) {
// Step 0: Pre-processing // Step 0: Pre-processing
self.semantic.push_stmt(stmt); self.semantic.push_statement(stmt);
// Track whether we've seen docstrings, non-imports, etc. // Track whether we've seen docstrings, non-imports, etc.
match stmt { match stmt {
@ -288,7 +288,7 @@ where
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY; self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
if !self.semantic.seen_import_boundary() if !self.semantic.seen_import_boundary()
&& !helpers::is_assignment_to_a_dunder(stmt) && !helpers::is_assignment_to_a_dunder(stmt)
&& !helpers::in_nested_block(self.semantic.parents()) && !helpers::in_nested_block(self.semantic.current_statements())
{ {
self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY; self.semantic.flags |= SemanticModelFlags::IMPORT_BOUNDARY;
} }
@ -372,7 +372,7 @@ where
); );
} else if &alias.name == "*" { } else if &alias.name == "*" {
self.semantic self.semantic
.scope_mut() .current_scope_mut()
.add_star_import(StarImport { level, module }); .add_star_import(StarImport { level, module });
} else { } else {
let mut flags = BindingFlags::EXTERNAL; let mut flags = BindingFlags::EXTERNAL;
@ -421,7 +421,7 @@ where
BindingKind::Global, BindingKind::Global,
BindingFlags::GLOBAL, BindingFlags::GLOBAL,
); );
let scope = self.semantic.scope_mut(); let scope = self.semantic.current_scope_mut();
scope.add(name, binding_id); scope.add(name, binding_id);
} }
} }
@ -444,7 +444,7 @@ where
BindingKind::Nonlocal(scope_id), BindingKind::Nonlocal(scope_id),
BindingFlags::NONLOCAL, BindingFlags::NONLOCAL,
); );
let scope = self.semantic.scope_mut(); let scope = self.semantic.current_scope_mut();
scope.add(name, binding_id); scope.add(name, binding_id);
} }
} }
@ -657,7 +657,7 @@ where
// available at runtime. // available at runtime.
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements // See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
let runtime_annotation = if self.semantic.future_annotations() { let runtime_annotation = if self.semantic.future_annotations() {
if self.semantic.scope().kind.is_class() { if self.semantic.current_scope().kind.is_class() {
let baseclasses = &self let baseclasses = &self
.settings .settings
.flake8_type_checking .flake8_type_checking
@ -676,7 +676,7 @@ where
} }
} else { } else {
matches!( matches!(
self.semantic.scope().kind, self.semantic.current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module ScopeKind::Class(_) | ScopeKind::Module
) )
}; };
@ -777,7 +777,7 @@ where
analyze::statement(stmt, self); analyze::statement(stmt, self);
self.semantic.flags = flags_snapshot; self.semantic.flags = flags_snapshot;
self.semantic.pop_stmt(); self.semantic.pop_statement();
} }
fn visit_annotation(&mut self, expr: &'b Expr) { fn visit_annotation(&mut self, expr: &'b Expr) {
@ -813,7 +813,7 @@ where
return; return;
} }
self.semantic.push_expr(expr); self.semantic.push_expression(expr);
// Store the flags prior to any further descent, so that we can restore them after visiting // Store the flags prior to any further descent, so that we can restore them after visiting
// the node. // the node.
@ -841,7 +841,7 @@ where
}) => { }) => {
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() { if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
if id == "locals" && ctx.is_load() { if id == "locals" && ctx.is_load() {
let scope = self.semantic.scope_mut(); let scope = self.semantic.current_scope_mut();
scope.set_uses_locals(); scope.set_uses_locals();
} }
} }
@ -1231,7 +1231,7 @@ where
analyze::expression(expr, self); analyze::expression(expr, self);
self.semantic.flags = flags_snapshot; self.semantic.flags = flags_snapshot;
self.semantic.pop_expr(); self.semantic.pop_expression();
} }
fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) { fn visit_except_handler(&mut self, except_handler: &'b ExceptHandler) {
@ -1611,7 +1611,7 @@ impl<'a> Checker<'a> {
} }
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) { fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
let parent = self.semantic.stmt(); let parent = self.semantic.current_statement();
if matches!( if matches!(
parent, parent,
@ -1646,7 +1646,7 @@ impl<'a> Checker<'a> {
return; return;
} }
let scope = self.semantic.scope(); let scope = self.semantic.current_scope();
if scope.kind.is_module() if scope.kind.is_module()
&& match parent { && match parent {
@ -1696,7 +1696,11 @@ impl<'a> Checker<'a> {
return; return;
} }
if self.semantic.expr_ancestors().any(Expr::is_named_expr_expr) { if self
.semantic
.current_expressions()
.any(Expr::is_named_expr_expr)
{
self.add_binding( self.add_binding(
id, id,
expr.range(), expr.range(),
@ -1721,7 +1725,7 @@ impl<'a> Checker<'a> {
self.semantic.resolve_del(id, expr.range()); self.semantic.resolve_del(id, expr.range());
if helpers::on_conditional_branch(&mut self.semantic.parents()) { if helpers::on_conditional_branch(&mut self.semantic.current_statements()) {
return; return;
} }
@ -1729,7 +1733,7 @@ impl<'a> Checker<'a> {
let binding_id = let binding_id =
self.semantic self.semantic
.push_binding(expr.range(), BindingKind::Deletion, BindingFlags::empty()); .push_binding(expr.range(), BindingKind::Deletion, BindingFlags::empty());
let scope = self.semantic.scope_mut(); let scope = self.semantic.current_scope_mut();
scope.add(id, binding_id); scope.add(id, binding_id);
} }
@ -1821,7 +1825,7 @@ impl<'a> Checker<'a> {
for snapshot in deferred_functions { for snapshot in deferred_functions {
self.semantic.restore(snapshot); self.semantic.restore(snapshot);
match &self.semantic.stmt() { match &self.semantic.current_statement() {
Stmt::FunctionDef(ast::StmtFunctionDef { Stmt::FunctionDef(ast::StmtFunctionDef {
body, parameters, .. body, parameters, ..
}) })

View file

@ -63,7 +63,7 @@ fn matches_string_format_expression(expr: &Expr, model: &SemanticModel) -> bool
}) => { }) => {
// Only evaluate the full BinOp, not the nested components. // Only evaluate the full BinOp, not the nested components.
if model if model
.expr_parent() .current_expression_parent()
.map_or(true, |parent| !parent.is_bin_op_expr()) .map_or(true, |parent| !parent.is_bin_op_expr())
{ {
if any_over_expr(expr, &has_string_literal) { if any_over_expr(expr, &has_string_literal) {

View file

@ -77,7 +77,7 @@ fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
/// B019 /// B019
pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) { pub(crate) fn cached_instance_method(checker: &mut Checker, decorator_list: &[Decorator]) {
if !checker.semantic().scope().kind.is_class() { if !checker.semantic().current_scope().kind.is_class() {
return; return;
} }
for decorator in decorator_list { for decorator in decorator_list {

View file

@ -97,7 +97,7 @@ pub(crate) fn setattr_with_constant(
if let Stmt::Expr(ast::StmtExpr { if let Stmt::Expr(ast::StmtExpr {
value: child, value: child,
range: _, range: _,
}) = checker.semantic().stmt() }) = checker.semantic().current_statement()
{ {
if expr == child.as_ref() { if expr == child.as_ref() {
let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range()); let mut diagnostic = Diagnostic::new(SetAttrWithConstant, expr.range());

View file

@ -159,7 +159,7 @@ pub(crate) fn unused_loop_control_variable(checker: &mut Checker, target: &Expr,
if certainty.into() { if certainty.into() {
// Avoid fixing if the variable, or any future bindings to the variable, are // Avoid fixing if the variable, or any future bindings to the variable, are
// used _after_ the loop. // used _after_ the loop.
let scope = checker.semantic().scope(); let scope = checker.semantic().current_scope();
if scope if scope
.get_all(name) .get_all(name)
.map(|binding_id| checker.semantic().binding(binding_id)) .map(|binding_id| checker.semantic().binding(binding_id))

View file

@ -43,8 +43,8 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: &
}; };
let (Some(grandparent), Some(parent)) = ( let (Some(grandparent), Some(parent)) = (
checker.semantic().expr_grandparent(), checker.semantic().current_expression_grandparent(),
checker.semantic().expr_parent(), checker.semantic().current_expression_parent(),
) else { ) else {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
CallDatetimeStrptimeWithoutZone, CallDatetimeStrptimeWithoutZone,

View file

@ -6,7 +6,7 @@ use crate::checkers::ast::Checker;
/// Check if the parent expression is a call to `astimezone`. This assumes that /// Check if the parent expression is a call to `astimezone`. This assumes that
/// the current expression is a `datetime.datetime` object. /// the current expression is a `datetime.datetime` object.
pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool { pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool {
checker.semantic().expr_parent().is_some_and( |parent| { checker.semantic().current_expression_parent().is_some_and( |parent| {
matches!(parent, Expr::Attribute(ExprAttribute { attr, .. }) if attr.as_str() == "astimezone") matches!(parent, Expr::Attribute(ExprAttribute { attr, .. }) if attr.as_str() == "astimezone")
}) })
} }

View file

@ -66,7 +66,7 @@ pub(crate) fn any_eq_ne_annotation(checker: &mut Checker, name: &str, parameters
return; return;
}; };
if !checker.semantic().scope().kind.is_class() { if !checker.semantic().current_scope().kind.is_class() {
return; return;
} }

View file

@ -91,7 +91,7 @@ pub(crate) fn custom_type_var_return_type(
return; return;
}; };
if !checker.semantic().scope().kind.is_class() { if !checker.semantic().current_scope().kind.is_class() {
return; return;
}; };

View file

@ -119,7 +119,7 @@ pub(crate) fn non_self_return_type(
parameters: &Parameters, parameters: &Parameters,
async_: bool, async_: bool,
) { ) {
let ScopeKind::Class(class_def) = checker.semantic().scope().kind else { let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind else {
return; return;
}; };

View file

@ -349,8 +349,8 @@ fn is_type_var_like_call(expr: &Expr, semantic: &SemanticModel) -> bool {
fn is_special_assignment(target: &Expr, semantic: &SemanticModel) -> bool { fn is_special_assignment(target: &Expr, semantic: &SemanticModel) -> bool {
if let Expr::Name(ast::ExprName { id, .. }) = target { if let Expr::Name(ast::ExprName { id, .. }) = target {
match id.as_str() { match id.as_str() {
"__all__" => semantic.scope().kind.is_module(), "__all__" => semantic.current_scope().kind.is_module(),
"__match_args__" | "__slots__" => semantic.scope().kind.is_class(), "__match_args__" | "__slots__" => semantic.current_scope().kind.is_class(),
_ => false, _ => false,
} }
} else { } else {
@ -569,7 +569,9 @@ pub(crate) fn unannotated_assignment_in_stub(
return; return;
} }
if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) = checker.semantic().scope().kind { if let ScopeKind::Class(ast::StmtClassDef { arguments, .. }) =
checker.semantic().current_scope().kind
{
if is_enum(arguments.as_deref(), checker.semantic()) { if is_enum(arguments.as_deref(), checker.semantic()) {
return; return;
} }

View file

@ -63,7 +63,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
return; return;
} }
if !checker.semantic().scope().kind.is_class() { if !checker.semantic().current_scope().kind.is_class() {
return; return;
} }
@ -96,11 +96,12 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
stmt.identifier(), stmt.identifier(),
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
let stmt = checker.semantic().stmt(); let stmt = checker.semantic().current_statement();
let parent = checker.semantic().stmt_parent(); let parent = checker.semantic().current_statement_parent();
let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer()); let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer());
diagnostic.set_fix( diagnostic.set_fix(
Fix::automatic(edit).isolate(checker.isolation(checker.semantic().stmt_parent())), Fix::automatic(edit)
.isolate(checker.isolation(checker.semantic().current_statement_parent())),
); );
} }
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);

View file

@ -43,7 +43,7 @@ impl AlwaysAutofixableViolation for StringOrBytesTooLong {
/// PYI053 /// PYI053
pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, expr: &Expr) { pub(crate) fn string_or_bytes_too_long(checker: &mut Checker, expr: &Expr) {
// Ignore docstrings. // Ignore docstrings.
if is_docstring_stmt(checker.semantic().stmt()) { if is_docstring_stmt(checker.semantic().current_statement()) {
return; return;
} }

View file

@ -173,7 +173,8 @@ pub(crate) fn unused_private_type_var(
let Some(source) = binding.source else { let Some(source) = binding.source else {
continue; continue;
}; };
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = checker.semantic().stmts[source] let Stmt::Assign(ast::StmtAssign { targets, value, .. }) =
checker.semantic().statements[source]
else { else {
continue; continue;
}; };
@ -217,7 +218,7 @@ pub(crate) fn unused_private_protocol(
continue; continue;
}; };
let Stmt::ClassDef(class_def) = checker.semantic().stmts[source] else { let Stmt::ClassDef(class_def) = checker.semantic().statements[source] else {
continue; continue;
}; };
@ -260,7 +261,7 @@ pub(crate) fn unused_private_type_alias(
}; };
let Stmt::AnnAssign(ast::StmtAnnAssign { let Stmt::AnnAssign(ast::StmtAnnAssign {
target, annotation, .. target, annotation, ..
}) = checker.semantic().stmts[source] }) = checker.semantic().statements[source]
else { else {
continue; continue;
}; };
@ -304,7 +305,7 @@ pub(crate) fn unused_private_typed_dict(
let Some(source) = binding.source else { let Some(source) = binding.source else {
continue; continue;
}; };
let Stmt::ClassDef(class_def) = checker.semantic().stmts[source] else { let Stmt::ClassDef(class_def) = checker.semantic().statements[source] else {
continue; continue;
}; };

View file

@ -246,8 +246,8 @@ pub(crate) fn unittest_assertion(
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
// We're converting an expression to a statement, so avoid applying the fix if // We're converting an expression to a statement, so avoid applying the fix if
// the assertion is part of a larger expression. // the assertion is part of a larger expression.
if checker.semantic().stmt().is_expr_stmt() if checker.semantic().current_statement().is_expr_stmt()
&& checker.semantic().expr_parent().is_none() && checker.semantic().current_expression_parent().is_none()
&& !checker.indexer().comment_ranges().intersects(expr.range()) && !checker.indexer().comment_ranges().intersects(expr.range())
{ {
if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) { if let Ok(stmt) = unittest_assert.generate_assert(args, keywords) {

View file

@ -77,7 +77,7 @@ pub(crate) fn private_member_access(checker: &mut Checker, expr: &Expr) {
// Ignore accesses on instances within special methods (e.g., `__eq__`). // Ignore accesses on instances within special methods (e.g., `__eq__`).
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) = if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) =
checker.semantic().scope().kind checker.semantic().current_scope().kind
{ {
if matches!( if matches!(
name.as_str(), name.as_str(),

View file

@ -155,14 +155,14 @@ pub(crate) fn negation_with_equal_op(
if !matches!(&ops[..], [CmpOp::Eq]) { if !matches!(&ops[..], [CmpOp::Eq]) {
return; return;
} }
if is_exception_check(checker.semantic().stmt()) { if is_exception_check(checker.semantic().current_statement()) {
return; return;
} }
// Avoid flagging issues in dunder implementations. // Avoid flagging issues in dunder implementations.
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) if let ScopeKind::Function(ast::StmtFunctionDef { name, .. })
| ScopeKind::AsyncFunction(ast::StmtAsyncFunctionDef { name, .. }) = | ScopeKind::AsyncFunction(ast::StmtAsyncFunctionDef { name, .. }) =
&checker.semantic().scope().kind &checker.semantic().current_scope().kind
{ {
if is_dunder_method(name) { if is_dunder_method(name) {
return; return;
@ -213,14 +213,14 @@ pub(crate) fn negation_with_not_equal_op(
if !matches!(&ops[..], [CmpOp::NotEq]) { if !matches!(&ops[..], [CmpOp::NotEq]) {
return; return;
} }
if is_exception_check(checker.semantic().stmt()) { if is_exception_check(checker.semantic().current_statement()) {
return; return;
} }
// Avoid flagging issues in dunder implementations. // Avoid flagging issues in dunder implementations.
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) if let ScopeKind::Function(ast::StmtFunctionDef { name, .. })
| ScopeKind::AsyncFunction(ast::StmtAsyncFunctionDef { name, .. }) = | ScopeKind::AsyncFunction(ast::StmtAsyncFunctionDef { name, .. }) =
&checker.semantic().scope().kind &checker.semantic().current_scope().kind
{ {
if is_dunder_method(name) { if is_dunder_method(name) {
return; return;

View file

@ -43,7 +43,7 @@ impl Violation for OpenFileWithContextHandler {
/// Return `true` if the current expression is nested in an `await /// Return `true` if the current expression is nested in an `await
/// exit_stack.enter_async_context` call. /// exit_stack.enter_async_context` call.
fn match_async_exit_stack(semantic: &SemanticModel) -> bool { fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
let Some(expr) = semantic.expr_grandparent() else { let Some(expr) = semantic.current_expression_grandparent() else {
return false; return false;
}; };
let Expr::Await(ast::ExprAwait { value, range: _ }) = expr else { let Expr::Await(ast::ExprAwait { value, range: _ }) = expr else {
@ -58,7 +58,7 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
if attr != "enter_async_context" { if attr != "enter_async_context" {
return false; return false;
} }
for parent in semantic.parents() { for parent in semantic.current_statements() {
if let Stmt::With(ast::StmtWith { items, .. }) = parent { if let Stmt::With(ast::StmtWith { items, .. }) = parent {
for item in items { for item in items {
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr { if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
@ -77,7 +77,7 @@ fn match_async_exit_stack(semantic: &SemanticModel) -> bool {
/// Return `true` if the current expression is nested in an /// Return `true` if the current expression is nested in an
/// `exit_stack.enter_context` call. /// `exit_stack.enter_context` call.
fn match_exit_stack(semantic: &SemanticModel) -> bool { fn match_exit_stack(semantic: &SemanticModel) -> bool {
let Some(expr) = semantic.expr_parent() else { let Some(expr) = semantic.current_expression_parent() else {
return false; return false;
}; };
let Expr::Call(ast::ExprCall { func, .. }) = expr else { let Expr::Call(ast::ExprCall { func, .. }) = expr else {
@ -89,7 +89,7 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool {
if attr != "enter_context" { if attr != "enter_context" {
return false; return false;
} }
for parent in semantic.parents() { for parent in semantic.current_statements() {
if let Stmt::With(ast::StmtWith { items, .. }) = parent { if let Stmt::With(ast::StmtWith { items, .. }) = parent {
for item in items { for item in items {
if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr { if let Expr::Call(ast::ExprCall { func, .. }) = &item.context_expr {
@ -133,7 +133,7 @@ pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr)
} }
// Ex) `with open("foo.txt") as f: ...` // Ex) `with open("foo.txt") as f: ...`
if checker.semantic().stmt().is_with_stmt() { if checker.semantic().current_statement().is_with_stmt() {
return; return;
} }

View file

@ -60,7 +60,7 @@ impl Violation for ReimplementedBuiltin {
/// SIM110, SIM111 /// SIM110, SIM111
pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) { pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
if !checker.semantic().scope().kind.is_any_function() { if !checker.semantic().current_scope().kind.is_any_function() {
return; return;
} }
@ -73,7 +73,7 @@ pub(crate) fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt) {
// - `for` loop with an `else: return True` or `else: return False`. // - `for` loop with an `else: return True` or `else: return False`.
// - `for` loop followed by `return True` or `return False`. // - `for` loop followed by `return True` or `return False`.
let Some(terminal) = match_else_return(stmt).or_else(|| { let Some(terminal) = match_else_return(stmt).or_else(|| {
let parent = checker.semantic().stmt_parent()?; let parent = checker.semantic().current_statement_parent()?;
let suite = traversal::suite(stmt, parent)?; let suite = traversal::suite(stmt, parent)?;
let sibling = traversal::next_sibling(stmt, suite)?; let sibling = traversal::next_sibling(stmt, suite)?;
match_sibling_return(stmt, sibling) match_sibling_return(stmt, sibling)

View file

@ -35,7 +35,7 @@ pub(crate) fn runtime_evaluated(
} }
fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticModel) -> bool { fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticModel) -> bool {
let ScopeKind::Class(class_def) = &semantic.scope().kind else { let ScopeKind::Class(class_def) = &semantic.current_scope().kind else {
return false; return false;
}; };
@ -49,7 +49,7 @@ fn runtime_evaluated_base_class(base_classes: &[String], semantic: &SemanticMode
} }
fn runtime_evaluated_decorators(decorators: &[String], semantic: &SemanticModel) -> bool { fn runtime_evaluated_decorators(decorators: &[String], semantic: &SemanticModel) -> bool {
let ScopeKind::Class(class_def) = &semantic.scope().kind else { let ScopeKind::Class(class_def) = &semantic.current_scope().kind else {
return false; return false;
}; };

View file

@ -58,8 +58,8 @@ pub(crate) fn empty_type_checking_block(checker: &mut Checker, stmt: &ast::StmtI
let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, stmt.range()); let mut diagnostic = Diagnostic::new(EmptyTypeCheckingBlock, stmt.range());
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
// Delete the entire type-checking block. // Delete the entire type-checking block.
let stmt = checker.semantic().stmt(); let stmt = checker.semantic().current_statement();
let parent = checker.semantic().stmt_parent(); let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer()); let edit = autofix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer());
diagnostic.set_fix(Fix::automatic(edit).isolate(checker.isolation(parent))); diagnostic.set_fix(Fix::automatic(edit).isolate(checker.isolation(parent)));
} }

View file

@ -93,7 +93,7 @@ pub(crate) fn runtime_import_in_type_checking_block(
.is_runtime() .is_runtime()
}) })
{ {
let Some(stmt_id) = binding.source else { let Some(statement_id) = binding.source else {
continue; continue;
}; };
@ -113,20 +113,23 @@ pub(crate) fn runtime_import_in_type_checking_block(
}) })
{ {
ignores_by_statement ignores_by_statement
.entry(stmt_id) .entry(statement_id)
.or_default() .or_default()
.push(import); .push(import);
} else { } else {
errors_by_statement.entry(stmt_id).or_default().push(import); errors_by_statement
.entry(statement_id)
.or_default()
.push(import);
} }
} }
} }
// Generate a diagnostic for every import, but share a fix across all imports within the same // Generate a diagnostic for every import, but share a fix across all imports within the same
// statement (excluding those that are ignored). // statement (excluding those that are ignored).
for (stmt_id, imports) in errors_by_statement { for (statement_id, imports) in errors_by_statement {
let fix = if checker.patch(Rule::RuntimeImportInTypeCheckingBlock) { let fix = if checker.patch(Rule::RuntimeImportInTypeCheckingBlock) {
fix_imports(checker, stmt_id, &imports).ok() fix_imports(checker, statement_id, &imports).ok()
} else { } else {
None None
}; };
@ -189,9 +192,9 @@ struct ImportBinding<'a> {
} }
/// Generate a [`Fix`] to remove runtime imports from a type-checking block. /// Generate a [`Fix`] to remove runtime imports from a type-checking block.
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> { fn fix_imports(checker: &Checker, statement_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let stmt = checker.semantic().stmts[stmt_id]; let statement = checker.semantic().statements[statement_id];
let parent = checker.semantic().stmts.parent(stmt); let parent = checker.semantic().statements.parent(statement);
let member_names: Vec<Cow<'_, str>> = imports let member_names: Vec<Cow<'_, str>> = imports
.iter() .iter()
.map(|ImportBinding { import, .. }| import.member_name()) .map(|ImportBinding { import, .. }| import.member_name())
@ -209,7 +212,7 @@ fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) ->
// Step 1) Remove the import. // Step 1) Remove the import.
let remove_import_edit = autofix::edits::remove_unused_imports( let remove_import_edit = autofix::edits::remove_unused_imports(
member_names.iter().map(AsRef::as_ref), member_names.iter().map(AsRef::as_ref),
stmt, statement,
parent, parent,
checker.locator(), checker.locator(),
checker.stylist(), checker.stylist(),
@ -219,7 +222,7 @@ fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) ->
// Step 2) Add the import to the top-level. // Step 2) Add the import to the top-level.
let add_import_edit = checker.importer().runtime_import_edit( let add_import_edit = checker.importer().runtime_import_edit(
&ImportedMembers { &ImportedMembers {
statement: stmt, statement,
names: member_names.iter().map(AsRef::as_ref).collect(), names: member_names.iter().map(AsRef::as_ref).collect(),
}, },
at, at,

View file

@ -265,7 +265,7 @@ pub(crate) fn typing_only_runtime_import(
continue; continue;
} }
let Some(stmt_id) = binding.source else { let Some(statement_id) = binding.source else {
continue; continue;
}; };
@ -282,12 +282,12 @@ pub(crate) fn typing_only_runtime_import(
}) })
{ {
ignores_by_statement ignores_by_statement
.entry((stmt_id, import_type)) .entry((statement_id, import_type))
.or_default() .or_default()
.push(import); .push(import);
} else { } else {
errors_by_statement errors_by_statement
.entry((stmt_id, import_type)) .entry((statement_id, import_type))
.or_default() .or_default()
.push(import); .push(import);
} }
@ -296,9 +296,9 @@ pub(crate) fn typing_only_runtime_import(
// Generate a diagnostic for every import, but share a fix across all imports within the same // Generate a diagnostic for every import, but share a fix across all imports within the same
// statement (excluding those that are ignored). // statement (excluding those that are ignored).
for ((stmt_id, import_type), imports) in errors_by_statement { for ((statement_id, import_type), imports) in errors_by_statement {
let fix = if checker.patch(rule_for(import_type)) { let fix = if checker.patch(rule_for(import_type)) {
fix_imports(checker, stmt_id, &imports).ok() fix_imports(checker, statement_id, &imports).ok()
} else { } else {
None None
}; };
@ -402,9 +402,9 @@ fn is_exempt(name: &str, exempt_modules: &[&str]) -> bool {
} }
/// Generate a [`Fix`] to remove typing-only imports from a runtime context. /// Generate a [`Fix`] to remove typing-only imports from a runtime context.
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> { fn fix_imports(checker: &Checker, statement_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let stmt = checker.semantic().stmts[stmt_id]; let statement = checker.semantic().statements[statement_id];
let parent = checker.semantic().stmts.parent(stmt); let parent = checker.semantic().statements.parent(statement);
let member_names: Vec<Cow<'_, str>> = imports let member_names: Vec<Cow<'_, str>> = imports
.iter() .iter()
.map(|ImportBinding { import, .. }| import.member_name()) .map(|ImportBinding { import, .. }| import.member_name())
@ -422,7 +422,7 @@ fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) ->
// Step 1) Remove the import. // Step 1) Remove the import.
let remove_import_edit = autofix::edits::remove_unused_imports( let remove_import_edit = autofix::edits::remove_unused_imports(
member_names.iter().map(AsRef::as_ref), member_names.iter().map(AsRef::as_ref),
stmt, statement,
parent, parent,
checker.locator(), checker.locator(),
checker.stylist(), checker.stylist(),
@ -432,7 +432,7 @@ fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) ->
// Step 2) Add the import to a `TYPE_CHECKING` block. // Step 2) Add the import to a `TYPE_CHECKING` block.
let add_import_edit = checker.importer().typing_import_edit( let add_import_edit = checker.importer().typing_import_edit(
&ImportedMembers { &ImportedMembers {
statement: stmt, statement,
names: member_names.iter().map(AsRef::as_ref).collect(), names: member_names.iter().map(AsRef::as_ref).collect(),
}, },
at, at,

View file

@ -53,7 +53,7 @@ pub(crate) fn attr(checker: &mut Checker, attr: &str, value: &Expr, attr_expr: &
}; };
// Avoid flagging on function calls (e.g., `df.values()`). // Avoid flagging on function calls (e.g., `df.values()`).
if let Some(parent) = checker.semantic().expr_parent() { if let Some(parent) = checker.semantic().current_expression_parent() {
if matches!(parent, Expr::Call(_)) { if matches!(parent, Expr::Call(_)) {
return; return;
} }

View file

@ -85,8 +85,8 @@ pub(crate) fn inplace_argument(checker: &mut Checker, call: &ast::ExprCall) {
// 2. The call is part of a larger expression (we're converting an expression to a // 2. The call is part of a larger expression (we're converting an expression to a
// statement, and expressions can't contain statements). // statement, and expressions can't contain statements).
if !seen_star if !seen_star
&& checker.semantic().stmt().is_expr_stmt() && checker.semantic().current_statement().is_expr_stmt()
&& checker.semantic().expr_parent().is_none() && checker.semantic().current_expression_parent().is_none()
{ {
if let Some(fix) = convert_inplace_argument_to_assignment( if let Some(fix) = convert_inplace_argument_to_assignment(
call, call,

View file

@ -69,7 +69,7 @@ pub(crate) fn mixed_case_variable_in_class_scope(
return; return;
} }
let parent = checker.semantic().stmt(); let parent = checker.semantic().current_statement();
if helpers::is_named_tuple_assignment(parent, checker.semantic()) if helpers::is_named_tuple_assignment(parent, checker.semantic())
|| helpers::is_typed_dict_class(arguments, checker.semantic()) || helpers::is_typed_dict_class(arguments, checker.semantic())

View file

@ -75,7 +75,7 @@ pub(crate) fn mixed_case_variable_in_global_scope(checker: &mut Checker, expr: &
return; return;
} }
let parent = checker.semantic().stmt(); let parent = checker.semantic().current_statement();
if helpers::is_named_tuple_assignment(parent, checker.semantic()) { if helpers::is_named_tuple_assignment(parent, checker.semantic()) {
return; return;
} }

View file

@ -65,7 +65,7 @@ pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &E
return; return;
} }
let parent = checker.semantic().stmt(); let parent = checker.semantic().current_statement();
if helpers::is_named_tuple_assignment(parent, checker.semantic()) if helpers::is_named_tuple_assignment(parent, checker.semantic())
|| helpers::is_typed_dict_assignment(parent, checker.semantic()) || helpers::is_typed_dict_assignment(parent, checker.semantic())
|| helpers::is_type_var_assignment(parent, checker.semantic()) || helpers::is_type_var_assignment(parent, checker.semantic())

View file

@ -167,7 +167,7 @@ fn is_unused(expr: &Expr, model: &SemanticModel) -> bool {
// //
// print(bar) // print(bar)
// ``` // ```
let scope = model.scope(); let scope = model.current_scope();
scope scope
.get_all(id) .get_all(id)
.map(|binding_id| model.binding(binding_id)) .map(|binding_id| model.binding(binding_id))

View file

@ -102,12 +102,12 @@ pub(crate) fn unnecessary_list_cast(checker: &mut Checker, iter: &Expr) {
range: iterable_range, range: iterable_range,
.. ..
}) => { }) => {
let scope = checker.semantic().scope(); let scope = checker.semantic().current_scope();
if let Some(binding_id) = scope.get(id) { if let Some(binding_id) = scope.get(id) {
let binding = checker.semantic().binding(binding_id); let binding = checker.semantic().binding(binding_id);
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() { if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
if let Some(parent_id) = binding.source { if let Some(parent_id) = binding.source {
let parent = checker.semantic().stmts[parent_id]; let parent = checker.semantic().statements[parent_id];
if let Stmt::Assign(ast::StmtAssign { value, .. }) if let Stmt::Assign(ast::StmtAssign { value, .. })
| Stmt::AnnAssign(ast::StmtAnnAssign { | Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value), .. value: Some(value), ..

View file

@ -116,10 +116,10 @@ pub(crate) fn lambda_assignment(
// rewriting it as a function declaration may break type-checking. // rewriting it as a function declaration may break type-checking.
// See: https://github.com/astral-sh/ruff/issues/3046 // See: https://github.com/astral-sh/ruff/issues/3046
// See: https://github.com/astral-sh/ruff/issues/5421 // See: https://github.com/astral-sh/ruff/issues/5421
if (annotation.is_some() && checker.semantic().scope().kind.is_class()) if (annotation.is_some() && checker.semantic().current_scope().kind.is_class())
|| checker || checker
.semantic() .semantic()
.scope() .current_scope()
.get_all(id) .get_all(id)
.any(|binding_id| checker.semantic().binding(binding_id).kind.is_annotation()) .any(|binding_id| checker.semantic().binding(binding_id).kind.is_annotation())
{ {

View file

@ -33,7 +33,7 @@ impl Violation for ReturnOutsideFunction {
pub(crate) fn return_outside_function(checker: &mut Checker, stmt: &Stmt) { pub(crate) fn return_outside_function(checker: &mut Checker, stmt: &Stmt) {
if matches!( if matches!(
checker.semantic().scope().kind, checker.semantic().current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module ScopeKind::Class(_) | ScopeKind::Module
) { ) {
checker checker

View file

@ -116,7 +116,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
continue; continue;
}; };
let Some(stmt_id) = binding.source else { let Some(statement_id) = binding.source else {
continue; continue;
}; };
@ -132,12 +132,12 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
}) })
{ {
ignored ignored
.entry((stmt_id, binding.exceptions)) .entry((statement_id, binding.exceptions))
.or_default() .or_default()
.push(import); .push(import);
} else { } else {
unused unused
.entry((stmt_id, binding.exceptions)) .entry((statement_id, binding.exceptions))
.or_default() .or_default()
.push(import); .push(import);
} }
@ -148,13 +148,13 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
// Generate a diagnostic for every import, but share a fix across all imports within the same // Generate a diagnostic for every import, but share a fix across all imports within the same
// statement (excluding those that are ignored). // statement (excluding those that are ignored).
for ((stmt_id, exceptions), imports) in unused { for ((statement_id, exceptions), imports) in unused {
let in_except_handler = let in_except_handler =
exceptions.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR); exceptions.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
let multiple = imports.len() > 1; let multiple = imports.len() > 1;
let fix = if !in_init && !in_except_handler && checker.patch(Rule::UnusedImport) { let fix = if !in_init && !in_except_handler && checker.patch(Rule::UnusedImport) {
fix_imports(checker, stmt_id, &imports).ok() fix_imports(checker, statement_id, &imports).ok()
} else { } else {
None None
}; };
@ -225,9 +225,9 @@ struct ImportBinding<'a> {
} }
/// Generate a [`Fix`] to remove unused imports from a statement. /// Generate a [`Fix`] to remove unused imports from a statement.
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> { fn fix_imports(checker: &Checker, statement_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let stmt = checker.semantic().stmts[stmt_id]; let statement = checker.semantic().statements[statement_id];
let parent = checker.semantic().stmts.parent(stmt); let parent = checker.semantic().statements.parent(statement);
let member_names: Vec<Cow<'_, str>> = imports let member_names: Vec<Cow<'_, str>> = imports
.iter() .iter()
@ -236,7 +236,7 @@ fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) ->
let edit = autofix::edits::remove_unused_imports( let edit = autofix::edits::remove_unused_imports(
member_names.iter().map(AsRef::as_ref), member_names.iter().map(AsRef::as_ref),
stmt, statement,
parent, parent,
checker.locator(), checker.locator(),
checker.stylist(), checker.stylist(),

View file

@ -326,8 +326,8 @@ pub(crate) fn unused_variable(checker: &Checker, scope: &Scope, diagnostics: &mu
let mut diagnostic = Diagnostic::new(UnusedVariable { name }, range); let mut diagnostic = Diagnostic::new(UnusedVariable { name }, range);
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
if let Some(source) = source { if let Some(source) = source {
let stmt = checker.semantic().stmts[source]; let stmt = checker.semantic().statements[source];
let parent = checker.semantic().stmts.parent(stmt); let parent = checker.semantic().statements.parent(stmt);
if let Some(fix) = remove_unused_variable(stmt, parent, range, checker) { if let Some(fix) = remove_unused_variable(stmt, parent, range, checker) {
diagnostic.set_fix(fix); diagnostic.set_fix(fix);
} }

View file

@ -55,7 +55,7 @@ impl Violation for YieldOutsideFunction {
pub(crate) fn yield_outside_function(checker: &mut Checker, expr: &Expr) { pub(crate) fn yield_outside_function(checker: &mut Checker, expr: &Expr) {
if matches!( if matches!(
checker.semantic().scope().kind, checker.semantic().current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module ScopeKind::Class(_) | ScopeKind::Module
) { ) {
let keyword = match expr { let keyword = match expr {

View file

@ -23,7 +23,7 @@ pub(super) fn type_param_name(arguments: &Arguments) -> Option<&str> {
} }
pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &Settings) -> bool { pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &Settings) -> bool {
let scope = semantic.scope(); let scope = semantic.current_scope();
let (ScopeKind::Function(ast::StmtFunctionDef { let (ScopeKind::Function(ast::StmtFunctionDef {
name, name,
decorator_list, decorator_list,

View file

@ -67,7 +67,7 @@ pub(crate) fn compare_to_empty_string(
// DataFrame and np.ndarray indexing. // DataFrame and np.ndarray indexing.
if checker if checker
.semantic() .semantic()
.expr_ancestors() .current_expressions()
.any(Expr::is_subscript_expr) .any(Expr::is_subscript_expr)
{ {
return; return;

View file

@ -29,7 +29,7 @@ pub(crate) fn invalid_str_return(checker: &mut Checker, name: &str, body: &[Stmt
return; return;
} }
if !checker.semantic().scope().kind.is_class() { if !checker.semantic().current_scope().kind.is_class() {
return; return;
} }

View file

@ -62,7 +62,7 @@ pub(crate) fn self_assigning_variable(checker: &mut Checker, target: &Expr, valu
// Assignments in class bodies are attributes (e.g., `x = x` assigns `x` to `self.x`, and thus // Assignments in class bodies are attributes (e.g., `x = x` assigns `x` to `self.x`, and thus
// is not a self-assignment). // is not a self-assignment).
if checker.semantic().scope().kind.is_class() { if checker.semantic().current_scope().kind.is_class() {
return; return;
} }

View file

@ -143,7 +143,7 @@ pub(crate) fn unexpected_special_method_signature(
decorator_list: &[Decorator], decorator_list: &[Decorator],
parameters: &Parameters, parameters: &Parameters,
) { ) {
if !checker.semantic().scope().kind.is_class() { if !checker.semantic().current_scope().kind.is_class() {
return; return;
} }

View file

@ -38,7 +38,7 @@ impl Violation for YieldFromInAsyncFunction {
/// PLE1700 /// PLE1700
pub(crate) fn yield_from_in_async_function(checker: &mut Checker, expr: &ExprYieldFrom) { pub(crate) fn yield_from_in_async_function(checker: &mut Checker, expr: &ExprYieldFrom) {
let scope = checker.semantic().scope(); let scope = checker.semantic().current_scope();
if scope.kind.is_async_function() { if scope.kind.is_async_function() {
checker checker
.diagnostics .diagnostics

View file

@ -149,7 +149,7 @@ pub(crate) fn native_literals(
if checker.semantic().in_f_string() { if checker.semantic().in_f_string() {
if checker if checker
.semantic() .semantic()
.expr_ancestors() .current_expressions()
.filter(|expr| expr.is_joined_str_expr()) .filter(|expr| expr.is_joined_str_expr())
.count() .count()
> 1 > 1

View file

@ -124,8 +124,8 @@ fn fix_py2_block(checker: &Checker, stmt_if: &StmtIf, branch: &IfElifBranch) ->
BranchKind::If => match stmt_if.elif_else_clauses.first() { BranchKind::If => match stmt_if.elif_else_clauses.first() {
// If we have a lone `if`, delete as statement (insert pass in parent if required) // If we have a lone `if`, delete as statement (insert pass in parent if required)
None => { None => {
let stmt = checker.semantic().stmt(); let stmt = checker.semantic().current_statement();
let parent = checker.semantic().stmt_parent(); let parent = checker.semantic().current_statement_parent();
let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer()); let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer());
Some(Fix::suggested(edit)) Some(Fix::suggested(edit))
} }

View file

@ -80,14 +80,14 @@ pub(crate) fn super_call_with_parameters(
if !is_super_call_with_arguments(func, args) { if !is_super_call_with_arguments(func, args) {
return; return;
} }
let scope = checker.semantic().scope(); let scope = checker.semantic().current_scope();
// Check: are we in a Function scope? // Check: are we in a Function scope?
if !scope.kind.is_any_function() { if !scope.kind.is_any_function() {
return; return;
} }
let mut parents = checker.semantic().parents(); let mut parents = checker.semantic().current_statements();
// For a `super` invocation to be unnecessary, the first argument needs to match // For a `super` invocation to be unnecessary, the first argument needs to match
// the enclosing class, and the second argument needs to match the first // the enclosing class, and the second argument needs to match the first

View file

@ -122,14 +122,14 @@ pub(crate) fn unnecessary_builtin_import(
); );
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {
let stmt = checker.semantic().stmt(); let statement = checker.semantic().current_statement();
let parent = checker.semantic().stmt_parent(); let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::remove_unused_imports( let edit = autofix::edits::remove_unused_imports(
unused_imports unused_imports
.iter() .iter()
.map(|alias| &alias.name) .map(|alias| &alias.name)
.map(ruff_python_ast::Identifier::as_str), .map(ruff_python_ast::Identifier::as_str),
stmt, statement,
parent, parent,
checker.locator(), checker.locator(),
checker.stylist(), checker.stylist(),

View file

@ -111,14 +111,14 @@ pub(crate) fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, name
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| { diagnostic.try_set_fix(|| {
let stmt = checker.semantic().stmt(); let statement = checker.semantic().current_statement();
let parent = checker.semantic().stmt_parent(); let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::remove_unused_imports( let edit = autofix::edits::remove_unused_imports(
unused_imports unused_imports
.iter() .iter()
.map(|alias| &alias.name) .map(|alias| &alias.name)
.map(ruff_python_ast::Identifier::as_str), .map(ruff_python_ast::Identifier::as_str),
stmt, statement,
parent, parent,
checker.locator(), checker.locator(),
checker.stylist(), checker.stylist(),

View file

@ -63,8 +63,8 @@ pub(crate) fn useless_metaclass_type(
let mut diagnostic = Diagnostic::new(UselessMetaclassType, stmt.range()); let mut diagnostic = Diagnostic::new(UselessMetaclassType, stmt.range());
if checker.patch(diagnostic.kind.rule()) { if checker.patch(diagnostic.kind.rule()) {
let stmt = checker.semantic().stmt(); let stmt = checker.semantic().current_statement();
let parent = checker.semantic().stmt_parent(); let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer()); let edit = autofix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer());
diagnostic.set_fix(Fix::automatic(edit).isolate(checker.isolation(parent))); diagnostic.set_fix(Fix::automatic(edit).isolate(checker.isolation(parent)));
} }

View file

@ -166,7 +166,7 @@ fn concatenate_expressions(expr: &Expr) -> Option<(Expr, Type)> {
pub(crate) fn collection_literal_concatenation(checker: &mut Checker, expr: &Expr) { pub(crate) fn collection_literal_concatenation(checker: &mut Checker, expr: &Expr) {
// If the expression is already a child of an addition, we'll have analyzed it already. // If the expression is already a child of an addition, we'll have analyzed it already.
if matches!( if matches!(
checker.semantic().expr_parent(), checker.semantic().current_expression_parent(),
Some(Expr::BinOp(ast::ExprBinOp { Some(Expr::BinOp(ast::ExprBinOp {
op: Operator::Add, op: Operator::Add,
.. ..

View file

@ -185,7 +185,7 @@ impl<'a> Binding<'a> {
/// Returns the range of the binding's parent. /// Returns the range of the binding's parent.
pub fn parent_range(&self, semantic: &SemanticModel) -> Option<TextRange> { pub fn parent_range(&self, semantic: &SemanticModel) -> Option<TextRange> {
self.source self.source
.map(|node_id| semantic.stmts[node_id]) .map(|node_id| semantic.statements[node_id])
.and_then(|parent| { .and_then(|parent| {
if parent.is_import_from_stmt() { if parent.is_import_from_stmt() {
Some(parent.range()) Some(parent.range())

View file

@ -31,16 +31,16 @@ pub struct SemanticModel<'a> {
module_path: Option<&'a [String]>, module_path: Option<&'a [String]>,
/// Stack of all visited statements. /// Stack of all visited statements.
pub stmts: Nodes<'a, Stmt>, pub statements: Nodes<'a, Stmt>,
/// The identifier of the current statement. /// The identifier of the current statement.
stmt_id: Option<NodeId>, statement_id: Option<NodeId>,
/// Stack of all visited expressions. /// Stack of all visited expressions.
exprs: Nodes<'a, Expr>, expressions: Nodes<'a, Expr>,
/// The identifier of the current expression. /// The identifier of the current expression.
expr_id: Option<NodeId>, expression_id: Option<NodeId>,
/// Stack of all scopes, along with the identifier of the current scope. /// Stack of all scopes, along with the identifier of the current scope.
pub scopes: Scopes<'a>, pub scopes: Scopes<'a>,
@ -132,10 +132,10 @@ impl<'a> SemanticModel<'a> {
Self { Self {
typing_modules, typing_modules,
module_path: module.path(), module_path: module.path(),
stmts: Nodes::<Stmt>::default(), statements: Nodes::<Stmt>::default(),
stmt_id: None, statement_id: None,
exprs: Nodes::<Expr>::default(), expressions: Nodes::<Expr>::default(),
expr_id: None, expression_id: None,
scopes: Scopes::default(), scopes: Scopes::default(),
scope_id: ScopeId::global(), scope_id: ScopeId::global(),
definitions: Definitions::for_module(module), definitions: Definitions::for_module(module),
@ -225,7 +225,7 @@ impl<'a> SemanticModel<'a> {
flags, flags,
references: Vec::new(), references: Vec::new(),
scope: self.scope_id, scope: self.scope_id,
source: self.stmt_id, source: self.statement_id,
context: self.execution_context(), context: self.execution_context(),
exceptions: self.exceptions(), exceptions: self.exceptions(),
}) })
@ -233,7 +233,7 @@ impl<'a> SemanticModel<'a> {
/// Return the current [`Binding`] for a given `name`. /// Return the current [`Binding`] for a given `name`.
pub fn find_binding(&self, member: &str) -> Option<&Binding> { pub fn find_binding(&self, member: &str) -> Option<&Binding> {
self.scopes() self.current_scopes()
.find_map(|scope| scope.get(member)) .find_map(|scope| scope.get(member))
.map(|binding_id| &self.bindings[binding_id]) .map(|binding_id| &self.bindings[binding_id])
} }
@ -686,7 +686,9 @@ impl<'a> SemanticModel<'a> {
) -> Option<ImportedName> { ) -> Option<ImportedName> {
// TODO(charlie): Pass in a slice. // TODO(charlie): Pass in a slice.
let module_path: Vec<&str> = module.split('.').collect(); let module_path: Vec<&str> = module.split('.').collect();
self.scopes().enumerate().find_map(|(scope_index, scope)| { self.current_scopes()
.enumerate()
.find_map(|(scope_index, scope)| {
scope.bindings().find_map(|(name, binding_id)| { scope.bindings().find_map(|(name, binding_id)| {
let binding = &self.bindings[binding_id]; let binding = &self.bindings[binding_id];
match &binding.kind { match &binding.kind {
@ -698,13 +700,13 @@ impl<'a> SemanticModel<'a> {
if let Some(source) = binding.source { if let Some(source) = binding.source {
// Verify that `sys` isn't bound in an inner scope. // Verify that `sys` isn't bound in an inner scope.
if self if self
.scopes() .current_scopes()
.take(scope_index) .take(scope_index)
.all(|scope| !scope.has(name)) .all(|scope| !scope.has(name))
{ {
return Some(ImportedName { return Some(ImportedName {
name: format!("{name}.{member}"), name: format!("{name}.{member}"),
range: self.stmts[source].range(), range: self.statements[source].range(),
context: binding.context, context: binding.context,
}); });
} }
@ -716,17 +718,19 @@ impl<'a> SemanticModel<'a> {
// `from os.path import join as join2` -> `join2` // `from os.path import join as join2` -> `join2`
BindingKind::FromImport(FromImport { call_path }) => { BindingKind::FromImport(FromImport { call_path }) => {
if let Some((target_member, target_module)) = call_path.split_last() { if let Some((target_member, target_module)) = call_path.split_last() {
if target_module == module_path.as_slice() && target_member == &member { if target_module == module_path.as_slice()
&& target_member == &member
{
if let Some(source) = binding.source { if let Some(source) = binding.source {
// Verify that `join` isn't bound in an inner scope. // Verify that `join` isn't bound in an inner scope.
if self if self
.scopes() .current_scopes()
.take(scope_index) .take(scope_index)
.all(|scope| !scope.has(name)) .all(|scope| !scope.has(name))
{ {
return Some(ImportedName { return Some(ImportedName {
name: (*name).to_string(), name: (*name).to_string(),
range: self.stmts[source].range(), range: self.statements[source].range(),
context: binding.context, context: binding.context,
}); });
} }
@ -741,13 +745,13 @@ impl<'a> SemanticModel<'a> {
if let Some(source) = binding.source { if let Some(source) = binding.source {
// Verify that `os` isn't bound in an inner scope. // Verify that `os` isn't bound in an inner scope.
if self if self
.scopes() .current_scopes()
.take(scope_index) .take(scope_index)
.all(|scope| !scope.has(name)) .all(|scope| !scope.has(name))
{ {
return Some(ImportedName { return Some(ImportedName {
name: format!("{name}.{member}"), name: format!("{name}.{member}"),
range: self.stmts[source].range(), range: self.statements[source].range(),
context: binding.context, context: binding.context,
}); });
} }
@ -763,25 +767,29 @@ impl<'a> SemanticModel<'a> {
} }
/// Push a [`Stmt`] onto the stack. /// Push a [`Stmt`] onto the stack.
pub fn push_stmt(&mut self, stmt: &'a Stmt) { pub fn push_statement(&mut self, stmt: &'a Stmt) {
self.stmt_id = Some(self.stmts.insert(stmt, self.stmt_id)); self.statement_id = Some(self.statements.insert(stmt, self.statement_id));
} }
/// Pop the current [`Stmt`] off the stack. /// Pop the current [`Stmt`] off the stack.
pub fn pop_stmt(&mut self) { pub fn pop_statement(&mut self) {
let node_id = self.stmt_id.expect("Attempted to pop without statement"); let node_id = self
self.stmt_id = self.stmts.parent_id(node_id); .statement_id
.expect("Attempted to pop without statement");
self.statement_id = self.statements.parent_id(node_id);
} }
/// Push a [`Expr`] onto the stack. /// Push a [`Expr`] onto the stack.
pub fn push_expr(&mut self, expr: &'a Expr) { pub fn push_expression(&mut self, expr: &'a Expr) {
self.expr_id = Some(self.exprs.insert(expr, self.expr_id)); self.expression_id = Some(self.expressions.insert(expr, self.expression_id));
} }
/// Pop the current [`Expr`] off the stack. /// Pop the current [`Expr`] off the stack.
pub fn pop_expr(&mut self) { pub fn pop_expression(&mut self) {
let node_id = self.expr_id.expect("Attempted to pop without expression"); let node_id = self
self.expr_id = self.exprs.parent_id(node_id); .expression_id
.expect("Attempted to pop without expression");
self.expression_id = self.expressions.parent_id(node_id);
} }
/// Push a [`Scope`] with the given [`ScopeKind`] onto the stack. /// Push a [`Scope`] with the given [`ScopeKind`] onto the stack.
@ -811,69 +819,94 @@ impl<'a> SemanticModel<'a> {
} }
/// Return the current `Stmt`. /// Return the current `Stmt`.
pub fn stmt(&self) -> &'a Stmt { pub fn current_statement(&self) -> &'a Stmt {
let node_id = self.stmt_id.expect("No current statement"); let node_id = self.statement_id.expect("No current statement");
self.stmts[node_id] self.statements[node_id]
} }
/// Return the parent `Stmt` of the current `Stmt`, if any. /// Returns an [`Iterator`] over the current statement hierarchy, from the current [`Stmt`]
pub fn stmt_parent(&self) -> Option<&'a Stmt> { /// through to any parents.
let node_id = self.stmt_id.expect("No current statement"); pub fn current_statements(&self) -> impl Iterator<Item = &'a Stmt> + '_ {
let parent_id = self.stmts.parent_id(node_id)?; self.statement_id
Some(self.stmts[parent_id])
}
/// Return the current `Expr`.
pub fn expr(&self) -> Option<&'a Expr> {
let node_id = self.expr_id?;
Some(self.exprs[node_id])
}
/// Return the parent `Expr` of the current `Expr`, if any.
pub fn expr_parent(&self) -> Option<&'a Expr> {
self.expr_ancestors().next()
}
/// Return the grandparent `Expr` of the current `Expr`, if any.
pub fn expr_grandparent(&self) -> Option<&'a Expr> {
self.expr_ancestors().nth(1)
}
/// Return an [`Iterator`] over the current `Expr` parents.
pub fn expr_ancestors(&self) -> impl Iterator<Item = &'a Expr> + '_ {
self.expr_id
.iter() .iter()
.copied()
.flat_map(|id| { .flat_map(|id| {
self.exprs self.statements
.ancestor_ids(id) .ancestor_ids(*id)
.skip(1) .map(|id| &self.statements[id])
.map(|id| &self.exprs[id])
}) })
.copied() .copied()
} }
/// Returns a reference to the global scope /// Return the parent `Stmt` of the current `Stmt`, if any.
pub fn current_statement_parent(&self) -> Option<&'a Stmt> {
self.current_statements().nth(1)
}
/// Return the grandparent `Stmt` of the current `Stmt`, if any.
pub fn current_statement_grandparent(&self) -> Option<&'a Stmt> {
self.current_statements().nth(2)
}
/// Return the current `Expr`.
pub fn current_expression(&self) -> Option<&'a Expr> {
let node_id = self.expression_id?;
Some(self.expressions[node_id])
}
/// Returns an [`Iterator`] over the current statement hierarchy, from the current [`Expr`]
/// through to any parents.
pub fn current_expressions(&self) -> impl Iterator<Item = &'a Expr> + '_ {
self.expression_id
.iter()
.flat_map(|id| {
self.expressions
.ancestor_ids(*id)
.map(|id| &self.expressions[id])
})
.copied()
}
/// Return the parent [`Expr`] of the current [`Expr`], if any.
pub fn current_expression_parent(&self) -> Option<&'a Expr> {
self.current_expressions().nth(1)
}
/// Return the grandparent [`Expr`] of the current [`Expr`], if any.
pub fn current_expression_grandparent(&self) -> Option<&'a Expr> {
self.current_expressions().nth(2)
}
/// Returns a reference to the global [`Scope`].
pub fn global_scope(&self) -> &Scope<'a> { pub fn global_scope(&self) -> &Scope<'a> {
self.scopes.global() self.scopes.global()
} }
/// Returns a mutable reference to the global scope /// Returns a mutable reference to the global [`Scope`].
pub fn global_scope_mut(&mut self) -> &mut Scope<'a> { pub fn global_scope_mut(&mut self) -> &mut Scope<'a> {
self.scopes.global_mut() self.scopes.global_mut()
} }
/// Returns the current top most scope. /// Returns the current top-most [`Scope`].
pub fn scope(&self) -> &Scope<'a> { pub fn current_scope(&self) -> &Scope<'a> {
&self.scopes[self.scope_id] &self.scopes[self.scope_id]
} }
/// Returns the parent of the given scope, if any. /// Returns a mutable reference to the current top-most [`Scope`].
pub fn current_scope_mut(&mut self) -> &mut Scope<'a> {
&mut self.scopes[self.scope_id]
}
/// Returns an iterator over all scopes, starting from the current [`Scope`].
pub fn current_scopes(&self) -> impl Iterator<Item = &Scope> {
self.scopes.ancestors(self.scope_id)
}
/// Returns the parent of the given [`Scope`], if any.
pub fn parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> { pub fn parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
scope.parent.map(|scope_id| &self.scopes[scope_id]) scope.parent.map(|scope_id| &self.scopes[scope_id])
} }
/// Returns the first parent of the given scope that is not a [`ScopeKind::Type`] scope, if any. /// Returns the first parent of the given [`Scope`] that is not of [`ScopeKind::Type`], if any.
pub fn first_non_type_parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> { pub fn first_non_type_parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
let mut current_scope = scope; let mut current_scope = scope;
while let Some(parent) = self.parent_scope(current_scope) { while let Some(parent) = self.parent_scope(current_scope) {
@ -886,22 +919,6 @@ impl<'a> SemanticModel<'a> {
None None
} }
/// Returns a mutable reference to the current top most scope.
pub fn scope_mut(&mut self) -> &mut Scope<'a> {
&mut self.scopes[self.scope_id]
}
/// Returns an iterator over all scopes, starting from the current scope.
pub fn scopes(&self) -> impl Iterator<Item = &Scope> {
self.scopes.ancestors(self.scope_id)
}
/// Returns an iterator over all parent statements.
pub fn parents(&self) -> impl Iterator<Item = &Stmt> + '_ {
let node_id = self.stmt_id.expect("No current statement");
self.stmts.ancestor_ids(node_id).map(|id| self.stmts[id])
}
/// Set the [`Globals`] for the current [`Scope`]. /// Set the [`Globals`] for the current [`Scope`].
pub fn set_globals(&mut self, globals: Globals<'a>) { pub fn set_globals(&mut self, globals: Globals<'a>) {
// If any global bindings don't already exist in the global scope, add them. // If any global bindings don't already exist in the global scope, add them.
@ -916,7 +933,7 @@ impl<'a> SemanticModel<'a> {
range: *range, range: *range,
references: Vec::new(), references: Vec::new(),
scope: self.scope_id, scope: self.scope_id,
source: self.stmt_id, source: self.statement_id,
context: self.execution_context(), context: self.execution_context(),
exceptions: self.exceptions(), exceptions: self.exceptions(),
flags: BindingFlags::empty(), flags: BindingFlags::empty(),
@ -964,13 +981,13 @@ impl<'a> SemanticModel<'a> {
pub fn at_top_level(&self) -> bool { pub fn at_top_level(&self) -> bool {
self.scope_id.is_global() self.scope_id.is_global()
&& self && self
.stmt_id .statement_id
.map_or(true, |stmt_id| self.stmts.parent_id(stmt_id).is_none()) .map_or(true, |stmt_id| self.statements.parent_id(stmt_id).is_none())
} }
/// Return `true` if the model is in an async context. /// Return `true` if the model is in an async context.
pub fn in_async_context(&self) -> bool { pub fn in_async_context(&self) -> bool {
for scope in self.scopes() { for scope in self.current_scopes() {
if scope.kind.is_async_function() { if scope.kind.is_async_function() {
return true; return true;
} }
@ -1048,8 +1065,8 @@ impl<'a> SemanticModel<'a> {
pub fn snapshot(&self) -> Snapshot { pub fn snapshot(&self) -> Snapshot {
Snapshot { Snapshot {
scope_id: self.scope_id, scope_id: self.scope_id,
stmt_id: self.stmt_id, stmt_id: self.statement_id,
expr_id: self.expr_id, expr_id: self.expression_id,
definition_id: self.definition_id, definition_id: self.definition_id,
flags: self.flags, flags: self.flags,
} }
@ -1065,8 +1082,8 @@ impl<'a> SemanticModel<'a> {
flags, flags,
} = snapshot; } = snapshot;
self.scope_id = scope_id; self.scope_id = scope_id;
self.stmt_id = stmt_id; self.statement_id = stmt_id;
self.expr_id = expr_id; self.expression_id = expr_id;
self.definition_id = definition_id; self.definition_id = definition_id;
self.flags = flags; self.flags = flags;
} }