diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 97b8e6b9ad..27cdde1017 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -264,9 +264,21 @@ def f( # fmt: on ``` -Nonetheless, `Protocol` can still be used as the second argument to `issubclass()` at runtime: +Nonetheless, `Protocol` is an instance of `type` at runtime, and therefore can still be used as the +second argument to `issubclass()` at runtime: ```py +import abc +import typing +from ty_extensions import TypeOf + +reveal_type(type(Protocol)) # revealed: +# revealed: tuple[, , , ] +reveal_type(type(Protocol).__mro__) +static_assert(is_subtype_of(TypeOf[Protocol], type)) +static_assert(is_subtype_of(TypeOf[Protocol], abc.ABCMeta)) +static_assert(is_subtype_of(TypeOf[Protocol], typing._ProtocolMeta)) + # Could also be `Literal[True]`, but `bool` is fine: reveal_type(issubclass(MyProtocol, Protocol)) # revealed: bool ``` diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 48766457d7..9a45eb6b7d 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3694,6 +3694,7 @@ pub enum KnownClass { ParamSpec, ParamSpecArgs, ParamSpecKwargs, + ProtocolMeta, TypeVarTuple, TypeAliasType, NoDefaultType, @@ -3821,6 +3822,7 @@ impl KnownClass { | Self::NamedTupleFallback | Self::NamedTupleLike | Self::ConstraintSet + | Self::ProtocolMeta | Self::TypedDictFallback => Some(Truthiness::Ambiguous), Self::Tuple => None, @@ -3903,6 +3905,7 @@ impl KnownClass { | KnownClass::ConstraintSet | KnownClass::TypedDictFallback | KnownClass::BuiltinFunctionType + | KnownClass::ProtocolMeta | KnownClass::Template => false, } } @@ -3982,6 +3985,7 @@ impl KnownClass { | KnownClass::ConstraintSet | KnownClass::TypedDictFallback | KnownClass::BuiltinFunctionType + | KnownClass::ProtocolMeta | KnownClass::Template => false, } } @@ -4060,6 +4064,7 @@ impl KnownClass { | KnownClass::NamedTupleFallback | KnownClass::ConstraintSet | KnownClass::BuiltinFunctionType + | KnownClass::ProtocolMeta | KnownClass::Template => false, } } @@ -4151,6 +4156,7 @@ impl KnownClass { | Self::ConstraintSet | Self::TypedDictFallback | Self::BuiltinFunctionType + | Self::ProtocolMeta | Self::Template => false, } } @@ -4250,6 +4256,7 @@ impl KnownClass { Self::ConstraintSet => "ConstraintSet", Self::TypedDictFallback => "TypedDictFallback", Self::Template => "Template", + Self::ProtocolMeta => "_ProtocolMeta", } } @@ -4477,6 +4484,7 @@ impl KnownClass { | Self::StdlibAlias | Self::Iterable | Self::Iterator + | Self::ProtocolMeta | Self::SupportsIndex => KnownModule::Typing, Self::TypeAliasType | Self::TypeVarTuple @@ -4596,6 +4604,7 @@ impl KnownClass { | Self::ConstraintSet | Self::TypedDictFallback | Self::BuiltinFunctionType + | Self::ProtocolMeta | Self::Template => Some(false), Self::Tuple => None, @@ -4680,6 +4689,7 @@ impl KnownClass { | Self::ConstraintSet | Self::TypedDictFallback | Self::BuiltinFunctionType + | Self::ProtocolMeta | Self::Template => false, } } @@ -4773,6 +4783,7 @@ impl KnownClass { "ConstraintSet" => Self::ConstraintSet, "TypedDictFallback" => Self::TypedDictFallback, "Template" => Self::Template, + "_ProtocolMeta" => Self::ProtocolMeta, _ => return None, }; @@ -4855,9 +4866,9 @@ impl KnownClass { | Self::TypeVarTuple | Self::Iterable | Self::Iterator + | Self::ProtocolMeta | Self::NewType => matches!(module, KnownModule::Typing | KnownModule::TypingExtensions), Self::Deprecated => matches!(module, KnownModule::Warnings | KnownModule::TypingExtensions), - } } diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs index de4dc4d3f1..721def0dee 100644 --- a/crates/ty_python_semantic/src/types/special_form.rs +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -160,9 +160,13 @@ impl SpecialFormType { | Self::Bottom | Self::Intersection | Self::CallableTypeOf - | Self::Protocol // actually `_ProtocolMeta` at runtime but this is what typeshed says | Self::ReadOnly => KnownClass::SpecialForm, + // Typeshed says it's an instance of `_SpecialForm`, + // but then we wouldn't recognise things like `issubclass(`X, Protocol)` + // as being valid. + Self::Protocol => KnownClass::ProtocolMeta, + Self::Generic | Self::Any => KnownClass::Type, Self::List