-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hug power operators if its operands are "simple" #2726
Changes from 11 commits
80ba597
cd84e5c
5e6e8c2
45b62ee
83e9416
b42a01b
a4fdf42
56cb900
bd2df7f
4fa8be8
93ddad3
b6e8758
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,12 @@ | |
from black.strings import get_string_prefix, fix_docstring | ||
from black.strings import normalize_string_prefix, normalize_string_quotes | ||
from black.trans import Transformer, CannotTransform, StringMerger | ||
from black.trans import StringSplitter, StringParenWrapper, StringParenStripper | ||
from black.trans import ( | ||
StringSplitter, | ||
StringParenWrapper, | ||
StringParenStripper, | ||
hug_power_op, | ||
) | ||
ichard26 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
from black.mode import Mode, Feature, Preview | ||
|
||
from blib2to3.pytree import Node, Leaf | ||
|
@@ -338,9 +343,9 @@ def transform_line( | |
): | ||
# Only apply basic string preprocessing, since lines shouldn't be split here. | ||
if Preview.string_processing in mode: | ||
transformers = [string_merge, string_paren_strip] | ||
transformers = [string_merge, string_paren_strip, hug_power_op] | ||
else: | ||
transformers = [] | ||
transformers = [hug_power_op] | ||
elif line.is_def: | ||
transformers = [left_hand_split] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should hug_power_op be in here? Maybe it's easier to just unconditionally add it at the end. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yeah I forgot that power ops can exist as parameter default expressions, gah. Good catch! |
||
else: | ||
|
@@ -390,6 +395,7 @@ def _rhs( | |
standalone_comment_split, | ||
string_paren_wrap, | ||
rhs, | ||
hug_power_op, | ||
] | ||
else: | ||
transformers = [ | ||
|
@@ -398,12 +404,18 @@ def _rhs( | |
string_split, | ||
string_paren_wrap, | ||
rhs, | ||
hug_power_op, | ||
] | ||
else: | ||
if line.inside_brackets: | ||
transformers = [delimiter_split, standalone_comment_split, rhs] | ||
transformers = [ | ||
delimiter_split, | ||
standalone_comment_split, | ||
rhs, | ||
hug_power_op, | ||
] | ||
else: | ||
transformers = [rhs] | ||
transformers = [rhs, hug_power_op] | ||
|
||
for transform in transformers: | ||
# We are accumulating lines in `result` because we might want to abort | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -24,9 +24,9 @@ | |||||
import sys | ||||||
|
||||||
if sys.version_info < (3, 8): | ||||||
from typing_extensions import Final | ||||||
from typing_extensions import Literal, Final | ||||||
else: | ||||||
from typing import Final | ||||||
from typing import Literal, Final | ||||||
|
||||||
from mypy_extensions import trait | ||||||
|
||||||
|
@@ -71,6 +71,88 @@ def TErr(err_msg: str) -> Err[CannotTransform]: | |||||
return Err(cant_transform) | ||||||
|
||||||
|
||||||
def hug_power_op(line: Line, features: Collection[Feature]) -> Iterator[Line]: | ||||||
"""A transformer which normalizes spacing around power operators.""" | ||||||
|
||||||
# Performance optimization to avoid unnecessary Leaf clones and other ops. | ||||||
for leaf in line.leaves: | ||||||
if leaf.type == token.DOUBLESTAR: | ||||||
break | ||||||
else: | ||||||
raise CannotTransform("No doublestar token was found in the line.") | ||||||
|
||||||
def is_simple_lookup(index: int, step: Literal[1, -1]) -> bool: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be better to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So for some reason I'm starting to really like nested functions as part of larger functions. The nested functions aren't intended for generic reuse (for now) but are logically a bit more separate if that makes sense. I'm happy to change this bit as I understand this may be not a style most would like. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, my intuition would be to make private functions in that case. But style-wise I think it's a matter of taste, I was just more concerned about the potential performance hit with math-heavy code. Haven't tested it at all though 😄 |
||||||
# Brackets and parenthesises indicate calls, subscripts, etc. ... | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
# basically stuff that doesn't count as "simple". Only a NAME lookup | ||||||
# or dotted lookup (eg. NAME.NAME) is OK. | ||||||
if step == -1: | ||||||
disallowed = {token.RPAR, token.RSQB} | ||||||
else: | ||||||
disallowed = {token.LPAR, token.LSQB} | ||||||
|
||||||
while 0 <= index < len(line.leaves): | ||||||
current = line.leaves[index] | ||||||
if current.type in disallowed: | ||||||
return False | ||||||
if current.type not in {token.NAME, token.DOT} or current.value == "for": | ||||||
# If the current token isn't disallowed, we'll assume this is simple as | ||||||
# only the disallowed tokens are semantically attached to this lookup | ||||||
# expression we're checking. Also, stop early if we hit the 'for' bit | ||||||
# of a comprehension. | ||||||
return True | ||||||
|
||||||
index += step | ||||||
|
||||||
return True | ||||||
|
||||||
def is_simple_operand(index: int, kind: Literal["base", "exponent"]) -> bool: | ||||||
# An operand is considered "simple" if's a NAME, a numeric CONSTANT, a simple | ||||||
# lookup (see above), with or without a preceding unary operator. | ||||||
start = line.leaves[index] | ||||||
if start.type in {token.NAME, token.NUMBER}: | ||||||
return is_simple_lookup(index, step=(1 if kind == "exponent" else -1)) | ||||||
|
||||||
if start.type in {token.PLUS, token.MINUS, token.TILDE}: | ||||||
if line.leaves[index + 1].type in {token.NAME, token.NUMBER}: | ||||||
# step is always one as bases with a preceding unary op will be checked | ||||||
# for simplicity starting from the next token (so it'll hit the check | ||||||
# above). | ||||||
return is_simple_lookup(index + 1, step=1) | ||||||
|
||||||
return False | ||||||
|
||||||
leaves: List[Leaf] = [] | ||||||
should_hug = False | ||||||
for idx, leaf in enumerate(line.leaves): | ||||||
new_leaf = leaf.clone() | ||||||
if should_hug: | ||||||
new_leaf.prefix = "" | ||||||
should_hug = False | ||||||
|
||||||
should_hug = ( | ||||||
(0 < idx < len(line.leaves) - 1) | ||||||
and leaf.type == token.DOUBLESTAR | ||||||
and is_simple_operand(idx - 1, kind="base") | ||||||
and line.leaves[idx - 1].value != "lambda" | ||||||
and is_simple_operand(idx + 1, kind="exponent") | ||||||
) | ||||||
if should_hug: | ||||||
new_leaf.prefix = "" | ||||||
|
||||||
leaves.append(new_leaf) | ||||||
|
||||||
yield Line( | ||||||
mode=line.mode, | ||||||
depth=line.depth, | ||||||
leaves=leaves, | ||||||
comments=line.comments, | ||||||
bracket_tracker=line.bracket_tracker, | ||||||
inside_brackets=line.inside_brackets, | ||||||
should_split_rhs=line.should_split_rhs, | ||||||
magic_trailing_comma=line.magic_trailing_comma, | ||||||
) | ||||||
|
||||||
|
||||||
class StringTransformer(ABC): | ||||||
""" | ||||||
An implementation of the Transformer protocol that relies on its | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reading it through once more, we could also say that the surrounding spaces are removed for unaries and simple powers. Now it just says that they are an exception to the rule.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused, could you elaborate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry, I missed this: I meant to say that we don't actually say what happens to unaries and powers. We only say that they are an exception to the rule. Maybe something like: "[the only exceptions are unaries and simple pows], for which the spaces are removed."