From 63481bb9264a8c3756577089414d222da9c7fab0 Mon Sep 17 00:00:00 2001 From: rdrll <13176405+rdrll@users.noreply.github.com> Date: Tue, 27 Jun 2023 07:23:39 -0700 Subject: [PATCH] Fix a magical comment caused internal error (#3740) `is_type_comment` now specifically deals with general type comments for a leaf. `is_type_ignore_comment` now handles type comments contains ignore annotation for a leaf `is_type_ignore_comment_string` used to determine if a string is an ignore type comment --- CHANGES.md | 2 + src/black/linegen.py | 10 ++++- src/black/lines.py | 5 ++- src/black/nodes.py | 23 +++++++++-- ...ine_consecutive_open_parentheses_ignore.py | 41 +++++++++++++++++++ 5 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py diff --git a/CHANGES.md b/CHANGES.md index 6fa0e4b7cc0..2dfed8a0dc4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,8 @@ - Fix a bug where an illegal trailing comma was added to return type annotations using PEP 604 unions (#3735) +- Fix a bug where multi-line open parenthesis magic comment like `type: ignore` were not + correctly parsed (#3740) ### Preview style diff --git a/src/black/linegen.py b/src/black/linegen.py index ad21307c311..5ef3bbd1705 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -49,6 +49,7 @@ is_stub_body, is_stub_suite, is_tuple_containing_walrus, + is_type_ignore_comment_string, is_vararg, is_walrus_assignment, is_yield, @@ -1399,8 +1400,13 @@ def maybe_make_parens_invisible_in_atom( if is_lpar_token(first) and is_rpar_token(last): middle = node.children[1] # make parentheses invisible - first.value = "" - last.value = "" + if ( + # If the prefix of `middle` includes a type comment with + # ignore annotation, then we do not remove the parentheses + not is_type_ignore_comment_string(middle.prefix.strip()) + ): + first.value = "" + last.value = "" maybe_make_parens_invisible_in_atom( middle, parent=parent, diff --git a/src/black/lines.py b/src/black/lines.py index daf0444d24e..ea8fe520756 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -28,6 +28,7 @@ is_multiline_string, is_one_sequence_between, is_type_comment, + is_type_ignore_comment, is_with_or_async_with_stmt, replace_child, syms, @@ -251,7 +252,7 @@ def contains_uncollapsable_type_comments(self) -> bool: for comment in comments: if is_type_comment(comment): if comment_seen or ( - not is_type_comment(comment, " ignore") + not is_type_ignore_comment(comment) and leaf_id not in ignored_ids ): return True @@ -288,7 +289,7 @@ def contains_unsplittable_type_ignore(self) -> bool: # line. for node in self.leaves[-2:]: for comment in self.comments.get(id(node), []): - if is_type_comment(comment, " ignore"): + if is_type_ignore_comment(comment): return True return False diff --git a/src/black/nodes.py b/src/black/nodes.py index 45070909df4..b019b0c6440 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -816,12 +816,27 @@ def is_async_stmt_or_funcdef(leaf: Leaf) -> bool: ) -def is_type_comment(leaf: Leaf, suffix: str = "") -> bool: - """Return True if the given leaf is a special comment. - Only returns true for type comments for now.""" +def is_type_comment(leaf: Leaf) -> bool: + """Return True if the given leaf is a type comment. This function should only + be used for general type comments (excluding ignore annotations, which should + use `is_type_ignore_comment`). Note that general type comments are no longer + used in modern version of Python, this function may be deprecated in the future.""" t = leaf.type v = leaf.value - return t in {token.COMMENT, STANDALONE_COMMENT} and v.startswith("# type:" + suffix) + return t in {token.COMMENT, STANDALONE_COMMENT} and v.startswith("# type:") + + +def is_type_ignore_comment(leaf: Leaf) -> bool: + """Return True if the given leaf is a type comment with ignore annotation.""" + t = leaf.type + v = leaf.value + return t in {token.COMMENT, STANDALONE_COMMENT} and is_type_ignore_comment_string(v) + + +def is_type_ignore_comment_string(value: str) -> bool: + """Return True if the given string match with type comment with + ignore annotation.""" + return value.startswith("# type: ignore") def wrap_in_parentheses(parent: Node, child: LN, *, visible: bool = True) -> None: diff --git a/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py b/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py new file mode 100644 index 00000000000..6ec8bb45408 --- /dev/null +++ b/tests/data/simple_cases/multiline_consecutive_open_parentheses_ignore.py @@ -0,0 +1,41 @@ +# This is a regression test. Issue #3737 + +a = ( # type: ignore + int( # type: ignore + int( # type: ignore + int( # type: ignore + 6 + ) + ) + ) +) + +b = ( + int( + 6 + ) +) + +print( "111") # type: ignore +print( "111" ) # type: ignore +print( "111" ) # type: ignore + + +# output + + +# This is a regression test. Issue #3737 + +a = ( # type: ignore + int( # type: ignore + int( # type: ignore + int(6) # type: ignore + ) + ) +) + +b = int(6) + +print("111") # type: ignore +print("111") # type: ignore +print("111") # type: ignore \ No newline at end of file