Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 85f40b5

Browse files
authored
Correctly handle variadic instances with empty arguments (#16238)
Fixes #16199 It was surprisingly hard to fix, because all possible fixes strongly interfered with the code that makes "no-args" aliases possible: ```python l = list x: l[int] # OK, same as list[int] ``` So after all I re-organized (and actually simplified) that old code.
1 parent 2bcec24 commit 85f40b5

9 files changed

Lines changed: 116 additions & 53 deletions

File tree

mypy/checkexpr.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4662,7 +4662,10 @@ class C(Generic[T, Unpack[Ts]]): ...
46624662
info = t.type_object()
46634663
# We reuse the logic from semanal phase to reduce code duplication.
46644664
fake = Instance(info, args, line=ctx.line, column=ctx.column)
4665-
if not validate_instance(fake, self.chk.fail):
4665+
# This code can be only called either from checking a type application, or from
4666+
# checking a type alias (after the caller handles no_args aliases), so we know it
4667+
# was initially an IndexExpr, and we allow empty tuple type arguments.
4668+
if not validate_instance(fake, self.chk.fail, empty_tuple_index=True):
46664669
fix_instance(
46674670
fake, self.chk.fail, self.chk.note, disallow_any=False, options=self.chk.options
46684671
)

mypy/expandtype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def expand_type_by_instance(typ: Type, instance: Instance) -> Type:
8989
def expand_type_by_instance(typ: Type, instance: Instance) -> Type:
9090
"""Substitute type variables in type using values from an Instance.
9191
Type variables are considered to be bound by the class declaration."""
92-
if not instance.args:
92+
if not instance.args and not instance.type.has_type_var_tuple_type:
9393
return typ
9494
else:
9595
variables: dict[TypeVarId, Type] = {}

mypy/messages.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2505,8 +2505,10 @@ def format_literal_value(typ: LiteralType) -> str:
25052505
else:
25062506
base_str = itype.type.name
25072507
if not itype.args:
2508-
# No type arguments, just return the type name
2509-
return base_str
2508+
if not itype.type.has_type_var_tuple_type:
2509+
# No type arguments, just return the type name
2510+
return base_str
2511+
return base_str + "[()]"
25102512
elif itype.type.fullname == "builtins.tuple":
25112513
item_type_str = format(itype.args[0])
25122514
return f"{'tuple' if options.use_lowercase_names() else 'Tuple'}[{item_type_str}, ...]"

mypy/semanal.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,11 @@
231231
check_for_explicit_any,
232232
detect_diverging_alias,
233233
find_self_type,
234-
fix_instance_types,
234+
fix_instance,
235235
has_any_from_unimported_type,
236236
no_subscript_builtin_alias,
237237
type_constructors,
238+
validate_instance,
238239
)
239240
from mypy.typeops import function_type, get_type_vars, try_getting_str_literals_from_type
240241
from mypy.types import (
@@ -722,7 +723,9 @@ def create_alias(self, tree: MypyFile, target_name: str, alias: str, name: str)
722723
target = self.named_type_or_none(target_name, [])
723724
assert target is not None
724725
# Transform List to List[Any], etc.
725-
fix_instance_types(target, self.fail, self.note, self.options)
726+
fix_instance(
727+
target, self.fail, self.note, disallow_any=False, options=self.options
728+
)
726729
alias_node = TypeAlias(
727730
target,
728731
alias,
@@ -3455,7 +3458,7 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ
34553458

34563459
def analyze_alias(
34573460
self, name: str, rvalue: Expression, allow_placeholder: bool = False
3458-
) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str]]:
3461+
) -> tuple[Type | None, list[TypeVarLikeType], set[str], list[str], bool]:
34593462
"""Check if 'rvalue' is a valid type allowed for aliasing (e.g. not a type variable).
34603463
34613464
If yes, return the corresponding type, a list of
@@ -3474,7 +3477,7 @@ def analyze_alias(
34743477
self.fail(
34753478
"Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE
34763479
)
3477-
return None, [], set(), []
3480+
return None, [], set(), [], False
34783481

34793482
found_type_vars = typ.accept(TypeVarLikeQuery(self, self.tvar_scope))
34803483
tvar_defs: list[TypeVarLikeType] = []
@@ -3508,7 +3511,8 @@ def analyze_alias(
35083511
new_tvar_defs.append(td)
35093512

35103513
qualified_tvars = [node.fullname for _name, node in found_type_vars]
3511-
return analyzed, new_tvar_defs, depends_on, qualified_tvars
3514+
empty_tuple_index = typ.empty_tuple_index if isinstance(typ, UnboundType) else False
3515+
return analyzed, new_tvar_defs, depends_on, qualified_tvars, empty_tuple_index
35123516

35133517
def is_pep_613(self, s: AssignmentStmt) -> bool:
35143518
if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
@@ -3591,9 +3595,10 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
35913595
alias_tvars: list[TypeVarLikeType] = []
35923596
depends_on: set[str] = set()
35933597
qualified_tvars: list[str] = []
3598+
empty_tuple_index = False
35943599
else:
35953600
tag = self.track_incomplete_refs()
3596-
res, alias_tvars, depends_on, qualified_tvars = self.analyze_alias(
3601+
res, alias_tvars, depends_on, qualified_tvars, empty_tuple_index = self.analyze_alias(
35973602
lvalue.name, rvalue, allow_placeholder=True
35983603
)
35993604
if not res:
@@ -3626,8 +3631,15 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
36263631
# Note: with the new (lazy) type alias representation we only need to set no_args to True
36273632
# if the expected number of arguments is non-zero, so that aliases like A = List work.
36283633
# However, eagerly expanding aliases like Text = str is a nice performance optimization.
3629-
no_args = isinstance(res, Instance) and not res.args # type: ignore[misc]
3630-
fix_instance_types(res, self.fail, self.note, self.options)
3634+
no_args = (
3635+
isinstance(res, ProperType)
3636+
and isinstance(res, Instance)
3637+
and not res.args
3638+
and not empty_tuple_index
3639+
)
3640+
if isinstance(res, ProperType) and isinstance(res, Instance):
3641+
if not validate_instance(res, self.fail, empty_tuple_index):
3642+
fix_instance(res, self.fail, self.note, disallow_any=False, options=self.options)
36313643
# Aliases defined within functions can't be accessed outside
36323644
# the function, since the symbol table will no longer
36333645
# exist. Work around by expanding them eagerly when used.

mypy/subtypes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ def visit_instance(self, left: Instance) -> bool:
544544
right_args = (
545545
right_prefix + (TupleType(list(right_middle), fallback),) + right_suffix
546546
)
547-
if not self.proper_subtype:
547+
if not self.proper_subtype and t.args:
548548
for arg in map(get_proper_type, t.args):
549549
if isinstance(arg, UnpackType):
550550
unpacked = get_proper_type(arg.type)
@@ -557,6 +557,8 @@ def visit_instance(self, left: Instance) -> bool:
557557
break
558558
else:
559559
return True
560+
if len(left_args) != len(right_args):
561+
return False
560562
type_params = zip(left_args, right_args, right.type.defn.type_vars)
561563
else:
562564
type_params = zip(t.args, right.args, right.type.defn.type_vars)

mypy/typeanal.py

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
ParamSpecType,
6363
PartialType,
6464
PlaceholderType,
65+
ProperType,
6566
RawExpressionType,
6667
RequiredType,
6768
SyntheticTypeVisitor,
@@ -89,7 +90,6 @@
8990
has_type_vars,
9091
)
9192
from mypy.types_utils import is_bad_type_type_item
92-
from mypy.typetraverser import TypeTraverserVisitor
9393
from mypy.typevars import fill_typevars
9494

9595
T = TypeVar("T")
@@ -425,9 +425,10 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
425425
# The only case where instantiate_type_alias() can return an incorrect instance is
426426
# when it is top-level instance, so no need to recurse.
427427
if (
428-
isinstance(res, Instance) # type: ignore[misc]
429-
and not self.defining_alias
430-
and not validate_instance(res, self.fail)
428+
isinstance(res, ProperType)
429+
and isinstance(res, Instance)
430+
and not (self.defining_alias and self.nesting_level == 0)
431+
and not validate_instance(res, self.fail, t.empty_tuple_index)
431432
):
432433
fix_instance(
433434
res,
@@ -442,7 +443,7 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
442443
res = get_proper_type(res)
443444
return res
444445
elif isinstance(node, TypeInfo):
445-
return self.analyze_type_with_type_info(node, t.args, t)
446+
return self.analyze_type_with_type_info(node, t.args, t, t.empty_tuple_index)
446447
elif node.fullname in TYPE_ALIAS_NAMES:
447448
return AnyType(TypeOfAny.special_form)
448449
# Concatenate is an operator, no need for a proper type
@@ -700,7 +701,7 @@ def get_omitted_any(self, typ: Type, fullname: str | None = None) -> AnyType:
700701
return get_omitted_any(disallow_any, self.fail, self.note, typ, self.options, fullname)
701702

702703
def analyze_type_with_type_info(
703-
self, info: TypeInfo, args: Sequence[Type], ctx: Context
704+
self, info: TypeInfo, args: Sequence[Type], ctx: Context, empty_tuple_index: bool
704705
) -> Type:
705706
"""Bind unbound type when were able to find target TypeInfo.
706707
@@ -735,7 +736,9 @@ def analyze_type_with_type_info(
735736

736737
# Check type argument count.
737738
instance.args = tuple(flatten_nested_tuples(instance.args))
738-
if not self.defining_alias and not validate_instance(instance, self.fail):
739+
if not (self.defining_alias and self.nesting_level == 0) and not validate_instance(
740+
instance, self.fail, empty_tuple_index
741+
):
739742
fix_instance(
740743
instance,
741744
self.fail,
@@ -1203,7 +1206,7 @@ def visit_placeholder_type(self, t: PlaceholderType) -> Type:
12031206
else:
12041207
# TODO: Handle non-TypeInfo
12051208
assert isinstance(n.node, TypeInfo)
1206-
return self.analyze_type_with_type_info(n.node, t.args, t)
1209+
return self.analyze_type_with_type_info(n.node, t.args, t, False)
12071210

12081211
def analyze_callable_args_for_paramspec(
12091212
self, callable_args: Type, ret_type: Type, fallback: Instance
@@ -2256,7 +2259,7 @@ def make_optional_type(t: Type) -> Type:
22562259
return UnionType([t, NoneType()], t.line, t.column)
22572260

22582261

2259-
def validate_instance(t: Instance, fail: MsgCallback) -> bool:
2262+
def validate_instance(t: Instance, fail: MsgCallback, empty_tuple_index: bool) -> bool:
22602263
"""Check if this is a well-formed instance with respect to argument count/positions."""
22612264
# TODO: combine logic with instantiate_type_alias().
22622265
if any(unknown_unpack(a) for a in t.args):
@@ -2279,8 +2282,9 @@ def validate_instance(t: Instance, fail: MsgCallback) -> bool:
22792282
)
22802283
return False
22812284
elif not t.args:
2282-
# The Any arguments should be set by the caller.
2283-
return False
2285+
if not (empty_tuple_index and len(t.type.type_vars) == 1):
2286+
# The Any arguments should be set by the caller.
2287+
return False
22842288
else:
22852289
# We also need to check if we are not performing a type variable tuple split.
22862290
unpack = find_unpack_in_list(t.args)
@@ -2313,34 +2317,6 @@ def validate_instance(t: Instance, fail: MsgCallback) -> bool:
23132317
return True
23142318

23152319

2316-
def fix_instance_types(t: Type, fail: MsgCallback, note: MsgCallback, options: Options) -> None:
2317-
"""Recursively fix all instance types (type argument count) in a given type.
2318-
2319-
For example 'Union[Dict, List[str, int]]' will be transformed into
2320-
'Union[Dict[Any, Any], List[Any]]' in place.
2321-
"""
2322-
t.accept(InstanceFixer(fail, note, options))
2323-
2324-
2325-
class InstanceFixer(TypeTraverserVisitor):
2326-
def __init__(self, fail: MsgCallback, note: MsgCallback, options: Options) -> None:
2327-
self.fail = fail
2328-
self.note = note
2329-
self.options = options
2330-
2331-
def visit_instance(self, typ: Instance) -> None:
2332-
super().visit_instance(typ)
2333-
if not validate_instance(typ, self.fail):
2334-
fix_instance(
2335-
typ,
2336-
self.fail,
2337-
self.note,
2338-
disallow_any=False,
2339-
options=self.options,
2340-
use_generic_error=True,
2341-
)
2342-
2343-
23442320
def find_self_type(typ: Type, lookup: Callable[[str], SymbolTableNode | None]) -> bool:
23452321
return typ.accept(HasSelfType(lookup))
23462322

mypy/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3163,6 +3163,8 @@ def visit_instance(self, t: Instance) -> str:
31633163
s += f"[{self.list_str(t.args)}, ...]"
31643164
else:
31653165
s += f"[{self.list_str(t.args)}]"
3166+
elif t.type.has_type_var_tuple_type and len(t.type.type_vars) == 1:
3167+
s += "[()]"
31663168
if self.id_mapper:
31673169
s += f"<{self.id_mapper.id(t.type)}>"
31683170
return s

test-data/unit/check-flags.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2277,3 +2277,22 @@ list(2) # E: No overload variant of "list" matches argument type "int" [call-o
22772277
# N: def [T] __init__(self) -> List[T] \
22782278
# N: def [T] __init__(self, x: Iterable[T]) -> List[T]
22792279
[builtins fixtures/list.pyi]
2280+
2281+
[case testNestedGenericInAliasDisallow]
2282+
# flags: --disallow-any-generics
2283+
from typing import TypeVar, Generic, List, Union
2284+
2285+
class C(Generic[T]): ...
2286+
2287+
A = Union[C, List] # E: Missing type parameters for generic type "C" \
2288+
# E: Missing type parameters for generic type "List"
2289+
[builtins fixtures/list.pyi]
2290+
2291+
[case testNestedGenericInAliasAllow]
2292+
# flags: --allow-any-generics
2293+
from typing import TypeVar, Generic, List, Union
2294+
2295+
class C(Generic[T]): ...
2296+
2297+
A = Union[C, List] # OK
2298+
[builtins fixtures/list.pyi]

test-data/unit/check-typevar-tuple.test

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ variadic_single: Variadic[int]
118118
reveal_type(variadic_single) # N: Revealed type is "__main__.Variadic[builtins.int]"
119119

120120
empty: Variadic[()]
121-
reveal_type(empty) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[Any, ...]]]"
121+
reveal_type(empty) # N: Revealed type is "__main__.Variadic[()]"
122+
123+
omitted: Variadic
124+
reveal_type(omitted) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[Any, ...]]]"
122125

