Skip to content

Commit

Permalink
Fix crashes on special forms in protocol bodies (#13526)
Browse files Browse the repository at this point in the history
Fixes #6801
Fixes #10577
Fixes #12642
Fixes #12337
Fixes #10639 
Fixes #13390 

All these crashes are in a sense duplicates of each other. Fix is trivial, except I decided to ban type aliases in protocol bodies. Already in the examples in issues, I have found two cases where people wrote `foo = list[str]`, where they clearly wanted `foo: list[str]`. This can cause hard to spot false negatives.
  • Loading branch information
ilevkivskyi authored Aug 26, 2022
1 parent 74e1737 commit fd6760c
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 1 deletion.
5 changes: 4 additions & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2906,7 +2906,10 @@ def protocol_members(self) -> list[str]:
assert self.mro, "This property can be only accessed after MRO is (re-)calculated"
for base in self.mro[:-1]: # we skip "object" since everyone implements it
if base.is_protocol:
for name in base.names:
for name, node in base.names.items():
if isinstance(node.node, (TypeAlias, TypeVarExpr)):
# These are auxiliary definitions (and type aliases are prohibited).
continue
members.add(name)
return sorted(list(members))

Expand Down
6 changes: 6 additions & 0 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3269,6 +3269,12 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
current_node = existing.node if existing else alias_node
assert isinstance(current_node, TypeAlias)
self.disable_invalid_recursive_aliases(s, current_node)
if self.is_class_scope():
assert self.type is not None
if self.type.is_protocol:
self.fail("Type aliases are prohibited in protocol bodies", s)
if not lvalue.name[0].isupper():
self.note("Use variable annotation syntax to define protocol members", s)
return True

def disable_invalid_recursive_aliases(
Expand Down
31 changes: 31 additions & 0 deletions test-data/unit/check-protocols.test
Original file line number Diff line number Diff line change
Expand Up @@ -3546,3 +3546,34 @@ S = TypeVar("S")
def test(arg: P[S]) -> S: ...
b: Type[B]
reveal_type(test(b)) # N: Revealed type is "__main__.B"

[case testTypeAliasInProtocolBody]
from typing import Protocol, List

class P(Protocol):
x = List[str] # E: Type aliases are prohibited in protocol bodies \
# N: Use variable annotation syntax to define protocol members

class C:
x: int
def foo(x: P) -> None: ...
foo(C()) # No extra error here
[builtins fixtures/list.pyi]

[case testTypeVarInProtocolBody]
from typing import Protocol, TypeVar

class C(Protocol):
T = TypeVar('T')
def __call__(self, t: T) -> T: ...

def f_bad(t: int) -> int:
return t

S = TypeVar("S")
def f_good(t: S) -> S:
return t

g: C = f_bad # E: Incompatible types in assignment (expression has type "Callable[[int], int]", variable has type "C") \
# N: "C.__call__" has type "Callable[[Arg(T, 't')], T]"
g = f_good # OK

0 comments on commit fd6760c

Please sign in to comment.