Skip to content

Commit 92b828f

Browse files
committed
Also check assignment target in unpack assignments and for loops
1 parent 1ee40b7 commit 92b828f

File tree

3 files changed

+70
-17
lines changed

3 files changed

+70
-17
lines changed

crates/red_knot_python_semantic/resources/mdtest/attributes.md

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -439,17 +439,32 @@ reveal_type(Foo.__class__) # revealed: Literal[type]
439439
## Module attributes
440440

441441
```py path=mod.py
442-
global_symbol: int = 1
442+
global_symbol: str = "a"
443443
```
444444

445445
```py
446446
import mod
447447

448-
reveal_type(mod.global_symbol) # revealed: int
449-
mod.global_symbol = 2
448+
reveal_type(mod.global_symbol) # revealed: str
449+
mod.global_symbol = "b"
450450

451-
# error: [invalid-assignment] "Object of type `Literal["1"]` is not assignable to `int`"
452-
mod.global_symbol = "1"
451+
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
452+
mod.global_symbol = 1
453+
454+
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `str`"
455+
(_, mod.global_symbol) = (..., 1)
456+
457+
class IntIterator:
458+
def __next__(self) -> int:
459+
return 42
460+
461+
class IntIterable:
462+
def __iter__(self) -> IntIterator:
463+
return IntIterator()
464+
465+
# error: [invalid-assignment] "Object of type `int` is not assignable to `str`"
466+
for mod.global_symbol in IntIterable():
467+
pass
453468
```
454469

455470
## Literal types

crates/red_knot_python_semantic/src/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3319,6 +3319,14 @@ impl<'db> IterationOutcome<'db> {
33193319
}
33203320
}
33213321
}
3322+
3323+
fn unwrap_without_diagnostic(self) -> Type<'db> {
3324+
match self {
3325+
Self::Iterable { element_ty } => element_ty,
3326+
Self::NotIterable { .. } => Type::unknown(),
3327+
Self::PossiblyUnboundDunderIter { element_ty, .. } => element_ty,
3328+
}
3329+
}
33223330
}
33233331

33243332
#[derive(Debug, Copy, Clone, PartialEq, Eq)]

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,24 +1973,46 @@ impl<'db> TypeInferenceBuilder<'db> {
19731973
}
19741974
}
19751975

1976-
/// Infer the definition types involved in a `target` expression.
1976+
/// Infer the (definition) types involved in a `target` expression.
19771977
///
19781978
/// This is used for assignment statements, for statements, etc. with a single or multiple
1979-
/// targets (unpacking).
1979+
/// targets (unpacking). If `target` is an attribute expression, we check that the assignment
1980+
/// is valid. For 'target's that are definitions, this check happens elsewhere.
19801981
///
19811982
/// # Panics
19821983
///
19831984
/// If the `value` is not a standalone expression.
19841985
fn infer_target(&mut self, target: &ast::Expr, value: &ast::Expr) {
1986+
let check_assignment_ty = match target {
1987+
ast::Expr::Name(_) => None,
1988+
_ => Some(self.infer_standalone_expression(value)),
1989+
};
1990+
self.infer_target_and_check_assignment(target, check_assignment_ty);
1991+
}
1992+
1993+
/// Similar to [`infer_target`], but with a custom type to check the assignment against.
1994+
fn infer_target_and_check_assignment(
1995+
&mut self,
1996+
target: &ast::Expr,
1997+
check_assignment_ty: Option<Type<'db>>,
1998+
) {
19851999
match target {
19862000
ast::Expr::Name(name) => self.infer_definition(name),
19872001
ast::Expr::List(ast::ExprList { elts, .. })
19882002
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
2003+
let mut check_assignment_tys: Box<dyn Iterator<Item = Type>> =
2004+
match check_assignment_ty {
2005+
Some(Type::Tuple(tuple)) => {
2006+
Box::new(tuple.elements(self.db()).into_iter().copied())
2007+
}
2008+
Some(ty) => Box::new(std::iter::repeat(
2009+
ty.iterate(self.db()).unwrap_without_diagnostic(),
2010+
)),
2011+
None => Box::new(std::iter::empty()),
2012+
};
2013+
19892014
for element in elts {
1990-
self.infer_target(element, value);
1991-
}
1992-
if elts.is_empty() {
1993-
self.infer_standalone_expression(value);
2015+
self.infer_target_and_check_assignment(element, check_assignment_tys.next());
19942016
}
19952017
}
19962018
ast::Expr::Attribute(
@@ -2002,15 +2024,14 @@ impl<'db> TypeInferenceBuilder<'db> {
20022024
let lhs_ty = self.infer_attribute_expression(lhs_expr);
20032025
self.store_expression_type(target, lhs_ty);
20042026

2005-
let rhs_ty = self.infer_standalone_expression(value);
2006-
2007-
if !rhs_ty.is_assignable_to(self.db(), lhs_ty) {
2008-
report_invalid_assignment(&self.context, target.into(), lhs_ty, rhs_ty);
2027+
if let Some(rhs_ty) = check_assignment_ty {
2028+
if !rhs_ty.is_assignable_to(self.db(), lhs_ty) {
2029+
report_invalid_assignment(&self.context, target.into(), lhs_ty, rhs_ty);
2030+
}
20092031
}
20102032
}
20112033
_ => {
20122034
// TODO: Remove this once we handle all possible assignment targets.
2013-
self.infer_standalone_expression(value);
20142035
self.infer_expression(target);
20152036
}
20162037
}
@@ -2279,7 +2300,16 @@ impl<'db> TypeInferenceBuilder<'db> {
22792300
is_async: _,
22802301
} = for_statement;
22812302

2282-
self.infer_target(target, iter);
2303+
let check_assignment_ty = match target.as_ref() {
2304+
ast::Expr::Name(_) => None, // Check will be done in `infer_definition`
2305+
_ => {
2306+
let iterable_ty = self.infer_standalone_expression(iter);
2307+
let iterator_ty = iterable_ty.iterate(self.db()).unwrap_without_diagnostic();
2308+
Some(iterator_ty)
2309+
}
2310+
};
2311+
self.infer_target_and_check_assignment(target, check_assignment_ty);
2312+
22832313
self.infer_body(body);
22842314
self.infer_body(orelse);
22852315
}

0 commit comments

Comments
 (0)