Skip to content

Commit 07b0d80

Browse files
committed
Adjust error handling for literals in NewType and TypeVars
This commit modifies mypy so that the caller of TypeAnalyzer can optionally suppress "Invalid type" related error messages. This resolves python#5989: mypy will now stop recommending using Literal[...] when doing `A = NewType('A', 4)` or `T = TypeVar('T', bound=4)`. (The former suggestion is a bad one: you can't create a NewType of a Literal[...] type. The latter suggestion is a valid but stupid one: `T = TypeVar('T', bound=Literal[4])` is basically the same thing as doing just `T = Literal[4].
1 parent 310706c commit 07b0d80

File tree

7 files changed

+43
-26
lines changed

7 files changed

+43
-26
lines changed

mypy/plugin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ def anal_type(self, t: Type, *,
180180
tvar_scope: Optional[TypeVarScope] = None,
181181
allow_tuple_literal: bool = False,
182182
allow_unbound_tvars: bool = False,
183+
report_invalid_types: bool = True,
183184
third_pass: bool = False) -> Type:
184185
"""Analyze an unbound type."""
185186
raise NotImplementedError

mypy/semanal.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,7 @@ def update_metaclass(self, defn: ClassDef) -> None:
12831283
return
12841284
defn.metaclass = metas.pop()
12851285

1286-
def expr_to_analyzed_type(self, expr: Expression) -> Type:
1286+
def expr_to_analyzed_type(self, expr: Expression, report_invalid_types: bool = True) -> Type:
12871287
if isinstance(expr, CallExpr):
12881288
expr.accept(self)
12891289
info = self.named_tuple_analyzer.check_namedtuple(expr, None, self.is_func_scope())
@@ -1295,7 +1295,7 @@ def expr_to_analyzed_type(self, expr: Expression) -> Type:
12951295
fallback = Instance(info, [])
12961296
return TupleType(info.tuple_type.items, fallback=fallback)
12971297
typ = expr_to_unanalyzed_type(expr)
1298-
return self.anal_type(typ)
1298+
return self.anal_type(typ, report_invalid_types=report_invalid_types)
12991299

