Skip to content

Commit

Permalink
Disallow bytes in TypeVar, NewType, and TypedDict names (#13273)
Browse files Browse the repository at this point in the history
Similar to #13271
  • Loading branch information
JelleZijlstra authored Jul 28, 2022
1 parent 35ab579 commit e311f82
Show file tree
Hide file tree
Showing 6 changed files with 16 additions and 9 deletions.
2 changes: 1 addition & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3457,7 +3457,7 @@ def check_typevarlike_name(self, call: CallExpr, name: str, context: Context) ->
if len(call.args) < 1:
self.fail(f"Too few arguments for {typevarlike_type}()", context)
return False
if not isinstance(call.args[0], (StrExpr, BytesExpr)) or not call.arg_kinds[0] == ARG_POS:
if not isinstance(call.args[0], StrExpr) or not call.arg_kinds[0] == ARG_POS:
self.fail(f"{typevarlike_type}() expects a string literal as first argument", context)
return False
elif call.args[0].value != name:
Expand Down
3 changes: 1 addition & 2 deletions mypy/semanal_newtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
Argument,
AssignmentStmt,
Block,
BytesExpr,
CallExpr,
Context,
FuncDef,
Expand Down Expand Up @@ -177,7 +176,7 @@ def check_newtype_args(
return None, False

# Check first argument
if not isinstance(args[0], (StrExpr, BytesExpr)):
if not isinstance(args[0], StrExpr):
self.fail("Argument 1 to NewType(...) must be a string literal", context)
has_failed = True
elif args[0].value != name:
Expand Down
5 changes: 2 additions & 3 deletions mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
ARG_NAMED,
ARG_POS,
AssignmentStmt,
BytesExpr,
CallExpr,
ClassDef,
Context,
Expand Down Expand Up @@ -293,7 +292,7 @@ def parse_typeddict_args(
return self.fail_typeddict_arg(
f'Unexpected keyword argument "{call.arg_names[2]}" for "TypedDict"', call
)
if not isinstance(args[0], (StrExpr, BytesExpr)):
if not isinstance(args[0], StrExpr):
return self.fail_typeddict_arg(
"TypedDict() expects a string literal as the first argument", call
)
Expand Down Expand Up @@ -337,7 +336,7 @@ def parse_typeddict_fields_with_types(
items: List[str] = []
types: List[Type] = []
for (field_name_expr, field_type_expr) in dict_items:
if isinstance(field_name_expr, (StrExpr, BytesExpr)):
if isinstance(field_name_expr, StrExpr):
key = field_name_expr.value
items.append(key)
if key in seen_keys:
Expand Down
7 changes: 4 additions & 3 deletions test-data/unit/check-newtype.test
Original file line number Diff line number Diff line change
Expand Up @@ -272,10 +272,11 @@ from typing import NewType
a = NewType('b', int) # E: String argument 1 "b" to NewType(...) does not match variable name "a"
b = NewType('b', 3) # E: Argument 2 to NewType(...) must be a valid type
c = NewType(2, int) # E: Argument 1 to NewType(...) must be a string literal
d = NewType(b'f', int) # E: Argument 1 to NewType(...) must be a string literal
foo = "d"
d = NewType(foo, int) # E: Argument 1 to NewType(...) must be a string literal
e = NewType(name='e', tp=int) # E: NewType(...) expects exactly two positional arguments
f = NewType('f', tp=int) # E: NewType(...) expects exactly two positional arguments
e = NewType(foo, int) # E: Argument 1 to NewType(...) must be a string literal
f = NewType(name='e', tp=int) # E: NewType(...) expects exactly two positional arguments
g = NewType('f', tp=int) # E: NewType(...) expects exactly two positional arguments
[out]

[case testNewTypeWithAnyFails]
Expand Down
7 changes: 7 additions & 0 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,13 @@ d = {'x': int, 'y': int}
Point = TypedDict('Point', {**d}) # E: Invalid TypedDict() field name
[builtins fixtures/dict.pyi]

[case testCannotCreateTypedDictTypeWithBytes]
from mypy_extensions import TypedDict
Point = TypedDict(b'Point', {'x': int, 'y': int}) # E: TypedDict() expects a string literal as the first argument
# This technically works at runtime but doesn't make sense.
Point2 = TypedDict('Point2', {b'x': int}) # E: Invalid TypedDict() field name
[builtins fixtures/dict.pyi]

-- NOTE: The following code works at runtime but is not yet supported by mypy.
-- Keyword arguments may potentially be supported in the future.
[case testCannotCreateTypedDictTypeWithNonpositionalArgs]
Expand Down
1 change: 1 addition & 0 deletions test-data/unit/semanal-errors.test
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ from typing import TypeVar
a = TypeVar() # E: Too few arguments for TypeVar()
b = TypeVar(x='b') # E: TypeVar() expects a string literal as first argument
c = TypeVar(1) # E: TypeVar() expects a string literal as first argument
T = TypeVar(b'T') # E: TypeVar() expects a string literal as first argument
d = TypeVar('D') # E: String argument 1 "D" to TypeVar(...) does not match variable name "d"
e = TypeVar('e', int, str, x=1) # E: Unexpected argument to "TypeVar()": "x"
f = TypeVar('f', (int, str), int) # E: Type expected
Expand Down

0 comments on commit e311f82

Please sign in to comment.