From f9e8e0bda5cfbb54d6a8f9e482aa25da28a1a635 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 1 Jan 2024 20:15:15 -0800 Subject: [PATCH] Allow unary + in Literal (#16729) Implements python/typing#1550 Fixes #16727 (a trivial bug I found while implementing this feature) --- mypy/fastparse.py | 16 +++++++++++----- test-data/unit/check-literal.test | 11 ++++++++--- test-data/unit/fixtures/ops.pyi | 4 ++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index cba01eab2e4e..13b9b5c8a871 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -126,7 +126,7 @@ import ast as ast3 # TODO: Index, ExtSlice are deprecated in 3.9. -from ast import AST, Attribute, Call, FunctionType, Index, Name, Starred, UnaryOp, USub +from ast import AST, Attribute, Call, FunctionType, Index, Name, Starred, UAdd, UnaryOp, USub def ast3_parse( @@ -1940,13 +1940,19 @@ def visit_Constant(self, n: Constant) -> Type: # UnaryOp(op, operand) def visit_UnaryOp(self, n: UnaryOp) -> Type: - # We support specifically Literal[-4] and nothing else. - # For example, Literal[+4] or Literal[~6] is not supported. + # We support specifically Literal[-4], Literal[+4], and nothing else. + # For example, Literal[~6] or Literal[not False] is not supported. typ = self.visit(n.operand) - if isinstance(typ, RawExpressionType) and isinstance(n.op, USub): - if isinstance(typ.literal_value, int): + if ( + isinstance(typ, RawExpressionType) + # Use type() because we do not want to allow bools. + and type(typ.literal_value) is int # noqa: E721 + ): + if isinstance(n.op, USub): typ.literal_value *= -1 return typ + if isinstance(n.op, UAdd): + return typ return self.invalid_type(n) def numeric_type(self, value: object, n: AST) -> Type: diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index d9ad68385ad1..ea2aa5068e85 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -591,7 +591,6 @@ from typing_extensions import Literal def dummy() -> int: return 3 a: Literal[3 + 4] # E: Invalid type: Literal[...] cannot contain arbitrary expressions b: Literal[" foo ".trim()] # E: Invalid type: Literal[...] cannot contain arbitrary expressions -c: Literal[+42] # E: Invalid type: Literal[...] cannot contain arbitrary expressions d: Literal[~12] # E: Invalid type: Literal[...] cannot contain arbitrary expressions e: Literal[dummy()] # E: Invalid type: Literal[...] cannot contain arbitrary expressions [builtins fixtures/tuple.pyi] @@ -2739,7 +2738,7 @@ def test() -> None: ... [builtins fixtures/bool.pyi] -[case testNegativeIntLiteral] +[case testUnaryOpLiteral] from typing_extensions import Literal a: Literal[-2] = -2 @@ -2747,8 +2746,14 @@ b: Literal[-1] = -1 c: Literal[0] = 0 d: Literal[1] = 1 e: Literal[2] = 2 +f: Literal[+1] = 1 +g: Literal[+2] = 2 + +x: Literal[+True] = True # E: Invalid type: Literal[...] cannot contain arbitrary expressions +y: Literal[-True] = -1 # E: Invalid type: Literal[...] cannot contain arbitrary expressions +z: Literal[~0] = 0 # E: Invalid type: Literal[...] cannot contain arbitrary expressions [out] -[builtins fixtures/float.pyi] +[builtins fixtures/ops.pyi] [case testNegativeIntLiteralWithFinal] from typing_extensions import Literal, Final diff --git a/test-data/unit/fixtures/ops.pyi b/test-data/unit/fixtures/ops.pyi index 9cc4d22eb0a7..df3b163166ad 100644 --- a/test-data/unit/fixtures/ops.pyi +++ b/test-data/unit/fixtures/ops.pyi @@ -24,8 +24,6 @@ class tuple(Sequence[Tco]): class function: pass -class bool: pass - class str: def __init__(self, x: 'int') -> None: pass def __add__(self, x: 'str') -> 'str': pass @@ -54,6 +52,8 @@ class int: def __gt__(self, x: 'int') -> bool: pass def __ge__(self, x: 'int') -> bool: pass +class bool(int): pass + class float: def __add__(self, x: 'float') -> 'float': pass def __radd__(self, x: 'float') -> 'float': pass