Skip to content

Commit e293411

Browse files
authored
[ty] get_protocol_members returns a frozenset, not a tuple (#18284)
1 parent 53d19f8 commit e293411

File tree

2 files changed

+18
-27
lines changed

2 files changed

+18
-27
lines changed

crates/ty_python_semantic/resources/mdtest/protocols.md

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,7 @@ class Foo(Protocol):
375375
def method_member(self) -> bytes:
376376
return b"foo"
377377

378-
# TODO: actually a frozenset (requires support for legacy generics)
379-
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["method_member"], Literal["x"], Literal["y"], Literal["z"]]
378+
reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["method_member", "x", "y", "z"]]
380379
```
381380

382381
Certain special attributes and methods are not considered protocol members at runtime, and should
@@ -394,8 +393,7 @@ class Lumberjack(Protocol):
394393
def __init__(self, x: int) -> None:
395394
self.x = x
396395

397-
# TODO: actually a frozenset
398-
reveal_type(get_protocol_members(Lumberjack)) # revealed: tuple[Literal["x"]]
396+
reveal_type(get_protocol_members(Lumberjack)) # revealed: frozenset[Literal["x"]]
399397
```
400398

401399
A sub-protocol inherits and extends the members of its superclass protocol(s):
@@ -407,13 +405,11 @@ class Bar(Protocol):
407405
class Baz(Bar, Protocol):
408406
ham: memoryview
409407

410-
# TODO: actually a frozenset
411-
reveal_type(get_protocol_members(Baz)) # revealed: tuple[Literal["ham"], Literal["spam"]]
408+
reveal_type(get_protocol_members(Baz)) # revealed: frozenset[Literal["ham", "spam"]]
412409

413410
class Baz2(Bar, Foo, Protocol): ...
414411

415-
# TODO: actually a frozenset
416-
# revealed: tuple[Literal["method_member"], Literal["spam"], Literal["x"], Literal["y"], Literal["z"]]
412+
# revealed: frozenset[Literal["method_member", "spam", "x", "y", "z"]]
417413
reveal_type(get_protocol_members(Baz2))
418414
```
419415

@@ -441,8 +437,7 @@ class Foo(Protocol):
441437
e = 56
442438
def f(self) -> None: ...
443439

444-
# TODO: actually a frozenset
445-
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["d"], Literal["e"], Literal["f"]]
440+
reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]]
446441
```
447442

448443
## Invalid calls to `get_protocol_members()`
@@ -673,8 +668,7 @@ class LotsOfBindings(Protocol):
673668
case l: # TODO: this should error with `[invalid-protocol]` (`l` is not declared)
674669
...
675670

676-
# TODO: actually a frozenset
677-
# 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"]]
671+
# revealed: frozenset[Literal["Nested", "NestedProtocol", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]]
678672
reveal_type(get_protocol_members(LotsOfBindings))
679673
```
680674

@@ -702,9 +696,7 @@ class Foo(Protocol):
702696

703697
# Note: the list of members does not include `a`, `b` or `c`,
704698
# as none of these attributes is declared in the class body.
705-
#
706-
# TODO: actually a frozenset
707-
reveal_type(get_protocol_members(Foo)) # revealed: tuple[Literal["non_init_method"], Literal["x"], Literal["y"]]
699+
reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["non_init_method", "x", "y"]]
708700
```
709701

710702
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):
717709
class Sub(Super, Protocol):
718710
x = 42 # no error here, since it's declared in the superclass
719711

720-
# TODO: actually frozensets
721-
reveal_type(get_protocol_members(Super)) # revealed: tuple[Literal["x"]]
722-
reveal_type(get_protocol_members(Sub)) # revealed: tuple[Literal["x"]]
712+
reveal_type(get_protocol_members(Super)) # revealed: frozenset[Literal["x"]]
713+
reveal_type(get_protocol_members(Sub)) # revealed: frozenset[Literal["x"]]
723714
```
724715

725716
If a protocol has 0 members, then all other types are assignable to it, and all fully static types

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -667,15 +667,15 @@ impl<'db> Bindings<'db> {
667667
Some(KnownFunction::GetProtocolMembers) => {
668668
if let [Some(Type::ClassLiteral(class))] = overload.parameter_types() {
669669
if let Some(protocol_class) = class.into_protocol_class(db) {
670-
// TODO: actually a frozenset at runtime (requires support for legacy generic classes)
671-
overload.set_return_type(Type::Tuple(TupleType::new(
672-
db,
673-
protocol_class
674-
.interface(db)
675-
.members(db)
676-
.map(|member| Type::string_literal(db, member.name()))
677-
.collect::<Box<[Type<'db>]>>(),
678-
)));
670+
let member_names = protocol_class
671+
.interface(db)
672+
.members(db)
673+
.map(|member| Type::string_literal(db, member.name()));
674+
let specialization = UnionType::from_elements(db, member_names);
675+
overload.set_return_type(
676+
KnownClass::FrozenSet
677+
.to_specialized_instance(db, [specialization]),
678+
);
679679
}
680680
}
681681
}

0 commit comments

Comments
 (0)