From b1a987318f7f77be268a3d3ec26793bd59cd3173 Mon Sep 17 00:00:00 2001 From: Douglas Creager Date: Wed, 27 Aug 2025 14:53:00 -0400 Subject: [PATCH] move typevar arms around --- .../mdtest/generics/legacy/functions.md | 2 +- .../mdtest/generics/pep695/functions.md | 2 +- crates/ty_python_semantic/src/types.rs | 160 ++++++++---------- 3 files changed, 71 insertions(+), 93 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md index 3175ed7216..42941acac3 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/functions.md @@ -279,7 +279,7 @@ T = TypeVar("T", int, str) def same_constrained_types(t1: T, t2: T) -> T: # TODO: no error - # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T@same_constrained_types`, found `int | Unknown`" return t1 + t2 ``` 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 a9224d46c8..0a70d9ae3f 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/functions.md @@ -246,7 +246,7 @@ methods that are compatible with the return type, so the `return` expression is ```py def same_constrained_types[T: (int, str)](t1: T, t2: T) -> T: # TODO: no error - # error: [unsupported-operator] "Operator `+` is unsupported between objects of type `T@same_constrained_types` and `T@same_constrained_types`" + # error: [invalid-return-type] "Return type does not match returned value: expected `T@same_constrained_types`, found `int | Unknown`" return t1 + t2 ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 80d428b265..fc1495ba41 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -1568,85 +1568,6 @@ impl<'db> Type<'db> { Type::NonInferableTypeVar(rhs_bound_typevar), ) if lhs_bound_typevar == rhs_bound_typevar => ConstraintSet::from(true), - // A fully static typevar is a subtype of its upper bound, and to something similar to - // the union of its constraints. An unbound, unconstrained, fully static typevar has an - // implicit upper bound of `object` (which is handled above). - (Type::NonInferableTypeVar(bound_typevar), _) - if bound_typevar.typevar(db).bound_or_constraints(db).is_some() => - { - match bound_typevar.typevar(db).bound_or_constraints(db) { - None => unreachable!(), - // Verify that the upper bound satisfies the relation. (If it does, than any - // subtype of the upper bound will too.) Also add a safety constraint that - // ensures that the typevar specializes to a subtype of the upper bound. - Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { - ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, bound) - .implies(db, || { - bound.has_relation_to_impl(db, target, relation, visitor) - }) - } - Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { - constraints.elements(db).iter().when_all(db, |constraint| { - // Note that constrained typevars must specialize to _exactly_ one of the - // constraints, not to a _subtype_ of one. - ConstraintSet::constrain_typevar( - db, - bound_typevar, - *constraint, - *constraint, - ) - .implies(db, || { - constraint.has_relation_to_impl(db, target, relation, visitor) - }) - }) - } - } - } - - // If the typevar is constrained, there must be multiple constraints, and the typevar - // might be specialized to any one of them. However, the constraints do not have to be - // disjoint, which means an lhs type might be a subtype of all of the constraints. - (_, Type::NonInferableTypeVar(bound_typevar)) - if !bound_typevar - .typevar(db) - .constraints(db) - .when_some_and(|constraints| { - constraints.iter().when_all(db, |constraint| { - ConstraintSet::constrain_typevar( - db, - bound_typevar, - *constraint, - *constraint, - ) - .implies(db, || { - self.has_relation_to_impl(db, *constraint, relation, visitor) - }) - }) - }) - .is_never_satisfied() => - { - // TODO: The repetition here isn't great, but we really need the fallthrough logic, - // where this arm only engages if it returns true (or in the world of constraints, - // not false). Once we're using real constraint sets instead of bool, we should be - // able to simplify the typevar logic. - bound_typevar - .typevar(db) - .constraints(db) - .when_some_and(|constraints| { - constraints.iter().when_all(db, |constraint| { - ConstraintSet::constrain_typevar( - db, - bound_typevar, - *constraint, - *constraint, - ) - .implies(db, || { - self.has_relation_to_impl(db, *constraint, relation, visitor) - }) - }) - }) - } - (Type::TypeVar(_), _) if relation.is_assignability() => { // The implicit lower bound of a typevar is `Never`, which means // that it is always assignable to any other type. @@ -1656,9 +1577,6 @@ impl<'db> Type<'db> { ConstraintSet::from(true) } - // `Never` is the bottom type, the empty set. - (_, Type::Never) => ConstraintSet::from(false), - (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| { elem_ty.has_relation_to_impl(db, target, relation, visitor) }), @@ -1686,12 +1604,74 @@ impl<'db> Type<'db> { }) } - // Other than the special cases checked above, no other types are a subtype of a - // typevar, since there's no guarantee what type the typevar will be specialized to. - // (If the typevar is bounded, it might be specialized to a smaller type than the - // bound. This is true even if the bound is a final class, since the typevar can still - // be specialized to `Never`.) - (_, Type::NonInferableTypeVar(_)) => ConstraintSet::from(false), + // A non-inferable typevar must satisfy the relation for every possible specialization. + (Type::NonInferableTypeVar(bound_typevar), _) => { + match bound_typevar.typevar(db).bound_or_constraints(db) { + // Verify that the upper bound satisfies the relation. (If it does, than any + // subtype of the upper bound will too.) + None => { + let bound = Type::object(); + ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, bound) + .implies(db, || { + bound.has_relation_to_impl(db, target, relation, visitor) + }) + } + Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { + ConstraintSet::constrain_typevar(db, bound_typevar, Type::Never, bound) + .implies(db, || { + bound.has_relation_to_impl(db, target, relation, visitor) + }) + } + // Verify that each constraint satisfies the relation. (Note that constrained + // typevars must specialize to _exactly_ one of the constraints, not to a + // _subtype_ of one.) + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { + constraints.elements(db).iter().when_all(db, |constraint| { + ConstraintSet::constrain_typevar( + db, + bound_typevar, + *constraint, + *constraint, + ) + .implies(db, || { + constraint.has_relation_to_impl(db, target, relation, visitor) + }) + }) + } + } + } + + (_, Type::NonInferableTypeVar(bound_typevar)) => { + match bound_typevar.typevar(db).bound_or_constraints(db) { + // There is no guarantee which type an unconstrained typevar might specialize + // to. Never (which is checked above) is the only type that is a subtype of any + // specialization. + None | Some(TypeVarBoundOrConstraints::UpperBound(_)) => { + ConstraintSet::from(false) + } + + // A constrained typevar, on the other hand, does have a fixed set of types + // that it can specialize to, so we can check them all exhaustively. + Some(TypeVarBoundOrConstraints::Constraints(constraints)) => { + constraints.elements(db).iter().when_all(db, |constraint| { + ConstraintSet::constrain_typevar( + db, + bound_typevar, + *constraint, + *constraint, + ) + .implies(db, || { + self.has_relation_to_impl(db, *constraint, relation, visitor) + }) + }) + } + } + } + + // `Never` is the bottom type, the empty set. + // Other than one unlikely edge case (TypeVars bound to `Never`), + // no other type is a subtype of or assignable to `Never`. + (_, Type::Never) => ConstraintSet::from(false), (_, Type::TypeVar(typevar)) if relation.is_assignability() @@ -1968,9 +1948,7 @@ impl<'db> Type<'db> { // Other than the special cases enumerated above, `Instance` types and typevars are // never subtypes of any other variants - (Type::NominalInstance(_) | Type::NonInferableTypeVar(_), _) => { - ConstraintSet::from(false) - } + (Type::NominalInstance(_), _) => ConstraintSet::from(false), } }