## Change Log
+### 18.4a3
+
+* generalized star expression handling, including double stars; this
+ fixes multiplication making expressions "unsafe" for trailing commas (#132)
+
### 18.4a2
* fixed parsing of unaligned standalone comments (#99, #112)
token.DOUBLESTAR,
token.DOUBLESLASH,
}
-VARARGS = {token.STAR, token.DOUBLESTAR}
+STARS = {token.STAR, token.DOUBLESTAR}
+VARARGS_PARENTS = {
+ syms.arglist,
+ syms.argument, # double star in arglist
+ syms.trailer, # single argument to call
+ syms.typedargslist,
+ syms.varargslist, # lambdas
+}
+UNPACKING_PARENTS = {
+ syms.atom, # single element of a list or set literal
+ syms.dictsetmaker,
+ syms.listmaker,
+ syms.testlist_gexp,
+}
COMPREHENSION_PRIORITY = 20
COMMA_PRIORITY = 10
LOGIC_PRIORITY = 5
# that, too.
return prevp.prefix
- elif prevp.type == token.DOUBLESTAR:
- if (
- prevp.parent
- and prevp.parent.type in {
- syms.arglist,
- syms.argument,
- syms.dictsetmaker,
- syms.parameters,
- syms.typedargslist,
- syms.varargslist,
- }
- ):
+ elif prevp.type in STARS:
+ if is_vararg(prevp, within=VARARGS_PARENTS | UNPACKING_PARENTS):
return NO
elif prevp.type == token.COLON:
elif (
prevp.parent
- and prevp.parent.type in {syms.factor, syms.star_expr}
+ and prevp.parent.type == syms.factor
and prevp.type in MATH_OPERATORS
):
return NO
Higher numbers are higher priority.
"""
- if (
- leaf.type in VARARGS
- and leaf.parent
- and leaf.parent.type in {syms.argument, syms.typedargslist, syms.dictsetmaker}
- ):
+ if is_vararg(leaf, within=VARARGS_PARENTS | UNPACKING_PARENTS):
# * and ** might also be MATH_OPERATORS but in this case they are not.
# Don't treat them as a delimiter.
return 0
lowest_depth = min(lowest_depth, leaf.bracket_depth)
if (
leaf.bracket_depth == lowest_depth
- and leaf.type == token.STAR
- or leaf.type == token.DOUBLESTAR
+ and is_vararg(leaf, within=VARARGS_PARENTS)
):
trailing_comma_safe = trailing_comma_safe and py36
leaf_priority = delimiters.get(id(leaf))
)
+def is_vararg(leaf: Leaf, within: Set[NodeType]) -> bool:
+ """Return True if `leaf` is a star or double star in a vararg or kwarg.
+
+ If `within` includes VARARGS_PARENTS, this applies to function signatures.
+ If `within` includes COLLECTION_LIBERALS_PARENTS, it applies to right
+ hand-side extended iterable unpacking (PEP 3132) and additional unpacking
+ generalizations (PEP 448).
+ """
+ if leaf.type not in STARS or not leaf.parent:
+ return False
+
+ p = leaf.parent
+ if p.type == syms.star_expr:
+ # Star expressions are also used as assignment targets in extended
+ # iterable unpacking (PEP 3132). See what its parent is instead.
+ if not p.parent:
+ return False
+
+ p = p.parent
+
+ return p.type in within
+
+
def max_delimiter_priority_in_atom(node: LN) -> int:
if node.type != syms.atom:
return 0
.. autofunction:: black.is_python36
+.. autofunction:: black.is_vararg
+
Formatting
----------
True
False
1
-@@ -29,65 +29,74 @@
+@@ -29,59 +29,73 @@
~great
+value
-1
[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]
-[1, 2, 3,]
+[1, 2, 3]
+ [*a]
+ [*range(10)]
+-[*a, 4, 5,]
+-[4, *a, 5,]
+-[this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, *more]
++[*a, 4, 5]
++[4, *a, 5]
++[
++ this_is_a_very_long_variable_which_will_force_a_delimiter_split,
++ element,
++ another,
++ *more,
++]
{i for i in (1, 2, 3)}
{(i ** 2) for i in (1, 2, 3)}
-{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}
+ **kwargs
+) # note: no trailing comma pre-3.6
call(*gidgets[:2])
+ call(a, *gidgets[:2])
call(**self.screen_kwargs)
+ call(b, **self.screen_kwargs)
lukasz.langa.pl
- call.me(maybe)
- 1 .real
+@@ -90,11 +104,11 @@
1.0 .real
....__class__
list[str]
]
slice[0]
slice[0:1]
-@@ -114,79 +123,113 @@
+@@ -121,85 +135,119 @@
numpy[-(c + 1):, d]
numpy[:, l[-2]]
numpy[:, ::-1]
+((i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
(*starred)
--{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs} # no trailing comma, this file is not 3.6+
+-{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs}
+{
+ "id": "1",
+ "type": "type",
+ "ended_at": now() + timedelta(days=10),
+ "priority": 1,
+ "import_session_id": 1,
-+ **kwargs
-+} # no trailing comma, this file is not 3.6+
++ **kwargs,
++}
a = (1,)
b = 1,
c = 1
+).all()
Ø = set()
authors.łukasz.say_thanks()
+ mapping = {
+ A: 0.25 * (10.0 / 12),
+ B: 0.1 * (10.0 / 12),
+ C: 0.1 * (10.0 / 12),
+ D: 0.1 * (10.0 / 12),
+ }
+
def gen():
[]
[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]
[1, 2, 3,]
+[*a]
+[*range(10)]
+[*a, 4, 5,]
+[4, *a, 5,]
+[this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, *more]
{i for i in (1, 2, 3)}
{(i ** 2) for i in (1, 2, 3)}
{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}
call(arg, another, kwarg='hey', **kwargs)
call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6
call(*gidgets[:2])
+call(a, *gidgets[:2])
call(**self.screen_kwargs)
+call(b, **self.screen_kwargs)
lukasz.langa.pl
call.me(maybe)
1 .real
((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))
(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
(*starred)
-{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs} # no trailing comma, this file is not 3.6+
+{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs}
a = (1,)
b = 1,
c = 1
result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc(),).all()
Ø = set()
authors.łukasz.say_thanks()
+mapping = {
+ A: 0.25 * (10.0 / 12),
+ B: 0.1 * (10.0 / 12),
+ C: 0.1 * (10.0 / 12),
+ D: 0.1 * (10.0 / 12),
+}
def gen():
yield from outside_of_generator
[]
[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]
[1, 2, 3]
+[*a]
+[*range(10)]
+[*a, 4, 5]
+[4, *a, 5]
+[
+ this_is_a_very_long_variable_which_will_force_a_delimiter_split,
+ element,
+ another,
+ *more,
+]
{i for i in (1, 2, 3)}
{(i ** 2) for i in (1, 2, 3)}
{(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
**kwargs
) # note: no trailing comma pre-3.6
call(*gidgets[:2])
+call(a, *gidgets[:2])
call(**self.screen_kwargs)
+call(b, **self.screen_kwargs)
lukasz.langa.pl
call.me(maybe)
1 .real
"ended_at": now() + timedelta(days=10),
"priority": 1,
"import_session_id": 1,
- **kwargs
-} # no trailing comma, this file is not 3.6+
+ **kwargs,
+}
a = (1,)
b = 1,
c = 1
).all()
Ø = set()
authors.łukasz.say_thanks()
+mapping = {
+ A: 0.25 * (10.0 / 12),
+ B: 0.1 * (10.0 / 12),
+ C: 0.1 * (10.0 / 12),
+ D: 0.1 * (10.0 / 12),
+}
def gen():
$
""", re.MULTILINE | re.VERBOSE
)
+def trailing_comma():
+ mapping = {
+ A: 0.25 * (10.0 / 12),
+ B: 0.1 * (10.0 / 12),
+ C: 0.1 * (10.0 / 12),
+ D: 0.1 * (10.0 / 12),
+}
# output
""",
re.MULTILINE | re.VERBOSE,
)
+
+
+def trailing_comma():
+ mapping = {
+ A: 0.25 * (10.0 / 12),
+ B: 0.1 * (10.0 / 12),
+ C: 0.1 * (10.0 / 12),
+ D: 0.1 * (10.0 / 12),
+ }
msg = (
f"Expected diff isn't equal to the actual. If you made changes "
f"to expression.py and this is an anticipated difference, "
- f"overwrite tests/expression.diff with {dump}."
+ f"overwrite tests/expression.diff with {dump}"
)
self.assertEqual(expected, actual, msg)