Skip to content

Commit 1e50e89

Browse files
committed
re-infer RHS of annotated assignments in isolation for assignability diagnostics
1 parent c026788 commit 1e50e89

File tree

5 files changed

+54
-33
lines changed

5 files changed

+54
-33
lines changed

crates/ty_python_semantic/resources/mdtest/assignment/annotations.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,23 +131,23 @@ m: IntList = [1, 2, 3]
131131
reveal_type(m) # revealed: list[int]
132132

133133
# TODO: this should type-check and avoid literal promotion
134-
# error: [invalid-assignment] "Object of type `list[int]` is not assignable to `list[Literal[1, 2, 3]]`"
134+
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
135135
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
136136
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
137137

138138
# TODO: this should type-check and avoid literal promotion
139-
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `list[LiteralString]`"
139+
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
140140
o: list[typing.LiteralString] = ["a", "b", "c"]
141141
reveal_type(o) # revealed: list[LiteralString]
142142
```
143143

144144
## Incorrect collection literal assignments are complained aobut
145145

146146
```py
147-
# error: [invalid-assignment] "Object of type `list[int]` is not assignable to `list[str]`"
147+
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[str]`"
148148
a: list[str] = [1, 2, 3]
149149

150-
# error: [invalid-assignment] "Object of type `set[int | str]` is not assignable to `set[int]`"
150+
# error: [invalid-assignment] "Object of type `set[Unknown | int | str]` is not assignable to `set[int]`"
151151
b: set[int] = {1, 2, "3"}
152152
```
153153

@@ -263,8 +263,7 @@ reveal_type(d) # revealed: list[int | tuple[int, int]]
263263
e: list[int] = f(True)
264264
reveal_type(e) # revealed: list[int]
265265

266-
# TODO: the RHS should be inferred as `list[Literal["a"]]` here
267-
# error: [invalid-assignment] "Object of type `list[int | Literal["a"]]` is not assignable to `list[int]`"
266+
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `list[int]`"
268267
g: list[int] = f("a")
269268

270269
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `tuple[int]`"

crates/ty_python_semantic/src/types.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ pub use self::diagnostic::TypeCheckDiagnostics;
2525
pub(crate) use self::diagnostic::register_lints;
2626
pub(crate) use self::infer::{
2727
TypeContext, infer_deferred_types, infer_definition_types, infer_expression_type,
28-
infer_expression_types, infer_scope_types, static_expression_truthiness,
28+
infer_expression_types, infer_isolated_expression, infer_scope_types,
29+
static_expression_truthiness,
2930
};
3031
pub(crate) use self::signatures::{CallableSignature, Signature};
3132
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};

crates/ty_python_semantic/src/types/diagnostic.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use super::{
66
add_inferred_python_version_hint_to_diagnostic,
77
};
88
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
9-
use crate::semantic_index::definition::Definition;
9+
use crate::semantic_index::definition::{Definition, DefinitionKind};
1010
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
1111
use crate::suppression::FileSuppressionId;
1212
use crate::types::call::CallError;
@@ -19,7 +19,7 @@ use crate::types::string_annotation::{
1919
};
2020
use crate::types::{
2121
ClassType, DynamicType, LintDiagnosticGuard, Protocol, ProtocolInstanceType, SubclassOfInner,
22-
binding_type,
22+
binding_type, infer_isolated_expression,
2323
};
2424
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClass};
2525
use crate::util::diagnostics::format_enumeration;
@@ -1940,15 +1940,24 @@ fn report_invalid_assignment_with_message(
19401940
}
19411941
}
19421942

