Skip to content

Commit 3549c0e

Browse files
authored
Fix error reporting context for missing generic type arguments (#7100)
Fixes #7077 The logic of the fix is quite straightforward: emit the error immediately when we fix an instance type, and not in a traversal after the main pass. Note that I fix this only for the new analyser, because the old one will be anyway deprecated soon. This causes a bit of code churn because we need to pass the flags to several functions, but the logic should not change much. I didn't add many new tests, since we have a bunch of existing tests. It would probably make sense to play with this, suggestions for more tests are very welcome.
1 parent b279f4c commit 3549c0e

File tree

5 files changed

+88
-46
lines changed

5 files changed

+88
-46
lines changed

mypy/newsemanal/semanal.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ def add_builtin_aliases(self, tree: MypyFile) -> None:
431431
target = self.named_type_or_none(target_name, [])
432432
assert target is not None
433433
# Transform List to List[Any], etc.
434-
fix_instance_types(target, self.fail)
434+
fix_instance_types(target, self.fail, disallow_any=False)
435435
alias_node = TypeAlias(target, alias,
436436
line=-1, column=-1, # there is no context
437437
no_args=True, normalized=True)
@@ -2372,7 +2372,9 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
23722372
# so we need to replace it with non-explicit Anys.
23732373
res = make_any_non_explicit(res)
23742374
no_args = isinstance(res, Instance) and not res.args
2375-
fix_instance_types(res, self.fail)
2375+
fix_instance_types(res, self.fail,
2376+
disallow_any=self.options.disallow_any_generics and
2377+
not self.is_typeshed_stub_file and not no_args)
23762378
if isinstance(s.rvalue, (IndexExpr, CallExpr)): # CallExpr is for `void = type(None)`
23772379
s.rvalue.analyzed = TypeAliasExpr(res, alias_tvars, no_args)
23782380
s.rvalue.analyzed.line = s.line

mypy/newsemanal/semanal_typeargs.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import List
99

1010
from mypy.nodes import TypeInfo, Context, MypyFile, FuncItem, ClassDef, Block
11-
from mypy.types import Type, Instance, TypeVarType, AnyType, TypeOfAny
11+
from mypy.types import Type, Instance, TypeVarType, AnyType
1212
from mypy.mixedtraverser import MixedTraverserVisitor
1313
from mypy.subtypes import is_subtype
1414
from mypy.sametypes import is_same_type
@@ -69,13 +69,6 @@ def visit_instance(self, t: Instance) -> None:
6969
arg, info.name(), tvar.upper_bound), t)
7070
super().visit_instance(t)
7171

72-
def visit_any(self, t: AnyType) -> None:
73-
if not self.options.disallow_any_generics or self.is_typeshed_file:
74-
return
75-
76-
if t.type_of_any == TypeOfAny.from_omitted_generics:
77-
self.fail(message_registry.BARE_GENERIC, t)
78-
7972
def check_type_var_values(self, type: TypeInfo, actuals: List[Type], arg_name: str,
8073
valids: List[Type], arg_number: int, context: Context) -> None:
8174
for actual in actuals:

mypy/newsemanal/typeanal.py

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,14 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
207207
all_vars = node.alias_tvars
208208
target = node.target
209209
an_args = self.anal_array(t.args)
210-
res = expand_type_alias(target, all_vars, an_args, self.fail, node.no_args, t)
210+
disallow_any = self.options.disallow_any_generics and not self.is_typeshed_stub
211+
res = expand_type_alias(target, all_vars, an_args, self.fail, node.no_args, t,
212+
disallow_any=disallow_any)
211213
# The only case where expand_type_alias() can return an incorrect instance is
212214
# when it is top-level instance, so no need to recurse.
213215
if (isinstance(res, Instance) and len(res.args) != len(res.type.type_vars) and
214216
not self.defining_alias):
215-
fix_instance(res, self.fail)
217+
fix_instance(res, self.fail, disallow_any=disallow_any, use_generic_error=True)
216218
return res
217219
elif isinstance(node, TypeInfo):
218220
return self.analyze_type_with_type_info(node, t.args, t)
@@ -254,9 +256,9 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
254256
return AnyType(TypeOfAny.special_form)
255257
if len(t.args) == 0 and not t.empty_tuple_index:
256258
# Bare 'Tuple' is same as 'tuple'
257-
if self.options.disallow_any_generics and not self.is_typeshed_stub:
258-
self.fail(message_registry.BARE_GENERIC, t)
259-
return self.named_type('builtins.tuple', line=t.line, column=t.column)
259+
any_type = self.get_omitted_any(t)
260+
return self.named_type('builtins.tuple', [any_type],
261+
line=t.line, column=t.column)
260262
if len(t.args) == 2 and isinstance(t.args[1], EllipsisType):
261263
# Tuple[T, ...] (uniform, variable-length tuple)
262264
instance = self.named_type('builtins.tuple', [self.anal_type(t.args[0])])
@@ -276,8 +278,7 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
276278
return self.analyze_callable_type(t)
277279
elif fullname == 'typing.Type':
278280
if len(t.args) == 0:
279-
any_type = AnyType(TypeOfAny.from_omitted_generics,
280-
line=t.line, column=t.column)
281+
any_type = self.get_omitted_any(t)
281282
return TypeType(any_type, line=t.line, column=t.column)
282283
if len(t.args) != 1:
283284
self.fail('Type[...] must have exactly one type argument', t)
@@ -302,6 +303,10 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
302303
return self.analyze_literal_type(t)
303304
return None
304305

306+
def get_omitted_any(self, ctx: Context, fullname: Optional[str] = None) -> AnyType:
307+
disallow_any = not self.is_typeshed_stub and self.options.disallow_any_generics
308+
return get_omitted_any(disallow_any, self.fail, ctx, fullname)
309+
305310
def analyze_type_with_type_info(self, info: TypeInfo, args: List[Type], ctx: Context) -> Type:
306311
"""Bind unbound type when were able to find target TypeInfo.
307312
@@ -318,19 +323,9 @@ def analyze_type_with_type_info(self, info: TypeInfo, args: List[Type], ctx: Con
318323
instance = Instance(info, self.anal_array(args), ctx.line, ctx.column)
319324
# Check type argument count.
320325
if len(instance.args) != len(info.type_vars) and not self.defining_alias:
321-
fix_instance(instance, self.fail)
322-
if not args and self.options.disallow_any_generics and not self.defining_alias:
323-
# We report/patch invalid built-in instances already during second pass.
324-
# This is done to avoid storing additional state on instances.
325-
# All other (including user defined) generics will be patched/reported
326-
# in the third pass.
327-
if not self.is_typeshed_stub and info.fullname() in nongen_builtins:
328-
alternative = nongen_builtins[info.fullname()]
329-
self.fail(message_registry.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), ctx)
330-
any_type = AnyType(TypeOfAny.from_error, line=ctx.line)
331-
else:
332-
any_type = AnyType(TypeOfAny.from_omitted_generics, line=ctx.line)
333-
instance.args = [any_type] * len(info.type_vars)
326+
fix_instance(instance, self.fail,
327+
disallow_any=self.options.disallow_any_generics and
328+
not self.is_typeshed_stub)
334329

335330
tup = info.tuple_type
336331
if tup is not None:
@@ -557,8 +552,7 @@ def analyze_callable_type(self, t: UnboundType) -> Type:
557552
fallback = self.named_type('builtins.function')
558553
if len(t.args) == 0:
559554
# Callable (bare). Treat as Callable[..., Any].
560-
any_type = AnyType(TypeOfAny.from_omitted_generics,
561-
line=t.line, column=t.column)
555+
any_type = self.get_omitted_any(t)
562556
ret = CallableType([any_type, any_type],
563557
[nodes.ARG_STAR, nodes.ARG_STAR2],
564558
[None, None],
@@ -846,14 +840,33 @@ def tuple_type(self, items: List[Type]) -> TupleType:
846840
TypeVarList = List[Tuple[str, TypeVarExpr]]
847841

848842

849-
def fix_instance(t: Instance, fail: Callable[[str, Context], None]) -> None:
843+
def get_omitted_any(disallow_any: bool, fail: Callable[[str, Context], None],
844+
ctx: Context, fullname: Optional[str] = None) -> AnyType:
845+
if disallow_any:
846+
if fullname in nongen_builtins:
847+
# We use a dedicated error message for builtin generics (as the most common case).
848+
alternative = nongen_builtins[fullname]
849+
fail(message_registry.IMPLICIT_GENERIC_ANY_BUILTIN.format(alternative), ctx)
850+
else:
851+
fail(message_registry.BARE_GENERIC, ctx)
852+
any_type = AnyType(TypeOfAny.from_error, line=ctx.line, column=ctx.column)
853+
else:
854+
any_type = AnyType(TypeOfAny.from_omitted_generics, line=ctx.line, column=ctx.column)
855+
return any_type
856+
857+
858+
def fix_instance(t: Instance, fail: Callable[[str, Context], None],
859+
disallow_any: bool, use_generic_error: bool = False) -> None:
850860
"""Fix a malformed instance by replacing all type arguments with Any.
851861
852862
Also emit a suitable error if this is not due to implicit Any's.
853863
"""
854864
if len(t.args) == 0:
855-
any_type = AnyType(TypeOfAny.from_omitted_generics,
856-
line=t.line, column=t.column)
865+
if use_generic_error:
866+
fullname = None # type: Optional[str]
867+
else:
868+
fullname = t.type.fullname()
869+
any_type = get_omitted_any(disallow_any, fail, t, fullname)
857870
t.args = [any_type] * len(t.type.type_vars)
858871
return
859872
# Invalid number of type parameters.
@@ -876,7 +889,8 @@ def fix_instance(t: Instance, fail: Callable[[str, Context], None]) -> None:
876889

877890

878891
def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type],
879-
fail: Callable[[str, Context], None], no_args: bool, ctx: Context) -> Type:
892+
fail: Callable[[str, Context], None], no_args: bool, ctx: Context, *,
893+
disallow_any: bool = False) -> Type:
880894
"""Expand a (generic) type alias target following the rules outlined in TypeAlias docstring.
881895
882896
Here:
@@ -892,7 +906,8 @@ def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type],
892906
if exp_len > 0 and act_len == 0:
893907
# Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...]
894908
assert alias_tvars is not None
895-
return set_any_tvars(target, alias_tvars, ctx.line, ctx.column)
909+
return set_any_tvars(target, alias_tvars, ctx.line, ctx.column,
910+
disallow_any=disallow_any, fail=fail)
896911
if exp_len == 0 and act_len == 0:
897912
if no_args:
898913
assert isinstance(target, Instance)
@@ -907,7 +922,7 @@ def expand_type_alias(target: Type, alias_tvars: List[str], args: List[Type],
907922
fail('Bad number of arguments for type alias, expected: %s, given: %s'
908923
% (exp_len, act_len), ctx)
909924
return set_any_tvars(target, alias_tvars or [],
910-
ctx.line, ctx.column, implicit=False)
925+
ctx.line, ctx.column, from_error=True)
911926
typ = replace_alias_tvars(target, alias_tvars, args, ctx.line, ctx.column)
912927
# HACK: Implement FlexibleAlias[T, typ] by expanding it to typ here.
913928
if (isinstance(typ, Instance)
@@ -938,11 +953,17 @@ def replace_alias_tvars(tp: Type, vars: List[str], subs: List[Type],
938953

939954

940955
def set_any_tvars(tp: Type, vars: List[str],
941-
newline: int, newcolumn: int, implicit: bool = True) -> Type:
942-
if implicit:
943-
type_of_any = TypeOfAny.from_omitted_generics
956+
newline: int, newcolumn: int, *,
957+
from_error: bool = False,
958+
disallow_any: bool = False,
959+
fail: Optional[Callable[[str, Context], None]] = None) -> Type:
960+
if from_error or disallow_any:
961+
type_of_any = TypeOfAny.from_error
944962
else:
945-
type_of_any = TypeOfAny.special_form
963+
type_of_any = TypeOfAny.from_omitted_generics
964+
if disallow_any:
965+
assert fail is not None
966+
fail(message_registry.BARE_GENERIC, Context(newline, newcolumn))
946967
any_type = AnyType(type_of_any, line=newline, column=newcolumn)
947968
return replace_alias_tvars(tp, vars, [any_type] * len(vars), newline, newcolumn)
948969

@@ -1112,20 +1133,22 @@ def make_optional_type(t: Type) -> Type:
11121133
return UnionType([t, NoneType()], t.line, t.column)
11131134

11141135

1115-
def fix_instance_types(t: Type, fail: Callable[[str, Context], None]) -> None:
1136+
def fix_instance_types(t: Type, fail: Callable[[str, Context], None],
1137+
disallow_any: bool) -> None:
11161138
"""Recursively fix all instance types (type argument count) in a given type.
11171139
11181140
For example 'Union[Dict, List[str, int]]' will be transformed into
11191141
'Union[Dict[Any, Any], List[Any]]' in place.
11201142
"""
1121-
t.accept(InstanceFixer(fail))
1143+
t.accept(InstanceFixer(fail, disallow_any))
11221144

11231145

11241146
class InstanceFixer(TypeTraverserVisitor):
1125-
def __init__(self, fail: Callable[[str, Context], None]) -> None:
1147+
def __init__(self, fail: Callable[[str, Context], None], disallow_any: bool) -> None:
11261148
self.fail = fail
1149+
self.disallow_any = disallow_any
11271150

11281151
def visit_instance(self, typ: Instance) -> None:
11291152
super().visit_instance(typ)
11301153
if len(typ.args) != len(typ.type.type_vars):
1131-
fix_instance(typ, self.fail)
1154+
fix_instance(typ, self.fail, self.disallow_any, use_generic_error=True)

test-data/unit/check-flags.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,3 +1162,12 @@ implicit_reexport = True
11621162
implicit_reexport = False
11631163
[out]
11641164
main:2: error: Module 'other_module_2' has no attribute 'a'
1165+
1166+
[case testImplicitAnyOKForNoArgs]
1167+
# flags: --disallow-any-generics --show-column-numbers
1168+
from typing import List
1169+
1170+
A = List # OK
1171+
B = List[A] # E:10: Missing type parameters for generic type
1172+
x: A # E:4: Missing type parameters for generic type
1173+
[builtins fixtures/list.pyi]

test-data/unit/check-newsemanal.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2813,6 +2813,21 @@ S = TypeVar('S', covariant=True, contravariant=True) # E: TypeVar cannot be bot
28132813
class Yes: ...
28142814
[builtins fixtures/bool.pyi]
28152815

2816+
[case testNewAnalyzerDisallowAnyGenericsMessages]
2817+
# mypy: disallow-any-generics
2818+
from a import B
2819+
x: B
2820+
2821+
[file a.py]
2822+
from typing import TypeVar, List
2823+
2824+
T = TypeVar('T')
2825+
2826+
A = List[T]
2827+
B = A
2828+
2829+
[builtins fixtures/list.pyi]
2830+
28162831
[case testNewAnalyzerVarTypeVarNoCrash]
28172832
from typing import Callable, TypeVar
28182833

0 commit comments

Comments
 (0)