diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index f14b838b6a..a2116c8f97 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -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 diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index a94022f9c3..67a8721be0 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -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::]>>(), - ))); + 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]), + ); } } }