13001300
def verify_base_classes(self, defn: ClassDef) -> bool:
13011301
info = defn.info
@@ -1686,6 +1686,7 @@ def type_analyzer(self, *,
16861686
tvar_scope: Optional[TypeVarScope] = None,
16871687
allow_tuple_literal: bool = False,
16881688
allow_unbound_tvars: bool = False,
1689+
report_invalid_types: bool = True,
16891690
third_pass: bool = False) -> TypeAnalyser:
16901691
if tvar_scope is None:
16911692
tvar_scope = self.tvar_scope
@@ -1696,6 +1697,7 @@ def type_analyzer(self, *,
16961697
self.is_typeshed_stub_file,
16971698
allow_unbound_tvars=allow_unbound_tvars,
16981699
allow_tuple_literal=allow_tuple_literal,
1700+
report_invalid_types=report_invalid_types,
16991701
allow_unnormalized=self.is_stub_file,
17001702
third_pass=third_pass)
17011703
tpan.in_dynamic_func = bool(self.function_stack and self.function_stack[-1].is_dynamic())
@@ -1706,10 +1708,12 @@ def anal_type(self, t: Type, *,
17061708
tvar_scope: Optional[TypeVarScope] = None,
17071709
allow_tuple_literal: bool = False,
17081710
allow_unbound_tvars: bool = False,
1711+
report_invalid_types: bool = True,
17091712
third_pass: bool = False) -> Type:
17101713
a = self.type_analyzer(tvar_scope=tvar_scope,
17111714
allow_unbound_tvars=allow_unbound_tvars,
17121715
allow_tuple_literal=allow_tuple_literal,
1716+
report_invalid_types=report_invalid_types,
17131717
third_pass=third_pass)
17141718
typ = t.accept(a)
17151719
self.add_type_alias_deps(a.aliases_used)
@@ -2394,7 +2398,12 @@ def process_typevar_parameters(self, args: List[Expression],
23942398
self.fail("TypeVar cannot have both values and an upper bound", context)
23952399
return None
23962400
try:
2397-
upper_bound = self.expr_to_analyzed_type(param_value)
2401+
upper_bound = self.expr_to_analyzed_type(param_value,
2402+
report_invalid_types=False)
2403+
if isinstance(upper_bound, AnyType) and upper_bound.is_from_error:
2404+
self.fail("TypeVar 'bound' must be a type", param_value)
2405+
# Note: we do not return 'None' here -- we want to continue
2406+
# using the AnyType as the upper bound.
23982407
except TypeTranslationError:
23992408
self.fail("TypeVar 'bound' must be a type", param_value)
24002409
return None

mypy/semanal_newtype.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,11 @@ def check_newtype_args(self, name: str, call: CallExpr, context: Context) -> Opt
114114
self.fail(msg, context)
115115
return None
116116

117-
old_type = self.api.anal_type(unanalyzed_type)
117+
old_type = self.api.anal_type(unanalyzed_type, report_invalid_types=False)
118118

119119
# The caller of this function assumes that if we return a Type, it's always
120120
# a valid one. So, we translate AnyTypes created from errors into None.
121-
if isinstance(old_type, AnyType) and old_type.type_of_any == TypeOfAny.from_error:
121+
if isinstance(old_type, AnyType) and old_type.is_from_error:
122122
self.fail(msg, context)
123123
return None
124124

mypy/semanal_shared.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def anal_type(self, t: Type, *,
9191
tvar_scope: Optional[TypeVarScope] = None,
9292
allow_tuple_literal: bool = False,
9393
allow_unbound_tvars: bool = False,
94+
report_invalid_types: bool = True,
9495
third_pass: bool = False) -> Type:
9596
raise NotImplementedError
9697

mypy/typeanal.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def __init__(self,
157157
allow_tuple_literal: bool = False,
158158
allow_unnormalized: bool = False,
159159
allow_unbound_tvars: bool = False,
160+
report_invalid_types: bool = True,
160161
third_pass: bool = False) -> None:
161162
self.api = api
162163
self.lookup = api.lookup_qualified
@@ -174,6 +175,11 @@ def __init__(self,
174175
self.allow_unnormalized = allow_unnormalized
175176
# Should we accept unbound type variables (always OK in aliases)?
176177
self.allow_unbound_tvars = allow_unbound_tvars or defining_alias
178+
# Should we report an error whenever we encounter a RawExpressionType outside
179+
# of a Literal context: e.g. whenever we encounter an invalid type? Normally,
180+
# we want to report an error, but the caller may want to do more specialized
181+
# error handling.
182+
self.report_invalid_types = report_invalid_types
177183
self.plugin = plugin
178184
self.options = options
179185
self.is_typeshed_stub = is_typeshed_stub
@@ -499,24 +505,25 @@ def visit_raw_expression_type(self, t: RawExpressionType) -> Type:
499505
# this method so it generates and returns an actual LiteralType
500506
# instead.
501507

502-
if t.base_type_name in ('builtins.int', 'builtins.bool'):
503-
# The only time it makes sense to use an int or bool is inside of
504-
# a literal type.
505-
msg = "Invalid type: try using Literal[{}] instead?".format(repr(t.literal_value))
506-
elif t.base_type_name in ('builtins.float', 'builtins.complex'):
507-
# We special-case warnings for floats and complex numbers.
508-
msg = "Invalid type: {} literals cannot be used as a type".format(t.simple_name())
509-
else:
510-
# And in all other cases, we default to a generic error message.
511-
# Note: the reason why we use a generic error message for strings
512-
# but not ints or bools is because whenever we see an out-of-place
513-
# string, it's unclear if the user meant to construct a literal type
514-
# or just misspelled a regular type. So we avoid guessing.
515-
msg = 'Invalid type comment or annotation'
516-
517-
self.fail(msg, t)
518-
if t.note is not None:
519-
self.note_func(t.note, t)
508+
if self.report_invalid_types:
509+
if t.base_type_name in ('builtins.int', 'builtins.bool'):
510+
# The only time it makes sense to use an int or bool is inside of
511+
# a literal type.
512+
msg = "Invalid type: try using Literal[{}] instead?".format(repr(t.literal_value))
513+
elif t.base_type_name in ('builtins.float', 'builtins.complex'):
514+
# We special-case warnings for floats and complex numbers.
515+
msg = "Invalid type: {} literals cannot be used as a type".format(t.simple_name())
516+
else:
517+
# And in all other cases, we default to a generic error message.
518+
# Note: the reason why we use a generic error message for strings
519+
# but not ints or bools is because whenever we see an out-of-place
520+
# string, it's unclear if the user meant to construct a literal type
521+
# or just misspelled a regular type. So we avoid guessing.
522+
msg = 'Invalid type comment or annotation'
523+
524+
self.fail(msg, t)
525+
if t.note is not None:
526+
self.note_func(t.note, t)
520527

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

test-data/unit/check-newtype.test

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,7 @@ tmp/m.py:14: error: Revealed type is 'builtins.int'
269269
from typing import NewType
270270

271271
a = NewType('b', int) # E: String argument 1 'b' to NewType(...) does not match variable name 'a'
272-
b = NewType('b', 3) # E: Argument 2 to NewType(...) must be a valid type \
273-
# E: Invalid type: try using Literal[3] instead?
272+
b = NewType('b', 3) # E: Argument 2 to NewType(...) must be a valid type
274273
c = NewType(2, int) # E: Argument 1 to NewType(...) must be a string literal
275274
foo = "d"
276275
d = NewType(foo, int) # E: Argument 1 to NewType(...) must be a string literal

test-data/unit/semanal-errors.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ e = TypeVar('e', int, str, x=1) # E: Unexpected argument to TypeVar(): x
980980
f = TypeVar('f', (int, str), int) # E: Type expected
981981
g = TypeVar('g', int) # E: TypeVar cannot have only a single constraint
982982
h = TypeVar('h', x=(int, str)) # E: Unexpected argument to TypeVar(): x
983-
i = TypeVar('i', bound=1) # E: Invalid type: try using Literal[1] instead?
983+
i = TypeVar('i', bound=1) # E: TypeVar 'bound' must be a type
984984
[out]
985985

986986
[case testMoreInvalidTypevarArguments]

0 commit comments

Comments
 (0)