mirror of https://github.com/astral-sh/ruff
fallback to `Unknown` with multiple applicable type contexts
This commit is contained in:
parent
78f21945c2
commit
8e25f88a84
|
|
@ -402,40 +402,48 @@ python-version = "3.12"
|
||||||
`generic_list.py`:
|
`generic_list.py`:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Literal
|
from typing import Literal, Sequence
|
||||||
|
|
||||||
def f[T](x: T) -> list[T]:
|
def f[T](x: T) -> list[T]:
|
||||||
return [x]
|
return [x]
|
||||||
|
|
||||||
a = f("a")
|
x1 = f("a")
|
||||||
reveal_type(a) # revealed: list[str]
|
reveal_type(x1) # revealed: list[str]
|
||||||
|
|
||||||
b: list[int | Literal["a"]] = f("a")
|
x2: list[int | Literal["a"]] = f("a")
|
||||||
reveal_type(b) # revealed: list[int | Literal["a"]]
|
reveal_type(x2) # revealed: list[int | Literal["a"]]
|
||||||
|
|
||||||
c: list[int | str] = f("a")
|
x3: list[int | str] = f("a")
|
||||||
reveal_type(c) # revealed: list[int | str]
|
reveal_type(x3) # revealed: list[int | str]
|
||||||
|
|
||||||
d: list[int | tuple[int, int]] = f((1, 2))
|
x4: list[int | tuple[int, int]] = f((1, 2))
|
||||||
reveal_type(d) # revealed: list[int | tuple[int, int]]
|
reveal_type(x4) # revealed: list[int | tuple[int, int]]
|
||||||
|
|
||||||
e: list[int] = f(True)
|
x5: list[int] = f(True)
|
||||||
reveal_type(e) # revealed: list[int]
|
reveal_type(x5) # revealed: list[int]
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `list[int | str]` is not assignable to `list[int]`"
|
# error: [invalid-assignment] "Object of type `list[int | str]` is not assignable to `list[int]`"
|
||||||
g: list[int] = f("a")
|
x6: list[int] = f("a")
|
||||||
|
|
||||||
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `tuple[int]`"
|
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `tuple[int]`"
|
||||||
h: tuple[int] = f("a")
|
x7: tuple[int] = f("a")
|
||||||
|
|
||||||
def f2[T: int](x: T) -> T:
|
def f2[T: int](x: T) -> T:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
i: int = f2(True)
|
x8: int = f2(True)
|
||||||
reveal_type(i) # revealed: Literal[True]
|
reveal_type(x8) # revealed: Literal[True]
|
||||||
|
|
||||||
j: int | str = f2(True)
|
x9: int | str = f2(True)
|
||||||
reveal_type(j) # revealed: Literal[True]
|
reveal_type(x9) # revealed: Literal[True]
|
||||||
|
|
||||||
|
# TODO: We could choose a concrete type here.
|
||||||
|
x10: list[int | str] | list[int | None] = [1, 2, 3]
|
||||||
|
reveal_type(x10) # revealed: list[Unknown | int]
|
||||||
|
|
||||||
|
# TODO: And here similarly.
|
||||||
|
x11: Sequence[int | str] | Sequence[int | None] = [1, 2, 3]
|
||||||
|
reveal_type(x11) # revealed: list[Unknown | int]
|
||||||
```
|
```
|
||||||
|
|
||||||
A function's arguments are also inferred using the type context:
|
A function's arguments are also inferred using the type context:
|
||||||
|
|
|
||||||
|
|
@ -1872,8 +1872,8 @@ impl<'db> SpecializationBuilder<'db> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Infer type mappings for the specialization in the reverse direction, i.e., where the given type contains
|
/// Infer type mappings for the specialization in the reverse direction, i.e., where the given type, not the
|
||||||
/// inferable type variables.
|
/// declared type, contains inferable type variables.
|
||||||
pub(crate) fn infer_reverse(
|
pub(crate) fn infer_reverse(
|
||||||
&mut self,
|
&mut self,
|
||||||
formal: Type<'db>,
|
formal: Type<'db>,
|
||||||
|
|
@ -1885,7 +1885,7 @@ impl<'db> SpecializationBuilder<'db> {
|
||||||
TypeContext::default(),
|
TypeContext::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Collect all type variables on the actual type.
|
// Collect any type variables on the formal type.
|
||||||
let mut formal_type_vars = Vec::new();
|
let mut formal_type_vars = Vec::new();
|
||||||
formal.visit_specialization(self.db, TypeContext::default(), |typevar, _, _, _| {
|
formal.visit_specialization(self.db, TypeContext::default(), |typevar, _, _, _| {
|
||||||
formal_type_vars.push(typevar);
|
formal_type_vars.push(typevar);
|
||||||
|
|
|
||||||
|
|
@ -7738,7 +7738,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
generic_context.inferable_typevars(self.db()),
|
generic_context.inferable_typevars(self.db()),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(tcx) = tcx.annotation {
|
if let Some(tcx) = tcx.annotation
|
||||||
|
// If there are multiple potential type contexts, we fallback to `Unknown`.
|
||||||
|
// TODO: We could perform multi-inference here.
|
||||||
|
&& tcx
|
||||||
|
.filter_union(self.db(), |ty| ty.class_specialization(self.db()).is_some())
|
||||||
|
.class_specialization(self.db())
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
let collection_instance =
|
let collection_instance =
|
||||||
Type::instance(self.db(), ClassType::Generic(collection_alias));
|
Type::instance(self.db(), ClassType::Generic(collection_alias));
|
||||||
builder.infer_reverse(tcx, collection_instance).ok()?;
|
builder.infer_reverse(tcx, collection_instance).ok()?;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue