mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
re-infer RHS of annotated assignments in isolation for assignability diagnostics
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
This commit is contained in:
parent
5f294f9f2e
commit
12086dfa69
5 changed files with 54 additions and 33 deletions
|
@ -131,12 +131,12 @@ m: IntList = [1, 2, 3]
|
||||||
reveal_type(m) # revealed: list[int]
|
reveal_type(m) # revealed: list[int]
|
||||||
|
|
||||||
# TODO: this should type-check and avoid literal promotion
|
# TODO: this should type-check and avoid literal promotion
|
||||||
# error: [invalid-assignment] "Object of type `list[int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
||||||
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
|
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
|
||||||
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
|
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
|
||||||
|
|
||||||
# TODO: this should type-check and avoid literal promotion
|
# TODO: this should type-check and avoid literal promotion
|
||||||
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `list[LiteralString]`"
|
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
|
||||||
o: list[typing.LiteralString] = ["a", "b", "c"]
|
o: list[typing.LiteralString] = ["a", "b", "c"]
|
||||||
reveal_type(o) # revealed: list[LiteralString]
|
reveal_type(o) # revealed: list[LiteralString]
|
||||||
```
|
```
|
||||||
|
@ -144,10 +144,10 @@ reveal_type(o) # revealed: list[LiteralString]
|
||||||
## Incorrect collection literal assignments are complained aobut
|
## Incorrect collection literal assignments are complained aobut
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# error: [invalid-assignment] "Object of type `list[int]` is not assignable to `list[str]`"
|
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[str]`"
|
||||||
a: list[str] = [1, 2, 3]
|
a: list[str] = [1, 2, 3]
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `set[int | str]` is not assignable to `set[int]`"
|
# error: [invalid-assignment] "Object of type `set[Unknown | int | str]` is not assignable to `set[int]`"
|
||||||
b: set[int] = {1, 2, "3"}
|
b: set[int] = {1, 2, "3"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -263,8 +263,7 @@ reveal_type(d) # revealed: list[int | tuple[int, int]]
|
||||||
e: list[int] = f(True)
|
e: list[int] = f(True)
|
||||||
reveal_type(e) # revealed: list[int]
|
reveal_type(e) # revealed: list[int]
|
||||||
|
|
||||||
# TODO: the RHS should be inferred as `list[Literal["a"]]` here
|
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `list[int]`"
|
||||||
# error: [invalid-assignment] "Object of type `list[int | Literal["a"]]` is not assignable to `list[int]`"
|
|
||||||
g: list[int] = f("a")
|
g: list[int] = f("a")
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `tuple[int]`"
|
# error: [invalid-assignment] "Object of type `list[Literal["a"]]` is not assignable to `tuple[int]`"
|
||||||
|
|
|
@ -25,7 +25,8 @@ pub use self::diagnostic::TypeCheckDiagnostics;
|
||||||
pub(crate) use self::diagnostic::register_lints;
|
pub(crate) use self::diagnostic::register_lints;
|
||||||
pub(crate) use self::infer::{
|
pub(crate) use self::infer::{
|
||||||
TypeContext, infer_deferred_types, infer_definition_types, infer_expression_type,
|
TypeContext, infer_deferred_types, infer_definition_types, infer_expression_type,
|
||||||
infer_expression_types, infer_scope_types, static_expression_truthiness,
|
infer_expression_types, infer_isolated_expression, infer_scope_types,
|
||||||
|
static_expression_truthiness,
|
||||||
};
|
};
|
||||||
pub(crate) use self::signatures::{CallableSignature, Signature};
|
pub(crate) use self::signatures::{CallableSignature, Signature};
|
||||||
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
pub(crate) use self::subclass_of::{SubclassOfInner, SubclassOfType};
|
||||||
|
|
|
@ -6,7 +6,7 @@ use super::{
|
||||||
add_inferred_python_version_hint_to_diagnostic,
|
add_inferred_python_version_hint_to_diagnostic,
|
||||||
};
|
};
|
||||||
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
use crate::lint::{Level, LintRegistryBuilder, LintStatus};
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||||
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
|
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
|
||||||
use crate::suppression::FileSuppressionId;
|
use crate::suppression::FileSuppressionId;
|
||||||
use crate::types::call::CallError;
|
use crate::types::call::CallError;
|
||||||
|
@ -19,7 +19,7 @@ use crate::types::string_annotation::{
|
||||||
};
|
};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ClassType, DynamicType, LintDiagnosticGuard, Protocol, ProtocolInstanceType, SubclassOfInner,
|
ClassType, DynamicType, LintDiagnosticGuard, Protocol, ProtocolInstanceType, SubclassOfInner,
|
||||||
binding_type,
|
binding_type, infer_isolated_expression,
|
||||||
};
|
};
|
||||||
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClass};
|
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClass};
|
||||||
use crate::util::diagnostics::format_enumeration;
|
use crate::util::diagnostics::format_enumeration;
|
||||||
|
@ -1940,15 +1940,24 @@ fn report_invalid_assignment_with_message(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn report_invalid_assignment(
|
pub(super) fn report_invalid_assignment<'db>(
|
||||||
context: &InferContext,
|
context: &InferContext<'db, '_>,
|
||||||
node: AnyNodeRef,
|
node: AnyNodeRef,
|
||||||
|
definition: Definition<'db>,
|
||||||
target_ty: Type,
|
target_ty: Type,
|
||||||
source_ty: Type,
|
mut source_ty: Type<'db>,
|
||||||
) {
|
) {
|
||||||
let settings =
|
let settings =
|
||||||
DisplaySettings::from_possibly_ambiguous_type_pair(context.db(), target_ty, source_ty);
|
DisplaySettings::from_possibly_ambiguous_type_pair(context.db(), target_ty, source_ty);
|
||||||
|
|
||||||
|
if let DefinitionKind::AnnotatedAssignment(annotated_assignment) = definition.kind(context.db())
|
||||||
|
&& let Some(value) = annotated_assignment.value(context.module())
|
||||||
|
{
|
||||||
|
// Re-infer the RHS of the annotated assignment, ignoring the type context, for more precise
|
||||||
|
// error messages.
|
||||||
|
source_ty = infer_isolated_expression(context.db(), definition.scope(context.db()), value);
|
||||||
|
}
|
||||||
|
|
||||||
report_invalid_assignment_with_message(
|
report_invalid_assignment_with_message(
|
||||||
context,
|
context,
|
||||||
node,
|
node,
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
//! be considered a bug.)
|
//! be considered a bug.)
|
||||||
|
|
||||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||||
|
use ruff_python_ast as ast;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use salsa;
|
use salsa;
|
||||||
|
@ -217,6 +218,24 @@ fn infer_expression_types_impl<'db>(
|
||||||
.finish_expression()
|
.finish_expression()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Infer the type of an expression in isolation.
|
||||||
|
///
|
||||||
|
/// The type returned by this function may be different than the type of the expression
|
||||||
|
/// if it was inferred within its region, as it does not account for surrounding type context.
|
||||||
|
/// This can be useful to re-infer the type of an expression for diagnostics.
|
||||||
|
pub(crate) fn infer_isolated_expression<'db>(
|
||||||
|
db: &'db dyn Db,
|
||||||
|
scope: ScopeId<'db>,
|
||||||
|
expr: &ast::Expr,
|
||||||
|
) -> Type<'db> {
|
||||||
|
let file = scope.file(db);
|
||||||
|
let module = parsed_module(db, file).load(db);
|
||||||
|
let index = semantic_index(db, file);
|
||||||
|
|
||||||
|
TypeInferenceBuilder::new(db, InferenceRegion::Scope(scope), index, &module)
|
||||||
|
.infer_isolated_expression(expr)
|
||||||
|
}
|
||||||
|
|
||||||
fn expression_cycle_recover<'db>(
|
fn expression_cycle_recover<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
_value: &ExpressionInference<'db>,
|
_value: &ExpressionInference<'db>,
|
||||||
|
|
|
@ -1522,7 +1522,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bound_ty.is_assignable_to(db, declared_ty) {
|
if !bound_ty.is_assignable_to(db, declared_ty) {
|
||||||
report_invalid_assignment(&self.context, node, declared_ty, bound_ty);
|
report_invalid_assignment(&self.context, node, binding, declared_ty, bound_ty);
|
||||||
// allow declarations to override inference in case of invalid assignment
|
// allow declarations to override inference in case of invalid assignment
|
||||||
bound_ty = declared_ty;
|
bound_ty = declared_ty;
|
||||||
}
|
}
|
||||||
|
@ -1679,9 +1679,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
report_invalid_assignment(
|
report_invalid_assignment(
|
||||||
&self.context,
|
&self.context,
|
||||||
node,
|
node,
|
||||||
|
definition,
|
||||||
declared_ty.inner_type(),
|
declared_ty.inner_type(),
|
||||||
inferred_ty,
|
inferred_ty,
|
||||||
);
|
);
|
||||||
|
|
||||||
// if the assignment is invalid, fall back to assuming the annotation is correct
|
// if the assignment is invalid, fall back to assuming the annotation is correct
|
||||||
(declared_ty, declared_ty.inner_type())
|
(declared_ty, declared_ty.inner_type())
|
||||||
}
|
}
|
||||||
|
@ -5336,29 +5338,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
panic!("Typeshed should always have a `{name}` class in `builtins.pyi` with a single type variable")
|
panic!("Typeshed should always have a `{name}` class in `builtins.pyi` with a single type variable")
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut elements_are_assignable = true;
|
|
||||||
let mut inferred_elt_tys = Vec::with_capacity(elts.len());
|
|
||||||
|
|
||||||
// Infer the type of each element in the collection literal.
|
|
||||||
for elt in elts {
|
|
||||||
let inferred_elt_ty = self.infer_expression(elt, TypeContext::new(annotated_elts_ty));
|
|
||||||
inferred_elt_tys.push(inferred_elt_ty);
|
|
||||||
|
|
||||||
if let Some(annotated_elts_ty) = annotated_elts_ty {
|
|
||||||
elements_are_assignable &=
|
|
||||||
inferred_elt_ty.is_assignable_to(self.db(), annotated_elts_ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a set of constraints to infer a precise type for `T`.
|
// Create a set of constraints to infer a precise type for `T`.
|
||||||
let mut builder = SpecializationBuilder::new(self.db());
|
let mut builder = SpecializationBuilder::new(self.db());
|
||||||
|
|
||||||
match annotated_elts_ty {
|
match annotated_elts_ty {
|
||||||
// If the inferred type of any element is not assignable to the type annotation, we
|
// The annotated type acts as a constraint for `T`.
|
||||||
// ignore it, as to provide a more precise error message.
|
|
||||||
Some(_) if !elements_are_assignable => {}
|
|
||||||
|
|
||||||
// Otherwise, the annotated type acts as a constraint for `T`.
|
|
||||||
//
|
//
|
||||||
// Note that we infer the annotated type _before_ the elements, to closer match the order
|
// Note that we infer the annotated type _before_ the elements, to closer match the order
|
||||||
// of any unions written in the type annotation.
|
// of any unions written in the type annotation.
|
||||||
|
@ -5372,7 +5356,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The inferred type of each element acts as an additional constraint on `T`.
|
// The inferred type of each element acts as an additional constraint on `T`.
|
||||||
for inferred_elt_ty in inferred_elt_tys {
|
for elt in elts {
|
||||||
|
let inferred_elt_ty = self.infer_expression(elt, TypeContext::new(annotated_elts_ty));
|
||||||
|
|
||||||
// Convert any element literals to their promoted type form to avoid excessively large
|
// Convert any element literals to their promoted type form to avoid excessively large
|
||||||
// unions for large nested list literals, which the constraint solver struggles with.
|
// unions for large nested list literals, which the constraint solver struggles with.
|
||||||
let inferred_elt_ty =
|
let inferred_elt_ty =
|
||||||
|
@ -9032,6 +9018,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Infer the type of the given expression in isolation, ignoring the surrounding region.
|
||||||
|
pub(super) fn infer_isolated_expression(mut self, expr: &ast::Expr) -> Type<'db> {
|
||||||
|
let expr_ty = self.infer_expression_impl(expr, TypeContext::default());
|
||||||
|
let _ = self.context.finish();
|
||||||
|
expr_ty
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn finish_expression(mut self) -> ExpressionInference<'db> {
|
pub(super) fn finish_expression(mut self) -> ExpressionInference<'db> {
|
||||||
self.infer_region();
|
self.infer_region();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue