Skip to content

Commit 0de5234

Browse files
authored
Add more details for "Invalid type" errors (#7166)
Fixes #4030 This adds some more details to various `Invalid type` errors, and similar such as `Invalid type alias`, and `Invalid base class`. Unfortunately, `MessageBuilder` is not available in `typeanal.py`, so I put some error message formatting logic there.
1 parent 8782d63 commit 0de5234

28 files changed

+258
-134
lines changed

mypy/newsemanal/semanal.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,18 @@ def make_empty_type_info(self, defn: ClassDef) -> TypeInfo:
13481348
info.set_line(defn)
13491349
return info
13501350

1351+
def get_name_repr_of_expr(self, expr: Expression) -> Optional[str]:
1352+
"""Try finding a short simplified textual representation of a base class expression."""
1353+
if isinstance(expr, NameExpr):
1354+
return expr.name
1355+
if isinstance(expr, MemberExpr):
1356+
return get_member_expr_fullname(expr)
1357+
if isinstance(expr, IndexExpr):
1358+
return self.get_name_repr_of_expr(expr.base)
1359+
if isinstance(expr, CallExpr):
1360+
return self.get_name_repr_of_expr(expr.callee)
1361+
return None
1362+
13511363
def analyze_base_classes(
13521364
self,
13531365
base_type_exprs: List[Expression]) -> Optional[Tuple[List[Tuple[Type, Expression]],
@@ -1371,7 +1383,14 @@ def analyze_base_classes(
13711383
try:
13721384
base = self.expr_to_analyzed_type(base_expr, allow_placeholder=True)
13731385
except TypeTranslationError:
1374-
self.fail('Invalid base class', base_expr)
1386+
name = self.get_name_repr_of_expr(base_expr)
1387+
if isinstance(base_expr, CallExpr):
1388+
msg = 'Unsupported dynamic base class'
1389+
else:
1390+
msg = 'Invalid base class'
1391+
if name:
1392+
msg += ' "{}"'.format(name)
1393+
self.fail(msg, base_expr)
13751394
is_error = True
13761395
continue
13771396
if base is None:
@@ -1409,7 +1428,11 @@ def configure_base_classes(self,
14091428
self.fail(msg, base_expr)
14101429
info.fallback_to_any = True
14111430
else:
1412-
self.fail('Invalid base class', base_expr)
1431+
msg = 'Invalid base class'
1432+
name = self.get_name_repr_of_expr(base_expr)
1433+
if name:
1434+
msg += ' "{}"'.format(name)
1435+
self.fail(msg, base_expr)
14131436
info.fallback_to_any = True
14141437
if self.options.disallow_any_unimported and has_any_from_unimported_type(base):
14151438
if isinstance(base_expr, (NameExpr, MemberExpr)):
@@ -1421,7 +1444,7 @@ def configure_base_classes(self,
14211444
context=base_expr)
14221445

14231446
# Add 'object' as implicit base if there is no other base class.
1424-
if (not base_types and defn.fullname != 'builtins.object'):
1447+
if not base_types and defn.fullname != 'builtins.object':
14251448
base_types.append(self.object_type())
14261449

14271450
info.bases = base_types
@@ -3186,7 +3209,7 @@ def visit_with_stmt(self, s: WithStmt) -> None:
31863209
actual_targets = [t for t in s.target if t is not None]
31873210
if len(actual_targets) == 0:
31883211
# We have a type for no targets
3189-
self.fail('Invalid type comment', s)
3212+
self.fail('Invalid type comment: "with" statement has no targets', s)
31903213
elif len(actual_targets) == 1:
31913214
# We have one target and one type
31923215
types = [s.unanalyzed_type]
@@ -3196,10 +3219,10 @@ def visit_with_stmt(self, s: WithStmt) -> None:
31963219
types = s.unanalyzed_type.items.copy()
31973220
else:
31983221
# But it's the wrong number of items
3199-
self.fail('Incompatible number of types for `with` targets', s)
3222+
self.fail('Incompatible number of types for "with" targets', s)
32003223
else:
32013224
# We have multiple targets and one type
3202-
self.fail('Multiple types expected for multiple `with` targets', s)
3225+
self.fail('Multiple types expected for multiple "with" targets', s)
32033226

32043227
new_types = [] # type: List[Type]
32053228
for e, n in zip(s.expr, s.target):

mypy/newsemanal/typeanal.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
TypeInfo, Context, SymbolTableNode, Var, Expression,
2323
nongen_builtins, check_arg_names, check_arg_kinds, ARG_POS, ARG_NAMED,
2424
ARG_OPT, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, TypeVarExpr,
25-
TypeAlias, PlaceholderNode
25+
TypeAlias, PlaceholderNode, SYMBOL_FUNCBASE_TYPES, Decorator, MypyFile
2626
)
2727
from mypy.typetraverser import TypeTraverserVisitor
2828
from mypy.tvar_scope import TypeVarScope
@@ -72,7 +72,7 @@ def analyze_type_alias(node: Expression,
7272
try:
7373
type = expr_to_unanalyzed_type(node)
7474
except TypeTranslationError:
75-
api.fail('Invalid type alias', node)
75+
api.fail('Invalid type alias: expression is not a valid type', node)
7676
return None
7777
analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub,
7878
allow_unnormalized=allow_unnormalized, defining_alias=True,
@@ -401,7 +401,29 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl
401401
# None of the above options worked. We parse the args (if there are any)
402402
# to make sure there are no remaining semanal-only types, then give up.
403403
t = t.copy_modified(args=self.anal_array(t.args))
404-
self.fail('Invalid type "{}"'.format(name), t)
404+
# TODO: Move this message building logic to messages.py.
405+
notes = [] # type: List[str]
406+
if isinstance(sym.node, Var):
407+
# TODO: add a link to alias docs, see #3494.
408+
message = 'Variable "{}" is not valid as a type'
409+
elif isinstance(sym.node, (SYMBOL_FUNCBASE_TYPES, Decorator)):
410+
message = 'Function "{}" is not valid as a type'
411+
notes.append('Perhaps you need "Callable[...]" or a callback protocol?')
412+
elif isinstance(sym.node, MypyFile):
413+
# TODO: suggest a protocol when supported.
414+
message = 'Module "{}" is not valid as a type'
415+
elif unbound_tvar:
416+
message = 'Type variable "{}" is unbound'
417+
short = name.split('.')[-1]
418+
notes.append(('(Hint: Use "Generic[{}]" or "Protocol[{}]" base class'
419+
' to bind "{}" inside a class)').format(short, short, short))
420+
notes.append('(Hint: Use "{}" in function signature to bind "{}"'
421+
' inside a function)'.format(short, short))
422+
else:
423+
message = 'Cannot interpret reference "{}" as a type'
424+
self.fail(message.format(name), t)
425+
for note in notes:
426+
self.note(note, t)
405427

406428
# TODO: Would it be better to always return Any instead of UnboundType
407429
# in case of an error? On one hand, UnboundType has a name so error messages
@@ -422,7 +444,8 @@ def visit_deleted_type(self, t: DeletedType) -> Type:
422444
return t
423445

424446
def visit_type_list(self, t: TypeList) -> Type:
425-
self.fail('Invalid type', t)
447+
self.fail('Bracketed expression "[...]" is not valid as a type', t)
448+
self.note('Did you mean "List[...]"?', t)
426449
return AnyType(TypeOfAny.from_error)
427450

428451
def visit_callable_argument(self, t: CallableArgument) -> Type:
@@ -457,9 +480,9 @@ def visit_tuple_type(self, t: TupleType) -> Type:
457480
if t.implicit and not self.allow_tuple_literal:
458481
self.fail('Syntax error in type annotation', t)
459482
if len(t.items) == 1:
460-
self.note_func('Suggestion: Is there a spurious trailing comma?', t)
483+
self.note('Suggestion: Is there a spurious trailing comma?', t)
461484
else:
462-
self.note_func('Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn)', t)
485+
self.note('Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn)', t)
463486
return AnyType(TypeOfAny.from_error)
464487
star_count = sum(1 for item in t.items if isinstance(item, StarType))
465488
if star_count > 1:
@@ -512,7 +535,7 @@ def visit_raw_expression_type(self, t: RawExpressionType) -> Type:
512535

513536
self.fail(msg, t)
514537
if t.note is not None:
515-
self.note_func(t.note, t)
538+
self.note(t.note, t)
516539

517540
return AnyType(TypeOfAny.from_error, line=t.line, column=t.column)
518541

@@ -722,6 +745,9 @@ def analyze_type(self, t: Type) -> Type:
722745
def fail(self, msg: str, ctx: Context) -> None:
723746
self.fail_func(msg, ctx)
724747

748+
def note(self, msg: str, ctx: Context) -> None:
749+
self.note_func(msg, ctx)
750+
725751
@contextmanager
726752
def tvar_scope_frame(self) -> Iterator[None]:
727753
old_scope = self.tvar_scope

mypy/semanal.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,10 +2864,10 @@ def visit_with_stmt(self, s: WithStmt) -> None:
28642864
types = s.unanalyzed_type.items
28652865
else:
28662866
# But it's the wrong number of items
2867-
self.fail('Incompatible number of types for `with` targets', s)
2867+
self.fail('Incompatible number of types for "with" targets', s)
28682868
else:
28692869
# We have multiple targets and one type
2870-
self.fail('Multiple types expected for multiple `with` targets', s)
2870+
self.fail('Multiple types expected for multiple "with" targets', s)
28712871

28722872
new_types = [] # type: List[Type]
28732873
for e, n in zip(s.expr, s.target):

test-data/unit/check-basic.test

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,14 @@ import e
325325
1+'no' # E: Unsupported operand types for + ("int" and "str")
326326

327327
[case testModuleAsTypeNoCrash]
328+
# flags: --new-semantic-analyzer
328329
import mock
329330
from typing import Union
330331

331332
class A: ...
332333
class B: ...
333334

334-
x: Union[mock, A] # E: Invalid type "mock"
335+
x: Union[mock, A] # E: Module "mock" is not valid as a type
335336

336337
if isinstance(x, B):
337338
pass
@@ -340,13 +341,14 @@ if isinstance(x, B):
340341
[out]
341342

342343
[case testModuleAsTypeNoCrash2]
344+
# flags: --new-semantic-analyzer
343345
import mock
344346
from typing import overload, Any, Union
345347

346348
@overload
347349
def f(x: int) -> int: ...
348350
@overload
349-
def f(x: str) -> Union[mock, str]: ... # E: Invalid type "mock"
351+
def f(x: str) -> Union[mock, str]: ... # E: Module "mock" is not valid as a type
350352
def f(x):
351353
pass
352354

test-data/unit/check-classes.test

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4833,12 +4833,13 @@ reveal_type(Arc1[MyDestr]()) # N: Revealed type is '__main__.Arc1[__main__.MyDe
48334833
[typing fixtures/typing-full.pyi]
48344834

48354835
[case testSixMetaclassErrors]
4836+
# flags: --new-semantic-analyzer
48364837
import six
48374838
class M(type): pass
48384839
class A(object): pass
48394840
def f() -> type: return M
4840-
class C1(six.with_metaclass(M), object): pass # E: Invalid base class
4841-
class C2(C1, six.with_metaclass(M)): pass # E: Invalid base class
4841+
class C1(six.with_metaclass(M), object): pass # E: Unsupported dynamic base class "six.with_metaclass"
4842+
class C2(C1, six.with_metaclass(M)): pass # E: Unsupported dynamic base class "six.with_metaclass"
48424843
class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported
48434844
@six.add_metaclass(A) # E: Argument 1 to "add_metaclass" has incompatible type "Type[A]"; expected "Type[type]" \
48444845
# E: Metaclasses not inheriting from 'type' are not supported

test-data/unit/check-columns.test

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,32 +69,34 @@ if int():
6969
(f(b=object())) # E:6: Unexpected keyword argument "b" for "f"
7070

7171
[case testColumnInvalidType]
72+
# flags: --new-semantic-analyzer
7273
from typing import Iterable
7374

7475
bad = 0
7576

76-
def f(x: bad): # E:10: Invalid type "__main__.bad"
77-
y: bad # E:8: Invalid type "__main__.bad"
77+
def f(x: bad): # E:10: Variable "__main__.bad" is not valid as a type
78+
y: bad # E:8: Variable "__main__.bad" is not valid as a type
7879

7980
if int():
80-
def g(x): # E:5: Invalid type "__main__.bad"
81+
def g(x): # E:5: Variable "__main__.bad" is not valid as a type
8182
# type: (bad) -> None
82-
y = 0 # type: bad # E:9: Invalid type "__main__.bad"
83+
y = 0 # type: bad # E:9: Variable "__main__.bad" is not valid as a type
8384

84-
z: Iterable[bad] # E:13: Invalid type "__main__.bad"
85-
h: bad[int] # E:4: Invalid type "__main__.bad"
85+
z: Iterable[bad] # E:13: Variable "__main__.bad" is not valid as a type
86+
h: bad[int] # E:4: Variable "__main__.bad" is not valid as a type
8687

8788
[case testColumnInvalidType_python2]
89+
# flags: --new-semantic-analyzer
8890
from typing import Iterable
8991

9092
bad = 0
9193

9294
if int():
93-
def g(x): # E:5: Invalid type "__main__.bad"
95+
def g(x): # E:5: Variable "__main__.bad" is not valid as a type
9496
# type: (bad) -> None
95-
y = 0 # type: bad # E:9: Invalid type "__main__.bad"
97+
y = 0 # type: bad # E:9: Variable "__main__.bad" is not valid as a type
9698

97-
z = () # type: Iterable[bad] # E:5: Invalid type "__main__.bad"
99+
z = () # type: Iterable[bad] # E:5: Variable "__main__.bad" is not valid as a type
98100

99101
[case testColumnFunctionMissingTypeAnnotation]
100102
# flags: --disallow-untyped-defs

test-data/unit/check-custom-plugin.test

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -496,12 +496,12 @@ from mod import declarative_base, Column, Instr, non_declarative_base
496496
Bad1 = non_declarative_base()
497497
Bad2 = Bad3 = declarative_base()
498498

499-
class C1(Bad1): ... # E: Invalid type "__main__.Bad1" \
500-
# E: Invalid base class
501-
class C2(Bad2): ... # E: Invalid type "__main__.Bad2" \
502-
# E: Invalid base class
503-
class C3(Bad3): ... # E: Invalid type "__main__.Bad3" \
504-
# E: Invalid base class
499+
class C1(Bad1): ... # E: Variable "__main__.Bad1" is not valid as a type \
500+
# E: Invalid base class "Bad1"
501+
class C2(Bad2): ... # E: Variable "__main__.Bad2" is not valid as a type \
502+
# E: Invalid base class "Bad2"
503+
class C3(Bad3): ... # E: Variable "__main__.Bad3" is not valid as a type \
504+
# E: Invalid base class "Bad3"
505505
[file mod.py]
506506
from typing import Generic, TypeVar
507507
def declarative_base(): ...

test-data/unit/check-functions.test

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,7 @@ def Arg(x, y): pass
17201720
F = Callable[[Arg(int, 'x')], int] # E: Invalid argument constructor "__main__.Arg"
17211721

17221722
[case testCallableParsingFromExpr]
1723+
# flags: --new-semantic-analyzer
17231724
from typing import Callable, List
17241725
from mypy_extensions import Arg, VarArg, KwArg
17251726
import mypy_extensions
@@ -1737,12 +1738,12 @@ J = Callable[[VarArg(), KwArg()], int] # ok
17371738
K = Callable[[VarArg(), int], int] # E: Required positional args may not appear after default, named or var args
17381739
L = Callable[[Arg(name='x', type=int)], int] # ok
17391740
# I have commented out the following test because I don't know how to expect the "defined here" note part of the error.
1740-
# M = Callable[[Arg(gnome='x', type=int)], int] E: Invalid type alias E: Unexpected keyword argument "gnome" for "Arg"
1741+
# M = Callable[[Arg(gnome='x', type=int)], int] E: Invalid type alias: expression is not a valid type E: Unexpected keyword argument "gnome" for "Arg"
17411742
N = Callable[[Arg(name=None, type=int)], int] # ok
1742-
O = Callable[[List[Arg(int)]], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: Type expected within [...] # E: The type "Type[List[Any]]" is not generic and not indexable
1743+
O = Callable[[List[Arg(int)]], int] # E: Invalid type alias: expression is not a valid type # E: Value of type "int" is not indexable # E: Type expected within [...] # E: The type "Type[List[Any]]" is not generic and not indexable
17431744
P = Callable[[mypy_extensions.VarArg(int)], int] # ok
1744-
Q = Callable[[Arg(int, type=int)], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "type"
1745-
R = Callable[[Arg(int, 'x', name='y')], int] # E: Invalid type alias # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "name"
1745+
Q = Callable[[Arg(int, type=int)], int] # E: Invalid type alias: expression is not a valid type # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "type"
1746+
R = Callable[[Arg(int, 'x', name='y')], int] # E: Invalid type alias: expression is not a valid type # E: Value of type "int" is not indexable # E: "Arg" gets multiple values for keyword argument "name"
17461747

17471748
[builtins fixtures/dict.pyi]
17481749

test-data/unit/check-generics.test

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ reveal_type(f('a')) # N: Revealed type is '__main__.D[builtins.str*]'
722722
main:15: error: Argument 1 to "D" has incompatible type "int"; expected "Tuple[T, T]"
723723

724724
[case testGenericTypeAliasesSubclassingBad]
725+
# flags: --new-semantic-analyzer
725726
from typing import TypeVar, Generic, Tuple, Union
726727
T = TypeVar('T')
727728
class Node(Generic[T]):
@@ -733,7 +734,7 @@ UNode = Union[int, Node[T]]
733734

734735
class C(TupledNode): ... # Same as TupledNode[Any]
735736
class D(TupledNode[T]): ...
736-
class E(Generic[T], UNode[T]): ... # E: Invalid base class
737+
class E(Generic[T], UNode[T]): ... # E: Invalid base class "UNode"
737738

738739
reveal_type(D((1, 1))) # N: Revealed type is '__main__.D[builtins.int*]'
739740
[builtins fixtures/list.pyi]
@@ -962,6 +963,7 @@ O[int] # E: Bad number of arguments for type alias, expected: 0, given: 1 # E:
962963
[out]
963964

964965
[case testAliasesInClassBodyNormalVsSubscripted]
966+
# flags: --new-semantic-analyzer
965967
from typing import Union, Type, Iterable
966968

967969
class A: pass
@@ -976,9 +978,9 @@ class C:
976978
b = int # E: Cannot assign multiple types to name "b" without an explicit "Type[...]" annotation
977979
if int():
978980
c = int
979-
def f(self, x: a) -> None: pass # E: Invalid type "__main__.C.a"
981+
def f(self, x: a) -> None: pass # E: Variable "__main__.C.a" is not valid as a type
980982
def g(self, x: b) -> None: pass
981-
def h(self, x: c) -> None: pass # E: Invalid type "__main__.C.c"
983+
def h(self, x: c) -> None: pass # E: Variable "__main__.C.c" is not valid as a type
982984
x: b
983985
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]'
984986
[out]

test-data/unit/check-incremental.test

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,6 +2180,7 @@ tmp/b.py:1: error: Module 'c' has no attribute 'x'
21802180
tmp/b.py:1: error: Module 'c' has no attribute 'x'
21812181

21822182
[case testCacheDeletedAfterErrorsFound2]
2183+
# flags: --new-semantic-analyzer
21832184
import a
21842185
[file a.py]
21852186
from b import x
@@ -2195,9 +2196,11 @@ from b import x
21952196
1 + 1
21962197
[out]
21972198
[out2]
2198-
tmp/b.py:2: error: Invalid type "c.C"
2199+
tmp/b.py:2: error: Function "c.C" is not valid as a type
2200+
tmp/b.py:2: note: Perhaps you need "Callable[...]" or a callback protocol?
21992201
[out3]
2200-
tmp/b.py:2: error: Invalid type "c.C"
2202+
tmp/b.py:2: error: Function "c.C" is not valid as a type
2203+
tmp/b.py:2: note: Perhaps you need "Callable[...]" or a callback protocol?
22012204

22022205
[case testCacheDeletedAfterErrorsFound3]
22032206
import a

0 commit comments

Comments
 (0)