From 91688c0e5f800f9cf846e22b41d36f47f4abda6e Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Mon, 10 Aug 2020 14:50:47 -0700 Subject: [PATCH 01/14] Add '?' to Python grammar. --- Doc/library/token-list.inc | 4 ++++ Grammar/Tokens | 1 + Grammar/python.gram | 6 ++++-- Include/Python-ast.h | 3 ++- Include/token.h | 15 +++++++------- Lib/test/test_grammar.py | 3 +++ Lib/token.py | 22 +++++++++++--------- Lib/tokenize.py | 1 - Parser/Python.asdl | 2 +- Parser/parser.c | 42 +++++++++++++++++++++++++++++++++++++- Parser/token.c | 2 ++ Parser/tokenizer.c | 5 +++++ Python/Python-ast.c | 31 +++++++++++++++++++++++++++- Python/ast_unparse.c | 1 + 14 files changed, 114 insertions(+), 24 deletions(-) diff --git a/Doc/library/token-list.inc b/Doc/library/token-list.inc index 877d39a432ed70..d3c3a2b8b69e82 100644 --- a/Doc/library/token-list.inc +++ b/Doc/library/token-list.inc @@ -201,6 +201,10 @@ Token value for ``":="``. +.. data:: QUESTION + + Token value for ``"?"``. + .. data:: OP .. data:: AWAIT diff --git a/Grammar/Tokens b/Grammar/Tokens index 9de2da5d15fc3b..dbfcd310de86d3 100644 --- a/Grammar/Tokens +++ b/Grammar/Tokens @@ -53,6 +53,7 @@ ATEQUAL '@=' RARROW '->' ELLIPSIS '...' COLONEQUAL ':=' +QUESTION '?' OP AWAIT diff --git a/Grammar/python.gram b/Grammar/python.gram index c5a5dbe1724f3e..9f169be99acf1a 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -327,10 +327,12 @@ expressions[expr_ty]: _Py_Tuple(CHECK(_PyPegen_seq_insert_in_front(p, a, b)), Load, EXTRA) } | a=expression ',' { _Py_Tuple(CHECK(_PyPegen_singleton_seq(p, a)), Load, EXTRA) } | expression + expression[expr_ty] (memo): | a=disjunction 'if' b=disjunction 'else' c=expression { _Py_IfExp(b, a, c, EXTRA) } | disjunction | lambdef + | '?' a=expression { _Py_UnaryOp(Not, a, EXTRA) } lambdef[expr_ty]: | 'lambda' a=[lambda_params] ':' b=expression { _Py_Lambda((a) ? a : CHECK(_PyPegen_empty_arguments(p)), b, EXTRA) } @@ -656,8 +658,8 @@ invalid_assignment: RAISE_SYNTAX_ERROR_INVALID_TARGET(STAR_TARGETS, a) } | (star_targets '=')* a=yield_expr '=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "assignment to yield expression not possible") } | a=star_expressions augassign (yield_expr | star_expressions) { - RAISE_SYNTAX_ERROR_KNOWN_LOCATION( - a, + RAISE_SYNTAX_ERROR_KNOWN_LOCATION( + a, "'%s' is an illegal expression for augmented assignment", _PyPegen_get_expr_name(a) )} diff --git a/Include/Python-ast.h b/Include/Python-ast.h index e7afa1e6579e8d..851d3c51b10b32 100644 --- a/Include/Python-ast.h +++ b/Include/Python-ast.h @@ -25,7 +25,8 @@ typedef enum _operator { Add=1, Sub=2, Mult=3, MatMult=4, Div=5, Mod=6, Pow=7, LShift=8, RShift=9, BitOr=10, BitXor=11, BitAnd=12, FloorDiv=13 } operator_ty; -typedef enum _unaryop { Invert=1, Not=2, UAdd=3, USub=4 } unaryop_ty; +typedef enum _unaryop { Invert=1, Not=2, UAdd=3, USub=4, Question=5 } + unaryop_ty; typedef enum _cmpop { Eq=1, NotEq=2, Lt=3, LtE=4, Gt=5, GtE=6, Is=7, IsNot=8, In=9, NotIn=10 } cmpop_ty; diff --git a/Include/token.h b/Include/token.h index 9b8a3aae074674..c4f30f75ca7ffb 100644 --- a/Include/token.h +++ b/Include/token.h @@ -64,13 +64,14 @@ extern "C" { #define RARROW 51 #define ELLIPSIS 52 #define COLONEQUAL 53 -#define OP 54 -#define AWAIT 55 -#define ASYNC 56 -#define TYPE_IGNORE 57 -#define TYPE_COMMENT 58 -#define ERRORTOKEN 59 -#define N_TOKENS 63 +#define QUESTION 54 +#define OP 55 +#define AWAIT 56 +#define ASYNC 57 +#define TYPE_IGNORE 58 +#define TYPE_COMMENT 59 +#define ERRORTOKEN 60 +#define N_TOKENS 64 #define NT_OFFSET 256 /* Special definitions for cooperation with parser */ diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index ef7d1a15c7570d..e3033d3e0a6b9a 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -407,6 +407,9 @@ def test_var_annot_simple_exec(self): with self.assertRaises(KeyError): gns['__annotations__'] + # def test_annot_optional(self): + # x: ?int = None + def test_var_annot_custom_maps(self): # tests with custom locals() and __annotations__ ns = {'__annotations__': CNS()} diff --git a/Lib/token.py b/Lib/token.py index 493bf0426508fe..19de47c3cb8183 100644 --- a/Lib/token.py +++ b/Lib/token.py @@ -57,17 +57,18 @@ RARROW = 51 ELLIPSIS = 52 COLONEQUAL = 53 -OP = 54 -AWAIT = 55 -ASYNC = 56 -TYPE_IGNORE = 57 -TYPE_COMMENT = 58 +QUESTION = 54 +OP = 55 +AWAIT = 56 +ASYNC = 57 +TYPE_IGNORE = 58 +TYPE_COMMENT = 59 # These aren't used by the C tokenizer but are needed for tokenize.py -ERRORTOKEN = 59 -COMMENT = 60 -NL = 61 -ENCODING = 62 -N_TOKENS = 63 +ERRORTOKEN = 60 +COMMENT = 61 +NL = 62 +ENCODING = 63 +N_TOKENS = 64 # Special definitions for cooperation with parser NT_OFFSET = 256 @@ -113,6 +114,7 @@ '>=': GREATEREQUAL, '>>': RIGHTSHIFT, '>>=': RIGHTSHIFTEQUAL, + '?': QUESTION, '@': AT, '@=': ATEQUAL, '[': LSQB, diff --git a/Lib/tokenize.py b/Lib/tokenize.py index 1aee21b5e18fa7..306d8821c981dd 100644 --- a/Lib/tokenize.py +++ b/Lib/tokenize.py @@ -600,7 +600,6 @@ def _tokenize(readline, encoding): yield TokenInfo(ERRORTOKEN, line[pos], (lnum, pos), (lnum, pos+1), line) pos += 1 - # Add an implicit NEWLINE if the input doesn't end in one if last_line and last_line[-1] not in '\r\n': yield TokenInfo(NEWLINE, '', (lnum - 1, len(last_line)), (lnum - 1, len(last_line) + 1), '') diff --git a/Parser/Python.asdl b/Parser/Python.asdl index 889712b4b3d36e..09ff95760eff0b 100644 --- a/Parser/Python.asdl +++ b/Parser/Python.asdl @@ -97,7 +97,7 @@ module Python operator = Add | Sub | Mult | MatMult | Div | Mod | Pow | LShift | RShift | BitOr | BitXor | BitAnd | FloorDiv - unaryop = Invert | Not | UAdd | USub + unaryop = Invert | Not | UAdd | USub | Question cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn diff --git a/Parser/parser.c b/Parser/parser.c index 323cd0e0efae30..d26e38bc378c77 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -6982,7 +6982,11 @@ expressions_rule(Parser *p) return _res; } -// expression: disjunction 'if' disjunction 'else' expression | disjunction | lambdef +// expression: +// | disjunction 'if' disjunction 'else' expression +// | disjunction +// | lambdef +// | '?' expression static expr_ty expression_rule(Parser *p) { @@ -7089,6 +7093,42 @@ expression_rule(Parser *p) D(fprintf(stderr, "%*c%s expression[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambdef")); } + { // '?' expression + if (p->error_indicator) { + D(p->level--); + return NULL; + } + D(fprintf(stderr, "%*c> expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'?' expression")); + Token * _literal; + expr_ty a; + if ( + (_literal = _PyPegen_expect_token(p, 54)) // token='?' + && + (a = expression_rule(p)) // expression + ) + { + D(fprintf(stderr, "%*c+ expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'?' expression")); + Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); + if (_token == NULL) { + D(p->level--); + return NULL; + } + int _end_lineno = _token->end_lineno; + UNUSED(_end_lineno); // Only used by EXTRA macro + int _end_col_offset = _token->end_col_offset; + UNUSED(_end_col_offset); // Only used by EXTRA macro + _res = _Py_UnaryOp ( Not , a , EXTRA ); + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + D(p->level--); + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s expression[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'?' expression")); + } _res = NULL; done: _PyPegen_insert_memo(p, _mark, expression_type, _res); diff --git a/Parser/token.c b/Parser/token.c index a489668900901d..1541f17a89dc82 100644 --- a/Parser/token.c +++ b/Parser/token.c @@ -60,6 +60,7 @@ const char * const _PyParser_TokenNames[] = { "RARROW", "ELLIPSIS", "COLONEQUAL", + "QUESTION", "OP", "AWAIT", "ASYNC", @@ -93,6 +94,7 @@ PyToken_OneChar(int c1) case '<': return LESS; case '=': return EQUAL; case '>': return GREATER; + case '?': return QUESTION; case '@': return AT; case '[': return LSQB; case ']': return RSQB; diff --git a/Parser/tokenizer.c b/Parser/tokenizer.c index f3c1d9b20ade11..6c4295b8d00c8f 100644 --- a/Parser/tokenizer.c +++ b/Parser/tokenizer.c @@ -1378,6 +1378,11 @@ tok_get(struct tok_state *tok, const char **p_start, const char **p_end) return tok->done == E_EOF ? ENDMARKER : ERRORTOKEN; } + /* Check for Optional shorthand */ + if (c == '?') { + return QUESTION; + } + /* Identifier (most frequent token!) */ nonascii = 0; if (is_potential_identifier_start(c)) { diff --git a/Python/Python-ast.c b/Python/Python-ast.c index 694987dd077881..dee52ee7e35e60 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -106,6 +106,8 @@ typedef struct { PyObject *Pass_type; PyObject *Pow_singleton; PyObject *Pow_type; + PyObject *Question_singleton; + PyObject *Question_type; PyObject *RShift_singleton; PyObject *RShift_type; PyObject *Raise_type; @@ -326,6 +328,8 @@ static int astmodule_clear(PyObject *module) Py_CLEAR(astmodulestate(module)->Pass_type); Py_CLEAR(astmodulestate(module)->Pow_singleton); Py_CLEAR(astmodulestate(module)->Pow_type); + Py_CLEAR(astmodulestate(module)->Question_singleton); + Py_CLEAR(astmodulestate(module)->Question_type); Py_CLEAR(astmodulestate(module)->RShift_singleton); Py_CLEAR(astmodulestate(module)->RShift_type); Py_CLEAR(astmodulestate(module)->Raise_type); @@ -545,6 +549,8 @@ static int astmodule_traverse(PyObject *module, visitproc visit, void* arg) Py_VISIT(astmodulestate(module)->Pass_type); Py_VISIT(astmodulestate(module)->Pow_singleton); Py_VISIT(astmodulestate(module)->Pow_type); + Py_VISIT(astmodulestate(module)->Question_singleton); + Py_VISIT(astmodulestate(module)->Question_type); Py_VISIT(astmodulestate(module)->RShift_singleton); Py_VISIT(astmodulestate(module)->RShift_type); Py_VISIT(astmodulestate(module)->Raise_type); @@ -1868,7 +1874,7 @@ static int init_types(void) NULL); if (!state->FloorDiv_singleton) return 0; state->unaryop_type = make_type("unaryop", state->AST_type, NULL, 0, - "unaryop = Invert | Not | UAdd | USub"); + "unaryop = Invert | Not | UAdd | USub | Question"); if (!state->unaryop_type) return 0; if (!add_attributes(state->unaryop_type, NULL, 0)) return 0; state->Invert_type = make_type("Invert", state->unaryop_type, NULL, 0, @@ -1896,6 +1902,13 @@ static int init_types(void) state->USub_singleton = PyType_GenericNew((PyTypeObject *)state->USub_type, NULL, NULL); if (!state->USub_singleton) return 0; + state->Question_type = make_type("Question", state->unaryop_type, NULL, 0, + "Question"); + if (!state->Question_type) return 0; + state->Question_singleton = PyType_GenericNew((PyTypeObject + *)state->Question_type, NULL, + NULL); + if (!state->Question_singleton) return 0; state->cmpop_type = make_type("cmpop", state->AST_type, NULL, 0, "cmpop = Eq | NotEq | Lt | LtE | Gt | GtE | Is | IsNot | In | NotIn"); if (!state->cmpop_type) return 0; @@ -4697,6 +4710,9 @@ PyObject* ast2obj_unaryop(unaryop_ty o) case USub: Py_INCREF(astmodulestate_global->USub_singleton); return astmodulestate_global->USub_singleton; + case Question: + Py_INCREF(astmodulestate_global->Question_singleton); + return astmodulestate_global->Question_singleton; } Py_UNREACHABLE(); } @@ -8999,6 +9015,14 @@ obj2ast_unaryop(PyObject* obj, unaryop_ty* out, PyArena* arena) *out = USub; return 0; } + isinstance = PyObject_IsInstance(obj, astmodulestate_global->Question_type); + if (isinstance == -1) { + return 1; + } + if (isinstance) { + *out = Question; + return 0; + } PyErr_Format(PyExc_TypeError, "expected some sort of unaryop, but got %R", obj); return 1; @@ -10313,6 +10337,11 @@ PyInit__ast(void) goto error; } Py_INCREF(astmodulestate(m)->USub_type); + if (PyModule_AddObject(m, "Question", astmodulestate_global->Question_type) + < 0) { + goto error; + } + Py_INCREF(astmodulestate(m)->Question_type); if (PyModule_AddObject(m, "cmpop", astmodulestate_global->cmpop_type) < 0) { goto error; } diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c index e699751a05a055..6b35b6937dfd2e 100644 --- a/Python/ast_unparse.c +++ b/Python/ast_unparse.c @@ -181,6 +181,7 @@ append_ast_unaryop(_PyUnicodeWriter *writer, expr_ty e, int level) case Not: op = "not "; pr = PR_NOT; break; case UAdd: op = "+"; pr = PR_FACTOR; break; case USub: op = "-"; pr = PR_FACTOR; break; + case Question: op = '?'; pr = PR_FACTOR; break; default: PyErr_SetString(PyExc_SystemError, "unknown unary operator"); From a103a518648ce449d63d92446c06f90c81913219 Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Mon, 10 Aug 2020 14:51:36 -0700 Subject: [PATCH 02/14] Add test for ? syntax. --- Lib/test/test_grammar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index e3033d3e0a6b9a..9ca5470ef30448 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -407,8 +407,8 @@ def test_var_annot_simple_exec(self): with self.assertRaises(KeyError): gns['__annotations__'] - # def test_annot_optional(self): - # x: ?int = None + def test_annot_optional(self): + x: ?int = None def test_var_annot_custom_maps(self): # tests with custom locals() and __annotations__ From 5874ab6741376a1993773b5bb20ff5251dc7729f Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Mon, 10 Aug 2020 15:01:07 -0700 Subject: [PATCH 03/14] Update test_grammar. --- Lib/test/test_grammar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index 9ca5470ef30448..7e93434ace3e8e 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -409,6 +409,8 @@ def test_var_annot_simple_exec(self): def test_annot_optional(self): x: ?int = None + self.assertRaises(SyntaxError, eval, "?bar = 1") + def test_var_annot_custom_maps(self): # tests with custom locals() and __annotations__ From 445c658f2fb4916efd3030c1f0640ed86056e9f1 Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Mon, 10 Aug 2020 15:24:28 -0700 Subject: [PATCH 04/14] Add tests to test_typing. --- Lib/test/test_grammar.py | 1 - Lib/test/test_typing.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index 7e93434ace3e8e..b42f4764fc612e 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -411,7 +411,6 @@ def test_annot_optional(self): x: ?int = None self.assertRaises(SyntaxError, eval, "?bar = 1") - def test_var_annot_custom_maps(self): # tests with custom locals() and __annotations__ ns = {'__annotations__': CNS()} diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f429e883b59538..c13ffa3dfd009e 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3585,6 +3585,16 @@ def foo(a: A) -> Optional[BaseException]: assert isinstance(foo(KeyboardInterrupt), KeyboardInterrupt) assert foo(None) is None + def test_new_optional(self): + assert ?int == Optional[int] + + A = ?Type[BaseException] + def foo(a: A) -> ?BaseException: + if a is None: + return None + else: + return a() + assert isinstance(foo(KeyboardInterrupt), KeyboardInterrupt) class NewTypeTests(BaseTestCase): From 8e033adadb2c091483b4f57d7125318d6287076c Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Mon, 10 Aug 2020 15:59:01 -0700 Subject: [PATCH 05/14] Add new Question unary operator. --- Grammar/python.gram | 2 +- Include/opcode.h | 1 + Lib/opcode.py | 2 +- Lib/test/test_types.py | 1 - Parser/parser.c | 2 +- Python/ast_unparse.c | 1 - Python/ceval.c | 10 ++++++++++ Python/compile.c | 3 +++ Python/opcode_targets.h | 2 +- 9 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Grammar/python.gram b/Grammar/python.gram index 9f169be99acf1a..eda120d7c57ea5 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -332,7 +332,7 @@ expression[expr_ty] (memo): | a=disjunction 'if' b=disjunction 'else' c=expression { _Py_IfExp(b, a, c, EXTRA) } | disjunction | lambdef - | '?' a=expression { _Py_UnaryOp(Not, a, EXTRA) } + | '?' a=expression { _Py_UnaryOp(Question, a, EXTRA) } lambdef[expr_ty]: | 'lambda' a=[lambda_params] ':' b=expression { _Py_Lambda((a) ? a : CHECK(_PyPegen_empty_arguments(p)), b, EXTRA) } diff --git a/Include/opcode.h b/Include/opcode.h index 19944fac0b9f2b..300dbdc6385a96 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -17,6 +17,7 @@ extern "C" { #define UNARY_POSITIVE 10 #define UNARY_NEGATIVE 11 #define UNARY_NOT 12 +#define UNARY_QUESTION 14 #define UNARY_INVERT 15 #define BINARY_MATRIX_MULTIPLY 16 #define INPLACE_MATRIX_MULTIPLY 17 diff --git a/Lib/opcode.py b/Lib/opcode.py index ac1aa535f66ff6..f0e88619c0794d 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -65,7 +65,7 @@ def jabs_op(name, op): def_op('UNARY_POSITIVE', 10) def_op('UNARY_NEGATIVE', 11) def_op('UNARY_NOT', 12) - +def_op('UNARY_QUESTION', 14) def_op('UNARY_INVERT', 15) def_op('BINARY_MATRIX_MULTIPLY', 16) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 49dc5bf40e3ed8..3518ab07fa6548 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -803,7 +803,6 @@ def test_union(self): self.assertDictEqual(mapping, {'a': 0, 'b': 1, 'c': 2}) self.assertDictEqual(other, {'c': 3, 'p': 0}) - class ClassCreationTests(unittest.TestCase): class Meta(type): diff --git a/Parser/parser.c b/Parser/parser.c index d26e38bc378c77..3effb41f8bec85 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -7117,7 +7117,7 @@ expression_rule(Parser *p) UNUSED(_end_lineno); // Only used by EXTRA macro int _end_col_offset = _token->end_col_offset; UNUSED(_end_col_offset); // Only used by EXTRA macro - _res = _Py_UnaryOp ( Not , a , EXTRA ); + _res = _Py_UnaryOp ( Question , a , EXTRA ); if (_res == NULL && PyErr_Occurred()) { p->error_indicator = 1; D(p->level--); diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c index 6b35b6937dfd2e..e699751a05a055 100644 --- a/Python/ast_unparse.c +++ b/Python/ast_unparse.c @@ -181,7 +181,6 @@ append_ast_unaryop(_PyUnicodeWriter *writer, expr_ty e, int level) case Not: op = "not "; pr = PR_NOT; break; case UAdd: op = "+"; pr = PR_FACTOR; break; case USub: op = "-"; pr = PR_FACTOR; break; - case Question: op = '?'; pr = PR_FACTOR; break; default: PyErr_SetString(PyExc_SystemError, "unknown unary operator"); diff --git a/Python/ceval.c b/Python/ceval.c index 0386929a5b2b37..a1461c1c24da12 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1624,6 +1624,16 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag) DISPATCH(); } + case TARGET(UNARY_QUESTION): { + PyObject *value = TOP(); + PyObject *res = value; + Py_DECREF(value); + SET_TOP(res); + if (res == NULL) + goto error; + DISPATCH(); + } + case TARGET(BINARY_POWER): { PyObject *exp = POP(); PyObject *base = TOP(); diff --git a/Python/compile.c b/Python/compile.c index 8fe82f91559e09..ad3e47f6002c31 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -878,6 +878,7 @@ stack_effect(int opcode, int oparg, int jump) case UNARY_NEGATIVE: case UNARY_NOT: case UNARY_INVERT: + case UNARY_QUESTION: return 0; case SET_ADD: @@ -3450,6 +3451,8 @@ unaryop(unaryop_ty op) return UNARY_POSITIVE; case USub: return UNARY_NEGATIVE; + case Question: + return UNARY_QUESTION; default: PyErr_Format(PyExc_SystemError, "unary op %d should not be possible", op); diff --git a/Python/opcode_targets.h b/Python/opcode_targets.h index 538fdbe3e0b5ae..9b8456809967ab 100644 --- a/Python/opcode_targets.h +++ b/Python/opcode_targets.h @@ -13,7 +13,7 @@ static void *opcode_targets[256] = { &&TARGET_UNARY_NEGATIVE, &&TARGET_UNARY_NOT, &&_unknown_opcode, - &&_unknown_opcode, + &&TARGET_UNARY_QUESTION, &&TARGET_UNARY_INVERT, &&TARGET_BINARY_MATRIX_MULTIPLY, &&TARGET_INPLACE_MATRIX_MULTIPLY, From 16bdaddae4fa08f099558e0932eb8581b821777a Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Mon, 10 Aug 2020 16:37:31 -0700 Subject: [PATCH 06/14] Add optionalobject. --- Include/Python.h | 1 + Include/optionalobject.h | 15 +++ Makefile.pre.in | 1 + Objects/optionalobject.c | 207 +++++++++++++++++++++++++++++ PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 5 +- Python/ceval.c | 3 +- 7 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 Include/optionalobject.h create mode 100644 Objects/optionalobject.c diff --git a/Include/Python.h b/Include/Python.h index 57f71d41d8d477..b9f3cee218e2fd 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -122,6 +122,7 @@ #include "iterobject.h" #include "genobject.h" #include "descrobject.h" +#include "optionalobject.h" #include "genericaliasobject.h" #include "warnings.h" #include "weakrefobject.h" diff --git a/Include/optionalobject.h b/Include/optionalobject.h new file mode 100644 index 00000000000000..ffeeafcb234efb --- /dev/null +++ b/Include/optionalobject.h @@ -0,0 +1,15 @@ + +#ifndef Py_OPTIONALTYPEOBJECT_H +#define Py_OPTIONALTYPEOBJECT_H +#ifdef __cplusplus +extern "C" +{ +#endif + + PyAPI_FUNC(PyObject *) Py_Optional(PyObject *); + PyAPI_DATA(PyTypeObject) Py_OptionalType; + +#ifdef __cplusplus +} +#endif +#endif /* !Py_OPTIONALTYPEOBJECT_H */ diff --git a/Makefile.pre.in b/Makefile.pre.in index 3428b9842a5a0f..23b2eda28a61ef 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -429,6 +429,7 @@ OBJECT_OBJS= \ Objects/typeobject.o \ Objects/unicodeobject.o \ Objects/unicodectype.o \ + Objects/optionalobject.o \ Objects/weakrefobject.o ########################################################################## diff --git a/Objects/optionalobject.c b/Objects/optionalobject.c new file mode 100644 index 00000000000000..11fee322fb92ae --- /dev/null +++ b/Objects/optionalobject.c @@ -0,0 +1,207 @@ +// types.Optional -- used to represent typing.Optional[int] as ?int +#include "Python.h" +#include "pycore_object.h" +#include "structmember.h" + + +typedef struct { + PyObject_HEAD + PyObject *args; +} optionalobject; + +static void +optionalobject_dealloc(PyObject *self) +{ + optionalobject *alias = (optionalobject *)self; + + _PyObject_GC_UNTRACK(self); + Py_XDECREF(alias->args); + self->ob_type->tp_free(self); +} + +static Py_hash_t +optional_hash(PyObject *self) +{ + optionalobject *alias = (optionalobject *)self; + Py_hash_t h1 = PyObject_Hash(alias->args); + if (h1 == -1) { + return -1; + } + return h1; +} + +static int +optional_traverse(PyObject *self, visitproc visit, void *arg) +{ + optionalobject *alias = (optionalobject *)self; + Py_VISIT(alias->args); + return 0; +} + +static PyMemberDef optional_members[] = { + {"__args__", T_OBJECT, offsetof(optionalobject, args), READONLY}, + {0} +}; + +static PyObject * +optional_getattro(PyObject *self, PyObject *name) +{ + return PyObject_GenericGetAttr(self, name); +} + +static PyObject * +optional_instancecheck(PyObject *self, PyObject *instance) +{ + // TODO: MM: Implement this. + return instance; +} + +static PyObject * +optional_subclasscheck(PyObject *self, PyObject *instance) +{ + // TODO: MM: Implement this. + return instance; +} + +static PyMethodDef optional_methods[] = { + {"__instancecheck__", optional_instancecheck, METH_O}, + {"__subclasscheck__", optional_subclasscheck, METH_O}, + {0}}; + + +static PyObject * +optional_richcompare(PyObject *a, PyObject *b, int op) +{ + return a; +} + + +static int +optional_repr_item(_PyUnicodeWriter *writer, PyObject *p) +{ + _Py_IDENTIFIER(__module__); + _Py_IDENTIFIER(__qualname__); + _Py_IDENTIFIER(__origin__); + _Py_IDENTIFIER(__args__); + PyObject *qualname = NULL; + PyObject *module = NULL; + PyObject *r = NULL; + PyObject *tmp; + int err; + + if (p == Py_Ellipsis) { + // The Ellipsis object + r = PyUnicode_FromString("..."); + goto done; + } + + if (_PyObject_LookupAttrId(p, &PyId___origin__, &tmp) < 0) { + goto done; + } + if (tmp != NULL) { + Py_DECREF(tmp); + if (_PyObject_LookupAttrId(p, &PyId___args__, &tmp) < 0) { + goto done; + } + if (tmp != NULL) { + Py_DECREF(tmp); + // It looks like a GenericAlias + goto use_repr; + } + } + + if (_PyObject_LookupAttrId(p, &PyId___qualname__, &qualname) < 0) { + goto done; + } + if (qualname == NULL) { + goto use_repr; + } + if (_PyObject_LookupAttrId(p, &PyId___module__, &module) < 0) { + goto done; + } + if (module == NULL || module == Py_None) { + goto use_repr; + } + + // Looks like a class + if (PyUnicode_Check(module) && + _PyUnicode_EqualToASCIIString(module, "builtins")) + { + // builtins don't need a module name + r = PyObject_Str(qualname); + goto done; + } + else { + r = PyUnicode_FromFormat("%S.%S", module, qualname); + goto done; + } + +use_repr: + r = PyObject_Repr(p); + +done: + Py_XDECREF(qualname); + Py_XDECREF(module); + if (r == NULL) { + // error if any of the above PyObject_Repr/PyUnicode_From* fail + err = -1; + } + else { + err = _PyUnicodeWriter_WriteStr(writer, r); + Py_DECREF(r); + } + return err; +} + +static PyObject * +optional_repr(PyObject *self) +{ + optionalobject *alias = (optionalobject *)self; + _PyUnicodeWriter writer; + _PyUnicodeWriter_Init(&writer); + if (_PyUnicodeWriter_WriteASCIIString(&writer, "?", 1) < 0) { + goto error; + } + if (optional_repr_item(&writer, alias->args) < 0) { + goto error; + } + return _PyUnicodeWriter_Finish(&writer); + +error: + _PyUnicodeWriter_Dealloc(&writer); + return NULL; +} + +PyTypeObject Py_OptionalType = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "types.Optional", + .tp_doc = "Represent an Optional type\n" + "\n" + "E.g. ?int", + .tp_basicsize = sizeof(optionalobject), + .tp_dealloc = optionalobject_dealloc, + .tp_alloc = PyType_GenericAlloc, + .tp_free = PyObject_GC_Del, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_hash = optional_hash, + .tp_traverse = optional_traverse, + .tp_getattro = optional_getattro, + .tp_members = optional_members, + .tp_methods = optional_methods, + .tp_richcompare = optional_richcompare, + .tp_repr = optional_repr, +}; + +PyObject * +Py_Optional(PyObject *args) +{ + optionalobject *alias = PyObject_GC_New(optionalobject, &Py_OptionalType); + if (alias == NULL) { + Py_DECREF(args); + return NULL; + } + + alias->args = args; + _PyObject_GC_TRACK(alias); + return (PyObject *) alias; +} diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index db26e38911bc05..1282b1e2f7bc2e 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -391,6 +391,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 1f3883768c9a25..64cf6113f273dc 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1178,10 +1178,13 @@ Objects + + Objects + Resource Files - \ No newline at end of file + diff --git a/Python/ceval.c b/Python/ceval.c index a1461c1c24da12..d81025a62a0a74 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1626,7 +1626,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag) case TARGET(UNARY_QUESTION): { PyObject *value = TOP(); - PyObject *res = value; + // TODO: Check it's a valid type. + PyObject *res = Py_Optional(value); Py_DECREF(value); SET_TOP(res); if (res == NULL) From 60249329d6cf7d3c70f1b99941a9b8389d432662 Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Tue, 11 Aug 2020 19:35:26 -0700 Subject: [PATCH 07/14] Work in progress commit. --- Objects/optionalobject.c | 16 +++++++++++++--- Parser/Python.asdl | 2 +- Python/ceval.c | 1 - Python/compile.c | 2 +- example.py | 7 +++++++ 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 example.py diff --git a/Objects/optionalobject.c b/Objects/optionalobject.c index 11fee322fb92ae..919b550aaa85a5 100644 --- a/Objects/optionalobject.c +++ b/Objects/optionalobject.c @@ -72,7 +72,18 @@ static PyMethodDef optional_methods[] = { static PyObject * optional_richcompare(PyObject *a, PyObject *b, int op) { - return a; + printf("\nB: \n"); + PyObject_Print(Py_TYPE(b), stdout, 0); + optionalobject *aa = (optionalobject *)a; + printf("\nA:"); + PyObject_Print(aa->args, stdout, 0); + if (op != Py_EQ && op != Py_NE) { + PyObject *result = Py_NotImplemented; + Py_INCREF(result); + return result; + } + + Py_RETURN_FALSE; } @@ -197,10 +208,9 @@ Py_Optional(PyObject *args) { optionalobject *alias = PyObject_GC_New(optionalobject, &Py_OptionalType); if (alias == NULL) { - Py_DECREF(args); return NULL; } - + Py_INCREF(args); alias->args = args; _PyObject_GC_TRACK(alias); return (PyObject *) alias; diff --git a/Parser/Python.asdl b/Parser/Python.asdl index 09ff95760eff0b..4ff475e344f058 100644 --- a/Parser/Python.asdl +++ b/Parser/Python.asdl @@ -54,8 +54,8 @@ module Python -- BoolOp() can use left & right? expr = BoolOp(boolop op, expr* values) | NamedExpr(expr target, expr value) - | BinOp(expr left, operator op, expr right) | UnaryOp(unaryop op, expr operand) + | BinOp(expr left, operator op, expr right) | Lambda(arguments args, expr body) | IfExp(expr test, expr body, expr orelse) | Dict(expr* keys, expr* values) diff --git a/Python/ceval.c b/Python/ceval.c index d81025a62a0a74..1ebbad4d73f010 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1626,7 +1626,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag) case TARGET(UNARY_QUESTION): { PyObject *value = TOP(); - // TODO: Check it's a valid type. PyObject *res = Py_Optional(value); Py_DECREF(value); SET_TOP(res); diff --git a/Python/compile.c b/Python/compile.c index ad3e47f6002c31..cf03f7a31831c3 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -874,11 +874,11 @@ stack_effect(int opcode, int oparg, int jump) return 2; /* Unary operators */ + case UNARY_QUESTION: case UNARY_POSITIVE: case UNARY_NEGATIVE: case UNARY_NOT: case UNARY_INVERT: - case UNARY_QUESTION: return 0; case SET_ADD: diff --git a/example.py b/example.py new file mode 100644 index 00000000000000..41c62bda2b3633 --- /dev/null +++ b/example.py @@ -0,0 +1,7 @@ +import typing + + + +assert ?int == typing.Optional[int] +print(?int == typing.Optional[int]) +print((?int) == typing.Optional[int]) From d1b3ff19aefd50a6e4d2060d783f9566420a3b8e Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Tue, 18 Aug 2020 19:19:15 -0700 Subject: [PATCH 08/14] Add test cases. --- Lib/test/test_types.py | 4 +++ Lib/test/test_typing.py | 1 + Objects/optionalobject.c | 68 +++++++++++++++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 3518ab07fa6548..bdcc10b6d61af6 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -7,6 +7,7 @@ import locale import sys import types +import typing import unittest.mock import weakref @@ -598,6 +599,9 @@ def test_method_descriptor_types(self): self.assertIsInstance(int.from_bytes, types.BuiltinMethodType) self.assertIsInstance(int.__new__, types.BuiltinMethodType) + def test_optional_operator_types(self): + self.assertEqual(?int, typing.Optional[int]) + class MappingProxyTests(unittest.TestCase): mappingproxy = types.MappingProxyType diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index c13ffa3dfd009e..ca93cf9869c1f9 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3587,6 +3587,7 @@ def foo(a: A) -> Optional[BaseException]: def test_new_optional(self): assert ?int == Optional[int] + assert ?int != int A = ?Type[BaseException] def foo(a: A) -> ?BaseException: diff --git a/Objects/optionalobject.c b/Objects/optionalobject.c index 919b550aaa85a5..d330c2df2dc5fc 100644 --- a/Objects/optionalobject.c +++ b/Objects/optionalobject.c @@ -52,14 +52,12 @@ optional_getattro(PyObject *self, PyObject *name) static PyObject * optional_instancecheck(PyObject *self, PyObject *instance) { - // TODO: MM: Implement this. return instance; } static PyObject * optional_subclasscheck(PyObject *self, PyObject *instance) { - // TODO: MM: Implement this. return instance; } @@ -68,21 +66,77 @@ static PyMethodDef optional_methods[] = { {"__subclasscheck__", optional_subclasscheck, METH_O}, {0}}; +static int +is_typing_module(PyObject *obj) { + PyObject *module = PyObject_GetAttrString(obj, "__module__"); + if (module == NULL) { + return -1; + } + int is_typing = PyUnicode_Check(module) && _PyUnicode_EqualToASCIIString(module, "typing"); + Py_DECREF(module); + return is_typing; +} + +static int +is_typing_name(PyObject *obj, char *name) +{ + PyTypeObject *type = Py_TYPE(obj); + if (strcmp(type->tp_name, name) != 0) { + return 0; + } + return is_typing_module(obj); +} static PyObject * optional_richcompare(PyObject *a, PyObject *b, int op) { - printf("\nB: \n"); - PyObject_Print(Py_TYPE(b), stdout, 0); - optionalobject *aa = (optionalobject *)a; - printf("\nA:"); - PyObject_Print(aa->args, stdout, 0); if (op != Py_EQ && op != Py_NE) { PyObject *result = Py_NotImplemented; Py_INCREF(result); return result; } + optionalobject *aa = (optionalobject *)a; + + int is_typing_union = is_typing_name(b, "_UnionGenericAlias"); + if (is_typing_union < 0) { + return NULL; + } + + PyTypeObject *type = Py_TYPE(b); + if (is_typing_union) { + // create a tuple of the optional arg + PyNone. + PyObject *a_tuple = PyTuple_Pack(2, aa->args, Py_TYPE(Py_None)); + if (a_tuple == NULL) { + return NULL; + } + PyObject *a_set = PySet_New(a_tuple); + if (a_set == NULL) { + Py_DECREF(a_set); + return NULL; + } + Py_DECREF(a_tuple); + PyObject* b_args = PyObject_GetAttrString(b, "__args__"); + if (b_args == NULL) { + Py_DECREF(a_set); + return NULL; + } + PyObject *b_set = PySet_New(b_args); + if (b_set == NULL) { + Py_DECREF(a_set); + Py_DECREF(b_args); + return NULL; + } + Py_DECREF(b_args); + PyObject *result = PyObject_RichCompare(a_set, b_set, op); + Py_DECREF(a_set); + Py_DECREF(b_set); + return result; + } else if (type == &Py_OptionalType) { + optionalobject *bb = (optionalobject *)b; + PyObject *result = PyObject_RichCompare(aa->args, bb->args, op); + return result; + } Py_RETURN_FALSE; } From b88935b4766e11723be728603de984c1dbf304da Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Tue, 18 Aug 2020 19:57:24 -0700 Subject: [PATCH 09/14] Implement issubclass and isinstance methods - Add basic test coverage. --- Lib/test/test_isinstance.py | 8 ++++++++ Lib/test/test_types.py | 2 +- Objects/optionalobject.c | 24 ++++++++++++++++++++++-- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 53639e984e48a7..ed96f91d856bdb 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -4,6 +4,7 @@ import unittest import sys +import typing @@ -271,6 +272,13 @@ def __bases__(self): self.assertEqual(True, issubclass(B(), int)) + def test_optional_isinstance(self): + self.assertTrue(issubclass(int, ?int)) + self.assertTrue(issubclass(Child, ?Super)) + self.assertTrue(isinstance(Child(), ?Super)) + self.assertTrue(isinstance(None, ?Super)) + + def blowstack(fxn, arg, compare_to): # Make sure that calling isinstance with a deeply nested tuple for its diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index bdcc10b6d61af6..78a18333435191 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -601,7 +601,7 @@ def test_method_descriptor_types(self): def test_optional_operator_types(self): self.assertEqual(?int, typing.Optional[int]) - + self.assertIsInstance(1, ?int) class MappingProxyTests(unittest.TestCase): mappingproxy = types.MappingProxyType diff --git a/Objects/optionalobject.c b/Objects/optionalobject.c index d330c2df2dc5fc..a334dd7e06938a 100644 --- a/Objects/optionalobject.c +++ b/Objects/optionalobject.c @@ -52,13 +52,33 @@ optional_getattro(PyObject *self, PyObject *name) static PyObject * optional_instancecheck(PyObject *self, PyObject *instance) { - return instance; + optionalobject *alias = (optionalobject *)self; + PyObject *arg = alias->args; + if (PyType_Check(arg) && PyObject_IsInstance(instance, arg) != 0) { + Py_RETURN_TRUE; + } + if (instance == Py_None) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; } static PyObject * optional_subclasscheck(PyObject *self, PyObject *instance) { - return instance; + if (!PyType_Check(instance)) { + PyErr_SetString(PyExc_TypeError, "issubclass() arg 1 must be a class"); + return NULL; + } + + optionalobject *alias = (optionalobject *)self; + PyObject *arg = alias->args; + + if (PyType_Check(arg) && (PyType_IsSubtype((PyTypeObject *)instance, (PyTypeObject *)arg) != 0)) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; } static PyMethodDef optional_methods[] = { From 4c33c50115f84c5c3b77333e306d404251cacbd3 Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Tue, 18 Aug 2020 20:00:32 -0700 Subject: [PATCH 10/14] Remove GC support. --- Objects/optionalobject.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/Objects/optionalobject.c b/Objects/optionalobject.c index a334dd7e06938a..0500568accc162 100644 --- a/Objects/optionalobject.c +++ b/Objects/optionalobject.c @@ -14,7 +14,6 @@ optionalobject_dealloc(PyObject *self) { optionalobject *alias = (optionalobject *)self; - _PyObject_GC_UNTRACK(self); Py_XDECREF(alias->args); self->ob_type->tp_free(self); } @@ -30,14 +29,6 @@ optional_hash(PyObject *self) return h1; } -static int -optional_traverse(PyObject *self, visitproc visit, void *arg) -{ - optionalobject *alias = (optionalobject *)self; - Py_VISIT(alias->args); - return 0; -} - static PyMemberDef optional_members[] = { {"__args__", T_OBJECT, offsetof(optionalobject, args), READONLY}, {0} @@ -266,10 +257,9 @@ PyTypeObject Py_OptionalType = { .tp_basicsize = sizeof(optionalobject), .tp_dealloc = optionalobject_dealloc, .tp_alloc = PyType_GenericAlloc, - .tp_free = PyObject_GC_Del, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + .tp_free = PyObject_Del, + .tp_flags = Py_TPFLAGS_DEFAULT, .tp_hash = optional_hash, - .tp_traverse = optional_traverse, .tp_getattro = optional_getattro, .tp_members = optional_members, .tp_methods = optional_methods, @@ -280,12 +270,11 @@ PyTypeObject Py_OptionalType = { PyObject * Py_Optional(PyObject *args) { - optionalobject *alias = PyObject_GC_New(optionalobject, &Py_OptionalType); + optionalobject *alias = PyObject_New(optionalobject, &Py_OptionalType); if (alias == NULL) { return NULL; } Py_INCREF(args); alias->args = args; - _PyObject_GC_TRACK(alias); return (PyObject *) alias; } From bb1aab957e4a0eeaa57019490adeb9b286e9d64c Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Wed, 19 Aug 2020 18:37:25 -0700 Subject: [PATCH 11/14] Make ? a postfix operator. --- Grammar/python.gram | 2 +- Include/Python-ast.h | 18 ++--- Lib/test/test_types.py | 4 +- Lib/test/test_typing.py | 8 +- Objects/optionalobject.c | 3 + Parser/parser.c | 167 ++++++++++++++++++++++++++------------ Python/Python-ast.c | 170 +++++++++++++++++++-------------------- 7 files changed, 218 insertions(+), 154 deletions(-) diff --git a/Grammar/python.gram b/Grammar/python.gram index eda120d7c57ea5..43a0b1e474b31f 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -332,7 +332,6 @@ expression[expr_ty] (memo): | a=disjunction 'if' b=disjunction 'else' c=expression { _Py_IfExp(b, a, c, EXTRA) } | disjunction | lambdef - | '?' a=expression { _Py_UnaryOp(Question, a, EXTRA) } lambdef[expr_ty]: | 'lambda' a=[lambda_params] ':' b=expression { _Py_Lambda((a) ? a : CHECK(_PyPegen_empty_arguments(p)), b, EXTRA) } @@ -454,6 +453,7 @@ factor[expr_ty] (memo): | '+' a=factor { _Py_UnaryOp(UAdd, a, EXTRA) } | '-' a=factor { _Py_UnaryOp(USub, a, EXTRA) } | '~' a=factor { _Py_UnaryOp(Invert, a, EXTRA) } + | a=factor '?' { _Py_UnaryOp(Question, a, EXTRA) } | power power[expr_ty]: | a=await_primary '**' b=factor { _Py_BinOp(a, Pow, b, EXTRA) } diff --git a/Include/Python-ast.h b/Include/Python-ast.h index 851d3c51b10b32..258f79ef166de7 100644 --- a/Include/Python-ast.h +++ b/Include/Python-ast.h @@ -224,7 +224,7 @@ struct _stmt { int end_col_offset; }; -enum _expr_kind {BoolOp_kind=1, NamedExpr_kind=2, BinOp_kind=3, UnaryOp_kind=4, +enum _expr_kind {BoolOp_kind=1, NamedExpr_kind=2, UnaryOp_kind=3, BinOp_kind=4, Lambda_kind=5, IfExp_kind=6, Dict_kind=7, Set_kind=8, ListComp_kind=9, SetComp_kind=10, DictComp_kind=11, GeneratorExp_kind=12, Await_kind=13, Yield_kind=14, @@ -245,17 +245,17 @@ struct _expr { expr_ty value; } NamedExpr; + struct { + unaryop_ty op; + expr_ty operand; + } UnaryOp; + struct { expr_ty left; operator_ty op; expr_ty right; } BinOp; - struct { - unaryop_ty op; - expr_ty operand; - } UnaryOp; - struct { arguments_ty args; expr_ty body; @@ -566,13 +566,13 @@ expr_ty _Py_BoolOp(boolop_ty op, asdl_seq * values, int lineno, int col_offset, expr_ty _Py_NamedExpr(expr_ty target, expr_ty value, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); +#define UnaryOp(a0, a1, a2, a3, a4, a5, a6) _Py_UnaryOp(a0, a1, a2, a3, a4, a5, a6) +expr_ty _Py_UnaryOp(unaryop_ty op, expr_ty operand, int lineno, int col_offset, + int end_lineno, int end_col_offset, PyArena *arena); #define BinOp(a0, a1, a2, a3, a4, a5, a6, a7) _Py_BinOp(a0, a1, a2, a3, a4, a5, a6, a7) expr_ty _Py_BinOp(expr_ty left, operator_ty op, expr_ty right, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); -#define UnaryOp(a0, a1, a2, a3, a4, a5, a6) _Py_UnaryOp(a0, a1, a2, a3, a4, a5, a6) -expr_ty _Py_UnaryOp(unaryop_ty op, expr_ty operand, int lineno, int col_offset, - int end_lineno, int end_col_offset, PyArena *arena); #define Lambda(a0, a1, a2, a3, a4, a5, a6) _Py_Lambda(a0, a1, a2, a3, a4, a5, a6) expr_ty _Py_Lambda(arguments_ty args, expr_ty body, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py index 78a18333435191..6a263574d938a0 100644 --- a/Lib/test/test_types.py +++ b/Lib/test/test_types.py @@ -600,8 +600,8 @@ def test_method_descriptor_types(self): self.assertIsInstance(int.__new__, types.BuiltinMethodType) def test_optional_operator_types(self): - self.assertEqual(?int, typing.Optional[int]) - self.assertIsInstance(1, ?int) + self.assertEqual(int?, typing.Optional[int]) + self.assertIsInstance(1, int?) class MappingProxyTests(unittest.TestCase): mappingproxy = types.MappingProxyType diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ca93cf9869c1f9..423f26e7bf5fe3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3586,11 +3586,11 @@ def foo(a: A) -> Optional[BaseException]: assert foo(None) is None def test_new_optional(self): - assert ?int == Optional[int] - assert ?int != int + assert int? == Optional[int] + assert int? != int - A = ?Type[BaseException] - def foo(a: A) -> ?BaseException: + A = Type[BaseException]? + def foo(a: A) -> BaseException?: if a is None: return None else: diff --git a/Objects/optionalobject.c b/Objects/optionalobject.c index 0500568accc162..ac62152cc884b2 100644 --- a/Objects/optionalobject.c +++ b/Objects/optionalobject.c @@ -148,6 +148,9 @@ optional_richcompare(PyObject *a, PyObject *b, int op) PyObject *result = PyObject_RichCompare(aa->args, bb->args, op); return result; } + if (op == Py_NE) { + Py_RETURN_TRUE; + } Py_RETURN_FALSE; } diff --git a/Parser/parser.c b/Parser/parser.c index 3effb41f8bec85..05ed85d8077dfa 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -166,7 +166,7 @@ static KeywordToken *reserved_keywords[] = { #define shift_expr_type 1097 // Left-recursive #define sum_type 1098 // Left-recursive #define term_type 1099 // Left-recursive -#define factor_type 1100 +#define factor_type 1100 // Left-recursive #define power_type 1101 #define await_primary_type 1102 #define primary_type 1103 // Left-recursive @@ -6861,7 +6861,11 @@ annotated_rhs_rule(Parser *p) return _res; } -// expressions: expression ((',' expression))+ ','? | expression ',' | expression +// expressions: +// | expression ((',' expression))+ ','? +// | expression ',' +// | expression '?' +// | expression static expr_ty expressions_rule(Parser *p) { @@ -6957,6 +6961,42 @@ expressions_rule(Parser *p) D(fprintf(stderr, "%*c%s expressions[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression ','")); } + { // expression '?' + if (p->error_indicator) { + D(p->level--); + return NULL; + } + D(fprintf(stderr, "%*c> expressions[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "expression '?'")); + expr_ty a; + Token * b; + if ( + (a = expression_rule(p)) // expression + && + (b = _PyPegen_expect_token(p, 54)) // token='?' + ) + { + D(fprintf(stderr, "%*c+ expressions[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression '?'")); + Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); + if (_token == NULL) { + D(p->level--); + return NULL; + } + int _end_lineno = _token->end_lineno; + UNUSED(_end_lineno); // Only used by EXTRA macro + int _end_col_offset = _token->end_col_offset; + UNUSED(_end_col_offset); // Only used by EXTRA macro + _res = _Py_UnaryOp ( Question , a , EXTRA ); + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + D(p->level--); + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s expressions[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "expression '?'")); + } { // expression if (p->error_indicator) { D(p->level--); @@ -6982,11 +7022,7 @@ expressions_rule(Parser *p) return _res; } -// expression: -// | disjunction 'if' disjunction 'else' expression -// | disjunction -// | lambdef -// | '?' expression +// expression: disjunction 'if' disjunction 'else' expression | disjunction | lambdef static expr_ty expression_rule(Parser *p) { @@ -7093,42 +7129,6 @@ expression_rule(Parser *p) D(fprintf(stderr, "%*c%s expression[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "lambdef")); } - { // '?' expression - if (p->error_indicator) { - D(p->level--); - return NULL; - } - D(fprintf(stderr, "%*c> expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'?' expression")); - Token * _literal; - expr_ty a; - if ( - (_literal = _PyPegen_expect_token(p, 54)) // token='?' - && - (a = expression_rule(p)) // expression - ) - { - D(fprintf(stderr, "%*c+ expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'?' expression")); - Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); - if (_token == NULL) { - D(p->level--); - return NULL; - } - int _end_lineno = _token->end_lineno; - UNUSED(_end_lineno); // Only used by EXTRA macro - int _end_col_offset = _token->end_col_offset; - UNUSED(_end_col_offset); // Only used by EXTRA macro - _res = _Py_UnaryOp ( Question , a , EXTRA ); - if (_res == NULL && PyErr_Occurred()) { - p->error_indicator = 1; - D(p->level--); - return NULL; - } - goto done; - } - p->mark = _mark; - D(fprintf(stderr, "%*c%s expression[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'?' expression")); - } _res = NULL; done: _PyPegen_insert_memo(p, _mark, expression_type, _res); @@ -9968,21 +9968,47 @@ term_raw(Parser *p) return _res; } -// factor: '+' factor | '-' factor | '~' factor | power +// Left-recursive +// factor: '+' factor | '-' factor | '~' factor | factor '?' | power +static expr_ty factor_raw(Parser *); static expr_ty factor_rule(Parser *p) { D(p->level++); - if (p->error_indicator) { - D(p->level--); - return NULL; - } expr_ty _res = NULL; if (_PyPegen_is_memoized(p, factor_type, &_res)) { D(p->level--); return _res; } int _mark = p->mark; + int _resmark = p->mark; + while (1) { + int tmpvar_7 = _PyPegen_update_memo(p, _mark, factor_type, _res); + if (tmpvar_7) { + D(p->level--); + return _res; + } + p->mark = _mark; + void *_raw = factor_raw(p); + if (_raw == NULL || p->mark <= _resmark) + break; + _resmark = p->mark; + _res = _raw; + } + p->mark = _resmark; + D(p->level--); + return _res; +} +static expr_ty +factor_raw(Parser *p) +{ + D(p->level++); + if (p->error_indicator) { + D(p->level--); + return NULL; + } + expr_ty _res = NULL; + int _mark = p->mark; if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) { p->error_indicator = 1; D(p->level--); @@ -10100,6 +10126,42 @@ factor_rule(Parser *p) D(fprintf(stderr, "%*c%s factor[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'~' factor")); } + { // factor '?' + if (p->error_indicator) { + D(p->level--); + return NULL; + } + D(fprintf(stderr, "%*c> factor[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "factor '?'")); + Token * _literal; + expr_ty a; + if ( + (a = factor_rule(p)) // factor + && + (_literal = _PyPegen_expect_token(p, 54)) // token='?' + ) + { + D(fprintf(stderr, "%*c+ factor[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "factor '?'")); + Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); + if (_token == NULL) { + D(p->level--); + return NULL; + } + int _end_lineno = _token->end_lineno; + UNUSED(_end_lineno); // Only used by EXTRA macro + int _end_col_offset = _token->end_col_offset; + UNUSED(_end_col_offset); // Only used by EXTRA macro + _res = _Py_UnaryOp ( Question , a , EXTRA ); + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + D(p->level--); + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s factor[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "factor '?'")); + } { // power if (p->error_indicator) { D(p->level--); @@ -10121,7 +10183,6 @@ factor_rule(Parser *p) } _res = NULL; done: - _PyPegen_insert_memo(p, _mark, factor_type, _res); D(p->level--); return _res; } @@ -10316,8 +10377,8 @@ primary_rule(Parser *p) int _mark = p->mark; int _resmark = p->mark; while (1) { - int tmpvar_7 = _PyPegen_update_memo(p, _mark, primary_type, _res); - if (tmpvar_7) { + int tmpvar_8 = _PyPegen_update_memo(p, _mark, primary_type, _res); + if (tmpvar_8) { D(p->level--); return _res; } @@ -13968,8 +14029,8 @@ t_primary_rule(Parser *p) int _mark = p->mark; int _resmark = p->mark; while (1) { - int tmpvar_8 = _PyPegen_update_memo(p, _mark, t_primary_type, _res); - if (tmpvar_8) { + int tmpvar_9 = _PyPegen_update_memo(p, _mark, t_primary_type, _res); + if (tmpvar_9) { D(p->level--); return _res; } diff --git a/Python/Python-ast.c b/Python/Python-ast.c index dee52ee7e35e60..f29c1dda36b6bb 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -915,15 +915,15 @@ static const char * const NamedExpr_fields[]={ "target", "value", }; +static const char * const UnaryOp_fields[]={ + "op", + "operand", +}; static const char * const BinOp_fields[]={ "left", "op", "right", }; -static const char * const UnaryOp_fields[]={ - "op", - "operand", -}; static const char * const Lambda_fields[]={ "args", "body", @@ -1603,8 +1603,8 @@ static int init_types(void) state->expr_type = make_type("expr", state->AST_type, NULL, 0, "expr = BoolOp(boolop op, expr* values)\n" " | NamedExpr(expr target, expr value)\n" - " | BinOp(expr left, operator op, expr right)\n" " | UnaryOp(unaryop op, expr operand)\n" + " | BinOp(expr left, operator op, expr right)\n" " | Lambda(arguments args, expr body)\n" " | IfExp(expr test, expr body, expr orelse)\n" " | Dict(expr* keys, expr* values)\n" @@ -1642,13 +1642,13 @@ static int init_types(void) NamedExpr_fields, 2, "NamedExpr(expr target, expr value)"); if (!state->NamedExpr_type) return 0; - state->BinOp_type = make_type("BinOp", state->expr_type, BinOp_fields, 3, - "BinOp(expr left, operator op, expr right)"); - if (!state->BinOp_type) return 0; state->UnaryOp_type = make_type("UnaryOp", state->expr_type, UnaryOp_fields, 2, "UnaryOp(unaryop op, expr operand)"); if (!state->UnaryOp_type) return 0; + state->BinOp_type = make_type("BinOp", state->expr_type, BinOp_fields, 3, + "BinOp(expr left, operator op, expr right)"); + if (!state->BinOp_type) return 0; state->Lambda_type = make_type("Lambda", state->expr_type, Lambda_fields, 2, "Lambda(arguments args, expr body)"); if (!state->Lambda_type) return 0; @@ -2760,32 +2760,26 @@ NamedExpr(expr_ty target, expr_ty value, int lineno, int col_offset, int } expr_ty -BinOp(expr_ty left, operator_ty op, expr_ty right, int lineno, int col_offset, - int end_lineno, int end_col_offset, PyArena *arena) +UnaryOp(unaryop_ty op, expr_ty operand, int lineno, int col_offset, int + end_lineno, int end_col_offset, PyArena *arena) { expr_ty p; - if (!left) { - PyErr_SetString(PyExc_ValueError, - "field 'left' is required for BinOp"); - return NULL; - } if (!op) { PyErr_SetString(PyExc_ValueError, - "field 'op' is required for BinOp"); + "field 'op' is required for UnaryOp"); return NULL; } - if (!right) { + if (!operand) { PyErr_SetString(PyExc_ValueError, - "field 'right' is required for BinOp"); + "field 'operand' is required for UnaryOp"); return NULL; } p = (expr_ty)PyArena_Malloc(arena, sizeof(*p)); if (!p) return NULL; - p->kind = BinOp_kind; - p->v.BinOp.left = left; - p->v.BinOp.op = op; - p->v.BinOp.right = right; + p->kind = UnaryOp_kind; + p->v.UnaryOp.op = op; + p->v.UnaryOp.operand = operand; p->lineno = lineno; p->col_offset = col_offset; p->end_lineno = end_lineno; @@ -2794,26 +2788,32 @@ BinOp(expr_ty left, operator_ty op, expr_ty right, int lineno, int col_offset, } expr_ty -UnaryOp(unaryop_ty op, expr_ty operand, int lineno, int col_offset, int - end_lineno, int end_col_offset, PyArena *arena) +BinOp(expr_ty left, operator_ty op, expr_ty right, int lineno, int col_offset, + int end_lineno, int end_col_offset, PyArena *arena) { expr_ty p; + if (!left) { + PyErr_SetString(PyExc_ValueError, + "field 'left' is required for BinOp"); + return NULL; + } if (!op) { PyErr_SetString(PyExc_ValueError, - "field 'op' is required for UnaryOp"); + "field 'op' is required for BinOp"); return NULL; } - if (!operand) { + if (!right) { PyErr_SetString(PyExc_ValueError, - "field 'operand' is required for UnaryOp"); + "field 'right' is required for BinOp"); return NULL; } p = (expr_ty)PyArena_Malloc(arena, sizeof(*p)); if (!p) return NULL; - p->kind = UnaryOp_kind; - p->v.UnaryOp.op = op; - p->v.UnaryOp.operand = operand; + p->kind = BinOp_kind; + p->v.BinOp.left = left; + p->v.BinOp.op = op; + p->v.BinOp.right = right; p->lineno = lineno; p->col_offset = col_offset; p->end_lineno = end_lineno; @@ -4180,39 +4180,39 @@ ast2obj_expr(void* _o) goto failed; Py_DECREF(value); break; - case BinOp_kind: - tp = (PyTypeObject *)astmodulestate_global->BinOp_type; + case UnaryOp_kind: + tp = (PyTypeObject *)astmodulestate_global->UnaryOp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_expr(o->v.BinOp.left); - if (!value) goto failed; - if (PyObject_SetAttr(result, astmodulestate_global->left, value) == -1) - goto failed; - Py_DECREF(value); - value = ast2obj_operator(o->v.BinOp.op); + value = ast2obj_unaryop(o->v.UnaryOp.op); if (!value) goto failed; if (PyObject_SetAttr(result, astmodulestate_global->op, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(o->v.BinOp.right); + value = ast2obj_expr(o->v.UnaryOp.operand); if (!value) goto failed; - if (PyObject_SetAttr(result, astmodulestate_global->right, value) == -1) + if (PyObject_SetAttr(result, astmodulestate_global->operand, value) == + -1) goto failed; Py_DECREF(value); break; - case UnaryOp_kind: - tp = (PyTypeObject *)astmodulestate_global->UnaryOp_type; + case BinOp_kind: + tp = (PyTypeObject *)astmodulestate_global->BinOp_type; result = PyType_GenericNew(tp, NULL, NULL); if (!result) goto failed; - value = ast2obj_unaryop(o->v.UnaryOp.op); + value = ast2obj_expr(o->v.BinOp.left); + if (!value) goto failed; + if (PyObject_SetAttr(result, astmodulestate_global->left, value) == -1) + goto failed; + Py_DECREF(value); + value = ast2obj_operator(o->v.BinOp.op); if (!value) goto failed; if (PyObject_SetAttr(result, astmodulestate_global->op, value) == -1) goto failed; Py_DECREF(value); - value = ast2obj_expr(o->v.UnaryOp.operand); + value = ast2obj_expr(o->v.BinOp.right); if (!value) goto failed; - if (PyObject_SetAttr(result, astmodulestate_global->operand, value) == - -1) + if (PyObject_SetAttr(result, astmodulestate_global->right, value) == -1) goto failed; Py_DECREF(value); break; @@ -7450,98 +7450,98 @@ obj2ast_expr(PyObject* obj, expr_ty* out, PyArena* arena) if (*out == NULL) goto failed; return 0; } - tp = astmodulestate_global->BinOp_type; + tp = astmodulestate_global->UnaryOp_type; isinstance = PyObject_IsInstance(obj, tp); if (isinstance == -1) { return 1; } if (isinstance) { - expr_ty left; - operator_ty op; - expr_ty right; + unaryop_ty op; + expr_ty operand; - if (_PyObject_LookupAttr(obj, astmodulestate_global->left, &tmp) < 0) { - return 1; - } - if (tmp == NULL) { - PyErr_SetString(PyExc_TypeError, "required field \"left\" missing from BinOp"); - return 1; - } - else { - int res; - res = obj2ast_expr(tmp, &left, arena); - if (res != 0) goto failed; - Py_CLEAR(tmp); - } if (_PyObject_LookupAttr(obj, astmodulestate_global->op, &tmp) < 0) { return 1; } if (tmp == NULL) { - PyErr_SetString(PyExc_TypeError, "required field \"op\" missing from BinOp"); + PyErr_SetString(PyExc_TypeError, "required field \"op\" missing from UnaryOp"); return 1; } else { int res; - res = obj2ast_operator(tmp, &op, arena); + res = obj2ast_unaryop(tmp, &op, arena); if (res != 0) goto failed; Py_CLEAR(tmp); } - if (_PyObject_LookupAttr(obj, astmodulestate_global->right, &tmp) < 0) { + if (_PyObject_LookupAttr(obj, astmodulestate_global->operand, &tmp) < + 0) { return 1; } if (tmp == NULL) { - PyErr_SetString(PyExc_TypeError, "required field \"right\" missing from BinOp"); + PyErr_SetString(PyExc_TypeError, "required field \"operand\" missing from UnaryOp"); return 1; } else { int res; - res = obj2ast_expr(tmp, &right, arena); + res = obj2ast_expr(tmp, &operand, arena); if (res != 0) goto failed; Py_CLEAR(tmp); } - *out = BinOp(left, op, right, lineno, col_offset, end_lineno, - end_col_offset, arena); + *out = UnaryOp(op, operand, lineno, col_offset, end_lineno, + end_col_offset, arena); if (*out == NULL) goto failed; return 0; } - tp = astmodulestate_global->UnaryOp_type; + tp = astmodulestate_global->BinOp_type; isinstance = PyObject_IsInstance(obj, tp); if (isinstance == -1) { return 1; } if (isinstance) { - unaryop_ty op; - expr_ty operand; + expr_ty left; + operator_ty op; + expr_ty right; + if (_PyObject_LookupAttr(obj, astmodulestate_global->left, &tmp) < 0) { + return 1; + } + if (tmp == NULL) { + PyErr_SetString(PyExc_TypeError, "required field \"left\" missing from BinOp"); + return 1; + } + else { + int res; + res = obj2ast_expr(tmp, &left, arena); + if (res != 0) goto failed; + Py_CLEAR(tmp); + } if (_PyObject_LookupAttr(obj, astmodulestate_global->op, &tmp) < 0) { return 1; } if (tmp == NULL) { - PyErr_SetString(PyExc_TypeError, "required field \"op\" missing from UnaryOp"); + PyErr_SetString(PyExc_TypeError, "required field \"op\" missing from BinOp"); return 1; } else { int res; - res = obj2ast_unaryop(tmp, &op, arena); + res = obj2ast_operator(tmp, &op, arena); if (res != 0) goto failed; Py_CLEAR(tmp); } - if (_PyObject_LookupAttr(obj, astmodulestate_global->operand, &tmp) < - 0) { + if (_PyObject_LookupAttr(obj, astmodulestate_global->right, &tmp) < 0) { return 1; } if (tmp == NULL) { - PyErr_SetString(PyExc_TypeError, "required field \"operand\" missing from UnaryOp"); + PyErr_SetString(PyExc_TypeError, "required field \"right\" missing from BinOp"); return 1; } else { int res; - res = obj2ast_expr(tmp, &operand, arena); + res = obj2ast_expr(tmp, &right, arena); if (res != 0) goto failed; Py_CLEAR(tmp); } - *out = UnaryOp(op, operand, lineno, col_offset, end_lineno, - end_col_offset, arena); + *out = BinOp(left, op, right, lineno, col_offset, end_lineno, + end_col_offset, arena); if (*out == NULL) goto failed; return 0; } @@ -10108,15 +10108,15 @@ PyInit__ast(void) goto error; } Py_INCREF(astmodulestate(m)->NamedExpr_type); - if (PyModule_AddObject(m, "BinOp", astmodulestate_global->BinOp_type) < 0) { - goto error; - } - Py_INCREF(astmodulestate(m)->BinOp_type); if (PyModule_AddObject(m, "UnaryOp", astmodulestate_global->UnaryOp_type) < 0) { goto error; } Py_INCREF(astmodulestate(m)->UnaryOp_type); + if (PyModule_AddObject(m, "BinOp", astmodulestate_global->BinOp_type) < 0) { + goto error; + } + Py_INCREF(astmodulestate(m)->BinOp_type); if (PyModule_AddObject(m, "Lambda", astmodulestate_global->Lambda_type) < 0) { goto error; From 4f36f346976f7ea965ccdb088e818f120da7b856 Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Wed, 19 Aug 2020 18:38:36 -0700 Subject: [PATCH 12/14] Fix tests. --- Lib/test/test_isinstance.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index ed96f91d856bdb..af5043c32faced 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -273,10 +273,10 @@ def __bases__(self): self.assertEqual(True, issubclass(B(), int)) def test_optional_isinstance(self): - self.assertTrue(issubclass(int, ?int)) - self.assertTrue(issubclass(Child, ?Super)) - self.assertTrue(isinstance(Child(), ?Super)) - self.assertTrue(isinstance(None, ?Super)) + self.assertTrue(issubclass(int, int?)) + self.assertTrue(issubclass(Child, Super?)) + self.assertTrue(isinstance(Child(), Super?)) + self.assertTrue(isinstance(None, Super?)) From 36657c935c0674ae4217336b4cf746f9a8a00439 Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Wed, 19 Aug 2020 18:41:06 -0700 Subject: [PATCH 13/14] Delete development test files. --- example.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 example.py diff --git a/example.py b/example.py deleted file mode 100644 index 41c62bda2b3633..00000000000000 --- a/example.py +++ /dev/null @@ -1,7 +0,0 @@ -import typing - - - -assert ?int == typing.Optional[int] -print(?int == typing.Optional[int]) -print((?int) == typing.Optional[int]) From b58acfd23c2e8d828f647fac425f90c21b1b76c0 Mon Sep 17 00:00:00 2001 From: Maggie Moss Date: Wed, 19 Aug 2020 18:42:27 -0700 Subject: [PATCH 14/14] Fix grammar test. --- Lib/test/test_grammar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index b42f4764fc612e..99a0ab590ca794 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -408,7 +408,7 @@ def test_var_annot_simple_exec(self): gns['__annotations__'] def test_annot_optional(self): - x: ?int = None + x: int? = None self.assertRaises(SyntaxError, eval, "?bar = 1") def test_var_annot_custom_maps(self):