Skip to content
Merged
Prev Previous commit
Next Next commit
Disallow other improper syntax with generator expressions.
  • Loading branch information
serhiy-storchaka committed Nov 13, 2017
commit 06b0abd2a99d349e131b86afa595f5b8b5b59bcd
12 changes: 10 additions & 2 deletions Doc/whatsnew/3.7.rst
Original file line number Diff line number Diff line change
Expand Up @@ -638,10 +638,18 @@ Changes in Python behavior

f(1 for x in [1],)

@deco(1 for x in [1])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with this one? While we've restricted the form that decorator expressions take to being a name (dotted or otherwise) plus an optional function call, we've never restricted the arguments passed to that function call.

At the generator expression level, it also still meets the "must be surrounded by parentheses rule".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same as with

@deco[0](1)

or

@deco(0)(1)

or

@(a+b*c)

This looks like valid Python expression syntax, but the syntax of decorator expression is intentionally more limited.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This aspect of the change is more akin to prohibiting @deco(a+b*c) - once the top level expression is in the form of a function call, the fact that it's part of a decorator expression shouldn't be altering what's permitted in the argument list.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case these syntax changes should be discussed and officially approved. Could you please open a topic on Python-Dev about omitting parenthesis in a generator expression in these two cases? I have arguments for these changes and against them, but I think you will formulate them better. I'll add my comments.

Despite the fact that currently the CPython parser allows such syntax, it is illegal, and allowing it officially is a changing of Python language specification.

def f():
pass

class C(1 for x in [1]):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one seems a bit odd to me as well, as we don't disallow other expressions in base class lists at compile time just because we know they can't produce a valid base class:

>>> class C([]): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list() takes at most 1 argument (3 given)
>>> class C({}): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: dict expected at most 1 arguments, got 3

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still can use

>>> class C((1 for x in [1])): pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: cannot create 'generator' instances

Omitting parenthesis in a generator expression is a special case for improving readability when pass a sole generator expression argument. The syntax of class definition doesn't have such special case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I think there's merit to that argument, I don't think it belongs in the same PR (or issue) as the one that addresses the problem with mishandling trailing commas in function calls. Unlike the trailing comma case, there's no syntactic ambiguity being addressed.

pass

Python 3.7 now correctly raises a :exc:`SyntaxError`, as a generator
expression always needs to be directly inside a set of parentheses
and cannot have a comma on either side.
(Contributed by Serhiy Storchaka in :issue:`XXXXX`.)
and cannot have a comma on either side, and the duplication of the
parentheses can be omitted only on calls.
(Contributed by Serhiy Storchaka in :issue:`32012`.)


Changes in the Python API
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,17 @@
SyntaxError: Generator expression must be parenthesized
>>> f((x for x in L), 1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> def deco(*args):
... return lambda f: f
>>> @deco(x for x in L)
... def g(*args, **kwargs):
... pass
Traceback (most recent call last):
SyntaxError: invalid syntax
>>> class C(x for x in L):
... pass
Traceback (most recent call last):
SyntaxError: invalid syntax

>>> def g(*args, **kwargs):
... print(args, sorted(kwargs.items()))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
SyntaxError is now raised when a generator expression without parenthesis is
passed as an argument but followed by a trailing comma. A generator expression
SyntaxError is now correctly raised when a generator expression without
parenthesis is passed as an argument, but followed by a trailing comma, or
it is used instead of an argument list in a decorator expression, or
instead of an inheritance list in a class definition. A generator expression
always needs to be directly inside a set of parentheses and cannot have a
comma on either side.
comma on either side. The duplication of the parentheses can be omitted
only on calls.
14 changes: 9 additions & 5 deletions Python/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ static stmt_ty ast_for_with_stmt(struct compiling *, const node *, int);
static stmt_ty ast_for_for_stmt(struct compiling *, const node *, int);

/* Note different signature for ast_for_call */
static expr_ty ast_for_call(struct compiling *, const node *, expr_ty);
static expr_ty ast_for_call(struct compiling *, const node *, expr_ty, int);

static PyObject *parsenumber(struct compiling *, const char *);
static expr_ty parsestrplus(struct compiling *, const node *n);
Expand Down Expand Up @@ -1545,7 +1545,7 @@ ast_for_decorator(struct compiling *c, const node *n)
name_expr = NULL;
}
else {
d = ast_for_call(c, CHILD(n, 3), name_expr);
d = ast_for_call(c, CHILD(n, 3), name_expr, 0);
if (!d)
return NULL;
name_expr = NULL;
Expand Down Expand Up @@ -2368,7 +2368,7 @@ ast_for_trailer(struct compiling *c, const node *n, expr_ty left_expr)
return Call(left_expr, NULL, NULL, LINENO(n),
n->n_col_offset, c->c_arena);
else
return ast_for_call(c, CHILD(n, 1), left_expr);
return ast_for_call(c, CHILD(n, 1), left_expr, 1);
}
else if (TYPE(CHILD(n, 0)) == DOT) {
PyObject *attr_id = NEW_IDENTIFIER(CHILD(n, 1));
Expand Down Expand Up @@ -2705,7 +2705,7 @@ ast_for_expr(struct compiling *c, const node *n)
}

static expr_ty
ast_for_call(struct compiling *c, const node *n, expr_ty func)
ast_for_call(struct compiling *c, const node *n, expr_ty func, int allowgen)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allowgen should be bool parameter and the callers should pass true or false.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python/ast.c:614:72: error: unknown type name ‘bool’; did you mean ‘_Bool’?
Python/ast.c:1548:53: error: ‘false’ undeclared (first use in this function); did you mean ‘fabsl’?
Python/ast.c:2371:60: error: ‘true’ undeclared (first use in this function); did you mean ‘time’?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to #include <stdbool.h>

{
/*
arglist: argument (',' argument)* [',']
Expand All @@ -2728,6 +2728,10 @@ ast_for_call(struct compiling *c, const node *n, expr_ty func)
nargs++;
else if (TYPE(CHILD(ch, 1)) == comp_for) {
nargs++;
if (!allowgen) {
ast_error(c, ch, "invalid syntax");
return NULL;
}
if (NCH(n) > 1) {
ast_error(c, ch, "Generator expression must be parenthesized");
return NULL;
Expand Down Expand Up @@ -3973,7 +3977,7 @@ ast_for_classdef(struct compiling *c, const node *n, asdl_seq *decorator_seq)
if (!dummy_name)
return NULL;
dummy = Name(dummy_name, Load, LINENO(n), n->n_col_offset, c->c_arena);
call = ast_for_call(c, CHILD(n, 3), dummy);
call = ast_for_call(c, CHILD(n, 3), dummy, 0);
if (!call)
return NULL;
}
Expand Down