diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index 14eee12406..6fbda7553a 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -389,7 +389,7 @@ To see the kinds and types of the protocol members, you can use the debugging ai ```py from ty_extensions import reveal_protocol_interface -from typing import SupportsIndex, SupportsAbs +from typing import SupportsIndex, SupportsAbs, ClassVar # error: [revealed-type] "Revealed protocol interface: `{"method_member": MethodMember(`(self) -> bytes`), "x": AttributeMember(`int`), "y": PropertyMember { get_type: `str` }, "z": PropertyMember { get_type: `int`, set_type: `int` }}`" reveal_protocol_interface(Foo) @@ -419,6 +419,12 @@ reveal_protocol_interface(BaseProto) # error: [revealed-type] "Revealed protocol interface: `{"member": MethodMember(`(self) -> bool`)}`" reveal_protocol_interface(SubProto) + +class ProtoWithClassVar(Protocol): + x: ClassVar[int] + +# error: [revealed-type] "Revealed protocol interface: `{"x": AttributeMember(`int`; ClassVar)}`" +reveal_protocol_interface(ProtoWithClassVar) ``` Certain special attributes and methods are not considered protocol members at runtime, and should diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 493bb4e10d..f18c11c088 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -8,8 +8,9 @@ use rustc_hash::FxHashMap; use super::TypeVarVariance; use crate::semantic_index::place_table; -use crate::types::signatures::ParameterForm; -use crate::types::{AttributeAssignmentError, CallArguments, HasRelationToVisitor, UnionType}; +use crate::types::{ + AttributeAssignmentError, CallArguments, HasRelationToVisitor, Signature, UnionType, +}; use crate::{ Db, FxOrderSet, place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations}, @@ -294,6 +295,7 @@ impl<'db> ProtocolMemberData<'db> { struct ProtocolMemberDataDisplay<'a, 'db> { db: &'db dyn Db, data: &'a ProtocolMemberKind<'db>, + qualifiers: TypeQualifiers, } impl std::fmt::Display for ProtocolMemberDataDisplay<'_, '_> { @@ -312,8 +314,16 @@ impl<'db> ProtocolMemberData<'db> { } d.finish() } - ProtocolMemberKind::Attribute(attribute) => { - write!(f, "AttributeMember(`{}`)", attribute.ty.display(self.db)) + ProtocolMemberKind::Attribute(AttributeMember { + ty, + bound_on_class: _, + }) => { + f.write_str("AttributeMember(")?; + write!(f, "`{}`", ty.display(self.db))?; + if self.qualifiers.contains(TypeQualifiers::CLASS_VAR) { + f.write_str("; ClassVar")?; + } + f.write_char(')') } } } @@ -322,6 +332,7 @@ impl<'db> ProtocolMemberData<'db> { ProtocolMemberDataDisplay { db, data: &self.kind, + qualifiers: self.qualifiers, } } } @@ -354,21 +365,17 @@ impl<'db> PropertyMember<'db> { _ => return None, }; + let set_type_from_signature = |sig: &Signature<'db>| match sig.parameters().as_slice() { + [_, parameter] if parameter.is_positional() && parameter.form.is_value() => { + Some(parameter.annotated_type().unwrap_or_else(Type::unknown)) + } + _ => None, + }; + let set_type = if let Some(signature) = setter_signature { - if let Some(ty) = UnionType::try_from_elements( - db, - signature - .iter() - .map(|sig| match sig.parameters().as_slice() { - [_, parameter] - if parameter.is_positional() - && parameter.form == ParameterForm::Value => - { - Some(parameter.annotated_type().unwrap_or_else(Type::unknown)) - } - _ => None, - }), - ) { + if let Some(ty) = + UnionType::try_from_elements(db, signature.iter().map(set_type_from_signature)) + { Some(ty) } else { return None; diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 11ca44bb8f..0ffbe921cc 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -1594,6 +1594,13 @@ pub(crate) enum ParameterForm { Type, } +impl ParameterForm { + /// Returns `true` if this is a value form. + pub(crate) const fn is_value(self) -> bool { + matches!(self, Self::Value) + } +} + #[cfg(test)] mod tests { use super::*;