Skip to content

Commit c47dc7b

Browse files
committed
Fix incorrect TypeVar default expansion order (Fixes #20336)
1 parent 67df116 commit c47dc7b

File tree

1 file changed

+35
-4
lines changed

1 file changed

+35
-4
lines changed

mypy/typeanal.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,11 +2102,42 @@ def fix_instance(
21022102
env[tv.id] = arg
21032103
t.args = tuple(args)
21042104
fix_type_var_tuple_argument(t)
2105+
t.args = tuple(args)
2106+
fix_type_var_tuple_argument(t)
21052107
if not t.type.has_type_var_tuple_type:
2106-
with state.strict_optional_set(options.strict_optional):
2107-
fixed = expand_type(t, env)
2108-
assert isinstance(fixed, Instance)
2109-
t.args = fixed.args
2108+
# Special-case only when the user is applying this generic using just the
2109+
# non-default type parameters (e.g. Foo[Y, X] inside class Foo[X, Y, Z=object]).
2110+
# In that case we want to preserve the user-written order of type arguments
2111+
# instead of letting expand_type() potentially canonicalize/reorder them.
2112+
from mypy.types import TypeVarType
2113+
2114+
own_tvars = [tv for tv in t.type.defn.type_vars if isinstance(tv, TypeVarType)]
2115+
2116+
# Index of the first TypeVar that has a default; all earlier ones are
2117+
# "non-default" parameters like X, Y in Foo[X, Y, Z=object].
2118+
first_default = len(own_tvars)
2119+
for i, tv in enumerate(own_tvars):
2120+
if tv.default is not None:
2121+
first_default = i
2122+
break
2123+
2124+
def is_own_tvar(arg: Type) -> bool:
2125+
return isinstance(arg, TypeVarType) and any(arg.id == tv.id for tv in own_tvars)
2126+
2127+
# Only skip expand_type() when:
2128+
# * all provided args are this class's own TypeVars
2129+
# * and we are only filling the non-default prefix (e.g. Foo[Y, X])
2130+
#
2131+
# This avoids interfering with more advanced cases where defaults are
2132+
# recursive, refer to other TypeVars, come from other files, etc.
2133+
if args and len(args) <= first_default and all(is_own_tvar(arg) for arg in args):
2134+
# Keep t.args as-is. Defaults will be handled elsewhere as usual.
2135+
pass
2136+
else:
2137+
with state.strict_optional_set(options.strict_optional):
2138+
fixed = expand_type(t, env)
2139+
assert isinstance(fixed, Instance)
2140+
t.args = fixed.args
21102141

21112142

21122143
def instantiate_type_alias(

0 commit comments

Comments
 (0)