Skip to content

Commit 47c5ba1

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

File tree

3 files changed

+70
-19
lines changed

3 files changed

+70
-19
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 & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,28 +1969,54 @@ impl<'db> TypeInferenceBuilder<'db> {
19691969
} = assignment;
19701970

19711971
for target in targets {
1972-
self.infer_target(target, value);
1972+
self.infer_target(target, value, |_, ty| ty);
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.
1981+
///
1982+
/// The `to_assigned_ty` function is used to convert the inferred type of the `value` expression
1983+
/// to the type that is eventually assigned to the `target`.
19801984
///
19811985
/// # Panics
19821986
///
19831987
/// If the `value` is not a standalone expression.
1984-
fn infer_target(&mut self, target: &ast::Expr, value: &ast::Expr) {
1988+
fn infer_target<F>(&mut self, target: &ast::Expr, value: &ast::Expr, to_assigned_ty: F)
1989+
where
1990+
F: Fn(&'db dyn Db, Type<'db>) -> Type<'db>,
1991+
{
1992+
let assigned_ty = match target {
1993+
ast::Expr::Name(_) => None,
1994+
_ => {
1995+
let value_ty = self.infer_standalone_expression(value);
1996+
1997+
Some(to_assigned_ty(self.db(), value_ty))
1998+
}
1999+
};
2000+
self.infer_target_impl(target, assigned_ty);
2001+
}
2002+
2003+
fn infer_target_impl(&mut self, target: &ast::Expr, assigned_ty: Option<Type<'db>>) {
19852004
match target {
19862005
ast::Expr::Name(name) => self.infer_definition(name),
19872006
ast::Expr::List(ast::ExprList { elts, .. })
19882007
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
2008+
let mut check_assignment_tys: Box<dyn Iterator<Item = Type>> = match assigned_ty {
2009+
Some(Type::Tuple(tuple)) => {
2010+
Box::new(tuple.elements(self.db()).into_iter().copied())
2011+
}
2012+
Some(ty) => Box::new(std::iter::repeat(
2013+
ty.iterate(self.db()).unwrap_without_diagnostic(),
2014+
)),
2015+
None => Box::new(std::iter::empty()),
2016+
};
2017+
19892018
for element in elts {
1990-
self.infer_target(element, value);
1991-
}
1992-
if elts.is_empty() {
1993-
self.infer_standalone_expression(value);
2019+
self.infer_target_impl(element, check_assignment_tys.next());
19942020
}
19952021
}
19962022
ast::Expr::Attribute(
@@ -2002,15 +2028,14 @@ impl<'db> TypeInferenceBuilder<'db> {
20022028
let lhs_ty = self.infer_attribute_expression(lhs_expr);
20032029
self.store_expression_type(target, lhs_ty);
20042030

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);
2031+
if let Some(rhs_ty) = assigned_ty {
2032+
if !rhs_ty.is_assignable_to(self.db(), lhs_ty) {
2033+
report_invalid_assignment(&self.context, target.into(), lhs_ty, rhs_ty);
2034+
}
20092035
}
20102036
}
20112037
_ => {
20122038
// TODO: Remove this once we handle all possible assignment targets.
2013-
self.infer_standalone_expression(value);
20142039
self.infer_expression(target);
20152040
}
20162041
}
@@ -2279,7 +2304,10 @@ impl<'db> TypeInferenceBuilder<'db> {
22792304
is_async: _,
22802305
} = for_statement;
22812306

2282-
self.infer_target(target, iter);
2307+
self.infer_target(target, iter, |db, iter_ty| {
2308+
iter_ty.iterate(db).unwrap_without_diagnostic()
2309+
});
2310+
22832311
self.infer_body(body);
22842312
self.infer_body(orelse);
22852313
}

0 commit comments

Comments
 (0)