Skip to content

Add more details for "Invalid type" errors #7166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions mypy/newsemanal/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,18 @@ def make_empty_type_info(self, defn: ClassDef) -> TypeInfo:
info.set_line(defn)
return info

def get_name_repr_of_expr(self, expr: Expression) -> Optional[str]:
"""Try finding a short simplified textual representation of a base class expression."""
if isinstance(expr, NameExpr):
return expr.name
if isinstance(expr, MemberExpr):
return get_member_expr_fullname(expr)
if isinstance(expr, IndexExpr):
return self.get_name_repr_of_expr(expr.base)
if isinstance(expr, CallExpr):
return self.get_name_repr_of_expr(expr.callee)
return None

def analyze_base_classes(
self,
base_type_exprs: List[Expression]) -> Optional[Tuple[List[Tuple[Type, Expression]],
Expand All @@ -1371,7 +1383,14 @@ def analyze_base_classes(
try:
base = self.expr_to_analyzed_type(base_expr, allow_placeholder=True)
except TypeTranslationError:
self.fail('Invalid base class', base_expr)
name = self.get_name_repr_of_expr(base_expr)
if isinstance(base_expr, CallExpr):
msg = 'Unsupported dynamic base class'
else:
msg = 'Invalid base class'
if name:
msg += ' "{}"'.format(name)
self.fail(msg, base_expr)
is_error = True
continue
if base is None:
Expand Down Expand Up @@ -1409,7 +1428,11 @@ def configure_base_classes(self,
self.fail(msg, base_expr)
info.fallback_to_any = True
else:
self.fail('Invalid base class', base_expr)
msg = 'Invalid base class'
name = self.get_name_repr_of_expr(base_expr)
if name:
msg += ' "{}"'.format(name)
self.fail(msg, base_expr)
info.fallback_to_any = True
if self.options.disallow_any_unimported and has_any_from_unimported_type(base):
if isinstance(base_expr, (NameExpr, MemberExpr)):
Expand All @@ -1421,7 +1444,7 @@ def configure_base_classes(self,
context=base_expr)

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

info.bases = base_types
Expand Down Expand Up @@ -3186,7 +3209,7 @@ def visit_with_stmt(self, s: WithStmt) -> None:
actual_targets = [t for t in s.target if t is not None]
if len(actual_targets) == 0:
# We have a type for no targets
self.fail('Invalid type comment', s)
self.fail('Invalid type comment: "with" statement has no targets', s)
elif len(actual_targets) == 1:
# We have one target and one type
types = [s.unanalyzed_type]
Expand All @@ -3196,10 +3219,10 @@ def visit_with_stmt(self, s: WithStmt) -> None:
types = s.unanalyzed_type.items.copy()
else:
# But it's the wrong number of items
self.fail('Incompatible number of types for `with` targets', s)
self.fail('Incompatible number of types for "with" targets', s)
else:
# We have multiple targets and one type
self.fail('Multiple types expected for multiple `with` targets', s)
self.fail('Multiple types expected for multiple "with" targets', s)

new_types = [] # type: List[Type]
for e, n in zip(s.expr, s.target):
Expand Down
40 changes: 33 additions & 7 deletions mypy/newsemanal/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
TypeInfo, Context, SymbolTableNode, Var, Expression,
nongen_builtins, check_arg_names, check_arg_kinds, ARG_POS, ARG_NAMED,
ARG_OPT, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2, TypeVarExpr,
TypeAlias, PlaceholderNode
TypeAlias, PlaceholderNode, SYMBOL_FUNCBASE_TYPES, Decorator, MypyFile
)
from mypy.typetraverser import TypeTraverserVisitor
from mypy.tvar_scope import TypeVarScope
Expand Down Expand Up @@ -72,7 +72,7 @@ def analyze_type_alias(node: Expression,
try:
type = expr_to_unanalyzed_type(node)
except TypeTranslationError:
api.fail('Invalid type alias', node)
api.fail('Invalid type alias: expression is not a valid type', node)
return None
analyzer = TypeAnalyser(api, tvar_scope, plugin, options, is_typeshed_stub,
allow_unnormalized=allow_unnormalized, defining_alias=True,
Expand Down Expand Up @@ -401,7 +401,29 @@ def analyze_unbound_type_without_type_info(self, t: UnboundType, sym: SymbolTabl
# None of the above options worked. We parse the args (if there are any)
# to make sure there are no remaining semanal-only types, then give up.
t = t.copy_modified(args=self.anal_array(t.args))
self.fail('Invalid type "{}"'.format(name), t)
# TODO: Move this message building logic to messages.py.
notes = [] # type: List[str]
if isinstance(sym.node, Var):
# TODO: add a link to alias docs, see #3494.
message = 'Variable "{}" is not valid as a type'
elif isinstance(sym.node, (SYMBOL_FUNCBASE_TYPES, Decorator)):
message = 'Function "{}" is not valid as a type'
notes.append('Perhaps you need "Callable[...]" or a callback protocol?')
elif isinstance(sym.node, MypyFile):
# TODO: suggest a protocol when supported.
message = 'Module "{}" is not valid as a type'
elif unbound_tvar:
message = 'Type variable "{}" is unbound'
short = name.split('.')[-1]
notes.append(('(Hint: Use "Generic[{}]" or "Protocol[{}]" base class'
' to bind "{}" inside a class)').format(short, short, short))
notes.append('(Hint: Use "{}" in function signature to bind "{}"'
' inside a function)'.format(short, short))
else:
message = 'Cannot interpret reference "{}" as a type'
self.fail(message.format(name), t)
for note in notes:
self.note(note, t)

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

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

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

self.fail(msg, t)
if t.note is not None:
self.note_func(t.note, t)
self.note(t.note, t)

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

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

def note(self, msg: str, ctx: Context) -> None:
self.note_func(msg, ctx)

@contextmanager
def tvar_scope_frame(self) -> Iterator[None]:
old_scope = self.tvar_scope
Expand Down
4 changes: 2 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2864,10 +2864,10 @@ def visit_with_stmt(self, s: WithStmt) -> None:
types = s.unanalyzed_type.items
else:
# But it's the wrong number of items
self.fail('Incompatible number of types for `with` targets', s)
self.fail('Incompatible number of types for "with" targets', s)
else:
# We have multiple targets and one type
self.fail('Multiple types expected for multiple `with` targets', s)
self.fail('Multiple types expected for multiple "with" targets', s)

new_types = [] # type: List[Type]
for e, n in zip(s.expr, s.target):
Expand Down
6 changes: 4 additions & 2 deletions test-data/unit/check-basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,14 @@ import e
1+'no' # E: Unsupported operand types for + ("int" and "str")

[case testModuleAsTypeNoCrash]
# flags: --new-semantic-analyzer
import mock
from typing import Union

class A: ...
class B: ...

x: Union[mock, A] # E: Invalid type "mock"
x: Union[mock, A] # E: Module "mock" is not valid as a type

if isinstance(x, B):
pass
Expand All @@ -340,13 +341,14 @@ if isinstance(x, B):
[out]

[case testModuleAsTypeNoCrash2]
# flags: --new-semantic-analyzer
import mock
from typing import overload, Any, Union

@overload
def f(x: int) -> int: ...
@overload
def f(x: str) -> Union[mock, str]: ... # E: Invalid type "mock"
def f(x: str) -> Union[mock, str]: ... # E: Module "mock" is not valid as a type
def f(x):
pass

Expand Down
5 changes: 3 additions & 2 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -4833,12 +4833,13 @@ reveal_type(Arc1[MyDestr]()) # N: Revealed type is '__main__.Arc1[__main__.MyDe
[typing fixtures/typing-full.pyi]

[case testSixMetaclassErrors]
# flags: --new-semantic-analyzer
import six
class M(type): pass
class A(object): pass
def f() -> type: return M
class C1(six.with_metaclass(M), object): pass # E: Invalid base class
class C2(C1, six.with_metaclass(M)): pass # E: Invalid base class
class C1(six.with_metaclass(M), object): pass # E: Unsupported dynamic base class "six.with_metaclass"
class C2(C1, six.with_metaclass(M)): pass # E: Unsupported dynamic base class "six.with_metaclass"
class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from 'type' are not supported
@six.add_metaclass(A) # E: Argument 1 to "add_metaclass" has incompatible type "Type[A]"; expected "Type[type]" \
# E: Metaclasses not inheriting from 'type' are not supported
Expand Down
20 changes: 11 additions & 9 deletions test-data/unit/check-columns.test
Original file line number Diff line number Diff line change
Expand Up @@ -69,32 +69,34 @@ if int():
(f(b=object())) # E:6: Unexpected keyword argument "b" for "f"

[case testColumnInvalidType]
# flags: --new-semantic-analyzer
from typing import Iterable

bad = 0

def f(x: bad): # E:10: Invalid type "__main__.bad"
y: bad # E:8: Invalid type "__main__.bad"
def f(x: bad): # E:10: Variable "__main__.bad" is not valid as a type
y: bad # E:8: Variable "__main__.bad" is not valid as a type

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

z: Iterable[bad] # E:13: Invalid type "__main__.bad"
h: bad[int] # E:4: Invalid type "__main__.bad"
z: Iterable[bad] # E:13: Variable "__main__.bad" is not valid as a type
h: bad[int] # E:4: Variable "__main__.bad" is not valid as a type

[case testColumnInvalidType_python2]
# flags: --new-semantic-analyzer
from typing import Iterable

bad = 0

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

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

[case testColumnFunctionMissingTypeAnnotation]
# flags: --disallow-untyped-defs
Expand Down
12 changes: 6 additions & 6 deletions test-data/unit/check-custom-plugin.test
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,12 @@ from mod import declarative_base, Column, Instr, non_declarative_base
Bad1 = non_declarative_base()
Bad2 = Bad3 = declarative_base()

class C1(Bad1): ... # E: Invalid type "__main__.Bad1" \
# E: Invalid base class
class C2(Bad2): ... # E: Invalid type "__main__.Bad2" \
# E: Invalid base class
class C3(Bad3): ... # E: Invalid type "__main__.Bad3" \
# E: Invalid base class
class C1(Bad1): ... # E: Variable "__main__.Bad1" is not valid as a type \
# E: Invalid base class "Bad1"
class C2(Bad2): ... # E: Variable "__main__.Bad2" is not valid as a type \
# E: Invalid base class "Bad2"
class C3(Bad3): ... # E: Variable "__main__.Bad3" is not valid as a type \
# E: Invalid base class "Bad3"
[file mod.py]
from typing import Generic, TypeVar
def declarative_base(): ...
Expand Down
9 changes: 5 additions & 4 deletions test-data/unit/check-functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1720,6 +1720,7 @@ def Arg(x, y): pass
F = Callable[[Arg(int, 'x')], int] # E: Invalid argument constructor "__main__.Arg"

[case testCallableParsingFromExpr]
# flags: --new-semantic-analyzer
from typing import Callable, List
from mypy_extensions import Arg, VarArg, KwArg
import mypy_extensions
Expand All @@ -1737,12 +1738,12 @@ J = Callable[[VarArg(), KwArg()], int] # ok
K = Callable[[VarArg(), int], int] # E: Required positional args may not appear after default, named or var args
L = Callable[[Arg(name='x', type=int)], int] # ok
# I have commented out the following test because I don't know how to expect the "defined here" note part of the error.
# M = Callable[[Arg(gnome='x', type=int)], int] E: Invalid type alias E: Unexpected keyword argument "gnome" for "Arg"
# 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"
N = Callable[[Arg(name=None, type=int)], int] # ok
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
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
P = Callable[[mypy_extensions.VarArg(int)], int] # ok
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"
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"
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"
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"

[builtins fixtures/dict.pyi]

Expand Down
8 changes: 5 additions & 3 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ reveal_type(f('a')) # N: Revealed type is '__main__.D[builtins.str*]'
main:15: error: Argument 1 to "D" has incompatible type "int"; expected "Tuple[T, T]"

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

class C(TupledNode): ... # Same as TupledNode[Any]
class D(TupledNode[T]): ...
class E(Generic[T], UNode[T]): ... # E: Invalid base class
class E(Generic[T], UNode[T]): ... # E: Invalid base class "UNode"

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

[case testAliasesInClassBodyNormalVsSubscripted]
# flags: --new-semantic-analyzer
from typing import Union, Type, Iterable

class A: pass
Expand All @@ -976,9 +978,9 @@ class C:
b = int # E: Cannot assign multiple types to name "b" without an explicit "Type[...]" annotation
if int():
c = int
def f(self, x: a) -> None: pass # E: Invalid type "__main__.C.a"
def f(self, x: a) -> None: pass # E: Variable "__main__.C.a" is not valid as a type
def g(self, x: b) -> None: pass
def h(self, x: c) -> None: pass # E: Invalid type "__main__.C.c"
def h(self, x: c) -> None: pass # E: Variable "__main__.C.c" is not valid as a type
x: b
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]'
[out]
Expand Down
7 changes: 5 additions & 2 deletions test-data/unit/check-incremental.test
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,7 @@ tmp/b.py:1: error: Module 'c' has no attribute 'x'
tmp/b.py:1: error: Module 'c' has no attribute 'x'

[case testCacheDeletedAfterErrorsFound2]
# flags: --new-semantic-analyzer
import a
[file a.py]
from b import x
Expand All @@ -2195,9 +2196,11 @@ from b import x
1 + 1
[out]
[out2]
tmp/b.py:2: error: Invalid type "c.C"
tmp/b.py:2: error: Function "c.C" is not valid as a type
tmp/b.py:2: note: Perhaps you need "Callable[...]" or a callback protocol?
[out3]
tmp/b.py:2: error: Invalid type "c.C"
tmp/b.py:2: error: Function "c.C" is not valid as a type
tmp/b.py:2: note: Perhaps you need "Callable[...]" or a callback protocol?

[case testCacheDeletedAfterErrorsFound3]
import a
Expand Down
Loading