1943-
pub(super) fn report_invalid_assignment(
1944-
context: &InferContext,
1943+
pub(super) fn report_invalid_assignment<'db>(
1944+
context: &InferContext<'db, '_>,
19451945
node: AnyNodeRef,
1946+
definition: Definition<'db>,
19461947
target_ty: Type,
1947-
source_ty: Type,
1948+
mut source_ty: Type<'db>,
19481949
) {
19491950
let settings =
19501951
DisplaySettings::from_possibly_ambiguous_type_pair(context.db(), target_ty, source_ty);
19511952

1953+
if let DefinitionKind::AnnotatedAssignment(annotated_assignment) = definition.kind(context.db())
1954+
&& let Some(value) = annotated_assignment.value(context.module())
1955+
{
1956+
// Re-infer the RHS of the annotated assignment, ignoring the type context, for more precise
1957+
// error messages.
1958+
source_ty = infer_isolated_expression(context.db(), definition.scope(context.db()), value);
1959+
}
1960+
19521961
report_invalid_assignment_with_message(
19531962
context,
19541963
node,

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
//! be considered a bug.)
3838
3939
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
40+
use ruff_python_ast as ast;
4041
use ruff_text_size::Ranged;
4142
use rustc_hash::FxHashMap;
4243
use salsa;
@@ -217,6 +218,24 @@ fn infer_expression_types_impl<'db>(
217218
.finish_expression()
218219
}
219220

221+
/// Infer the type of an non-standalone expression in isolation.
222+
///
223+
/// The type returned by this function may be different than the type of the expression
224+
/// if it was inferred within its region, as it does not account for surrounding type context.
225+
/// This can be useful to re-infer the type of an expression for diagnostics.
226+
pub(crate) fn infer_isolated_expression<'db>(
227+
db: &'db dyn Db,
228+
scope: ScopeId<'db>,
229+
expr: &ast::Expr,
230+
) -> Type<'db> {
231+
let file = scope.file(db);
232+
let module = parsed_module(db, file).load(db);
233+
let index = semantic_index(db, file);
234+
235+
TypeInferenceBuilder::new(db, InferenceRegion::Scope(scope), index, &module)
236+
.infer_isolated_expression(expr)
237+
}
238+
220239
fn expression_cycle_recover<'db>(
221240
db: &'db dyn Db,
222241
_value: &ExpressionInference<'db>,

crates/ty_python_semantic/src/types/infer/builder.rs

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,7 +1522,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
15221522
}
15231523

15241524
if !bound_ty.is_assignable_to(db, declared_ty) {
1525-
report_invalid_assignment(&self.context, node, declared_ty, bound_ty);
1525+
report_invalid_assignment(&self.context, node, binding, declared_ty, bound_ty);
15261526
// allow declarations to override inference in case of invalid assignment
15271527
bound_ty = declared_ty;
15281528
}
@@ -1679,9 +1679,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
16791679
report_invalid_assignment(
16801680
&self.context,
16811681
node,
1682+
definition,
16821683
declared_ty.inner_type(),
16831684
inferred_ty,
16841685
);
1686+
16851687
// if the assignment is invalid, fall back to assuming the annotation is correct
16861688
(declared_ty, declared_ty.inner_type())
16871689
}
@@ -5336,29 +5338,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
53365338
panic!("Typeshed should always have a `{name}` class in `builtins.pyi` with a single type variable")
53375339
});
53385340

5339-
let mut elements_are_assignable = true;
5340-
let mut inferred_elt_tys = Vec::with_capacity(elts.len());
5341-
5342-
// Infer the type of each element in the collection literal.
5343-
for elt in elts {
5344-
let inferred_elt_ty = self.infer_expression(elt, TypeContext::new(annotated_elts_ty));
5345-
inferred_elt_tys.push(inferred_elt_ty);
5346-
5347-
if let Some(annotated_elts_ty) = annotated_elts_ty {
5348-
elements_are_assignable &=
5349-
inferred_elt_ty.is_assignable_to(self.db(), annotated_elts_ty);
5350-
}
5351-
}
5352-
53535341
// Create a set of constraints to infer a precise type for `T`.
53545342
let mut builder = SpecializationBuilder::new(self.db());
53555343

53565344
match annotated_elts_ty {
5357-
// If the inferred type of any element is not assignable to the type annotation, we
5358-
// ignore it, as to provide a more precise error message.
5359-
Some(_) if !elements_are_assignable => {}
5360-
5361-
// Otherwise, the annotated type acts as a constraint for `T`.
5345+
// The annotated type acts as a constraint for `T`.
53625346
//
53635347
// Note that we infer the annotated type _before_ the elements, to closer match the order
53645348
// of any unions written in the type annotation.
@@ -5372,7 +5356,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
53725356
}
53735357

53745358
// The inferred type of each element acts as an additional constraint on `T`.
5375-
for inferred_elt_ty in inferred_elt_tys {
5359+
for elt in elts {
5360+
let inferred_elt_ty = self.infer_expression(elt, TypeContext::new(annotated_elts_ty));
5361+
53765362
// Convert any element literals to their promoted type form to avoid excessively large
53775363
// unions for large nested list literals, which the constraint solver struggles with.
53785364
let inferred_elt_ty =
@@ -9032,6 +9018,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
90329018
}
90339019
}
90349020

9021+
/// Infer the type of the given non-standalone expression in isolation, ignoring the surrounding region.
9022+
pub(super) fn infer_isolated_expression(mut self, expr: &ast::Expr) -> Type<'db> {
9023+
let expr_ty = self.infer_expression(expr, TypeContext::default());
9024+
let _ = self.context.finish();
9025+
expr_ty
9026+
}
9027+
90359028
pub(super) fn finish_expression(mut self) -> ExpressionInference<'db> {
90369029
self.infer_region();
90379030

0 commit comments

Comments
 (0)