diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md index 180af1497a..20986c9d87 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -907,6 +907,23 @@ def _(x: list[str]): reveal_type(accepts_callable(GenericClass)(x, x)) ``` +### `Callable`s that return union types + +```py +from typing import Callable + +class Box[T]: + def get(self) -> T: + raise NotImplementedError + +def my_iter[T](f: Callable[[], T | None]) -> Box[T]: + return Box() + +def get_int() -> int | None: ... + +reveal_type(my_iter(get_int)) # revealed: Box[int] +``` + ### Don't include identical lower/upper bounds in type mapping multiple times This is was a performance regression reported in diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 0cafb153be..f39422e287 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1840,7 +1840,7 @@ impl<'db> Type<'db> { /// /// This method may have false negatives, but it should not have false positives. It should be /// a cheap shallow check, not an exhaustive recursive check. - fn subtyping_is_always_reflexive(self) -> bool { + const fn subtyping_is_always_reflexive(self) -> bool { match self { Type::Never | Type::FunctionLiteral(..) @@ -1861,6 +1861,9 @@ impl<'db> Type<'db> { | Type::AlwaysFalsy | Type::AlwaysTruthy | Type::PropertyInstance(_) + // `T` is always a subtype of itself, + // and `T` is always a subtype of `T | None` + | Type::TypeVar(_) // might inherit `Any`, but subtyping is still reflexive | Type::ClassLiteral(_) => true, @@ -1872,7 +1875,6 @@ impl<'db> Type<'db> { | Type::Union(_) | Type::Intersection(_) | Type::Callable(_) - | Type::TypeVar(_) | Type::BoundSuper(_) | Type::TypeIs(_) | Type::TypeGuard(_) diff --git a/crates/ty_python_semantic/src/types/relation.rs b/crates/ty_python_semantic/src/types/relation.rs index 515b5e36bf..4f977a0e6b 100644 --- a/crates/ty_python_semantic/src/types/relation.rs +++ b/crates/ty_python_semantic/src/types/relation.rs @@ -195,6 +195,17 @@ impl TypeRelation<'_> { pub(crate) const fn is_subtyping(self) -> bool { matches!(self, TypeRelation::Subtyping) } + + pub(crate) const fn can_safely_assume_reflexivity(self, ty: Type) -> bool { + match self { + TypeRelation::Assignability + | TypeRelation::ConstraintSetAssignability + | TypeRelation::Redundancy => true, + TypeRelation::Subtyping | TypeRelation::SubtypingAssuming(_) => { + ty.subtyping_is_always_reflexive() + } + } + } } #[salsa::tracked] @@ -329,7 +340,7 @@ impl<'db> Type<'db> { // // Note that we could do a full equivalence check here, but that would be both expensive // and unnecessary. This early return is only an optimisation. - if (!relation.is_subtyping() || self.subtyping_is_always_reflexive()) && self == target { + if relation.can_safely_assume_reflexivity(self) && self == target { return ConstraintSet::from(true); } @@ -460,46 +471,43 @@ impl<'db> Type<'db> { }, }), - // In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied: + // In general, a TypeVar `T` is not redundant with a type `S` unless one of the two conditions is satisfied: // 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`. // TypeVars without an explicit upper bound are treated as having an implicit upper bound of `object`. // 2. `T` is a constrained TypeVar and all of `T`'s constraints are subtypes of `S`. // // However, there is one exception to this general rule: for any given typevar `T`, // `T` will always be a subtype of any union containing `T`. - (Type::TypeVar(bound_typevar), Type::Union(union)) - if !bound_typevar.is_inferable(db, inferable) + (_, Type::Union(union)) + if relation.can_safely_assume_reflexivity(self) && union.elements(db).contains(&self) => { ConstraintSet::from(true) } // A similar rule applies in reverse to intersection types. - (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) - if !bound_typevar.is_inferable(db, inferable) + (Type::Intersection(intersection), _) + if relation.can_safely_assume_reflexivity(target) && intersection.positive(db).contains(&target) => { ConstraintSet::from(true) } - (Type::Intersection(intersection), Type::TypeVar(bound_typevar)) - if !bound_typevar.is_inferable(db, inferable) + (Type::Intersection(intersection), _) + if relation.is_assignability() + && intersection.positive(db).iter().any(Type::is_dynamic) => + { + // If the intersection contains `Any`/`Unknown`/`@Todo`, it is assignable to any type. + // `Any` could materialize to `Never`, `Never & T & ~S` simplifies to `Never` for any + // `T` and any `S`, and `Never` is a subtype of all types. + ConstraintSet::from(true) + } + (Type::Intersection(intersection), _) + if relation.can_safely_assume_reflexivity(target) && intersection.negative(db).contains(&target) => { ConstraintSet::from(false) } - // Two identical typevars must always solve to the same type, so they are always - // subtypes of each other and assignable to each other. - // - // Note that this is not handled by the early return at the beginning of this method, - // since subtyping between a TypeVar and an arbitrary other type cannot be guaranteed to be reflexive. - (Type::TypeVar(lhs_bound_typevar), Type::TypeVar(rhs_bound_typevar)) - if !lhs_bound_typevar.is_inferable(db, inferable) - && lhs_bound_typevar.is_same_typevar_as(db, rhs_bound_typevar) => - { - ConstraintSet::from(true) - } - // `type[T]` is a subtype of the class object `A` if every instance of `T` is a subtype of an instance // of `A`, and vice versa. (Type::SubclassOf(subclass_of), _)