123126
bad: Variadic[Unpack[Tuple[int, ...]], str, Unpack[Tuple[bool, ...]]] # E: More than one Unpack in a type is not allowed
124127
reveal_type(bad) # N: Revealed type is "__main__.Variadic[Unpack[builtins.tuple[builtins.int, ...]], builtins.str]"
@@ -1846,6 +1849,50 @@ def foo3(func: Callable[[int, Unpack[Args2]], T], *args: Unpack[Args2]) -> T:
18461849
return submit(func, 1, *args)
18471850
[builtins fixtures/tuple.pyi]
18481851

1852+
[case testTypeVarTupleEmptySpecialCase]
1853+
from typing import Any, Callable, Generic
1854+
from typing_extensions import Unpack, TypeVarTuple
1855+
1856+
Ts = TypeVarTuple("Ts")
1857+
class MyClass(Generic[Unpack[Ts]]):
1858+
func: Callable[[Unpack[Ts]], object]
1859+
1860+
def __init__(self, func: Callable[[Unpack[Ts]], object]) -> None:
1861+
self.func = func
1862+
1863+
explicit: MyClass[()]
1864+
reveal_type(explicit) # N: Revealed type is "__main__.MyClass[()]"
1865+
reveal_type(explicit.func) # N: Revealed type is "def () -> builtins.object"
1866+
1867+
a: Any
1868+
explicit_2 = MyClass[()](a)
1869+
reveal_type(explicit_2) # N: Revealed type is "__main__.MyClass[()]"
1870+
reveal_type(explicit_2.func) # N: Revealed type is "def () -> builtins.object"
1871+
1872+
Alias = MyClass[()]
1873+
explicit_3: Alias
1874+
reveal_type(explicit_3) # N: Revealed type is "__main__.MyClass[()]"
1875+
reveal_type(explicit_3.func) # N: Revealed type is "def () -> builtins.object"
1876+
1877+
explicit_4 = Alias(a)
1878+
reveal_type(explicit_4) # N: Revealed type is "__main__.MyClass[()]"
1879+
reveal_type(explicit_4.func) # N: Revealed type is "def () -> builtins.object"
1880+
1881+
def no_args() -> None: ...
1882+
implicit = MyClass(no_args)
1883+
reveal_type(implicit) # N: Revealed type is "__main__.MyClass[()]"
1884+
reveal_type(implicit.func) # N: Revealed type is "def () -> builtins.object"
1885+
1886+
def one_arg(__a: int) -> None: ...
1887+
x = MyClass(one_arg)
1888+
x = explicit # E: Incompatible types in assignment (expression has type "MyClass[()]", variable has type "MyClass[int]")
1889+
1890+
# Consistently handle special case for no argument aliases
1891+
Direct = MyClass
1892+
y = Direct(one_arg)
1893+
reveal_type(y) # N: Revealed type is "__main__.MyClass[builtins.int]"
1894+
[builtins fixtures/tuple.pyi]
1895+
18491896
[case testTypeVarTupleRuntimeTypeApplication]
18501897
from typing import Generic, TypeVar, Tuple
18511898
from typing_extensions import Unpack, TypeVarTuple

0 commit comments

Comments
 (0)