diff --git a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md index d95cc52fd1..6573b001af 100644 --- a/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/ty_python_semantic/resources/mdtest/assignment/annotations.md @@ -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: diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 13725d9358..5173dfee6e 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -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); diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index dec104afb0..7d7bfc1764 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -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()?;