Add box to stmt

This commit is contained in:
Charlie Marsh
2025-12-11 11:24:31 -05:00
parent c9155d5e72
commit cc850ec348
123 changed files with 2523 additions and 2786 deletions

View File

@@ -7,10 +7,7 @@ use crate::{BindingId, SemanticModel};
use ruff_python_ast as ast;
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{
ExceptHandler, Expr, ExprName, ExprStarred, ExprSubscript, ExprTuple, Stmt, StmtFor, StmtIf,
StmtMatch, StmtTry, StmtWhile, StmtWith,
};
use ruff_python_ast::{ExceptHandler, Expr, ExprName, ExprStarred, ExprSubscript, ExprTuple, Stmt};
/// Return `true` if any base class matches a [`QualifiedName`] predicate.
pub fn any_qualified_base_class(
@@ -183,18 +180,17 @@ pub fn any_member_declaration(
Stmt::FunctionDef(function_def) => Some(ClassMemberKind::FunctionDef(function_def)),
Stmt::Assign(assign) => Some(ClassMemberKind::Assign(assign)),
Stmt::AnnAssign(assign) => Some(ClassMemberKind::AnnAssign(assign)),
Stmt::With(StmtWith { body, .. }) => {
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound) {
Stmt::With(node) => {
if any_stmt_in_body(&node.body, func, ClassMemberBoundness::PossiblyUnbound) {
return true;
}
None
}
Stmt::For(StmtFor { body, orelse, .. })
| Stmt::While(StmtWhile { body, orelse, .. }) => {
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound)
|| any_stmt_in_body(orelse, func, ClassMemberBoundness::PossiblyUnbound)
Stmt::For(node) => {
if any_stmt_in_body(&node.body, func, ClassMemberBoundness::PossiblyUnbound)
|| any_stmt_in_body(&node.orelse, func, ClassMemberBoundness::PossiblyUnbound)
{
return true;
}
@@ -202,13 +198,19 @@ pub fn any_member_declaration(
None
}
Stmt::If(StmtIf {
body,
elif_else_clauses,
..
}) => {
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound)
|| elif_else_clauses.iter().any(|it| {
Stmt::While(node) => {
if any_stmt_in_body(&node.body, func, ClassMemberBoundness::PossiblyUnbound)
|| any_stmt_in_body(&node.orelse, func, ClassMemberBoundness::PossiblyUnbound)
{
return true;
}
None
}
Stmt::If(node) => {
if any_stmt_in_body(&node.body, func, ClassMemberBoundness::PossiblyUnbound)
|| node.elif_else_clauses.iter().any(|it| {
any_stmt_in_body(&it.body, func, ClassMemberBoundness::PossiblyUnbound)
})
{
@@ -217,8 +219,8 @@ pub fn any_member_declaration(
None
}
Stmt::Match(StmtMatch { cases, .. }) => {
if cases.iter().any(|it| {
Stmt::Match(node) => {
if node.cases.iter().any(|it| {
any_stmt_in_body(&it.body, func, ClassMemberBoundness::PossiblyUnbound)
}) {
return true;
@@ -227,17 +229,11 @@ pub fn any_member_declaration(
None
}
Stmt::Try(StmtTry {
body,
handlers,
orelse,
finalbody,
..
}) => {
if any_stmt_in_body(body, func, ClassMemberBoundness::PossiblyUnbound)
|| any_stmt_in_body(orelse, func, ClassMemberBoundness::PossiblyUnbound)
|| any_stmt_in_body(finalbody, func, ClassMemberBoundness::PossiblyUnbound)
|| handlers.iter().any(|ExceptHandler::ExceptHandler(it)| {
Stmt::Try(node) => {
if any_stmt_in_body(&node.body, func, ClassMemberBoundness::PossiblyUnbound)
|| any_stmt_in_body(&node.orelse, func, ClassMemberBoundness::PossiblyUnbound)
|| any_stmt_in_body(&node.finalbody, func, ClassMemberBoundness::PossiblyUnbound)
|| node.handlers.iter().any(|ExceptHandler::ExceptHandler(it)| {
any_stmt_in_body(&it.body, func, ClassMemberBoundness::PossiblyUnbound)
})
{
@@ -336,12 +332,10 @@ impl IsMetaclass {
fn has_metaclass_new_signature(class_def: &ast::StmtClassDef, semantic: &SemanticModel) -> bool {
// Look for a __new__ method in the class body
for stmt in &class_def.body {
let ast::Stmt::FunctionDef(ast::StmtFunctionDef {
name, parameters, ..
}) = stmt
else {
let ast::Stmt::FunctionDef(func_def) = stmt else {
continue;
};
let (name, parameters) = (&func_def.name, &func_def.parameters);
if name != "__new__" {
continue;

View File

@@ -1,6 +1,6 @@
use ruff_python_ast::helpers::map_callable;
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
use ruff_python_ast::{Decorator, Expr, Stmt, StmtExpr, StmtFunctionDef, StmtRaise};
use ruff_python_ast::{Decorator, Expr, Stmt, StmtExpr, StmtFunctionDef};
use crate::model::SemanticModel;
use crate::scope::Scope;
@@ -146,12 +146,7 @@ pub fn is_stub(function_def: &StmtFunctionDef, semantic: &SemanticModel) -> bool
Expr::StringLiteral(_) | Expr::EllipsisLiteral(_)
)
}
Stmt::Raise(StmtRaise {
range: _,
node_index: _,
exc: exception,
cause: _,
}) => exception.as_ref().is_some_and(|exc| {
Stmt::Raise(raise_stmt) => raise_stmt.exc.as_ref().is_some_and(|exc| {
semantic
.resolve_builtin_symbol(map_callable(exc))
.is_some_and(|name| matches!(name, "NotImplementedError" | "NotImplemented"))

View File

@@ -12,11 +12,7 @@ use crate::SemanticModel;
/// ```
pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool {
match stmt {
Stmt::Expr(ast::StmtExpr {
value,
range: _,
node_index: _,
}) => match value.as_ref() {
Stmt::Expr(node) => match node.value.as_ref() {
Expr::Call(ast::ExprCall { func, .. }) => semantic
.resolve_qualified_name(func.as_ref())
.is_some_and(|qualified_name| {
@@ -38,8 +34,8 @@ pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool {
}),
_ => false,
},
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => semantic
.resolve_qualified_name(map_subscript(target))
Stmt::AugAssign(node) => semantic
.resolve_qualified_name(map_subscript(&node.target))
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["sys", "path"])),
_ => false,
}
@@ -53,7 +49,7 @@ pub fn is_sys_path_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool {
/// ```
pub fn is_os_environ_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool {
match stmt {
Stmt::Expr(ast::StmtExpr { value, .. }) => match value.as_ref() {
Stmt::Expr(node) => match node.value.as_ref() {
Expr::Call(ast::ExprCall { func, .. }) => semantic
.resolve_qualified_name(func.as_ref())
.is_some_and(|qualified_name| {
@@ -69,25 +65,25 @@ pub fn is_os_environ_modification(stmt: &Stmt, semantic: &SemanticModel) -> bool
}),
_ => false,
},
Stmt::Delete(ast::StmtDelete { targets, .. }) => targets.iter().any(|target| {
Stmt::Delete(node) => node.targets.iter().any(|target| {
semantic
.resolve_qualified_name(map_subscript(target))
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["os", "environ"])
})
}),
Stmt::Assign(ast::StmtAssign { targets, .. }) => targets.iter().any(|target| {
Stmt::Assign(node) => node.targets.iter().any(|target| {
semantic
.resolve_qualified_name(map_subscript(target))
.is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["os", "environ"])
})
}),
Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => semantic
.resolve_qualified_name(map_subscript(target))
Stmt::AnnAssign(node) => semantic
.resolve_qualified_name(map_subscript(&node.target))
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["os", "environ"])),
Stmt::AugAssign(ast::StmtAugAssign { target, .. }) => semantic
.resolve_qualified_name(map_subscript(target))
Stmt::AugAssign(node) => semantic
.resolve_qualified_name(map_subscript(&node.target))
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["os", "environ"])),
_ => false,
}

View File

@@ -41,23 +41,31 @@ impl Terminal {
for stmt in stmts {
match stmt {
Stmt::For(ast::StmtFor { body, orelse, .. })
| Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
if always_breaks(body) {
Stmt::For(node) => {
if always_breaks(&node.body) {
continue;
}
terminal = terminal.and_then(Self::from_body(body));
terminal = terminal.and_then(Self::from_body(&node.body));
if !sometimes_breaks(body) {
terminal = terminal.and_then(Self::from_body(orelse));
if !sometimes_breaks(&node.body) {
terminal = terminal.and_then(Self::from_body(&node.orelse));
}
}
Stmt::If(ast::StmtIf {
body,
elif_else_clauses,
..
}) => {
Stmt::While(node) => {
if always_breaks(&node.body) {
continue;
}
terminal = terminal.and_then(Self::from_body(&node.body));
if !sometimes_breaks(&node.body) {
terminal = terminal.and_then(Self::from_body(&node.orelse));
}
}
Stmt::If(node) => {
let body = &node.body;
let elif_else_clauses = &node.elif_else_clauses;
let branch_terminal = Terminal::branches(
std::iter::once(Self::from_body(body)).chain(
elif_else_clauses
@@ -77,14 +85,14 @@ impl Terminal {
terminal = terminal.and_then(Terminal::ConditionalReturn);
}
}
Stmt::Match(ast::StmtMatch { cases, .. }) => {
Stmt::Match(node) => {
let branch_terminal = terminal.and_then(Terminal::branches(
cases.iter().map(|case| Self::from_body(&case.body)),
node.cases.iter().map(|case| Self::from_body(&case.body)),
));
// If the `match` is known to be exhaustive (by way of including a wildcard
// pattern)...
if cases.iter().any(is_wildcard) {
if node.cases.iter().any(is_wildcard) {
// And all branches return, then the `match` statement returns.
terminal = terminal.and_then(branch_terminal);
} else {
@@ -95,27 +103,21 @@ impl Terminal {
}
}
}
Stmt::Try(ast::StmtTry {
body,
handlers,
orelse,
finalbody,
..
}) => {
Stmt::Try(node) => {
// If the body returns, then this can't be a non-returning function. We assume
// that _any_ statement in the body could raise an exception, so we don't
// consider the body to be exhaustive. In other words, we assume the exception
// handlers exist for a reason.
let body_terminal = Self::from_body(body);
let body_terminal = Self::from_body(&node.body);
if body_terminal.has_any_return() {
terminal = terminal.and_then(Terminal::ConditionalReturn);
}
// If the `finally` block returns, the `try` block must also return. (Similarly,
// if the `finally` block raises, the `try` block must also raise.)
terminal = terminal.and_then(Self::from_body(finalbody));
terminal = terminal.and_then(Self::from_body(&node.finalbody));
let branch_terminal = Terminal::branches(handlers.iter().map(|handler| {
let branch_terminal = Terminal::branches(node.handlers.iter().map(|handler| {
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
body,
..
@@ -123,7 +125,7 @@ impl Terminal {
Self::from_body(body)
}));
if orelse.is_empty() {
if node.orelse.is_empty() {
// If there's no `else`, we may fall through, so only mark that this can't
// be a non-returning function if any of the branches return.
if branch_terminal.has_any_return() {
@@ -133,11 +135,11 @@ impl Terminal {
// If there's an `else`, we won't fall through. If all the handlers and
// the `else` block return,, the `try` block also returns.
terminal =
terminal.and_then(branch_terminal.branch(Terminal::from_body(orelse)));
terminal.and_then(branch_terminal.branch(Terminal::from_body(&node.orelse)));
}
}
Stmt::With(ast::StmtWith { body, .. }) => {
terminal = terminal.and_then(Self::from_body(body));
Stmt::With(node) => {
terminal = terminal.and_then(Self::from_body(&node.body));
}
Stmt::Return(_) => {
terminal = terminal.and_then(Terminal::RaiseOrReturn);
@@ -247,62 +249,52 @@ impl Terminal {
fn sometimes_breaks(stmts: &[Stmt]) -> bool {
for stmt in stmts {
match stmt {
Stmt::For(ast::StmtFor { body, orelse, .. }) => {
if Terminal::from_body(body).has_any_return() {
Stmt::For(node) => {
if Terminal::from_body(&node.body).has_any_return() {
return false;
}
if sometimes_breaks(orelse) {
if sometimes_breaks(&node.orelse) {
return true;
}
}
Stmt::While(ast::StmtWhile { body, orelse, .. }) => {
if Terminal::from_body(body).has_any_return() {
Stmt::While(node) => {
if Terminal::from_body(&node.body).has_any_return() {
return false;
}
if sometimes_breaks(orelse) {
if sometimes_breaks(&node.orelse) {
return true;
}
}
Stmt::If(ast::StmtIf {
body,
elif_else_clauses,
..
}) => {
if std::iter::once(body)
.chain(elif_else_clauses.iter().map(|clause| &clause.body))
Stmt::If(node) => {
if std::iter::once(&node.body)
.chain(node.elif_else_clauses.iter().map(|clause| &clause.body))
.any(|body| sometimes_breaks(body))
{
return true;
}
}
Stmt::Match(ast::StmtMatch { cases, .. }) => {
if cases.iter().any(|case| sometimes_breaks(&case.body)) {
Stmt::Match(node) => {
if node.cases.iter().any(|case| sometimes_breaks(&case.body)) {
return true;
}
}
Stmt::Try(ast::StmtTry {
body,
handlers,
orelse,
finalbody,
..
}) => {
if sometimes_breaks(body)
|| handlers.iter().any(|handler| {
Stmt::Try(node) => {
if sometimes_breaks(&node.body)
|| node.handlers.iter().any(|handler| {
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
body,
..
}) = handler;
sometimes_breaks(body)
})
|| sometimes_breaks(orelse)
|| sometimes_breaks(finalbody)
|| sometimes_breaks(&node.orelse)
|| sometimes_breaks(&node.finalbody)
{
return true;
}
}
Stmt::With(ast::StmtWith { body, .. }) => {
if sometimes_breaks(body) {
Stmt::With(node) => {
if sometimes_breaks(&node.body) {
return true;
}
}

View File

@@ -5,7 +5,6 @@ use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{
self as ast, Expr, ExprCall, ExprName, Operator, ParameterWithDefault, Parameters, Stmt,
StmtAssign,
};
use ruff_python_stdlib::typing::{
as_pep_585_generic, is_immutable_generic_type, is_immutable_non_generic_type,
@@ -366,9 +365,13 @@ pub fn is_immutable_newtype_call(
return false;
}
let Some(Stmt::Assign(StmtAssign { value, .. })) = binding.statement(semantic) else {
let Some(stmt) = binding.statement(semantic) else {
return false;
};
let Stmt::Assign(node) = stmt else {
return false;
};
let value = &node.value;
let Expr::Call(ExprCall {
func, arguments, ..
@@ -632,18 +635,20 @@ pub fn check_type<T: TypeChecker>(binding: &Binding, semantic: &SemanticModel) -
// ```
//
// The type checker might know how to infer the type based on `init_expr`.
Some(Stmt::Assign(ast::StmtAssign { targets, value, .. })) => targets
.iter()
.find_map(|target| match_value(binding, target, value))
.is_some_and(|value| T::match_initializer(value, semantic)),
Some(Stmt::Assign(node)) => {
node.targets
.iter()
.find_map(|target| match_value(binding, target, &node.value))
.is_some_and(|value| T::match_initializer(value, semantic))
}
// ```python
// x: annotation = some_expr
// ```
//
// In this situation, we check only the annotation.
Some(Stmt::AnnAssign(ast::StmtAnnAssign { annotation, .. })) => {
T::match_annotation(annotation, semantic)
Some(Stmt::AnnAssign(node)) => {
T::match_annotation(&node.annotation, semantic)
}
_ => false,
@@ -670,14 +675,16 @@ pub fn check_type<T: TypeChecker>(binding: &Binding, semantic: &SemanticModel) -
// with open("file.txt") as x:
// ...
// ```
Some(Stmt::With(ast::StmtWith { items, .. })) => items
.iter()
.find_map(|item| {
let target = item.optional_vars.as_ref()?;
let value = &item.context_expr;
match_value(binding, target, value)
})
.is_some_and(|value| T::match_initializer(value, semantic)),
Some(Stmt::With(node)) => {
node.items
.iter()
.find_map(|item| {
let target = item.optional_vars.as_ref()?;
let value = &item.context_expr;
match_value(binding, target, value)
})
.is_some_and(|value| T::match_initializer(value, semantic))
}
_ => false,
},
@@ -689,8 +696,8 @@ pub fn check_type<T: TypeChecker>(binding: &Binding, semantic: &SemanticModel) -
// ```
//
// We trust the annotation and see if the type checker matches the annotation.
Some(Stmt::FunctionDef(ast::StmtFunctionDef { parameters, .. })) => {
let Some(parameter) = find_parameter(parameters, binding) else {
Some(Stmt::FunctionDef(node)) => {
let Some(parameter) = find_parameter(&node.parameters, binding) else {
return false;
};
let Some(annotation) = parameter.annotation() else {
@@ -708,8 +715,8 @@ pub fn check_type<T: TypeChecker>(binding: &Binding, semantic: &SemanticModel) -
// ```
//
// It's a typed declaration, type annotation is the only source of information.
Some(Stmt::AnnAssign(ast::StmtAnnAssign { annotation, .. })) => {
T::match_annotation(annotation, semantic)
Some(Stmt::AnnAssign(node)) => {
T::match_annotation(&node.annotation, semantic)
}
_ => false,
},
@@ -719,9 +726,11 @@ pub fn check_type<T: TypeChecker>(binding: &Binding, semantic: &SemanticModel) -
// def foo() -> int:
// ...
// ```
Some(Stmt::FunctionDef(ast::StmtFunctionDef { returns, .. })) => returns
.as_ref()
.is_some_and(|return_ann| T::match_annotation(return_ann, semantic)),
Some(Stmt::FunctionDef(node)) => {
node.returns
.as_ref()
.is_some_and(|return_ann| T::match_annotation(return_ann, semantic))
}
_ => false,
},
@@ -1038,12 +1047,12 @@ pub fn is_dict(binding: &Binding, semantic: &SemanticModel) -> bool {
// ...
// ```
if matches!(binding.kind, BindingKind::Argument) {
if let Some(Stmt::FunctionDef(ast::StmtFunctionDef { parameters, .. })) =
binding.statement(semantic)
{
if let Some(kwarg_parameter) = parameters.kwarg.as_deref() {
if kwarg_parameter.name.range() == binding.range() {
return true;
if let Some(stmt) = binding.statement(semantic) {
if let Stmt::FunctionDef(node) = stmt {
if let Some(kwarg_parameter) = node.parameters.kwarg.as_deref() {
if kwarg_parameter.name.range() == binding.range() {
return true;
}
}
}
}
@@ -1091,12 +1100,12 @@ pub fn is_tuple(binding: &Binding, semantic: &SemanticModel) -> bool {
// ...
// ```
if matches!(binding.kind, BindingKind::Argument) {
if let Some(Stmt::FunctionDef(ast::StmtFunctionDef { parameters, .. })) =
binding.statement(semantic)
{
if let Some(arg_parameter) = parameters.vararg.as_deref() {
if arg_parameter.name.range() == binding.range() {
return true;
if let Some(stmt) = binding.statement(semantic) {
if let Stmt::FunctionDef(node) = stmt {
if let Some(arg_parameter) = node.parameters.vararg.as_deref() {
if arg_parameter.name.range() == binding.range() {
return true;
}
}
}
}
@@ -1183,10 +1192,14 @@ pub fn resolve_assignment<'a>(
let binding_id = semantic.resolve_name(name)?;
let statement = semantic.binding(binding_id).statement(semantic)?;
match statement {
Stmt::Assign(ast::StmtAssign { value, .. })
| Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value), ..
}) => {
Stmt::Assign(node) => {
let ast::ExprCall { func, .. } = node.value.as_call_expr()?;
let qualified_name = semantic.resolve_qualified_name(func)?;
Some(qualified_name.extend_members(reversed_tail.into_iter().rev()))
}
Stmt::AnnAssign(node) => {
let value = node.value.as_ref()?;
let ast::ExprCall { func, .. } = value.as_call_expr()?;
let qualified_name = semantic.resolve_qualified_name(func)?;
@@ -1237,24 +1250,23 @@ pub fn find_binding_value<'a>(binding: &Binding, semantic: &'a SemanticModel) ->
}
// Ex) `x = 1`
BindingKind::Assignment => match binding.statement(semantic) {
Some(Stmt::Assign(ast::StmtAssign { value, targets, .. })) => {
return targets
Some(Stmt::Assign(node)) => {
return node
.targets
.iter()
.find_map(|target| match_value(binding, target, value));
.find_map(|target| match_value(binding, target, &node.value));
}
Some(Stmt::AnnAssign(ast::StmtAnnAssign {
value: Some(value),
target,
..
})) => {
return match_value(binding, target, value);
Some(Stmt::AnnAssign(node)) => {
if let Some(value) = &node.value {
return match_value(binding, &node.target, value);
}
}
_ => {}
},
// Ex) `with open("file.txt") as f:`
BindingKind::WithItemVar => match binding.statement(semantic) {
Some(Stmt::With(ast::StmtWith { items, .. })) => {
return items.iter().find_map(|item| {
Some(Stmt::With(node)) => {
return node.items.iter().find_map(|item| {
let target = item.optional_vars.as_ref()?;
let value = &item.context_expr;
match_value(binding, target, value)

View File

@@ -4,7 +4,7 @@
use std::ops::Index;
use ruff_python_ast::{self as ast, Stmt};
use ruff_python_ast::Stmt;
use ruff_text_size::{Ranged, TextRange};
use rustc_hash::FxHashMap;
@@ -74,12 +74,8 @@ impl<'a> GlobalsVisitor<'a> {
impl<'a> StatementVisitor<'a> for GlobalsVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match stmt {
Stmt::Global(ast::StmtGlobal {
names,
range: _,
node_index: _,
}) => {
for name in names {
Stmt::Global(global_stmt) => {
for name in &global_stmt.names {
self.0.insert(name.as_str(), name.range());
}
}

View File

@@ -204,7 +204,7 @@ impl serde::Serialize for NameImport {
#[cfg(feature = "serde")]
impl<'de> serde::de::Deserialize<'de> for NameImports {
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use ruff_python_ast::{self as ast, Stmt};
use ruff_python_ast::Stmt;
use ruff_python_parser::Parsed;
struct AnyNameImportsVisitor;
@@ -225,30 +225,22 @@ impl<'de> serde::de::Deserialize<'de> for NameImports {
};
let imports = match stmt {
Stmt::ImportFrom(ast::StmtImportFrom {
module,
names,
level,
range: _,
node_index: _,
}) => names
Stmt::ImportFrom(import_from) => import_from
.names
.iter()
.map(|name| {
NameImport::ImportFrom(MemberNameImport {
module: module.as_deref().map(ToString::to_string),
module: import_from.module.as_deref().map(ToString::to_string),
name: Alias {
name: name.name.to_string(),
as_name: name.asname.as_deref().map(ToString::to_string),
},
level: *level,
level: import_from.level,
})
})
.collect(),
Stmt::Import(ast::StmtImport {
names,
range: _,
node_index: _,
}) => names
Stmt::Import(import_stmt) => import_stmt
.names
.iter()
.map(|name| {
NameImport::Import(ModuleNameImport {

View File

@@ -102,9 +102,9 @@ impl SemanticModel<'_> {
let mut flags = DunderAllFlags::empty();
if let Some(value) = match stmt {
Stmt::Assign(ast::StmtAssign { value, .. }) => Some(value),
Stmt::AnnAssign(ast::StmtAnnAssign { value, .. }) => value.as_ref(),
Stmt::AugAssign(ast::StmtAugAssign { value, .. }) => Some(value),
Stmt::Assign(node) => Some(&node.value),
Stmt::AnnAssign(node) => node.value.as_ref(),
Stmt::AugAssign(node) => Some(&node.value),
_ => None,
} {
if let Expr::BinOp(ast::ExprBinOp { left, right, .. }) = value.as_ref() {