[ty] get_protocol_members returns a frozenset, not a tuple (#18284)

This commit is contained in:
Alex Waygood 2025-05-23 19:20:34 -04:00 committed by GitHub
parent 53d19f8368
commit e293411679
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 18 additions and 27 deletions

View file

@ -375,8 +375,7 @@ class Foo(Protocol):
def method_member(self) -> bytes:
return b"foo"
# TODO: actually a frozenset (requires support for legacy generics)
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["method_member"], Literal["x"], Literal["y"], Literal["z"]]
reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["method_member", "x", "y", "z"]]
```
Certain special attributes and methods are not considered protocol members at runtime, and should
@ -394,8 +393,7 @@ class Lumberjack(Protocol):
def __init__(self, x: int) -> None:
self.x = x
# TODO: actually a frozenset
reveal_type(get_protocol_members(Lumberjack)) # revealed: tuple[Literal["x"]]
reveal_type(get_protocol_members(Lumberjack)) # revealed: frozenset[Literal["x"]]
```
A sub-protocol inherits and extends the members of its superclass protocol(s):
@ -407,13 +405,11 @@ class Bar(Protocol):
class Baz(Bar, Protocol):
ham: memoryview
# TODO: actually a frozenset
reveal_type(get_protocol_members(Baz)) # revealed: tuple[Literal["ham"], Literal["spam"]]
reveal_type(get_protocol_members(Baz)) # revealed: frozenset[Literal["ham", "spam"]]
class Baz2(Bar, Foo, Protocol): ...
# TODO: actually a frozenset
# revealed: tuple[Literal["method_member"], Literal["spam"], Literal["x"], Literal["y"], Literal["z"]]
# revealed: frozenset[Literal["method_member", "spam", "x", "y", "z"]]
reveal_type(get_protocol_members(Baz2))
```
@ -441,8 +437,7 @@ class Foo(Protocol):
e = 56
def f(self) -> None: ...
# TODO: actually a frozenset
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["d"], Literal["e"], Literal["f"]]
reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]]
```
## Invalid calls to `get_protocol_members()`
@ -673,8 +668,7 @@ class LotsOfBindings(Protocol):
case l: # TODO: this should error with `[invalid-protocol]` (`l` is not declared)
...
# TODO: actually a frozenset
# revealed: tuple[Literal["Nested"], Literal["NestedProtocol"], Literal["a"], Literal["b"], Literal["c"], Literal["d"], Literal["e"], Literal["f"], Literal["g"], Literal["h"], Literal["i"], Literal["j"], Literal["k"], Literal["l"]]
# revealed: frozenset[Literal["Nested", "NestedProtocol", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]]
reveal_type(get_protocol_members(LotsOfBindings))
```
@ -702,9 +696,7 @@ class Foo(Protocol):
# Note: the list of members does not include `a`, `b` or `c`,
# as none of these attributes is declared in the class body.
#
# TODO: actually a frozenset
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["non_init_method"], Literal["x"], Literal["y"]]
reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["non_init_method", "x", "y"]]
```
If a member is declared in a superclass of a protocol class, it is fine for it to be assigned to in
@ -717,9 +709,8 @@ class Super(Protocol):
class Sub(Super, Protocol):
x = 42 # no error here, since it's declared in the superclass
# TODO: actually frozensets
reveal_type(get_protocol_members(Super)) # revealed: tuple[Literal["x"]]
reveal_type(get_protocol_members(Sub)) # revealed: tuple[Literal["x"]]
reveal_type(get_protocol_members(Super)) # revealed: frozenset[Literal["x"]]
reveal_type(get_protocol_members(Sub)) # revealed: frozenset[Literal["x"]]
```
If a protocol has 0 members, then all other types are assignable to it, and all fully static types

View file

@ -667,15 +667,15 @@ impl<'db> Bindings<'db> {
Some(KnownFunction::GetProtocolMembers) => {
if let [Some(Type::ClassLiteral(class))] = overload.parameter_types() {
if let Some(protocol_class) = class.into_protocol_class(db) {
// TODO: actually a frozenset at runtime (requires support for legacy generic classes)
overload.set_return_type(Type::Tuple(TupleType::new(
db,
protocol_class
.interface(db)
.members(db)
.map(|member| Type::string_literal(db, member.name()))
.collect::<Box<[Type<'db>]>>(),
)));
let member_names = protocol_class
.interface(db)
.members(db)
.map(|member| Type::string_literal(db, member.name()));
let specialization = UnionType::from_elements(db, member_names);
overload.set_return_type(
KnownClass::FrozenSet
.to_specialized_instance(db, [specialization]),
);
}
}
}