diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 94a30d6379..4fcae53b59 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -1928,21 +1928,23 @@ from typing_extensions import TypeVar, Self, Protocol from ty_extensions import is_equivalent_to, static_assert, is_assignable_to, is_subtype_of class NewStyleClassScoped[T](Protocol): - def method(self, input: T) -> None: ... + def method(self: Self, input: T) -> None: ... S = TypeVar("S") class LegacyClassScoped(Protocol[S]): - def method(self, input: S) -> None: ... + def method(self: Self, input: S) -> None: ... -static_assert(is_equivalent_to(NewStyleClassScoped, LegacyClassScoped)) -static_assert(is_equivalent_to(NewStyleClassScoped[int], LegacyClassScoped[int])) +# TODO: these should pass +static_assert(is_equivalent_to(NewStyleClassScoped, LegacyClassScoped)) # error: [static-assert-error] +static_assert(is_equivalent_to(NewStyleClassScoped[int], LegacyClassScoped[int])) # error: [static-assert-error] class NominalGeneric[T]: def method(self, input: T) -> None: ... def _[T](x: T) -> T: - static_assert(is_equivalent_to(NewStyleClassScoped[T], LegacyClassScoped[T])) + # TODO: should pass + static_assert(is_equivalent_to(NewStyleClassScoped[T], LegacyClassScoped[T])) # error: [static-assert-error] static_assert(is_subtype_of(NominalGeneric[T], NewStyleClassScoped[T])) static_assert(is_subtype_of(NominalGeneric[T], LegacyClassScoped[T])) return x @@ -2016,17 +2018,27 @@ class NominalReturningSelfNotGeneric: # TODO: should pass static_assert(is_equivalent_to(LegacyFunctionScoped, NewStyleFunctionScoped)) # error: [static-assert-error] -static_assert(is_subtype_of(NominalNewStyle, NewStyleFunctionScoped)) -static_assert(is_subtype_of(NominalNewStyle, LegacyFunctionScoped)) +static_assert(is_assignable_to(NominalNewStyle, NewStyleFunctionScoped)) +static_assert(is_assignable_to(NominalNewStyle, LegacyFunctionScoped)) +# TODO: should pass +static_assert(is_subtype_of(NominalNewStyle, NewStyleFunctionScoped)) # error: [static-assert-error] +# TODO: should pass +static_assert(is_subtype_of(NominalNewStyle, LegacyFunctionScoped)) # error: [static-assert-error] static_assert(not is_assignable_to(NominalNewStyle, UsesSelf)) -static_assert(is_subtype_of(NominalLegacy, NewStyleFunctionScoped)) -static_assert(is_subtype_of(NominalLegacy, LegacyFunctionScoped)) +static_assert(is_assignable_to(NominalLegacy, NewStyleFunctionScoped)) +static_assert(is_assignable_to(NominalLegacy, LegacyFunctionScoped)) +# TODO: should pass +static_assert(is_subtype_of(NominalLegacy, NewStyleFunctionScoped)) # error: [static-assert-error] +# TODO: should pass +static_assert(is_subtype_of(NominalLegacy, LegacyFunctionScoped)) # error: [static-assert-error] static_assert(not is_assignable_to(NominalLegacy, UsesSelf)) static_assert(not is_assignable_to(NominalWithSelf, NewStyleFunctionScoped)) static_assert(not is_assignable_to(NominalWithSelf, LegacyFunctionScoped)) -static_assert(is_subtype_of(NominalWithSelf, UsesSelf)) +static_assert(is_assignable_to(NominalWithSelf, UsesSelf)) +# TODO: should pass +static_assert(is_subtype_of(NominalWithSelf, UsesSelf)) # error: [static-assert-error] # TODO: these should pass static_assert(not is_assignable_to(NominalNotGeneric, NewStyleFunctionScoped)) # error: [static-assert-error] @@ -2035,8 +2047,23 @@ static_assert(not is_assignable_to(NominalNotGeneric, UsesSelf)) static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, NewStyleFunctionScoped)) static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, LegacyFunctionScoped)) + # TODO: should pass static_assert(not is_assignable_to(NominalReturningSelfNotGeneric, UsesSelf)) # error: [static-assert-error] + +# These test cases are taken from the typing conformance suite: +class ShapeProtocolImplicitSelf(Protocol): + def set_scale(self, scale: float) -> Self: ... + +class ShapeProtocolExplicitSelf(Protocol): + def set_scale(self: Self, scale: float) -> Self: ... + +class BadReturnType: + def set_scale(self, scale: float) -> int: + return 42 + +static_assert(not is_assignable_to(BadReturnType, ShapeProtocolImplicitSelf)) +static_assert(not is_assignable_to(BadReturnType, ShapeProtocolExplicitSelf)) ``` ## Subtyping of protocols with `@classmethod` or `@staticmethod` members diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index b877db6592..2b0daa9c18 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -23,7 +23,6 @@ use crate::{ diagnostic::report_undeclared_protocol_member, signatures::{Parameter, Parameters}, todo_type, - visitor::any_over_type, }, }; @@ -571,24 +570,7 @@ impl<'a, 'db> ProtocolMember<'a, 'db> { attribute_type }; - let proto_member_as_bound_method = method.bind_self(db); - - if any_over_type( - db, - proto_member_as_bound_method, - &|t| matches!(t, Type::TypeVar(_)), - true, - ) { - // TODO: proper validation for generic methods on protocols - return ConstraintSet::from(true); - } - - attribute_type.has_relation_to_impl( - db, - proto_member_as_bound_method, - relation, - visitor, - ) + attribute_type.has_relation_to_impl(db, method.bind_self(db), relation, visitor) } // TODO: consider the types of the attribute on `other` for property members ProtocolMemberKind::Property(_) => ConstraintSet::from(matches!(