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 {
target, iter, body, ..
}) = &checker.semantic.stmt()
}) = &checker.semantic.current_statement()
{
if checker.enabled(Rule::UnusedLoopControlVariable) {
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 shadowed.source.map_or(true, |left| {
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;
@ -172,7 +176,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
if shadowed.kind.is_function_definition()
&& visibility::is_overload(
cast::decorator_list(
checker.semantic.stmts[shadowed.source.unwrap()],
checker.semantic.statements[shadowed.source.unwrap()],
),
&checker.semantic,
)
@ -195,7 +199,11 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
// If the bindings are in different forks, abort.
if shadowed.source.map_or(true, |left| {
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;

View file

@ -84,7 +84,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
// traverse nested unions.
let is_unchecked_union = checker
.semantic
.expr_grandparent()
.current_expression_grandparent()
.and_then(Expr::as_subscript_expr)
.map_or(true, |parent| {
!checker.semantic.match_typing_expr(&parent.value, "Union")
@ -206,11 +206,16 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
ExprContext::Store => {
if checker.enabled(Rule::NonLowercaseVariableInFunction) {
if checker.semantic.scope().kind.is_any_function() {
if checker.semantic.current_scope().kind.is_any_function() {
// Ignore globals.
if !checker.semantic.scope().get(id).is_some_and(|binding_id| {
checker.semantic.binding(binding_id).is_global()
}) {
if !checker
.semantic
.current_scope()
.get(id)
.is_some_and(|binding_id| {
checker.semantic.binding(binding_id).is_global()
})
{
pep8_naming::rules::non_lowercase_variable_in_function(
checker, expr, id,
);
@ -219,7 +224,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
if checker.enabled(Rule::MixedCaseVariableInClassScope) {
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(
checker,
@ -230,7 +235,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
}
}
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(
checker, expr, id,
);
@ -243,7 +248,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
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) {
flake8_builtins::rules::builtin_attribute_shadowing(
checker, class_def, id, *range,
@ -668,7 +673,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
flake8_comprehensions::rules::unnecessary_map(
checker,
expr,
checker.semantic.expr_parent(),
checker.semantic.current_expression_parent(),
func,
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
// traverse nested unions.
let is_unchecked_union = !matches!(
checker.semantic.expr_parent(),
checker.semantic.current_expression_parent(),
Some(Expr::BinOp(ast::ExprBinOp {
op: Operator::BitOr,
..

View file

@ -53,7 +53,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::BreakOutsideLoop) {
if let Some(diagnostic) = pyflakes::rules::break_outside_loop(
stmt,
&mut checker.semantic.parents().skip(1),
&mut checker.semantic.current_statements().skip(1),
) {
checker.diagnostics.push(diagnostic);
}
@ -63,7 +63,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::ContinueOutsideLoop) {
if let Some(diagnostic) = pyflakes::rules::continue_outside_loop(
stmt,
&mut checker.semantic.parents().skip(1),
&mut checker.semantic.current_statements().skip(1),
) {
checker.diagnostics.push(diagnostic);
}
@ -113,7 +113,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if let Some(diagnostic) =
pep8_naming::rules::invalid_first_argument_name_for_class_method(
checker,
checker.semantic.scope(),
checker.semantic.current_scope(),
name,
decorator_list,
parameters,
@ -125,7 +125,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::InvalidFirstArgumentNameForMethod) {
if let Some(diagnostic) = pep8_naming::rules::invalid_first_argument_name_for_method(
checker,
checker.semantic.scope(),
checker.semantic.current_scope(),
name,
decorator_list,
parameters,
@ -193,7 +193,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
if checker.enabled(Rule::DunderFunctionName) {
if let Some(diagnostic) = pep8_naming::rules::dunder_function_name(
checker.semantic.scope(),
checker.semantic.current_scope(),
stmt,
name,
&checker.settings.pep8_naming.ignore_names,
@ -348,7 +348,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::YieldInForLoop) {
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) {
flake8_builtins::rules::builtin_method_shadowing(
checker,
@ -766,7 +766,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
} else if &alias.name == "*" {
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(
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
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(
checker,
if_,
checker.semantic.stmt_parent(),
checker.semantic.current_statement_parent(),
);
}
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(
checker,
if_,
checker.semantic.stmt_parent(),
checker.semantic.current_statement_parent(),
);
}
if checker.enabled(Rule::OutdatedVersionBlock) {
@ -1110,7 +1110,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker,
stmt,
body,
checker.semantic.stmt_parent(),
checker.semantic.current_statement_parent(),
);
}
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.
if !checker
.semantic
.scopes()
.current_scopes()
.any(|scope| scope.kind.is_any_function())
{
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.
if !checker
.semantic
.scopes()
.current_scopes()
.any(|scope| scope.kind.is_any_function())
{
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.
let expr = model.expr()?;
let expr = model.current_expression()?;
let string_range = self.indexer.f_string_range(expr.start())?;
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.
pub(crate) fn isolation(&self, parent: Option<&Stmt>) -> IsolationLevel {
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| {
IsolationLevel::Group(node_id.into())
})
@ -264,7 +264,7 @@ where
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
// Step 0: Pre-processing
self.semantic.push_stmt(stmt);
self.semantic.push_statement(stmt);
// Track whether we've seen docstrings, non-imports, etc.
match stmt {
@ -288,7 +288,7 @@ where
self.semantic.flags |= SemanticModelFlags::FUTURES_BOUNDARY;
if !self.semantic.seen_import_boundary()
&& !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;
}
@ -372,7 +372,7 @@ where
);
} else if &alias.name == "*" {
self.semantic
.scope_mut()
.current_scope_mut()
.add_star_import(StarImport { level, module });
} else {
let mut flags = BindingFlags::EXTERNAL;
@ -421,7 +421,7 @@ where
BindingKind::Global,
BindingFlags::GLOBAL,
);
let scope = self.semantic.scope_mut();
let scope = self.semantic.current_scope_mut();
scope.add(name, binding_id);
}
}
@ -444,7 +444,7 @@ where
BindingKind::Nonlocal(scope_id),
BindingFlags::NONLOCAL,
);
let scope = self.semantic.scope_mut();
let scope = self.semantic.current_scope_mut();
scope.add(name, binding_id);
}
}
@ -657,7 +657,7 @@ where
// available at runtime.
// See: https://docs.python.org/3/reference/simple_stmts.html#annotated-assignment-statements
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
.settings
.flake8_type_checking
@ -676,7 +676,7 @@ where
}
} else {
matches!(
self.semantic.scope().kind,
self.semantic.current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module
)
};
@ -777,7 +777,7 @@ where
analyze::statement(stmt, self);
self.semantic.flags = flags_snapshot;
self.semantic.pop_stmt();
self.semantic.pop_statement();
}
fn visit_annotation(&mut self, expr: &'b Expr) {
@ -813,7 +813,7 @@ where
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
// the node.
@ -841,7 +841,7 @@ where
}) => {
if let Expr::Name(ast::ExprName { id, ctx, range: _ }) = func.as_ref() {
if id == "locals" && ctx.is_load() {
let scope = self.semantic.scope_mut();
let scope = self.semantic.current_scope_mut();
scope.set_uses_locals();
}
}
@ -1231,7 +1231,7 @@ where
analyze::expression(expr, self);
self.semantic.flags = flags_snapshot;
self.semantic.pop_expr();
self.semantic.pop_expression();
}
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) {
let parent = self.semantic.stmt();
let parent = self.semantic.current_statement();
if matches!(
parent,
@ -1646,7 +1646,7 @@ impl<'a> Checker<'a> {
return;
}
let scope = self.semantic.scope();
let scope = self.semantic.current_scope();
if scope.kind.is_module()
&& match parent {
@ -1696,7 +1696,11 @@ impl<'a> Checker<'a> {
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(
id,
expr.range(),
@ -1721,7 +1725,7 @@ impl<'a> Checker<'a> {
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;
}
@ -1729,7 +1733,7 @@ impl<'a> Checker<'a> {
let binding_id =
self.semantic
.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);
}
@ -1821,7 +1825,7 @@ impl<'a> Checker<'a> {
for snapshot in deferred_functions {
self.semantic.restore(snapshot);
match &self.semantic.stmt() {
match &self.semantic.current_statement() {
Stmt::FunctionDef(ast::StmtFunctionDef {
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.
if model
.expr_parent()
.current_expression_parent()
.map_or(true, |parent| !parent.is_bin_op_expr())
{
if any_over_expr(expr, &has_string_literal) {

View file

@ -77,7 +77,7 @@ fn is_cache_func(expr: &Expr, semantic: &SemanticModel) -> bool {
/// B019
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;
}
for decorator in decorator_list {

View file

@ -97,7 +97,7 @@ pub(crate) fn setattr_with_constant(
if let Stmt::Expr(ast::StmtExpr {
value: child,
range: _,
}) = checker.semantic().stmt()
}) = checker.semantic().current_statement()
{
if expr == child.as_ref() {
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() {
// Avoid fixing if the variable, or any future bindings to the variable, are
// used _after_ the loop.
let scope = checker.semantic().scope();
let scope = checker.semantic().current_scope();
if scope
.get_all(name)
.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)) = (
checker.semantic().expr_grandparent(),
checker.semantic().expr_parent(),
checker.semantic().current_expression_grandparent(),
checker.semantic().current_expression_parent(),
) else {
checker.diagnostics.push(Diagnostic::new(
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
/// the current expression is a `datetime.datetime` object.
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")
})
}

View file

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

View file

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

View file

@ -119,7 +119,7 @@ pub(crate) fn non_self_return_type(
parameters: &Parameters,
async_: bool,
) {
let ScopeKind::Class(class_def) = checker.semantic().scope().kind else {
let ScopeKind::Class(class_def) = checker.semantic().current_scope().kind else {
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 {
if let Expr::Name(ast::ExprName { id, .. }) = target {
match id.as_str() {
"__all__" => semantic.scope().kind.is_module(),
"__match_args__" | "__slots__" => semantic.scope().kind.is_class(),
"__all__" => semantic.current_scope().kind.is_module(),
"__match_args__" | "__slots__" => semantic.current_scope().kind.is_class(),
_ => false,
}
} else {
@ -569,7 +569,9 @@ pub(crate) fn unannotated_assignment_in_stub(
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()) {
return;
}

View file

@ -63,7 +63,7 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
return;
}
if !checker.semantic().scope().kind.is_class() {
if !checker.semantic().current_scope().kind.is_class() {
return;
}
@ -96,11 +96,12 @@ pub(crate) fn str_or_repr_defined_in_stub(checker: &mut Checker, stmt: &Stmt) {
stmt.identifier(),
);
if checker.patch(diagnostic.kind.rule()) {
let stmt = checker.semantic().stmt();
let parent = checker.semantic().stmt_parent();
let stmt = checker.semantic().current_statement();
let parent = checker.semantic().current_statement_parent();
let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer());
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);

View file

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

View file

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

View file

@ -246,8 +246,8 @@ pub(crate) fn unittest_assertion(
if checker.patch(diagnostic.kind.rule()) {
// We're converting an expression to a statement, so avoid applying the fix if
// the assertion is part of a larger expression.
if checker.semantic().stmt().is_expr_stmt()
&& checker.semantic().expr_parent().is_none()
if checker.semantic().current_statement().is_expr_stmt()
&& checker.semantic().current_expression_parent().is_none()
&& !checker.indexer().comment_ranges().intersects(expr.range())
{
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__`).
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) =
checker.semantic().scope().kind
checker.semantic().current_scope().kind
{
if matches!(
name.as_str(),

View file

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

View file

@ -43,7 +43,7 @@ impl Violation for OpenFileWithContextHandler {
/// Return `true` if the current expression is nested in an `await
/// exit_stack.enter_async_context` call.
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;
};
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" {
return false;
}
for parent in semantic.parents() {
for parent in semantic.current_statements() {
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
for item in items {
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
/// `exit_stack.enter_context` call.
fn match_exit_stack(semantic: &SemanticModel) -> bool {
let Some(expr) = semantic.expr_parent() else {
let Some(expr) = semantic.current_expression_parent() else {
return false;
};
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
@ -89,7 +89,7 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool {
if attr != "enter_context" {
return false;
}
for parent in semantic.parents() {
for parent in semantic.current_statements() {
if let Stmt::With(ast::StmtWith { items, .. }) = parent {
for item in items {
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: ...`
if checker.semantic().stmt().is_with_stmt() {
if checker.semantic().current_statement().is_with_stmt() {
return;
}

View file

@ -60,7 +60,7 @@ impl Violation for ReimplementedBuiltin {
/// SIM110, SIM111
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;
}
@ -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 followed by `return True` or `return False`.
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 sibling = traversal::next_sibling(stmt, suite)?;
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 {
let ScopeKind::Class(class_def) = &semantic.scope().kind else {
let ScopeKind::Class(class_def) = &semantic.current_scope().kind else {
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 {
let ScopeKind::Class(class_def) = &semantic.scope().kind else {
let ScopeKind::Class(class_def) = &semantic.current_scope().kind else {
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());
if checker.patch(diagnostic.kind.rule()) {
// Delete the entire type-checking block.
let stmt = checker.semantic().stmt();
let parent = checker.semantic().stmt_parent();
let stmt = checker.semantic().current_statement();
let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer());
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()
})
{
let Some(stmt_id) = binding.source else {
let Some(statement_id) = binding.source else {
continue;
};
@ -113,20 +113,23 @@ pub(crate) fn runtime_import_in_type_checking_block(
})
{
ignores_by_statement
.entry(stmt_id)
.entry(statement_id)
.or_default()
.push(import);
} 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
// 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) {
fix_imports(checker, stmt_id, &imports).ok()
fix_imports(checker, statement_id, &imports).ok()
} else {
None
};
@ -189,9 +192,9 @@ struct ImportBinding<'a> {
}
/// Generate a [`Fix`] to remove runtime imports from a type-checking block.
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let stmt = checker.semantic().stmts[stmt_id];
let parent = checker.semantic().stmts.parent(stmt);
fn fix_imports(checker: &Checker, statement_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let statement = checker.semantic().statements[statement_id];
let parent = checker.semantic().statements.parent(statement);
let member_names: Vec<Cow<'_, str>> = imports
.iter()
.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.
let remove_import_edit = autofix::edits::remove_unused_imports(
member_names.iter().map(AsRef::as_ref),
stmt,
statement,
parent,
checker.locator(),
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.
let add_import_edit = checker.importer().runtime_import_edit(
&ImportedMembers {
statement: stmt,
statement,
names: member_names.iter().map(AsRef::as_ref).collect(),
},
at,

View file

@ -265,7 +265,7 @@ pub(crate) fn typing_only_runtime_import(
continue;
}
let Some(stmt_id) = binding.source else {
let Some(statement_id) = binding.source else {
continue;
};
@ -282,12 +282,12 @@ pub(crate) fn typing_only_runtime_import(
})
{
ignores_by_statement
.entry((stmt_id, import_type))
.entry((statement_id, import_type))
.or_default()
.push(import);
} else {
errors_by_statement
.entry((stmt_id, import_type))
.entry((statement_id, import_type))
.or_default()
.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
// 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)) {
fix_imports(checker, stmt_id, &imports).ok()
fix_imports(checker, statement_id, &imports).ok()
} else {
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.
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let stmt = checker.semantic().stmts[stmt_id];
let parent = checker.semantic().stmts.parent(stmt);
fn fix_imports(checker: &Checker, statement_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let statement = checker.semantic().statements[statement_id];
let parent = checker.semantic().statements.parent(statement);
let member_names: Vec<Cow<'_, str>> = imports
.iter()
.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.
let remove_import_edit = autofix::edits::remove_unused_imports(
member_names.iter().map(AsRef::as_ref),
stmt,
statement,
parent,
checker.locator(),
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.
let add_import_edit = checker.importer().typing_import_edit(
&ImportedMembers {
statement: stmt,
statement,
names: member_names.iter().map(AsRef::as_ref).collect(),
},
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()`).
if let Some(parent) = checker.semantic().expr_parent() {
if let Some(parent) = checker.semantic().current_expression_parent() {
if matches!(parent, Expr::Call(_)) {
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
// statement, and expressions can't contain statements).
if !seen_star
&& checker.semantic().stmt().is_expr_stmt()
&& checker.semantic().expr_parent().is_none()
&& checker.semantic().current_statement().is_expr_stmt()
&& checker.semantic().current_expression_parent().is_none()
{
if let Some(fix) = convert_inplace_argument_to_assignment(
call,

View file

@ -69,7 +69,7 @@ pub(crate) fn mixed_case_variable_in_class_scope(
return;
}
let parent = checker.semantic().stmt();
let parent = checker.semantic().current_statement();
if helpers::is_named_tuple_assignment(parent, 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;
}
let parent = checker.semantic().stmt();
let parent = checker.semantic().current_statement();
if helpers::is_named_tuple_assignment(parent, checker.semantic()) {
return;
}

View file

@ -65,7 +65,7 @@ pub(crate) fn non_lowercase_variable_in_function(checker: &mut Checker, expr: &E
return;
}
let parent = checker.semantic().stmt();
let parent = checker.semantic().current_statement();
if helpers::is_named_tuple_assignment(parent, checker.semantic())
|| helpers::is_typed_dict_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)
// ```
let scope = model.scope();
let scope = model.current_scope();
scope
.get_all(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,
..
}) => {
let scope = checker.semantic().scope();
let scope = checker.semantic().current_scope();
if let Some(binding_id) = scope.get(id) {
let binding = checker.semantic().binding(binding_id);
if binding.kind.is_assignment() || binding.kind.is_named_expr_assignment() {
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, .. })
| Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value), ..

View file

@ -116,10 +116,10 @@ pub(crate) fn lambda_assignment(
// 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/5421
if (annotation.is_some() && checker.semantic().scope().kind.is_class())
if (annotation.is_some() && checker.semantic().current_scope().kind.is_class())
|| checker
.semantic()
.scope()
.current_scope()
.get_all(id)
.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) {
if matches!(
checker.semantic().scope().kind,
checker.semantic().current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module
) {
checker

View file

@ -116,7 +116,7 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
continue;
};
let Some(stmt_id) = binding.source else {
let Some(statement_id) = binding.source else {
continue;
};
@ -132,12 +132,12 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
})
{
ignored
.entry((stmt_id, binding.exceptions))
.entry((statement_id, binding.exceptions))
.or_default()
.push(import);
} else {
unused
.entry((stmt_id, binding.exceptions))
.entry((statement_id, binding.exceptions))
.or_default()
.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
// statement (excluding those that are ignored).
for ((stmt_id, exceptions), imports) in unused {
for ((statement_id, exceptions), imports) in unused {
let in_except_handler =
exceptions.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
let multiple = imports.len() > 1;
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 {
None
};
@ -225,9 +225,9 @@ struct ImportBinding<'a> {
}
/// Generate a [`Fix`] to remove unused imports from a statement.
fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let stmt = checker.semantic().stmts[stmt_id];
let parent = checker.semantic().stmts.parent(stmt);
fn fix_imports(checker: &Checker, statement_id: NodeId, imports: &[ImportBinding]) -> Result<Fix> {
let statement = checker.semantic().statements[statement_id];
let parent = checker.semantic().statements.parent(statement);
let member_names: Vec<Cow<'_, str>> = imports
.iter()
@ -236,7 +236,7 @@ fn fix_imports(checker: &Checker, stmt_id: NodeId, imports: &[ImportBinding]) ->
let edit = autofix::edits::remove_unused_imports(
member_names.iter().map(AsRef::as_ref),
stmt,
statement,
parent,
checker.locator(),
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);
if checker.patch(diagnostic.kind.rule()) {
if let Some(source) = source {
let stmt = checker.semantic().stmts[source];
let parent = checker.semantic().stmts.parent(stmt);
let stmt = checker.semantic().statements[source];
let parent = checker.semantic().statements.parent(stmt);
if let Some(fix) = remove_unused_variable(stmt, parent, range, checker) {
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) {
if matches!(
checker.semantic().scope().kind,
checker.semantic().current_scope().kind,
ScopeKind::Class(_) | ScopeKind::Module
) {
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 {
let scope = semantic.scope();
let scope = semantic.current_scope();
let (ScopeKind::Function(ast::StmtFunctionDef {
name,
decorator_list,

View file

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

View file

@ -29,7 +29,7 @@ pub(crate) fn invalid_str_return(checker: &mut Checker, name: &str, body: &[Stmt
return;
}
if !checker.semantic().scope().kind.is_class() {
if !checker.semantic().current_scope().kind.is_class() {
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
// is not a self-assignment).
if checker.semantic().scope().kind.is_class() {
if checker.semantic().current_scope().kind.is_class() {
return;
}

View file

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

View file

@ -38,7 +38,7 @@ impl Violation for YieldFromInAsyncFunction {
/// PLE1700
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() {
checker
.diagnostics

View file

@ -149,7 +149,7 @@ pub(crate) fn native_literals(
if checker.semantic().in_f_string() {
if checker
.semantic()
.expr_ancestors()
.current_expressions()
.filter(|expr| expr.is_joined_str_expr())
.count()
> 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() {
// If we have a lone `if`, delete as statement (insert pass in parent if required)
None => {
let stmt = checker.semantic().stmt();
let parent = checker.semantic().stmt_parent();
let stmt = checker.semantic().current_statement();
let parent = checker.semantic().current_statement_parent();
let edit = delete_stmt(stmt, parent, checker.locator(), checker.indexer());
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) {
return;
}
let scope = checker.semantic().scope();
let scope = checker.semantic().current_scope();
// Check: are we in a Function scope?
if !scope.kind.is_any_function() {
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
// 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()) {
diagnostic.try_set_fix(|| {
let stmt = checker.semantic().stmt();
let parent = checker.semantic().stmt_parent();
let statement = checker.semantic().current_statement();
let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::remove_unused_imports(
unused_imports
.iter()
.map(|alias| &alias.name)
.map(ruff_python_ast::Identifier::as_str),
stmt,
statement,
parent,
checker.locator(),
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()) {
diagnostic.try_set_fix(|| {
let stmt = checker.semantic().stmt();
let parent = checker.semantic().stmt_parent();
let statement = checker.semantic().current_statement();
let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::remove_unused_imports(
unused_imports
.iter()
.map(|alias| &alias.name)
.map(ruff_python_ast::Identifier::as_str),
stmt,
statement,
parent,
checker.locator(),
checker.stylist(),

View file

@ -63,8 +63,8 @@ pub(crate) fn useless_metaclass_type(
let mut diagnostic = Diagnostic::new(UselessMetaclassType, stmt.range());
if checker.patch(diagnostic.kind.rule()) {
let stmt = checker.semantic().stmt();
let parent = checker.semantic().stmt_parent();
let stmt = checker.semantic().current_statement();
let parent = checker.semantic().current_statement_parent();
let edit = autofix::edits::delete_stmt(stmt, parent, checker.locator(), checker.indexer());
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) {
// If the expression is already a child of an addition, we'll have analyzed it already.
if matches!(
checker.semantic().expr_parent(),
checker.semantic().current_expression_parent(),
Some(Expr::BinOp(ast::ExprBinOp {
op: Operator::Add,
..

View file

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

View file

@ -31,16 +31,16 @@ pub struct SemanticModel<'a> {
module_path: Option<&'a [String]>,
/// Stack of all visited statements.
pub stmts: Nodes<'a, Stmt>,
pub statements: Nodes<'a, Stmt>,
/// The identifier of the current statement.
stmt_id: Option<NodeId>,
statement_id: Option<NodeId>,
/// Stack of all visited expressions.
exprs: Nodes<'a, Expr>,
expressions: Nodes<'a, Expr>,
/// 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.
pub scopes: Scopes<'a>,
@ -132,10 +132,10 @@ impl<'a> SemanticModel<'a> {
Self {
typing_modules,
module_path: module.path(),
stmts: Nodes::<Stmt>::default(),
stmt_id: None,
exprs: Nodes::<Expr>::default(),
expr_id: None,
statements: Nodes::<Stmt>::default(),
statement_id: None,
expressions: Nodes::<Expr>::default(),
expression_id: None,
scopes: Scopes::default(),
scope_id: ScopeId::global(),
definitions: Definitions::for_module(module),
@ -225,7 +225,7 @@ impl<'a> SemanticModel<'a> {
flags,
references: Vec::new(),
scope: self.scope_id,
source: self.stmt_id,
source: self.statement_id,
context: self.execution_context(),
exceptions: self.exceptions(),
})
@ -233,7 +233,7 @@ impl<'a> SemanticModel<'a> {
/// Return the current [`Binding`] for a given `name`.
pub fn find_binding(&self, member: &str) -> Option<&Binding> {
self.scopes()
self.current_scopes()
.find_map(|scope| scope.get(member))
.map(|binding_id| &self.bindings[binding_id])
}
@ -686,102 +686,110 @@ impl<'a> SemanticModel<'a> {
) -> Option<ImportedName> {
// TODO(charlie): Pass in a slice.
let module_path: Vec<&str> = module.split('.').collect();
self.scopes().enumerate().find_map(|(scope_index, scope)| {
scope.bindings().find_map(|(name, binding_id)| {
let binding = &self.bindings[binding_id];
match &binding.kind {
// Ex) Given `module="sys"` and `object="exit"`:
// `import sys` -> `sys.exit`
// `import sys as sys2` -> `sys2.exit`
BindingKind::Import(Import { call_path }) => {
if call_path.as_ref() == module_path.as_slice() {
if let Some(source) = binding.source {
// Verify that `sys` isn't bound in an inner scope.
if self
.scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: format!("{name}.{member}"),
range: self.stmts[source].range(),
context: binding.context,
});
}
}
}
}
// Ex) Given `module="os.path"` and `object="join"`:
// `from os.path import join` -> `join`
// `from os.path import join as join2` -> `join2`
BindingKind::FromImport(FromImport { call_path }) => {
if let Some((target_member, target_module)) = call_path.split_last() {
if target_module == module_path.as_slice() && target_member == &member {
self.current_scopes()
.enumerate()
.find_map(|(scope_index, scope)| {
scope.bindings().find_map(|(name, binding_id)| {
let binding = &self.bindings[binding_id];
match &binding.kind {
// Ex) Given `module="sys"` and `object="exit"`:
// `import sys` -> `sys.exit`
// `import sys as sys2` -> `sys2.exit`
BindingKind::Import(Import { call_path }) => {
if call_path.as_ref() == module_path.as_slice() {
if let Some(source) = binding.source {
// Verify that `join` isn't bound in an inner scope.
// Verify that `sys` isn't bound in an inner scope.
if self
.scopes()
.current_scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: (*name).to_string(),
range: self.stmts[source].range(),
name: format!("{name}.{member}"),
range: self.statements[source].range(),
context: binding.context,
});
}
}
}
}
}
// Ex) Given `module="os"` and `object="name"`:
// `import os.path ` -> `os.name`
BindingKind::SubmoduleImport(SubmoduleImport { .. }) => {
if name == module {
if let Some(source) = binding.source {
// Verify that `os` isn't bound in an inner scope.
if self
.scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
// Ex) Given `module="os.path"` and `object="join"`:
// `from os.path import join` -> `join`
// `from os.path import join as join2` -> `join2`
BindingKind::FromImport(FromImport { call_path }) => {
if let Some((target_member, target_module)) = call_path.split_last() {
if target_module == module_path.as_slice()
&& target_member == &member
{
return Some(ImportedName {
name: format!("{name}.{member}"),
range: self.stmts[source].range(),
context: binding.context,
});
if let Some(source) = binding.source {
// Verify that `join` isn't bound in an inner scope.
if self
.current_scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: (*name).to_string(),
range: self.statements[source].range(),
context: binding.context,
});
}
}
}
}
}
// Ex) Given `module="os"` and `object="name"`:
// `import os.path ` -> `os.name`
BindingKind::SubmoduleImport(SubmoduleImport { .. }) => {
if name == module {
if let Some(source) = binding.source {
// Verify that `os` isn't bound in an inner scope.
if self
.current_scopes()
.take(scope_index)
.all(|scope| !scope.has(name))
{
return Some(ImportedName {
name: format!("{name}.{member}"),
range: self.statements[source].range(),
context: binding.context,
});
}
}
}
}
// Non-imports.
_ => {}
}
// Non-imports.
_ => {}
}
None
None
})
})
})
}
/// Push a [`Stmt`] onto the stack.
pub fn push_stmt(&mut self, stmt: &'a Stmt) {
self.stmt_id = Some(self.stmts.insert(stmt, self.stmt_id));
pub fn push_statement(&mut self, stmt: &'a Stmt) {
self.statement_id = Some(self.statements.insert(stmt, self.statement_id));
}
/// Pop the current [`Stmt`] off the stack.
pub fn pop_stmt(&mut self) {
let node_id = self.stmt_id.expect("Attempted to pop without statement");
self.stmt_id = self.stmts.parent_id(node_id);
pub fn pop_statement(&mut self) {
let node_id = self
.statement_id
.expect("Attempted to pop without statement");
self.statement_id = self.statements.parent_id(node_id);
}
/// Push a [`Expr`] onto the stack.
pub fn push_expr(&mut self, expr: &'a Expr) {
self.expr_id = Some(self.exprs.insert(expr, self.expr_id));
pub fn push_expression(&mut self, expr: &'a Expr) {
self.expression_id = Some(self.expressions.insert(expr, self.expression_id));
}
/// Pop the current [`Expr`] off the stack.
pub fn pop_expr(&mut self) {
let node_id = self.expr_id.expect("Attempted to pop without expression");
self.expr_id = self.exprs.parent_id(node_id);
pub fn pop_expression(&mut self) {
let node_id = self
.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.
@ -811,69 +819,94 @@ impl<'a> SemanticModel<'a> {
}
/// Return the current `Stmt`.
pub fn stmt(&self) -> &'a Stmt {
let node_id = self.stmt_id.expect("No current statement");
self.stmts[node_id]
pub fn current_statement(&self) -> &'a Stmt {
let node_id = self.statement_id.expect("No current statement");
self.statements[node_id]
}
/// Return the parent `Stmt` of the current `Stmt`, if any.
pub fn stmt_parent(&self) -> Option<&'a Stmt> {
let node_id = self.stmt_id.expect("No current statement");
let parent_id = self.stmts.parent_id(node_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
/// Returns an [`Iterator`] over the current statement hierarchy, from the current [`Stmt`]
/// through to any parents.
pub fn current_statements(&self) -> impl Iterator<Item = &'a Stmt> + '_ {
self.statement_id
.iter()
.copied()
.flat_map(|id| {
self.exprs
.ancestor_ids(id)
.skip(1)
.map(|id| &self.exprs[id])
self.statements
.ancestor_ids(*id)
.map(|id| &self.statements[id])
})
.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> {
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> {
self.scopes.global_mut()
}
/// Returns the current top most scope.
pub fn scope(&self) -> &Scope<'a> {
/// Returns the current top-most [`Scope`].
pub fn current_scope(&self) -> &Scope<'a> {
&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>> {
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>> {
let mut current_scope = scope;
while let Some(parent) = self.parent_scope(current_scope) {
@ -886,22 +919,6 @@ impl<'a> SemanticModel<'a> {
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`].
pub fn set_globals(&mut self, globals: Globals<'a>) {
// If any global bindings don't already exist in the global scope, add them.
@ -916,7 +933,7 @@ impl<'a> SemanticModel<'a> {
range: *range,
references: Vec::new(),
scope: self.scope_id,
source: self.stmt_id,
source: self.statement_id,
context: self.execution_context(),
exceptions: self.exceptions(),
flags: BindingFlags::empty(),
@ -964,13 +981,13 @@ impl<'a> SemanticModel<'a> {
pub fn at_top_level(&self) -> bool {
self.scope_id.is_global()
&& self
.stmt_id
.map_or(true, |stmt_id| self.stmts.parent_id(stmt_id).is_none())
.statement_id
.map_or(true, |stmt_id| self.statements.parent_id(stmt_id).is_none())
}
/// Return `true` if the model is in an async context.
pub fn in_async_context(&self) -> bool {
for scope in self.scopes() {
for scope in self.current_scopes() {
if scope.kind.is_async_function() {
return true;
}
@ -1048,8 +1065,8 @@ impl<'a> SemanticModel<'a> {
pub fn snapshot(&self) -> Snapshot {
Snapshot {
scope_id: self.scope_id,
stmt_id: self.stmt_id,
expr_id: self.expr_id,
stmt_id: self.statement_id,
expr_id: self.expression_id,
definition_id: self.definition_id,
flags: self.flags,
}
@ -1065,8 +1082,8 @@ impl<'a> SemanticModel<'a> {
flags,
} = snapshot;
self.scope_id = scope_id;
self.stmt_id = stmt_id;
self.expr_id = expr_id;
self.statement_id = stmt_id;
self.expression_id = expr_id;
self.definition_id = definition_id;
self.flags = flags;
}