diff --git a/py/compile.c b/py/compile.c index 7a359e662e..83a6681181 100644 --- a/py/compile.c +++ b/py/compile.c @@ -2513,6 +2513,9 @@ static void compile_atom_paren(compiler_t *comp, mp_parse_node_struct_t *pns) { if (MP_PARSE_NODE_IS_NULL(pns->nodes[0])) { // an empty tuple EMIT_ARG(build, 0, MP_EMIT_BUILD_TUPLE); + } else if (MP_PARSE_NODE_IS_ID(pns->nodes[0])) { + // a lone ID + compile_node(comp, pns->nodes[0]); } else { assert(MP_PARSE_NODE_IS_STRUCT_KIND(pns->nodes[0], PN_testlist_comp)); pns = (mp_parse_node_struct_t *)pns->nodes[0]; diff --git a/py/parse.c b/py/parse.c index 54be8b97d0..a4d74637d6 100644 --- a/py/parse.c +++ b/py/parse.c @@ -237,6 +237,8 @@ typedef struct _parser_t { mp_parse_tree_t tree; mp_parse_chunk_t *cur_chunk; + uint32_t cur_view; + #if MICROPY_COMP_CONST mp_map_t consts; #endif @@ -957,6 +959,9 @@ static void push_result_rule(parser_t *parser, size_t src_line, uint8_t rule_id, // need to keep parenthesis for () } else if (MP_PARSE_NODE_IS_STRUCT_KIND(pn, RULE_testlist_comp)) { // need to keep parenthesis for (a, b, ...) + } else if (MP_PARSE_NODE_IS_ID(pn) && MP_PARSE_IS_PARSING_ARGLIST(parser)) { + // Keep parentheses around single IDs that are function arguments + // since they may be keyword arguments. } else { // parenthesis around a single expression, so it's just the expression return; @@ -1044,6 +1049,8 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { parser.tree.chunk = NULL; parser.cur_chunk = NULL; + parser.cur_view = 0x0; + #if MICROPY_COMP_CONST mp_map_init(&parser.consts, 0); #endif @@ -1234,12 +1241,21 @@ mp_parse_tree_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind) { default: { assert((rule_act & RULE_ACT_KIND_MASK) == RULE_ACT_LIST); + if (rule_id == RULE_arglist && !MP_PARSE_IS_PARSING_ARGLIST(&parser)) { + parser.cur_view |= MP_PARSE_PARSING_ARGLIST; + } + // n=2 is: item item* // n=1 is: item (sep item)* // n=3 is: item (sep item)* [sep] bool had_trailing_sep; if (backtrack) { list_backtrack: + + if (rule_id == RULE_arglist) { + parser.cur_view &= ~MP_PARSE_PARSING_ARGLIST; + } + had_trailing_sep = false; if (n == 2) { if (i == 1) { diff --git a/py/parse.h b/py/parse.h index 5531e35cbb..ddb48b2466 100644 --- a/py/parse.h +++ b/py/parse.h @@ -31,6 +31,9 @@ #include "py/obj.h" +#define MP_PARSE_PARSING_ARGLIST (0x1) +#define MP_PARSE_IS_PARSING_ARGLIST(parser) (((parser)->cur_view) & MP_PARSE_PARSING_ARGLIST) + struct _mp_lexer_t; // a mp_parse_node_t is: diff --git a/tests/basics/fun2.py b/tests/basics/fun2.py index a3c3e7babf..f7560e71c0 100644 --- a/tests/basics/fun2.py +++ b/tests/basics/fun2.py @@ -8,3 +8,5 @@ def g(x): f(4 * x) g(3) +a = 3 +g((a)) # Making sure the extra parentheses cause no issues diff --git a/tests/basics/fun_kwargs_syntax.py b/tests/basics/fun_kwargs_syntax.py new file mode 100644 index 0000000000..4550ec556a --- /dev/null +++ b/tests/basics/fun_kwargs_syntax.py @@ -0,0 +1,19 @@ +# Test function call keyword argument syntax + +def test_syntax(code): + try: + eval(code) + except SyntaxError: + print("SyntaxError in '{}'".format(code)) + +def f(a): + return a + +# Should throw syntax errors. +test_syntax("f((a)=2)") +test_syntax("f(a()=2)") +test_syntax("f(a or b=2)") +test_syntax("f(2, (a)=3)") +test_syntax("f((a) = 1, (b) = 5)") +test_syntax("f((a, b) = 1)") +test_syntax("f(f(), (a)=2)")