diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 72fa7acbc8..32fd3930cf 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -456,6 +456,40 @@ reveal_type(int_container) # revealed: Container[int] reveal_type(int_container.set_value(1)) # revealed: Container[int] ``` +## Generic class with bounded type variable + +This is a regression test for . + +Calling a method on a generic class instance should work when the type parameter is specialized with +a type that satisfies a bound. + +```py +from typing import NewType + +class Base: ... + +class C[T: Base]: + x: T + + def g(self) -> None: + pass + +# Calling a method on a specialized instance should not produce an error +C[Base]().g() + +# Test with a NewType bound +K = NewType("K", int) + +class D[T: K]: + x: T + + def h(self) -> None: + pass + +# Calling a method on a specialized instance should not produce an error +D[K]().h() +``` + ## Protocols TODO: diff --git a/crates/ty_python_semantic/src/types/relation.rs b/crates/ty_python_semantic/src/types/relation.rs index 45c30e5bef..515b5e36bf 100644 --- a/crates/ty_python_semantic/src/types/relation.rs +++ b/crates/ty_python_semantic/src/types/relation.rs @@ -715,17 +715,6 @@ impl<'db> Type<'db> { } }) } - // All other `NewType` assignments fall back to the concrete base type. - (Type::NewTypeInstance(self_newtype), _) => { - self_newtype.concrete_base_type(db).has_relation_to_impl( - db, - target, - inferable, - relation, - relation_visitor, - disjointness_visitor, - ) - } (Type::Union(union), _) => union.elements(db).iter().when_all(db, |&elem_ty| { elem_ty.has_relation_to_impl( @@ -869,6 +858,21 @@ impl<'db> Type<'db> { ConstraintSet::from(false) } + // All other `NewType` assignments fall back to the concrete base type. + // This case must come after the TypeVar cases above, so that when checking + // `NewType <: TypeVar`, we use the TypeVar handling rather than falling back + // to the NewType's concrete base type. + (Type::NewTypeInstance(self_newtype), _) => { + self_newtype.concrete_base_type(db).has_relation_to_impl( + db, + target, + inferable, + relation, + relation_visitor, + disjointness_visitor, + ) + } + // Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`. // If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively. (left, Type::AlwaysFalsy) => ConstraintSet::from(left.bool(db).is_always_false()),