fallback to `Unknown` with multiple applicable type contexts

This commit is contained in:
Ibraheem Ahmed 2025-12-11 16:06:42 -05:00
parent 78f21945c2
commit 8e25f88a84
3 changed files with 36 additions and 21 deletions

View File

@ -402,40 +402,48 @@ python-version = "3.12"
`generic_list.py`:
```py
from typing import Literal
from typing import Literal, Sequence
def f[T](x: T) -> list[T]:
return [x]
a = f("a")
reveal_type(a) # revealed: list[str]
x1 = f("a")
reveal_type(x1) # revealed: list[str]
b: list[int | Literal["a"]] = f("a")
reveal_type(b) # revealed: list[int | Literal["a"]]
x2: list[int | Literal["a"]] = f("a")
reveal_type(x2) # revealed: list[int | Literal["a"]]
c: list[int | str] = f("a")
reveal_type(c) # revealed: list[int | str]
x3: list[int | str] = f("a")
reveal_type(x3) # revealed: list[int | str]
d: list[int | tuple[int, int]] = f((1, 2))
reveal_type(d) # revealed: list[int | tuple[int, int]]
x4: list[int | tuple[int, int]] = f((1, 2))
reveal_type(x4) # revealed: list[int | tuple[int, int]]
e: list[int] = f(True)
reveal_type(e) # revealed: list[int]
x5: list[int] = f(True)
reveal_type(x5) # revealed: 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]`"
h: tuple[int] = f("a")
x7: tuple[int] = f("a")
def f2[T: int](x: T) -> T:
return x
i: int = f2(True)
reveal_type(i) # revealed: Literal[True]
x8: int = f2(True)
reveal_type(x8) # revealed: Literal[True]
j: int | str = f2(True)
reveal_type(j) # revealed: Literal[True]
x9: int | str = f2(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:

View File

@ -1872,8 +1872,8 @@ impl<'db> SpecializationBuilder<'db> {
Ok(())
}
/// Infer type mappings for the specialization in the reverse direction, i.e., where the given type contains
/// inferable type variables.
/// Infer type mappings for the specialization in the reverse direction, i.e., where the given type, not the
/// declared type, contains inferable type variables.
pub(crate) fn infer_reverse(
&mut self,
formal: Type<'db>,
@ -1885,7 +1885,7 @@ impl<'db> SpecializationBuilder<'db> {
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();
formal.visit_specialization(self.db, TypeContext::default(), |typevar, _, _, _| {
formal_type_vars.push(typevar);

View File

@ -7738,7 +7738,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
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 =
Type::instance(self.db(), ClassType::Generic(collection_alias));
builder.infer_reverse(tcx, collection_instance).ok()?;