Fix await ellipses and remove async/await soft keyword/identifier support (#4676)

* Update tokenize.py

* Update driver.py

* Update test_black.py

* Update test_black.py

* Update python37.py

* Update tokenize.py

* Update CHANGES.md

* Update CHANGES.md

* Update faq.md

* Update driver.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
GiGaGon 2025-06-05 18:50:42 -07:00 committed by GitHub
parent 24e4cb20ab
commit e5e5dad792
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 18 additions and 77 deletions

View File

@ -17,6 +17,9 @@
- Fix crash when a tuple appears in the `as` clause of a `with` statement (#4634) - Fix crash when a tuple appears in the `as` clause of a `with` statement (#4634)
- Fix crash when tuple is used as a context manager inside a `with` statement (#4646) - Fix crash when tuple is used as a context manager inside a `with` statement (#4646)
- Fix crash on a `\\r\n` (#4673) - Fix crash on a `\\r\n` (#4673)
- Fix crash on `await ...` (where `...` is a literal `Ellipsis`) (#4676)
- Remove support for pre-python 3.7 `await/async` as soft keywords/variable names
(#4676)
### Preview style ### Preview style

View File

@ -93,6 +93,8 @@ Support for formatting Python 2 code was removed in version 22.0. While we've ma
plans to stop supporting older Python 3 minor versions immediately, their support might plans to stop supporting older Python 3 minor versions immediately, their support might
also be removed some time in the future without a deprecation period. also be removed some time in the future without a deprecation period.
`await`/`async` as soft keywords/indentifiers are no longer supported as of 25.2.0.
Runtime support for 3.6 was removed in version 22.10.0, for 3.7 in version 23.7.0, and Runtime support for 3.6 was removed in version 22.10.0, for 3.7 in version 23.7.0, and
for 3.8 in version 24.10.0. for 3.8 in version 24.10.0.

View File

@ -138,20 +138,13 @@ def transform_whitespace(
def tokenize(source: str, grammar: Optional[Grammar] = None) -> Iterator[TokenInfo]: def tokenize(source: str, grammar: Optional[Grammar] = None) -> Iterator[TokenInfo]:
async_keywords = False if grammar is None else grammar.async_keywords
lines = source.split("\n") lines = source.split("\n")
lines += [""] # For newline tokens in files that don't end in a newline lines += [""] # For newline tokens in files that don't end in a newline
line, column = 1, 0 line, column = 1, 0
token_iterator = pytokens.tokenize(source)
is_async = False
current_indent = 0
async_indent = 0
prev_token: Optional[pytokens.Token] = None prev_token: Optional[pytokens.Token] = None
try: try:
for token in token_iterator: for token in pytokens.tokenize(source):
token = transform_whitespace(token, source, prev_token) token = transform_whitespace(token, source, prev_token)
line, column = token.start_line, token.start_col line, column = token.start_line, token.start_col
@ -166,58 +159,18 @@ def tokenize(source: str, grammar: Optional[Grammar] = None) -> Iterator[TokenIn
prev_token = token prev_token = token
continue continue
if token.type == TokenType.indent:
current_indent += 1
if token.type == TokenType.dedent:
current_indent -= 1
if is_async and current_indent < async_indent:
is_async = False
source_line = lines[token.start_line - 1] source_line = lines[token.start_line - 1]
if token.type == TokenType.identifier and token_str in ("async", "await"): if token.type == TokenType.identifier and token_str in ("async", "await"):
# Black uses `async` and `await` token types just for those two keywords # Black uses `async` and `await` token types just for those two keywords
while True:
next_token = next(token_iterator)
next_str = source[next_token.start_index : next_token.end_index]
next_token = transform_whitespace(next_token, next_str, token)
if next_token.type == TokenType.whitespace:
continue
break
next_token_type = TOKEN_TYPE_MAP[next_token.type]
next_line = lines[next_token.start_line - 1]
if token_str == "async" and (
async_keywords
or (next_token_type == NAME and next_str in ("def", "for"))
):
is_async = True
async_indent = current_indent + 1
current_token_type = ASYNC
elif token_str == "await" and (async_keywords or is_async):
current_token_type = AWAIT
else:
current_token_type = TOKEN_TYPE_MAP[token.type]
yield ( yield (
current_token_type, ASYNC if token_str == "async" else AWAIT,
token_str, token_str,
(token.start_line, token.start_col), (token.start_line, token.start_col),
(token.end_line, token.end_col), (token.end_line, token.end_col),
source_line, source_line,
) )
yield ( elif token.type == TokenType.op and token_str == "...":
next_token_type,
next_str,
(next_token.start_line, next_token.start_col),
(next_token.end_line, next_token.end_col),
next_line,
)
prev_token = token
continue
if token.type == TokenType.op and token_str == "...":
# Black doesn't have an ellipsis token yet, yield 3 DOTs instead # Black doesn't have an ellipsis token yet, yield 3 DOTs instead
assert token.start_line == token.end_line assert token.start_line == token.end_line
assert token.end_col == token.start_col + 3 assert token.end_col == token.start_col + 3
@ -232,16 +185,14 @@ def tokenize(source: str, grammar: Optional[Grammar] = None) -> Iterator[TokenIn
(token.end_line, end_col), (token.end_line, end_col),
source_line, source_line,
) )
prev_token = token else:
continue yield (
TOKEN_TYPE_MAP[token.type],
yield ( token_str,
TOKEN_TYPE_MAP[token.type], (token.start_line, token.start_col),
token_str, (token.end_line, token.end_col),
(token.start_line, token.start_col), source_line,
(token.end_line, token.end_col), )
source_line,
)
prev_token = token prev_token = token
except pytokens.UnexpectedEOF: except pytokens.UnexpectedEOF:

View File

@ -10,6 +10,7 @@ def g():
async def func(): async def func():
await ...
if test: if test:
out_batched = [ out_batched = [
i i
@ -42,6 +43,7 @@ def g():
async def func(): async def func():
await ...
if test: if test:
out_batched = [ out_batched = [
i i

View File

@ -422,21 +422,6 @@ def test_skip_magic_trailing_comma(self) -> None:
) )
self.assertEqual(expected, actual, msg) self.assertEqual(expected, actual, msg)
@patch("black.dump_to_file", dump_to_stderr)
def test_async_as_identifier(self) -> None:
source_path = get_case_path("miscellaneous", "async_as_identifier")
_, source, expected = read_data_from_file(source_path)
actual = fs(source)
self.assertFormatEqual(expected, actual)
major, minor = sys.version_info[:2]
if major < 3 or (major <= 3 and minor < 7):
black.assert_equivalent(source, actual)
black.assert_stable(source, actual, DEFAULT_MODE)
# ensure black can parse this when the target is 3.6
self.invokeBlack([str(source_path), "--target-version", "py36"])
# but not on 3.7, because async/await is no longer an identifier
self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
@patch("black.dump_to_file", dump_to_stderr) @patch("black.dump_to_file", dump_to_stderr)
def test_python37(self) -> None: def test_python37(self) -> None:
source_path = get_case_path("cases", "python37") source_path = get_case_path("cases", "python37")
@ -449,8 +434,6 @@ def test_python37(self) -> None:
black.assert_stable(source, actual, DEFAULT_MODE) black.assert_stable(source, actual, DEFAULT_MODE)
# ensure black can parse this when the target is 3.7 # ensure black can parse this when the target is 3.7
self.invokeBlack([str(source_path), "--target-version", "py37"]) self.invokeBlack([str(source_path), "--target-version", "py37"])
# but not on 3.6, because we use async as a reserved keyword
self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
def test_tab_comment_indentation(self) -> None: def test_tab_comment_indentation(self) -> None:
contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n" contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"