Skip to content

Commit f4b006f

Browse files
Fix: Preserve parentheses when merging # type: ignore with other comments to prevent AST errors (#4888)
Co-authored-by: cobalt <61329810+cobaltt7@users.noreply.github.com>
1 parent 115dbcf commit f4b006f

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ directories.
124124
- Fix crash when multiple `# fmt: skip` comments are used in a multi-part if-clause, on
125125
string literals, or on dictionary entries with long lines (#4872)
126126
- Fix possible crash when `fmt: ` directives aren't on the top level (#4856)
127+
- Preserve parentheses when `# type: ignore` comments would be merged with other
128+
comments on the same line, preventing AST equivalence failures (#4888)
127129

128130
### Preview style
129131

src/black/lines.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,44 @@ def can_omit_invisible_parens(
948948
"""
949949
line = rhs.body
950950

951+
# We can't omit parens if doing so would result in a type: ignore comment
952+
# sharing a line with other comments, as that breaks type: ignore parsing.
953+
# Check if the opening bracket (last leaf of head) has comments that would merge
954+
# with comments from the first line of the body.
955+
if rhs.head.leaves:
956+
opening_bracket = rhs.head.leaves[-1]
957+
head_comments = rhs.head.comments.get(id(opening_bracket), [])
958+
959+
# If there are comments on the opening bracket line, check if any would
960+
# conflict with type: ignore comments in the body
961+
if head_comments:
962+
has_type_ignore_in_head = any(
963+
is_type_ignore_comment(comment, mode=rhs.head.mode)
964+
for comment in head_comments
965+
)
966+
has_other_comment_in_head = any(
967+
not is_type_ignore_comment(comment, mode=rhs.head.mode)
968+
for comment in head_comments
969+
)
970+
971+
# Check for comments in the body that would potentially end up on the
972+
# same line as the head comments when parens are removed
973+
has_type_ignore_in_body = False
974+
has_other_comment_in_body = False
975+
for leaf in rhs.body.leaves:
976+
for comment in rhs.body.comments.get(id(leaf), []):
977+
if is_type_ignore_comment(comment, mode=rhs.body.mode):
978+
has_type_ignore_in_body = True
979+
else:
980+
has_other_comment_in_body = True
981+
982+
# Preserve parens if we have both type: ignore and other comments that
983+
# could end up on the same line
984+
if (has_type_ignore_in_head and has_other_comment_in_body) or (
985+
has_other_comment_in_head and has_type_ignore_in_body
986+
):
987+
return False
988+
951989
# We need optional parens in order to split standalone comments to their own lines
952990
# if there are no nested parens around the standalone comments
953991
closing_bracket: Leaf | None = None
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import pandas as pd
2+
3+
interval_td = pd.Interval(
4+
pd.Timedelta("1 days"), pd.Timedelta("2 days"), closed="neither"
5+
)
6+
7+
_td = ( # pyright: ignore[reportOperatorIssue,reportUnknownVariableType]
8+
interval_td
9+
- pd.Interval( # type: ignore[operator]
10+
pd.Timedelta(1, "ns"), pd.Timedelta(2, "ns")
11+
)
12+
)
13+
14+
# output
15+
16+
import pandas as pd
17+
18+
interval_td = pd.Interval(
19+
pd.Timedelta("1 days"), pd.Timedelta("2 days"), closed="neither"
20+
)
21+
22+
_td = ( # pyright: ignore[reportOperatorIssue,reportUnknownVariableType]
23+
interval_td
24+
- pd.Interval( # type: ignore[operator]
25+
pd.Timedelta(1, "ns"), pd.Timedelta(2, "ns")
26+
)
27+
)

0 commit comments

Comments
 (0)