Skip to content

Commit fd6760c

Browse files
authored
Fix crashes on special forms in protocol bodies (#13526)
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.
1 parent 74e1737 commit fd6760c

File tree

3 files changed

+41
-1
lines changed

3 files changed

+41
-1
lines changed

mypy/nodes.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2906,7 +2906,10 @@ def protocol_members(self) -> list[str]:
29062906
assert self.mro, "This property can be only accessed after MRO is (re-)calculated"
29072907
for base in self.mro[:-1]: # we skip "object" since everyone implements it
29082908
if base.is_protocol:
2909-
for name in base.names:
2909+
for name, node in base.names.items():
2910+
if isinstance(node.node, (TypeAlias, TypeVarExpr)):
2911+
# These are auxiliary definitions (and type aliases are prohibited).
2912+
continue
29102913
members.add(name)
29112914
return sorted(list(members))
29122915

mypy/semanal.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3269,6 +3269,12 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
32693269
current_node = existing.node if existing else alias_node
32703270
assert isinstance(current_node, TypeAlias)
32713271
self.disable_invalid_recursive_aliases(s, current_node)
3272+
if self.is_class_scope():
3273+
assert self.type is not None
3274+
if self.type.is_protocol:
3275+
self.fail("Type aliases are prohibited in protocol bodies", s)
3276+
if not lvalue.name[0].isupper():
3277+
self.note("Use variable annotation syntax to define protocol members", s)
32723278
return True
32733279

32743280
def disable_invalid_recursive_aliases(

test-data/unit/check-protocols.test

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3546,3 +3546,34 @@ S = TypeVar("S")
35463546
def test(arg: P[S]) -> S: ...
35473547
b: Type[B]
35483548
reveal_type(test(b)) # N: Revealed type is "__main__.B"
3549+
3550+
[case testTypeAliasInProtocolBody]
3551+
from typing import Protocol, List
3552+
3553+
class P(Protocol):
3554+
x = List[str] # E: Type aliases are prohibited in protocol bodies \
3555+
# N: Use variable annotation syntax to define protocol members
3556+
3557+
class C:
3558+
x: int
3559+
def foo(x: P) -> None: ...
3560+
foo(C()) # No extra error here
3561+
[builtins fixtures/list.pyi]
3562+
3563+
[case testTypeVarInProtocolBody]
3564+
from typing import Protocol, TypeVar
3565+
3566+
class C(Protocol):
3567+
T = TypeVar('T')
3568+
def __call__(self, t: T) -> T: ...
3569+
3570+
def f_bad(t: int) -> int:
3571+
return t
3572+
3573+
S = TypeVar("S")
3574+
def f_good(t: S) -> S:
3575+
return t
3576+
3577+
g: C = f_bad # E: Incompatible types in assignment (expression has type "Callable[[int], int]", variable has type "C") \
3578+
# N: "C.__call__" has type "Callable[[Arg(T, 't')], T]"
3579+
g = f_good # OK

0 commit comments

Comments
 (0)