Skip to content

Commit 8597fc7

Browse files
committed
Some cleanup
1 parent dbe1c63 commit 8597fc7

6 files changed

Lines changed: 85 additions & 41 deletions

File tree

mypy/checkexpr.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4983,17 +4983,16 @@ class LongName(Generic[T]): ...
49834983
# A = List[Tuple[T, T]]
49844984
# x = A() <- same as List[Tuple[Any, Any]], see PEP 484.
49854985
disallow_any = self.chk.options.disallow_any_generics and self.is_callee
4986-
item = get_proper_type(
4987-
set_any_tvars(
4988-
alias,
4989-
[],
4990-
ctx.line,
4991-
ctx.column,
4992-
self.chk.options,
4993-
disallow_any=disallow_any,
4994-
fail=self.msg.fail,
4995-
)[0]
4986+
item, _ = set_any_tvars(
4987+
alias,
4988+
[],
4989+
ctx.line,
4990+
ctx.column,
4991+
self.chk.options,
4992+
disallow_any=disallow_any,
4993+
fail=self.msg.fail,
49964994
)
4995+
item = get_proper_type(item)
49974996
if isinstance(item, Instance):
49984997
# Normally we get a callable type (or overloaded) with .is_type_obj() true
49994998
# representing the class's constructor

mypy/nodes.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3168,6 +3168,9 @@ class TypeVarLikeExpr(SymbolNode, Expression):
31683168
# TypeVar(..., contravariant=True) defines a contravariant type
31693169
# variable.
31703170
variance: int
3171+
# Record instances and type aliases that appear bare/implicit in the default value
3172+
# of this type variable. This is needed to detect recursive type variable defaults.
3173+
default_depends: set[TypeInfo | TypeAlias] | None
31713174

31723175
def __init__(
31733176
self,
@@ -3186,7 +3189,7 @@ def __init__(
31863189
self.default = default
31873190
self.variance = variance
31883191
self.is_new_style = is_new_style
3189-
self.default_depends: set[TypeInfo | TypeAlias] | None = None
3192+
self.default_depends = None
31903193

31913194
@property
31923195
def name(self) -> str:
@@ -3826,6 +3829,16 @@ class is generic then it will be a type constructor of higher kind.
38263829
# appears in runtime context.
38273830
type_object_type: mypy.types.FunctionLike | None
38283831

3832+
# Type variables whose defaults depend on defaults of type variables in other classes
3833+
# and type aliases. We keep track of this to safely handle situations like this one:
3834+
# class C[T = D]: ...
3835+
# class D[S = C]: ...
3836+
# x: C
3837+
# Since we apply fix_instance() eagerly, inferring a precise type is quite tricky.
3838+
# Therefore, we infer the type of `x` as `C[D[Any]]` to avoid infinite recursion.
3839+
# Keys are type variable full names.
3840+
default_depends: dict[str, set[TypeAlias | TypeInfo]]
3841+
38293842
FLAGS: Final = [
38303843
"is_abstract",
38313844
"is_enum",
@@ -3887,7 +3900,7 @@ def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None
38873900
self.is_type_check_only = False
38883901
self.deprecated = None
38893902
self.type_object_type = None
3890-
self.default_depends: dict[str, set[TypeAlias | TypeInfo]] = {}
3903+
self.default_depends = {}
38913904

38923905
def add_type_vars(self) -> None:
38933906
self.has_type_var_tuple_type = False
@@ -4586,6 +4599,7 @@ def __init__(
45864599
self.eager = eager
45874600
self.python_3_12_type_alias = python_3_12_type_alias
45884601
self.tvar_tuple_index = None
4602+
# This plays the same role as TypeInfo.default_depends attribute.
45894603
self.default_depends: dict[str, set[TypeAlias | TypeInfo]] = {}
45904604
for i, t in enumerate(alias_tvars):
45914605
if isinstance(t, mypy.types.TypeVarTupleType):

mypy/semanal.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,10 @@ def __init__(
538538
# [b.py]
539539
# import foo.bar
540540
self.transitive_submodule_imports: dict[str, set[str]] = {}
541+
542+
# Instances and type aliases that were fixed using default valuers of type
543+
# variables. This can be used on-demand by type analyzer. Use record_fixed_type()
544+
# to create the set lazily.
541545
self.types_fixed: set[TypeInfo | TypeAlias] | None = None
542546

543547
# mypyc doesn't properly handle implementing an abstractproperty
@@ -1990,8 +1994,10 @@ def analyze_class(self, defn: ClassDef) -> None:
19901994
default_depends[tv.fullname] = tv.default_depends
19911995

19921996
if any(has_placeholder(tvd) for tvd in tvar_defs):
1993-
# Some type variable bounds or values are not ready, we need
1994-
# to re-analyze this class.
1997+
# Some type variable bounds or values are not ready, we need to
1998+
# re-analyze this class. Note we force progress to handle cases like
1999+
# class C[T = C], this matches logic in process_typevar_parameters()
2000+
# for "old style" type variables.
19952001
self.defer(force_progress=tvar_defs != defn.type_vars)
19962002

19972003
self.analyze_class_keywords(defn)
@@ -2283,7 +2289,8 @@ class Foo(Bar, Generic[T]): ...
22832289
22842290
Note that this is performed *before* semantic analysis.
22852291
2286-
Returns (remaining base expressions, inferred type variables, is protocol).
2292+
Returns a tuple:
2293+
(remaining base expressions, type variables, is protocol, type variable expressions).
22872294
"""
22882295
removed: list[int] = []
22892296
declared_tvars: TypeVarLikeList = []
@@ -3993,7 +4000,8 @@ def analyze_alias(
39934000
"""Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
39944001
39954002
If yes, return the corresponding type, a list of type variables for generic aliases,
3996-
a set of names the alias depends on, and True if the original type has empty tuple index.
4003+
a set of names the alias depends on, whether the original type has empty tuple index,
4004+
and any type variables whose defaults depend on other classes or type aliases.
39974005
An example for the dependencies:
39984006
A = int
39994007
B = str
@@ -4179,9 +4187,6 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
41794187
self.mark_incomplete(lvalue.name, rvalue, becomes_typeinfo=True)
41804188
return True
41814189

4182-
if any(has_placeholder(tv) for tv in alias_tvars):
4183-
self.defer()
4184-
41854190
self.add_type_alias_deps(depends_on)
41864191
check_for_explicit_any(res, self.options, self.is_typeshed_stub_file, self.msg, context=s)
41874192
# When this type alias gets "inlined", the Any is not explicit anymore,
@@ -4236,7 +4241,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
42364241
# An alias gets updated.
42374242
updated = False
42384243
if isinstance(existing.node, TypeAlias):
4239-
if existing.node.target != res or existing.node.alias_tvars != alias_tvars:
4244+
if existing.node.target != res:
42404245
# Copy expansion to the existing alias, this matches how we update base classes
42414246
# for a TypeInfo _in place_ if there are nested placeholders.
42424247
existing.node.target = res
@@ -4250,6 +4255,8 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
42504255
# Otherwise just replace existing placeholder with type alias *in place*.
42514256
existing._node = alias_node
42524257
updated = True
4258+
# TODO: switch type aliases to if has_placeholder(): process_placeholder() pattern.
4259+
# Type aliases are last notable exception from this logic.
42534260
if updated:
42544261
if self.final_iteration:
42554262
self.cannot_resolve_name(lvalue.name, "name", s)
@@ -4773,6 +4780,7 @@ def process_typevar_declaration(self, s: AssignmentStmt) -> bool:
47734780
n_values = call.arg_kinds[1:].count(ARG_POS)
47744781
values = self.analyze_value_types(call.args[1 : 1 + n_values])
47754782

4783+
# Reset fixed types both before and after each collection just in case.
47764784
self.types_fixed = None
47774785
res = self.process_typevar_parameters(
47784786
call.args[1 + n_values :],
@@ -5027,18 +5035,16 @@ def get_typevarlike_argument(
50275035
# class Custom(Generic[T]):
50285036
# ...
50295037
analyzed = PlaceholderType(None, [], context.line)
5030-
if report_invalid_typevar_arg:
5031-
if (
5032-
isinstance(analyzed, ProperType)
5033-
and isinstance(analyzed, AnyType)
5034-
and analyzed.is_from_error
5035-
):
5036-
self.fail(
5037-
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(
5038-
typevarlike_name, param_name
5039-
),
5040-
param_value,
5041-
)
5038+
if (
5039+
report_invalid_typevar_arg
5040+
and isinstance(analyzed, ProperType)
5041+
and isinstance(analyzed, AnyType)
5042+
and analyzed.is_from_error
5043+
):
5044+
self.fail(
5045+
message_registry.TYPEVAR_ARG_MUST_BE_TYPE.format(typevarlike_name, param_name),
5046+
param_value,
5047+
)
50425048
# Note: we do not return 'None' here -- we want to continue
50435049
# using the AnyType.
50445050
return analyzed
@@ -5757,10 +5763,9 @@ def visit_type_alias_stmt(self, s: TypeAliasStmt) -> None:
57575763
self.mark_incomplete(s.name.name, s.value, becomes_typeinfo=True)
57585764
return
57595765

5760-
# Now go through all new variables and temporary replace all tvars that still
5761-
# refer to some placeholders. We defer the whole alias and will revisit it again,
5762-
# as well as all its dependents.
57635766
if any(has_placeholder(tv) for tv in alias_tvars):
5767+
# Defer the alias if some type variables are not ready, same as for classes.
5768+
# Note: progress is forced below (if needed).
57645769
self.defer()
57655770

57665771
self.add_type_alias_deps(depends_on)

mypy/type_visitor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,15 @@ def visit_type_alias_type(self, t: TypeAliasType, /) -> bool:
595595
elif t in self.seen_aliases:
596596
return self.default
597597
self.seen_aliases.add(t)
598-
return get_proper_type(t).accept(self) or self.query_types(t.args)
598+
res = get_proper_type(t).accept(self)
599+
# This is a weird edge case: if a type alias has unused type variables, we
600+
# should visit arguments even if we didn't find anything in the expansion.
601+
# As an optimization, do this only for new style type aliases.
602+
assert t.alias is not None
603+
if self.strategy == ANY_STRATEGY:
604+
return res or (t.alias.python_3_12_type_alias and self.query_types(t.args))
605+
else:
606+
return res and (not t.alias.python_3_12_type_alias or self.query_types(t.args))
599607

600608
def query_types(self, types: list[Type] | tuple[Type, ...]) -> bool:
601609
"""Perform a query for a sequence of types using the strategy to combine the results."""

mypy/typeanal.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ def __init__(
269269
self.allow_type_any = allow_type_any
270270
self.allow_type_var_tuple = False
271271
self.allow_unpack = allow_unpack
272+
# Set when we are analyzing a default of a type variable.
272273
self.analyzing_tvar_def = analyzing_tvar_def
273274

274275
def lookup_qualified(
@@ -914,6 +915,8 @@ def analyze_type_with_type_info(
914915
analyzing_tvar_def=self.analyzing_tvar_def,
915916
)
916917
if self.analyzing_tvar_def and used_default:
918+
# For convenience, we make default depend on the original TypeInfo,
919+
# *not* on the special alias.
917920
self.api.record_fixed_type(info)
918921
return res
919922
return tup.copy_modified(
@@ -936,6 +939,8 @@ def analyze_type_with_type_info(
936939
analyzing_tvar_def=self.analyzing_tvar_def,
937940
)
938941
if self.analyzing_tvar_def and used_default:
942+
# For convenience, we make default depend on the original TypeInfo,
943+
# *not* on the special alias.
939944
self.api.record_fixed_type(info)
940945
return res
941946
# Create a named TypedDictType
@@ -2114,8 +2119,10 @@ def fix_instance(
21142119
if tv.has_default():
21152120
arg = tv.default
21162121
if analyzing_tvar_def:
2122+
# Record the use of default only when analyzing another default.
21172123
used_default = True
21182124
if is_typevar_default_recursive(tv.fullname, t.type):
2125+
# If this results in infinite recursion, use Any instead.
21192126
use_any = True
21202127
else:
21212128
use_any = True
@@ -2362,6 +2369,7 @@ def set_any_tvars(
23622369
if arg is None:
23632370
if tv.has_default():
23642371
arg = tv.default
2372+
# Same as for instances, record and avoid infinite recursion.
23652373
if analyzing_tvar_def:
23662374
used_default = True
23672375
if is_typevar_default_recursive(tv.fullname, node):
@@ -2401,16 +2409,19 @@ def set_any_tvars(
24012409
return t, used_default
24022410

24032411

2404-
def is_typevar_default_recursive(tv: str, start: TypeInfo | TypeAlias) -> bool:
2405-
if tv not in start.default_depends:
2412+
def is_typevar_default_recursive(tv_fname: str, start: TypeInfo | TypeAlias) -> bool:
2413+
"""Check if the type variable can lead to infinite recursion via defaults."""
2414+
if tv_fname not in start.default_depends:
24062415
return False
2407-
todo = start.default_depends[tv].copy()
2416+
todo = start.default_depends[tv_fname].copy()
24082417
seen: set[TypeAlias | TypeInfo] = set()
24092418
while todo:
24102419
node = todo.pop()
24112420
if node is start:
24122421
return True
24132422
if node in seen:
2423+
# We don't return True here, since we are interested only in
2424+
# recursion via the original type variable.
24142425
continue
24152426
seen.add(node)
24162427
for dep_nodes in node.default_depends.values():

mypy/types.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,12 @@ def __eq__(self, other: object) -> bool:
855855
if not isinstance(other, ParamSpecType):
856856
return NotImplemented
857857
# Upper bound can be ignored, since it's determined by flavor.
858-
return self.id == other.id and self.flavor == other.flavor and self.prefix == other.prefix
858+
return (
859+
self.id == other.id
860+
and self.flavor == other.flavor
861+
and self.prefix == other.prefix
862+
and self.default == other.default
863+
)
859864

860865
def serialize(self) -> JsonDict:
861866
assert not self.id.is_meta_var()
@@ -1004,7 +1009,9 @@ def __hash__(self) -> int:
10041009
def __eq__(self, other: object) -> bool:
10051010
if not isinstance(other, TypeVarTupleType):
10061011
return NotImplemented
1007-
return self.id == other.id and self.min_len == other.min_len
1012+
return (
1013+
self.id == other.id and self.min_len == other.min_len and self.default == other.default
1014+
)
10081015

10091016
def copy_modified(
10101017
self,

0 commit comments

Comments
 (0)