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 4 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
30 changes: 26 additions & 4 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,13 @@ 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)
msg = 'Invalid base class'
name = self.get_name_repr_of_expr(base_expr)
if name:
msg += ' "{}"'.format(name)
if isinstance(base_expr, CallExpr):
msg += ': Unsupported dynamic base class'
self.fail(msg, base_expr)
is_error = True
continue
if base is None:
Expand Down Expand Up @@ -1409,7 +1427,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 +1443,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 +3208,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 Down
19 changes: 15 additions & 4 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: cannot interpret right hand side as a 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,18 @@ 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.
if isinstance(sym.node, Var):
reason = 'Cannot use variable as type' # TODO: add link to alias docs, see #3494
elif isinstance(sym.node, (SYMBOL_FUNCBASE_TYPES, Decorator)):
reason = 'Cannot use function as type, use Callable[...] or a protocol instead'
elif isinstance(sym.node, MypyFile):
reason = 'Module cannot be used as type' # TODO: suggest a protocol when supported
elif unbound_tvar:
reason = 'Can only use bound type variables as types'
else:
reason = 'Cannot interpret reference as a type'
self.fail('Invalid type "{}": {}'.format(name, reason), 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 +433,7 @@ 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('Invalid type: Did you want to use "List[...]"?', t)
return AnyType(TypeOfAny.from_error)

def visit_callable_argument(self, t: CallableArgument) -> Type:
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: Invalid type "mock": Module cannot be used as type
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshedding: this error message could be shortened down to Module "mock" is not valid as a type without loss of clarity, I think.


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: Invalid type "mock": Module cannot be used as 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: Invalid base class "six.with_metaclass": Unsupported dynamic base class
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, this could be shortened to Unsupported dynamic base class "six.with_metaclass".

class C2(C1, six.with_metaclass(M)): pass # E: Invalid base class "six.with_metaclass": Unsupported dynamic base class
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: Invalid type "__main__.bad": Cannot use variable as type
y: bad # E:8: Invalid type "__main__.bad": Cannot use variable as type
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like above, this could be shortened to 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: Invalid type "__main__.bad": Cannot use variable as type
# type: (bad) -> None
y = 0 # type: bad # E:9: Invalid type "__main__.bad"
y = 0 # type: bad # E:9: Invalid type "__main__.bad": Cannot use variable as type

z: Iterable[bad] # E:13: Invalid type "__main__.bad"
h: bad[int] # E:4: Invalid type "__main__.bad"
z: Iterable[bad] # E:13: Invalid type "__main__.bad": Cannot use variable as type
h: bad[int] # E:4: Invalid type "__main__.bad": Cannot use variable as 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: Invalid type "__main__.bad": Cannot use variable as type
# type: (bad) -> None
y = 0 # type: bad # E:9: Invalid type "__main__.bad"
y = 0 # type: bad # E:9: Invalid type "__main__.bad": Cannot use variable as type

z = () # type: Iterable[bad] # E:5: Invalid type "__main__.bad"
z = () # type: Iterable[bad] # E:5: Invalid type "__main__.bad": Cannot use variable as 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: Invalid type "__main__.Bad1": Cannot use variable as type \
# E: Invalid base class "Bad1"
class C2(Bad2): ... # E: Invalid type "__main__.Bad2": Cannot use variable as type \
# E: Invalid base class "Bad2"
class C3(Bad3): ... # E: Invalid type "__main__.Bad3": Cannot use variable as 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: cannot interpret right hand side as a type E: Unexpected keyword argument "gnome" for "Arg"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bikeshedding: Invalid type alias: expression is not a valid type.

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: cannot interpret right hand side as a 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: cannot interpret right hand side as a 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: cannot interpret right hand side as a 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: Invalid type "__main__.C.a": Cannot use variable as 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: Invalid type "__main__.C.c": Cannot use variable as type
x: b
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]'
[out]
Expand Down
5 changes: 3 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,9 @@ from b import x
1 + 1
[out]
[out2]
tmp/b.py:2: error: Invalid type "c.C"
tmp/b.py:2: error: Invalid type "c.C": Cannot use function as type, use Callable[...] or a protocol instead
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, the first part could be shortened to Function "c.C" is not valid as a type. I'd move the final part to a separate note and rephrase it, since it's not clear of Callable[...] or a protocol is actually what the user wants. Maybe something like this:

b.py:2: error: Function "c.C" is not a valid type
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: Invalid type "c.C": Cannot use function as type, use Callable[...] or a protocol instead

[case testCacheDeletedAfterErrorsFound3]
import a
Expand Down
Loading