Skip to content

Commit f9d8f3a

Browse files
authored
Fix self-referential upper bound in new-style type variables (#17407)
Fixes #17347 This copies old-style `TypeVar` logic 1:1 (I know it is ugly, but I don't think there is anything better now). Also while I am touching this code, I am removing `third_pass` argument (third pass is not a thing for ~5 years now).
1 parent c4470f1 commit f9d8f3a

File tree

3 files changed

+19
-8
lines changed

3 files changed

+19
-8
lines changed

mypy/plugin.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ def anal_type(
328328
allow_tuple_literal: bool = False,
329329
allow_unbound_tvars: bool = False,
330330
report_invalid_types: bool = True,
331-
third_pass: bool = False,
332331
) -> Type | None:
333332
"""Analyze an unbound type.
334333

mypy/semanal.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,10 +1738,12 @@ def analyze_type_param(
17381738
) -> TypeVarLikeExpr | None:
17391739
fullname = self.qualified_name(type_param.name)
17401740
if type_param.upper_bound:
1741-
upper_bound = self.anal_type(type_param.upper_bound)
1741+
upper_bound = self.anal_type(type_param.upper_bound, allow_placeholder=True)
17421742
# TODO: we should validate the upper bound is valid for a given kind.
17431743
if upper_bound is None:
1744-
return None
1744+
# This and below copies special-casing for old-style type variables, that
1745+
# is equally necessary for new-style classes to break a vicious circle.
1746+
upper_bound = PlaceholderType(None, [], context.line)
17451747
else:
17461748
if type_param.kind == TYPE_VAR_TUPLE_KIND:
17471749
upper_bound = self.named_type("builtins.tuple", [self.object_type()])
@@ -1752,9 +1754,9 @@ def analyze_type_param(
17521754
values = []
17531755
if type_param.values:
17541756
for value in type_param.values:
1755-
analyzed = self.anal_type(value)
1757+
analyzed = self.anal_type(value, allow_placeholder=True)
17561758
if analyzed is None:
1757-
return None
1759+
analyzed = PlaceholderType(None, [], context.line)
17581760
values.append(analyzed)
17591761
return TypeVarExpr(
17601762
name=type_param.name,
@@ -7192,16 +7194,13 @@ def anal_type(
71927194
report_invalid_types: bool = True,
71937195
prohibit_self_type: str | None = None,
71947196
allow_type_any: bool = False,
7195-
third_pass: bool = False,
71967197
) -> Type | None:
71977198
"""Semantically analyze a type.
71987199
71997200
Args:
72007201
typ: Type to analyze (if already analyzed, this is a no-op)
72017202
allow_placeholder: If True, may return PlaceholderType if
72027203
encountering an incomplete definition
7203-
third_pass: Unused; only for compatibility with old semantic
7204-
analyzer
72057204
72067205
Return None only if some part of the type couldn't be bound *and* it
72077206
referred to an incomplete namespace or definition. In this case also

test-data/unit/check-python312.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,19 @@ d: E[int] # E: Type argument "int" of "E" must be a subtype of "str"
15921592
[builtins fixtures/tuple.pyi]
15931593
[typing fixtures/typing-full.pyi]
15941594

1595+
[case testCurrentClassWorksAsBound]
1596+
# flags: --enable-incomplete-feature=NewGenericSyntax
1597+
from typing import Protocol
1598+
1599+
class Comparable[T: Comparable](Protocol):
1600+
def compare(self, other: T) -> bool: ...
1601+
1602+
class Good:
1603+
def compare(self, other: Good) -> bool: ...
1604+
1605+
x: Comparable[Good]
1606+
y: Comparable[int] # E: Type argument "int" of "Comparable" must be a subtype of "Comparable[Any]"
1607+
15951608
[case testPEP695TypeAliasWithDifferentTargetTypes]
15961609
# flags: --enable-incomplete-feature=NewGenericSyntax
15971610
import types # We need GenericAlias from here, and test stubs don't bring in 'types'

0 commit comments

Comments
 (0)