mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
Fix duplicate unpack diagnostics (#14125)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
parent
a56ee9268e
commit
31681f66c9
4 changed files with 50 additions and 26 deletions
|
@ -145,13 +145,8 @@ reveal_type(f) # revealed: Unknown
|
||||||
|
|
||||||
### Non-iterable unpacking
|
### Non-iterable unpacking
|
||||||
|
|
||||||
TODO: Remove duplicate diagnostics. This is happening because for a sequence-like assignment target,
|
|
||||||
multiple definitions are created and the inference engine runs on each of them which results in
|
|
||||||
duplicate diagnostics.
|
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# error: "Object of type `Literal[1]` is not iterable"
|
# error: "Object of type `Literal[1]` is not iterable"
|
||||||
# error: "Object of type `Literal[1]` is not iterable"
|
|
||||||
a, b = 1
|
a, b = 1
|
||||||
reveal_type(a) # revealed: Unknown
|
reveal_type(a) # revealed: Unknown
|
||||||
reveal_type(b) # revealed: Unknown
|
reveal_type(b) # revealed: Unknown
|
||||||
|
|
|
@ -281,8 +281,12 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||||
debug_assert!(popped_assignment.is_some());
|
debug_assert!(popped_assignment.is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_assignment(&self) -> Option<&CurrentAssignment<'db>> {
|
fn current_assignment(&self) -> Option<CurrentAssignment<'db>> {
|
||||||
self.current_assignments.last()
|
self.current_assignments.last().copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_assignment_mut(&mut self) -> Option<&mut CurrentAssignment<'db>> {
|
||||||
|
self.current_assignments.last_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_pattern_constraint(
|
fn add_pattern_constraint(
|
||||||
|
@ -627,6 +631,7 @@ where
|
||||||
ast::Expr::List(_) | ast::Expr::Tuple(_) => {
|
ast::Expr::List(_) | ast::Expr::Tuple(_) => {
|
||||||
Some(CurrentAssignment::Assign {
|
Some(CurrentAssignment::Assign {
|
||||||
node,
|
node,
|
||||||
|
first: true,
|
||||||
unpack: Some(Unpack::new(
|
unpack: Some(Unpack::new(
|
||||||
self.db,
|
self.db,
|
||||||
self.file,
|
self.file,
|
||||||
|
@ -640,9 +645,11 @@ where
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ast::Expr::Name(_) => {
|
ast::Expr::Name(_) => Some(CurrentAssignment::Assign {
|
||||||
Some(CurrentAssignment::Assign { node, unpack: None })
|
node,
|
||||||
}
|
unpack: None,
|
||||||
|
first: false,
|
||||||
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -987,14 +994,19 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_definition {
|
if is_definition {
|
||||||
match self.current_assignment().copied() {
|
match self.current_assignment() {
|
||||||
Some(CurrentAssignment::Assign { node, unpack }) => {
|
Some(CurrentAssignment::Assign {
|
||||||
|
node,
|
||||||
|
first,
|
||||||
|
unpack,
|
||||||
|
}) => {
|
||||||
self.add_definition(
|
self.add_definition(
|
||||||
symbol,
|
symbol,
|
||||||
AssignmentDefinitionNodeRef {
|
AssignmentDefinitionNodeRef {
|
||||||
unpack,
|
unpack,
|
||||||
value: &node.value,
|
value: &node.value,
|
||||||
name: name_node,
|
name: name_node,
|
||||||
|
first,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1045,6 +1057,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(CurrentAssignment::Assign { first, .. }) = self.current_assignment_mut()
|
||||||
|
{
|
||||||
|
*first = false;
|
||||||
|
}
|
||||||
|
|
||||||
walk_expr(self, expr);
|
walk_expr(self, expr);
|
||||||
}
|
}
|
||||||
ast::Expr::Named(node) => {
|
ast::Expr::Named(node) => {
|
||||||
|
@ -1245,6 +1262,7 @@ where
|
||||||
enum CurrentAssignment<'a> {
|
enum CurrentAssignment<'a> {
|
||||||
Assign {
|
Assign {
|
||||||
node: &'a ast::StmtAssign,
|
node: &'a ast::StmtAssign,
|
||||||
|
first: bool,
|
||||||
unpack: Option<Unpack<'a>>,
|
unpack: Option<Unpack<'a>>,
|
||||||
},
|
},
|
||||||
AnnAssign(&'a ast::StmtAnnAssign),
|
AnnAssign(&'a ast::StmtAnnAssign),
|
||||||
|
|
|
@ -183,6 +183,7 @@ pub(crate) struct AssignmentDefinitionNodeRef<'a> {
|
||||||
pub(crate) unpack: Option<Unpack<'a>>,
|
pub(crate) unpack: Option<Unpack<'a>>,
|
||||||
pub(crate) value: &'a ast::Expr,
|
pub(crate) value: &'a ast::Expr,
|
||||||
pub(crate) name: &'a ast::ExprName,
|
pub(crate) name: &'a ast::ExprName,
|
||||||
|
pub(crate) first: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
@ -250,10 +251,12 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||||
unpack,
|
unpack,
|
||||||
value,
|
value,
|
||||||
name,
|
name,
|
||||||
|
first,
|
||||||
}) => DefinitionKind::Assignment(AssignmentDefinitionKind {
|
}) => DefinitionKind::Assignment(AssignmentDefinitionKind {
|
||||||
target: TargetKind::from(unpack),
|
target: TargetKind::from(unpack),
|
||||||
value: AstNodeRef::new(parsed.clone(), value),
|
value: AstNodeRef::new(parsed.clone(), value),
|
||||||
name: AstNodeRef::new(parsed, name),
|
name: AstNodeRef::new(parsed, name),
|
||||||
|
first,
|
||||||
}),
|
}),
|
||||||
DefinitionNodeRef::AnnotatedAssignment(assign) => {
|
DefinitionNodeRef::AnnotatedAssignment(assign) => {
|
||||||
DefinitionKind::AnnotatedAssignment(AstNodeRef::new(parsed, assign))
|
DefinitionKind::AnnotatedAssignment(AstNodeRef::new(parsed, assign))
|
||||||
|
@ -330,6 +333,7 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||||
value: _,
|
value: _,
|
||||||
unpack: _,
|
unpack: _,
|
||||||
name,
|
name,
|
||||||
|
first: _,
|
||||||
}) => name.into(),
|
}) => name.into(),
|
||||||
Self::AnnotatedAssignment(node) => node.into(),
|
Self::AnnotatedAssignment(node) => node.into(),
|
||||||
Self::AugmentedAssignment(node) => node.into(),
|
Self::AugmentedAssignment(node) => node.into(),
|
||||||
|
@ -535,10 +539,11 @@ pub struct AssignmentDefinitionKind<'db> {
|
||||||
target: TargetKind<'db>,
|
target: TargetKind<'db>,
|
||||||
value: AstNodeRef<ast::Expr>,
|
value: AstNodeRef<ast::Expr>,
|
||||||
name: AstNodeRef<ast::ExprName>,
|
name: AstNodeRef<ast::ExprName>,
|
||||||
|
first: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssignmentDefinitionKind<'_> {
|
impl<'db> AssignmentDefinitionKind<'db> {
|
||||||
pub(crate) fn target(&self) -> TargetKind {
|
pub(crate) fn target(&self) -> TargetKind<'db> {
|
||||||
self.target
|
self.target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,6 +554,10 @@ impl AssignmentDefinitionKind<'_> {
|
||||||
pub(crate) fn name(&self) -> &ast::ExprName {
|
pub(crate) fn name(&self) -> &ast::ExprName {
|
||||||
self.name.node()
|
self.name.node()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_first(&self) -> bool {
|
||||||
|
self.first
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -41,7 +41,8 @@ use crate::module_name::ModuleName;
|
||||||
use crate::module_resolver::{file_to_module, resolve_module};
|
use crate::module_resolver::{file_to_module, resolve_module};
|
||||||
use crate::semantic_index::ast_ids::{HasScopedAstId, HasScopedUseId, ScopedExpressionId};
|
use crate::semantic_index::ast_ids::{HasScopedAstId, HasScopedUseId, ScopedExpressionId};
|
||||||
use crate::semantic_index::definition::{
|
use crate::semantic_index::definition::{
|
||||||
Definition, DefinitionKind, DefinitionNodeKey, ExceptHandlerDefinitionKind, TargetKind,
|
AssignmentDefinitionKind, Definition, DefinitionKind, DefinitionNodeKey,
|
||||||
|
ExceptHandlerDefinitionKind, TargetKind,
|
||||||
};
|
};
|
||||||
use crate::semantic_index::expression::Expression;
|
use crate::semantic_index::expression::Expression;
|
||||||
use crate::semantic_index::semantic_index;
|
use crate::semantic_index::semantic_index;
|
||||||
|
@ -532,12 +533,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DefinitionKind::Assignment(assignment) => {
|
DefinitionKind::Assignment(assignment) => {
|
||||||
self.infer_assignment_definition(
|
self.infer_assignment_definition(assignment, definition);
|
||||||
assignment.target(),
|
|
||||||
assignment.value(),
|
|
||||||
assignment.name(),
|
|
||||||
definition,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
DefinitionKind::AnnotatedAssignment(annotated_assignment) => {
|
DefinitionKind::AnnotatedAssignment(annotated_assignment) => {
|
||||||
self.infer_annotated_assignment_definition(annotated_assignment.node(), definition);
|
self.infer_annotated_assignment_definition(annotated_assignment.node(), definition);
|
||||||
|
@ -1427,20 +1423,26 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
|
|
||||||
fn infer_assignment_definition(
|
fn infer_assignment_definition(
|
||||||
&mut self,
|
&mut self,
|
||||||
target: TargetKind<'db>,
|
assignment: &AssignmentDefinitionKind<'db>,
|
||||||
value: &ast::Expr,
|
|
||||||
name: &ast::ExprName,
|
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
) {
|
) {
|
||||||
|
let value = assignment.value();
|
||||||
|
let name = assignment.name();
|
||||||
|
|
||||||
self.infer_standalone_expression(value);
|
self.infer_standalone_expression(value);
|
||||||
|
|
||||||
let value_ty = self.expression_ty(value);
|
let value_ty = self.expression_ty(value);
|
||||||
let name_ast_id = name.scoped_ast_id(self.db, self.scope());
|
let name_ast_id = name.scoped_ast_id(self.db, self.scope());
|
||||||
|
|
||||||
let target_ty = match target {
|
let target_ty = match assignment.target() {
|
||||||
TargetKind::Sequence(unpack) => {
|
TargetKind::Sequence(unpack) => {
|
||||||
let unpacked = infer_unpack_types(self.db, unpack);
|
let unpacked = infer_unpack_types(self.db, unpack);
|
||||||
|
// Only copy the diagnostics if this is the first assignment to avoid duplicating the
|
||||||
|
// unpack assignments.
|
||||||
|
if assignment.is_first() {
|
||||||
self.diagnostics.extend(unpacked.diagnostics());
|
self.diagnostics.extend(unpacked.diagnostics());
|
||||||
|
}
|
||||||
|
|
||||||
unpacked.get(name_ast_id).unwrap_or(Type::Unknown)
|
unpacked.get(name_ast_id).unwrap_or(Type::Unknown)
|
||||||
}
|
}
|
||||||
TargetKind::Name => value_ty,
|
TargetKind::Name => value_ty,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue