Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/black/linegen.py: 11%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

871 statements  

1""" 

2Generating lines of code. 

3""" 

4 

5import re 

6import sys 

7from collections.abc import Collection, Iterator 

8from dataclasses import replace 

9from enum import Enum, auto 

10from functools import partial, wraps 

11from typing import Union, cast 

12 

13from black.brackets import ( 

14 COMMA_PRIORITY, 

15 COMPARATOR_PRIORITY, 

16 DOT_PRIORITY, 

17 STRING_PRIORITY, 

18 get_leaves_inside_matching_brackets, 

19 max_delimiter_priority_in_atom, 

20) 

21from black.comments import ( 

22 FMT_OFF, 

23 FMT_ON, 

24 contains_fmt_directive, 

25 generate_comments, 

26 list_comments, 

27) 

28from black.lines import ( 

29 Line, 

30 RHSResult, 

31 append_leaves, 

32 can_be_split, 

33 can_omit_invisible_parens, 

34 is_line_short_enough, 

35 line_to_string, 

36) 

37from black.mode import Feature, Mode, Preview 

38from black.nodes import ( 

39 ASSIGNMENTS, 

40 BRACKETS, 

41 CLOSING_BRACKETS, 

42 OPENING_BRACKETS, 

43 STANDALONE_COMMENT, 

44 STATEMENT, 

45 WHITESPACE, 

46 Visitor, 

47 ensure_visible, 

48 fstring_tstring_to_string, 

49 get_annotation_type, 

50 has_sibling_with_type, 

51 is_arith_like, 

52 is_async_stmt_or_funcdef, 

53 is_atom_with_invisible_parens, 

54 is_docstring, 

55 is_empty_tuple, 

56 is_generator, 

57 is_lpar_token, 

58 is_multiline_string, 

59 is_name_token, 

60 is_one_sequence_between, 

61 is_one_tuple, 

62 is_parent_function_or_class, 

63 is_part_of_annotation, 

64 is_rpar_token, 

65 is_stub_body, 

66 is_stub_suite, 

67 is_tuple, 

68 is_tuple_containing_star, 

69 is_tuple_containing_walrus, 

70 is_type_ignore_comment_string, 

71 is_vararg, 

72 is_walrus_assignment, 

73 is_yield, 

74 syms, 

75 wrap_in_parentheses, 

76) 

77from black.numerics import normalize_numeric_literal 

78from black.strings import ( 

79 fix_multiline_docstring, 

80 get_string_prefix, 

81 normalize_string_prefix, 

82 normalize_string_quotes, 

83 normalize_unicode_escape_sequences, 

84 str_width, 

85) 

86from black.trans import ( 

87 CannotTransform, 

88 StringMerger, 

89 StringParenStripper, 

90 StringParenWrapper, 

91 StringSplitter, 

92 Transformer, 

93 hug_power_op, 

94) 

95from blib2to3.pgen2 import token 

96from blib2to3.pytree import Leaf, Node 

97 

98# types 

99LeafID = int 

100LN = Union[Leaf, Node] 

101 

102 

103class CannotSplit(CannotTransform): 

104 """A readable split that fits the allotted line length is impossible.""" 

105 

106 

107# This isn't a dataclass because @dataclass + Generic breaks mypyc. 

108# See also https://github.com/mypyc/mypyc/issues/827. 

109class LineGenerator(Visitor[Line]): 

110 """Generates reformatted Line objects. Empty lines are not emitted. 

111 

112 Note: destroys the tree it's visiting by mutating prefixes of its leaves 

113 in ways that will no longer stringify to valid Python code on the tree. 

114 """ 

115 

116 def __init__(self, mode: Mode, features: Collection[Feature]) -> None: 

117 self.mode = mode 

118 self.features = features 

119 self.current_line: Line 

120 self.__post_init__() 

121 

122 def line(self, indent: int = 0) -> Iterator[Line]: 

123 """Generate a line. 

124 

125 If the line is empty, only emit if it makes sense. 

126 If the line is too long, split it first and then generate. 

127 

128 If any lines were generated, set up a new current_line. 

129 """ 

130 if not self.current_line: 

131 self.current_line.depth += indent 

132 return # Line is empty, don't emit. Creating a new one unnecessary. 

133 

134 if len(self.current_line.leaves) == 1 and is_async_stmt_or_funcdef( 

135 self.current_line.leaves[0] 

136 ): 

137 # Special case for async def/for/with statements. `visit_async_stmt` 

138 # adds an `ASYNC` leaf then visits the child def/for/with statement 

139 # nodes. Line yields from those nodes shouldn't treat the former 

140 # `ASYNC` leaf as a complete line. 

141 return 

142 

143 complete_line = self.current_line 

144 self.current_line = Line(mode=self.mode, depth=complete_line.depth + indent) 

145 yield complete_line 

146 

147 def visit_default(self, node: LN) -> Iterator[Line]: 

148 """Default `visit_*()` implementation. Recurses to children of `node`.""" 

149 if isinstance(node, Leaf): 

150 any_open_brackets = self.current_line.bracket_tracker.any_open_brackets() 

151 for comment in generate_comments(node, mode=self.mode): 

152 if any_open_brackets: 

153 # any comment within brackets is subject to splitting 

154 self.current_line.append(comment) 

155 elif comment.type == token.COMMENT: 

156 # regular trailing comment 

157 self.current_line.append(comment) 

158 yield from self.line() 

159 

160 else: 

161 # regular standalone comment 

162 yield from self.line() 

163 

164 self.current_line.append(comment) 

165 yield from self.line() 

166 

167 if any_open_brackets: 

168 node.prefix = "" 

169 if node.type not in WHITESPACE: 

170 self.current_line.append(node) 

171 yield from super().visit_default(node) 

172 

173 def visit_test(self, node: Node) -> Iterator[Line]: 

174 """Visit an `x if y else z` test""" 

175 

176 already_parenthesized = ( 

177 node.prev_sibling and node.prev_sibling.type == token.LPAR 

178 ) 

179 

180 if not already_parenthesized: 

181 # Similar to logic in wrap_in_parentheses 

182 lpar = Leaf(token.LPAR, "") 

183 rpar = Leaf(token.RPAR, "") 

184 prefix = node.prefix 

185 node.prefix = "" 

186 lpar.prefix = prefix 

187 node.insert_child(0, lpar) 

188 node.append_child(rpar) 

189 

190 yield from self.visit_default(node) 

191 

192 def visit_INDENT(self, node: Leaf) -> Iterator[Line]: 

193 """Increase indentation level, maybe yield a line.""" 

194 # In blib2to3 INDENT never holds comments. 

195 yield from self.line(+1) 

196 yield from self.visit_default(node) 

197 

198 def visit_DEDENT(self, node: Leaf) -> Iterator[Line]: 

199 """Decrease indentation level, maybe yield a line.""" 

200 # The current line might still wait for trailing comments. At DEDENT time 

201 # there won't be any (they would be prefixes on the preceding NEWLINE). 

202 # Emit the line then. 

203 yield from self.line() 

204 

205 # While DEDENT has no value, its prefix may contain standalone comments 

206 # that belong to the current indentation level. Get 'em. 

207 yield from self.visit_default(node) 

208 

209 # Finally, emit the dedent. 

210 yield from self.line(-1) 

211 

212 def visit_stmt( 

213 self, node: Node, keywords: set[str], parens: set[str] 

214 ) -> Iterator[Line]: 

215 """Visit a statement. 

216 

217 This implementation is shared for `if`, `while`, `for`, `try`, `except`, 

218 `def`, `with`, `class`, `assert`, and assignments. 

219 

220 The relevant Python language `keywords` for a given statement will be 

221 NAME leaves within it. This methods puts those on a separate line. 

222 

223 `parens` holds a set of string leaf values immediately after which 

224 invisible parens should be put. 

225 """ 

226 normalize_invisible_parens( 

227 node, parens_after=parens, mode=self.mode, features=self.features 

228 ) 

229 for child in node.children: 

230 if is_name_token(child) and child.value in keywords: 

231 yield from self.line() 

232 

233 yield from self.visit(child) 

234 

235 def visit_typeparams(self, node: Node) -> Iterator[Line]: 

236 yield from self.visit_default(node) 

237 node.children[0].prefix = "" 

238 

239 def visit_typevartuple(self, node: Node) -> Iterator[Line]: 

240 yield from self.visit_default(node) 

241 node.children[1].prefix = "" 

242 

243 def visit_paramspec(self, node: Node) -> Iterator[Line]: 

244 yield from self.visit_default(node) 

245 node.children[1].prefix = "" 

246 

247 def visit_dictsetmaker(self, node: Node) -> Iterator[Line]: 

248 if Preview.wrap_long_dict_values_in_parens in self.mode: 

249 for i, child in enumerate(node.children): 

250 if i == 0: 

251 continue 

252 if node.children[i - 1].type == token.COLON: 

253 if ( 

254 child.type == syms.atom 

255 and child.children[0].type in OPENING_BRACKETS 

256 and not is_walrus_assignment(child) 

257 ): 

258 maybe_make_parens_invisible_in_atom( 

259 child, 

260 parent=node, 

261 mode=self.mode, 

262 features=self.features, 

263 remove_brackets_around_comma=False, 

264 ) 

265 else: 

266 wrap_in_parentheses(node, child, visible=False) 

267 yield from self.visit_default(node) 

268 

269 def visit_funcdef(self, node: Node) -> Iterator[Line]: 

270 """Visit function definition.""" 

271 yield from self.line() 

272 

273 # Remove redundant brackets around return type annotation. 

274 is_return_annotation = False 

275 for child in node.children: 

276 if child.type == token.RARROW: 

277 is_return_annotation = True 

278 elif is_return_annotation: 

279 if child.type == syms.atom and child.children[0].type == token.LPAR: 

280 if maybe_make_parens_invisible_in_atom( 

281 child, 

282 parent=node, 

283 mode=self.mode, 

284 features=self.features, 

285 remove_brackets_around_comma=False, 

286 ): 

287 wrap_in_parentheses(node, child, visible=False) 

288 else: 

289 wrap_in_parentheses(node, child, visible=False) 

290 is_return_annotation = False 

291 

292 for child in node.children: 

293 yield from self.visit(child) 

294 

295 def visit_match_case(self, node: Node) -> Iterator[Line]: 

296 """Visit either a match or case statement.""" 

297 normalize_invisible_parens( 

298 node, parens_after=set(), mode=self.mode, features=self.features 

299 ) 

300 

301 yield from self.line() 

302 for child in node.children: 

303 yield from self.visit(child) 

304 

305 def visit_suite(self, node: Node) -> Iterator[Line]: 

306 """Visit a suite.""" 

307 if is_stub_suite(node): 

308 yield from self.visit(node.children[2]) 

309 else: 

310 yield from self.visit_default(node) 

311 

312 def visit_simple_stmt(self, node: Node) -> Iterator[Line]: 

313 """Visit a statement without nested statements.""" 

314 prev_type: int | None = None 

315 for child in node.children: 

316 if (prev_type is None or prev_type == token.SEMI) and is_arith_like(child): 

317 wrap_in_parentheses(node, child, visible=False) 

318 prev_type = child.type 

319 

320 if node.parent and node.parent.type in STATEMENT: 

321 if is_parent_function_or_class(node) and is_stub_body(node): 

322 yield from self.visit_default(node) 

323 else: 

324 yield from self.line(+1) 

325 yield from self.visit_default(node) 

326 yield from self.line(-1) 

327 

328 else: 

329 if node.parent and is_stub_suite(node.parent): 

330 node.prefix = "" 

331 yield from self.visit_default(node) 

332 return 

333 yield from self.line() 

334 yield from self.visit_default(node) 

335 

336 def visit_async_stmt(self, node: Node) -> Iterator[Line]: 

337 """Visit `async def`, `async for`, `async with`.""" 

338 yield from self.line() 

339 

340 children = iter(node.children) 

341 for child in children: 

342 yield from self.visit(child) 

343 

344 if child.type == token.ASYNC or child.type == STANDALONE_COMMENT: 

345 # STANDALONE_COMMENT happens when `# fmt: skip` is applied on the async 

346 # line. 

347 break 

348 

349 internal_stmt = next(children) 

350 yield from self.visit(internal_stmt) 

351 

352 def visit_decorators(self, node: Node) -> Iterator[Line]: 

353 """Visit decorators.""" 

354 for child in node.children: 

355 yield from self.line() 

356 yield from self.visit(child) 

357 

358 def visit_power(self, node: Node) -> Iterator[Line]: 

359 for idx, leaf in enumerate(node.children[:-1]): 

360 next_leaf = node.children[idx + 1] 

361 

362 if not isinstance(leaf, Leaf): 

363 continue 

364 

365 value = leaf.value.lower() 

366 if ( 

367 leaf.type == token.NUMBER 

368 and next_leaf.type == syms.trailer 

369 # Ensure that we are in an attribute trailer 

370 and next_leaf.children[0].type == token.DOT 

371 # It shouldn't wrap hexadecimal, binary and octal literals 

372 and not value.startswith(("0x", "0b", "0o")) 

373 # It shouldn't wrap complex literals 

374 and "j" not in value 

375 ): 

376 wrap_in_parentheses(node, leaf) 

377 

378 remove_await_parens(node, mode=self.mode, features=self.features) 

379 

380 yield from self.visit_default(node) 

381 

382 def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]: 

383 """Remove a semicolon and put the other statement on a separate line.""" 

384 yield from self.line() 

385 

386 def visit_ENDMARKER(self, leaf: Leaf) -> Iterator[Line]: 

387 """End of file. Process outstanding comments and end with a newline.""" 

388 yield from self.visit_default(leaf) 

389 yield from self.line() 

390 

391 def visit_STANDALONE_COMMENT(self, leaf: Leaf) -> Iterator[Line]: 

392 any_open_brackets = self.current_line.bracket_tracker.any_open_brackets() 

393 if not any_open_brackets: 

394 yield from self.line() 

395 # STANDALONE_COMMENT nodes created by our special handling in 

396 # normalize_fmt_off for comment-only blocks have fmt:off as the first 

397 # line and fmt:on as the last line (each directive on its own line, 

398 # not embedded in other text). These should be appended directly 

399 # without calling visit_default, which would process their prefix and 

400 # lose indentation. Normal STANDALONE_COMMENT nodes go through 

401 # visit_default. 

402 value = leaf.value 

403 lines = value.splitlines() 

404 is_fmt_off_block = ( 

405 len(lines) >= 2 

406 and contains_fmt_directive(lines[0], FMT_OFF) 

407 and contains_fmt_directive(lines[-1], FMT_ON) 

408 ) 

409 if is_fmt_off_block: 

410 # This is a fmt:off/on block from normalize_fmt_off - we still need 

411 # to process any prefix comments (like markdown comments) but append 

412 # the fmt block itself directly to preserve its formatting 

413 

414 # Only process prefix comments if there actually is a prefix with comments 

415 if leaf.prefix and any( 

416 line.strip().startswith("#") 

417 and not contains_fmt_directive(line.strip()) 

418 for line in leaf.prefix.split("\n") 

419 ): 

420 for comment in generate_comments(leaf, mode=self.mode): 

421 yield from self.line() 

422 self.current_line.append(comment) 

423 yield from self.line() 

424 # Clear the prefix since we've processed it as comments above 

425 leaf.prefix = "" 

426 

427 self.current_line.append(leaf) 

428 if not any_open_brackets: 

429 yield from self.line() 

430 else: 

431 # Normal standalone comment - process through visit_default 

432 yield from self.visit_default(leaf) 

433 

434 def visit_factor(self, node: Node) -> Iterator[Line]: 

435 """Force parentheses between a unary op and a binary power: 

436 

437 -2 ** 8 -> -(2 ** 8) 

438 """ 

439 _operator, operand = node.children 

440 if ( 

441 operand.type == syms.power 

442 and len(operand.children) == 3 

443 and operand.children[1].type == token.DOUBLESTAR 

444 ): 

445 lpar = Leaf(token.LPAR, "(") 

446 rpar = Leaf(token.RPAR, ")") 

447 index = operand.remove() or 0 

448 node.insert_child(index, Node(syms.atom, [lpar, operand, rpar])) 

449 yield from self.visit_default(node) 

450 

451 def visit_tname(self, node: Node) -> Iterator[Line]: 

452 """ 

453 Add potential parentheses around types in function parameter lists to be made 

454 into real parentheses in case the type hint is too long to fit on a line 

455 Examples: 

456 def foo(a: int, b: float = 7): ... 

457 

458 -> 

459 

460 def foo(a: (int), b: (float) = 7): ... 

461 """ 

462 if len(node.children) == 3 and maybe_make_parens_invisible_in_atom( 

463 node.children[2], parent=node, mode=self.mode, features=self.features 

464 ): 

465 wrap_in_parentheses(node, node.children[2], visible=False) 

466 

467 yield from self.visit_default(node) 

468 

469 def visit_STRING(self, leaf: Leaf) -> Iterator[Line]: 

470 normalize_unicode_escape_sequences(leaf) 

471 

472 if is_docstring(leaf) and not re.search(r"\\\s*\n", leaf.value): 

473 # We're ignoring docstrings with backslash newline escapes because changing 

474 # indentation of those changes the AST representation of the code. 

475 if self.mode.string_normalization: 

476 docstring = normalize_string_prefix(leaf.value) 

477 # We handle string normalization at the end of this method, but since 

478 # what we do right now acts differently depending on quote style (ex. 

479 # see padding logic below), there's a possibility for unstable 

480 # formatting. To avoid a situation where this function formats a 

481 # docstring differently on the second pass, normalize it early. 

482 docstring = normalize_string_quotes(docstring) 

483 else: 

484 docstring = leaf.value 

485 prefix = get_string_prefix(docstring) 

486 docstring = docstring[len(prefix) :] # Remove the prefix 

487 quote_char = docstring[0] 

488 # A natural way to remove the outer quotes is to do: 

489 # docstring = docstring.strip(quote_char) 

490 # but that breaks on """""x""" (which is '""x'). 

491 # So we actually need to remove the first character and the next two 

492 # characters but only if they are the same as the first. 

493 quote_len = 1 if docstring[1] != quote_char else 3 

494 docstring = docstring[quote_len:-quote_len] 

495 docstring_started_empty = not docstring 

496 indent = " " * 4 * self.current_line.depth 

497 

498 if is_multiline_string(leaf): 

499 docstring = fix_multiline_docstring(docstring, indent) 

500 else: 

501 docstring = docstring.strip() 

502 

503 has_trailing_backslash = False 

504 if docstring: 

505 # Add some padding if the docstring starts / ends with a quote mark. 

506 if docstring[0] == quote_char: 

507 docstring = " " + docstring 

508 if docstring[-1] == quote_char: 

509 docstring += " " 

510 if docstring[-1] == "\\": 

511 backslash_count = len(docstring) - len(docstring.rstrip("\\")) 

512 if backslash_count % 2: 

513 # Odd number of tailing backslashes, add some padding to 

514 # avoid escaping the closing string quote. 

515 docstring += " " 

516 has_trailing_backslash = True 

517 elif not docstring_started_empty: 

518 docstring = " " 

519 

520 # We could enforce triple quotes at this point. 

521 quote = quote_char * quote_len 

522 

523 # It's invalid to put closing single-character quotes on a new line. 

524 if quote_len == 3: 

525 # We need to find the length of the last line of the docstring 

526 # to find if we can add the closing quotes to the line without 

527 # exceeding the maximum line length. 

528 # If docstring is one line, we don't put the closing quotes on a 

529 # separate line because it looks ugly (#3320). 

530 lines = docstring.splitlines() 

531 last_line_length = len(lines[-1]) if docstring else 0 

532 

533 # If adding closing quotes would cause the last line to exceed 

534 # the maximum line length, and the closing quote is not 

535 # prefixed by a newline then put a line break before 

536 # the closing quotes 

537 if ( 

538 len(lines) > 1 

539 and last_line_length + quote_len > self.mode.line_length 

540 and len(indent) + quote_len <= self.mode.line_length 

541 and not has_trailing_backslash 

542 ): 

543 if leaf.value[-1 - quote_len] == "\n": 

544 leaf.value = prefix + quote + docstring + quote 

545 else: 

546 leaf.value = prefix + quote + docstring + "\n" + indent + quote 

547 else: 

548 leaf.value = prefix + quote + docstring + quote 

549 else: 

550 leaf.value = prefix + quote + docstring + quote 

551 

552 if self.mode.string_normalization and leaf.type == token.STRING: 

553 leaf.value = normalize_string_prefix(leaf.value) 

554 leaf.value = normalize_string_quotes(leaf.value) 

555 yield from self.visit_default(leaf) 

556 

557 def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]: 

558 normalize_numeric_literal(leaf) 

559 yield from self.visit_default(leaf) 

560 

561 def visit_atom(self, node: Node) -> Iterator[Line]: 

562 """Visit any atom""" 

563 if len(node.children) == 3: 

564 first = node.children[0] 

565 last = node.children[-1] 

566 if (first.type == token.LSQB and last.type == token.RSQB) or ( 

567 first.type == token.LBRACE and last.type == token.RBRACE 

568 ): 

569 # Lists or sets of one item 

570 maybe_make_parens_invisible_in_atom( 

571 node.children[1], 

572 parent=node, 

573 mode=self.mode, 

574 features=self.features, 

575 ) 

576 

577 yield from self.visit_default(node) 

578 

579 def visit_fstring(self, node: Node) -> Iterator[Line]: 

580 # If the fstring was converted to a STANDALONE_COMMENT by 

581 # normalize_fmt_off (e.g. it was inside a # fmt: off block), 

582 # skip the fstring-to-string conversion and just visit normally. 

583 if any(child.type == STANDALONE_COMMENT for child in node.children): 

584 yield from self.visit_default(node) 

585 return 

586 # currently we don't want to format and split f-strings at all. 

587 string_leaf = fstring_tstring_to_string(node) 

588 node.replace(string_leaf) 

589 if "\\" in string_leaf.value and any( 

590 "\\" in str(child) 

591 for child in node.children 

592 if child.type == syms.fstring_replacement_field 

593 ): 

594 # string normalization doesn't account for nested quotes, 

595 # causing breakages. skip normalization when nested quotes exist 

596 yield from self.visit_default(string_leaf) 

597 return 

598 yield from self.visit_STRING(string_leaf) 

599 

600 def visit_tstring(self, node: Node) -> Iterator[Line]: 

601 # If the tstring was converted to a STANDALONE_COMMENT by 

602 # normalize_fmt_off, skip the conversion and just visit normally. 

603 if any(child.type == STANDALONE_COMMENT for child in node.children): 

604 yield from self.visit_default(node) 

605 return 

606 # currently we don't want to format and split t-strings at all. 

607 string_leaf = fstring_tstring_to_string(node) 

608 node.replace(string_leaf) 

609 if "\\" in string_leaf.value and any( 

610 "\\" in str(child) 

611 for child in node.children 

612 if child.type == syms.fstring_replacement_field 

613 ): 

614 # string normalization doesn't account for nested quotes, 

615 # causing breakages. skip normalization when nested quotes exist 

616 yield from self.visit_default(string_leaf) 

617 return 

618 yield from self.visit_STRING(string_leaf) 

619 

620 # TODO: Uncomment Implementation to format f-string children 

621 # fstring_start = node.children[0] 

622 # fstring_end = node.children[-1] 

623 # assert isinstance(fstring_start, Leaf) 

624 # assert isinstance(fstring_end, Leaf) 

625 

626 # quote_char = fstring_end.value[0] 

627 # quote_idx = fstring_start.value.index(quote_char) 

628 # prefix, quote = ( 

629 # fstring_start.value[:quote_idx], 

630 # fstring_start.value[quote_idx:] 

631 # ) 

632 

633 # if not is_docstring(node, self.mode): 

634 # prefix = normalize_string_prefix(prefix) 

635 

636 # assert quote == fstring_end.value 

637 

638 # is_raw_fstring = "r" in prefix or "R" in prefix 

639 # middles = [ 

640 # leaf 

641 # for leaf in node.leaves() 

642 # if leaf.type == token.FSTRING_MIDDLE 

643 # ] 

644 

645 # if self.mode.string_normalization: 

646 # middles, quote = normalize_fstring_quotes(quote, middles, is_raw_fstring) 

647 

648 # fstring_start.value = prefix + quote 

649 # fstring_end.value = quote 

650 

651 # yield from self.visit_default(node) 

652 

653 def visit_comp_for(self, node: Node) -> Iterator[Line]: 

654 if Preview.wrap_comprehension_in in self.mode: 

655 normalize_invisible_parens( 

656 node, parens_after={"in"}, mode=self.mode, features=self.features 

657 ) 

658 yield from self.visit_default(node) 

659 

660 def visit_old_comp_for(self, node: Node) -> Iterator[Line]: 

661 yield from self.visit_comp_for(node) 

662 

663 def __post_init__(self) -> None: 

664 """You are in a twisty little maze of passages.""" 

665 self.current_line = Line(mode=self.mode) 

666 

667 v = self.visit_stmt 

668 Ø: set[str] = set() 

669 self.visit_assert_stmt = partial(v, keywords={"assert"}, parens={"assert", ","}) 

670 self.visit_if_stmt = partial( 

671 v, keywords={"if", "else", "elif"}, parens={"if", "elif"} 

672 ) 

673 self.visit_while_stmt = partial(v, keywords={"while", "else"}, parens={"while"}) 

674 self.visit_for_stmt = partial(v, keywords={"for", "else"}, parens={"for", "in"}) 

675 self.visit_try_stmt = partial( 

676 v, keywords={"try", "except", "else", "finally"}, parens=Ø 

677 ) 

678 self.visit_except_clause = partial(v, keywords={"except"}, parens={"except"}) 

679 self.visit_with_stmt = partial(v, keywords={"with"}, parens={"with"}) 

680 self.visit_classdef = partial(v, keywords={"class"}, parens=Ø) 

681 

682 self.visit_expr_stmt = partial(v, keywords=Ø, parens=ASSIGNMENTS) 

683 self.visit_return_stmt = partial(v, keywords={"return"}, parens={"return"}) 

684 self.visit_import_from = partial(v, keywords=Ø, parens={"import"}) 

685 self.visit_del_stmt = partial(v, keywords=Ø, parens={"del"}) 

686 self.visit_async_funcdef = self.visit_async_stmt 

687 self.visit_decorated = self.visit_decorators 

688 

689 # PEP 634 

690 self.visit_match_stmt = self.visit_match_case 

691 self.visit_case_block = self.visit_match_case 

692 self.visit_guard = partial(v, keywords=Ø, parens={"if"}) 

693 

694 

695# Remove when `simplify_power_operator_hugging` becomes stable. 

696def _hugging_power_ops_line_to_string( 

697 line: Line, 

698 features: Collection[Feature], 

699 mode: Mode, 

700) -> str | None: 

701 try: 

702 return line_to_string(next(hug_power_op(line, features, mode))) 

703 except CannotTransform: 

704 return None 

705 

706 

707def transform_line( 

708 line: Line, mode: Mode, features: Collection[Feature] = () 

709) -> Iterator[Line]: 

710 """Transform a `line`, potentially splitting it into many lines. 

711 

712 They should fit in the allotted `line_length` but might not be able to. 

713 

714 `features` are syntactical features that may be used in the output. 

715 """ 

716 if line.is_comment: 

717 yield line 

718 return 

719 

720 line_str = line_to_string(line) 

721 

722 if Preview.simplify_power_operator_hugging in mode: 

723 line_str_hugging_power_ops = line_str 

724 else: 

725 # We need the line string when power operators are hugging to determine if we 

726 # should split the line. Default to line_str, if no power operator are present 

727 # on the line. 

728 line_str_hugging_power_ops = ( 

729 _hugging_power_ops_line_to_string(line, features, mode) or line_str 

730 ) 

731 

732 ll = mode.line_length 

733 sn = mode.string_normalization 

734 string_merge = StringMerger(ll, sn) 

735 string_paren_strip = StringParenStripper(ll, sn) 

736 string_split = StringSplitter(ll, sn) 

737 string_paren_wrap = StringParenWrapper(ll, sn) 

738 

739 transformers: list[Transformer] 

740 if ( 

741 not line.contains_uncollapsable_type_comments() 

742 and not line.should_split_rhs 

743 and not line.magic_trailing_comma 

744 and ( 

745 is_line_short_enough(line, mode=mode, line_str=line_str_hugging_power_ops) 

746 or line.contains_unsplittable_type_ignore() 

747 ) 

748 and not (line.inside_brackets and line.contains_standalone_comments()) 

749 and not line.contains_implicit_multiline_string_with_comments() 

750 ): 

751 # Only apply basic string preprocessing, since lines shouldn't be split here. 

752 if Preview.string_processing in mode: 

753 transformers = [string_merge, string_paren_strip] 

754 else: 

755 transformers = [] 

756 elif line.is_def and not should_split_funcdef_with_rhs(line, mode): 

757 transformers = [left_hand_split] 

758 else: 

759 

760 def _rhs( 

761 self: object, line: Line, features: Collection[Feature], mode: Mode 

762 ) -> Iterator[Line]: 

763 """Wraps calls to `right_hand_split`. 

764 

765 The calls increasingly `omit` right-hand trailers (bracket pairs with 

766 content), meaning the trailers get glued together to split on another 

767 bracket pair instead. 

768 """ 

769 for omit in generate_trailers_to_omit(line, mode.line_length): 

770 lines = list(right_hand_split(line, mode, features, omit=omit)) 

771 # Note: this check is only able to figure out if the first line of the 

772 # *current* transformation fits in the line length. This is true only 

773 # for simple cases. All others require running more transforms via 

774 # `transform_line()`. This check doesn't know if those would succeed. 

775 if is_line_short_enough(lines[0], mode=mode) or ( 

776 omit and _over_length_only_due_to_subscript_comment(lines[0], mode) 

777 ): 

778 yield from lines 

779 return 

780 

781 # All splits failed, best effort split with no omits. 

782 # This mostly happens to multiline strings that are by definition 

783 # reported as not fitting a single line, as well as lines that contain 

784 # trailing commas (those have to be exploded). 

785 yield from right_hand_split(line, mode, features=features) 

786 

787 # HACK: nested functions (like _rhs) compiled by mypyc don't retain their 

788 # __name__ attribute which is needed in `run_transformer` further down. 

789 # Unfortunately a nested class breaks mypyc too. So a class must be created 

790 # via type ... https://github.com/mypyc/mypyc/issues/884 

791 rhs = type("rhs", (), {"__call__": _rhs})() 

792 

793 if Preview.string_processing in mode: 

794 if line.inside_brackets: 

795 transformers = [ 

796 string_merge, 

797 string_paren_strip, 

798 string_split, 

799 delimiter_split, 

800 standalone_comment_split, 

801 string_paren_wrap, 

802 rhs, 

803 ] 

804 else: 

805 transformers = [ 

806 string_merge, 

807 string_paren_strip, 

808 string_split, 

809 string_paren_wrap, 

810 rhs, 

811 ] 

812 else: 

813 if line.inside_brackets: 

814 transformers = [delimiter_split, standalone_comment_split, rhs] 

815 else: 

816 transformers = [rhs] 

817 

818 if Preview.simplify_power_operator_hugging not in mode: 

819 # It's always safe to attempt hugging of power operations and pretty much every 

820 # line could match. 

821 transformers.append(hug_power_op) 

822 

823 for transform in transformers: 

824 # We are accumulating lines in `result` because we might want to abort 

825 # mission and return the original line in the end, or attempt a different 

826 # split altogether. 

827 try: 

828 result = run_transformer(line, transform, mode, features, line_str=line_str) 

829 except CannotTransform: 

830 continue 

831 else: 

832 yield from result 

833 break 

834 

835 else: 

836 yield line 

837 

838 

839def should_split_funcdef_with_rhs(line: Line, mode: Mode) -> bool: 

840 """If a funcdef has a magic trailing comma in the return type, then we should first 

841 split the line with rhs to respect the comma. 

842 """ 

843 return_type_leaves: list[Leaf] = [] 

844 in_return_type = False 

845 

846 for leaf in line.leaves: 

847 if leaf.type == token.COLON: 

848 in_return_type = False 

849 if in_return_type: 

850 return_type_leaves.append(leaf) 

851 if leaf.type == token.RARROW: 

852 in_return_type = True 

853 

854 # using `bracket_split_build_line` will mess with whitespace, so we duplicate a 

855 # couple lines from it. 

856 result = Line(mode=line.mode, depth=line.depth) 

857 leaves_to_track = get_leaves_inside_matching_brackets(return_type_leaves) 

858 for leaf in return_type_leaves: 

859 result.append( 

860 leaf, 

861 preformatted=True, 

862 track_bracket=id(leaf) in leaves_to_track, 

863 ) 

864 

865 # we could also return true if the line is too long, and the return type is longer 

866 # than the param list. Or if `should_split_rhs` returns True. 

867 return result.magic_trailing_comma is not None 

868 

869 

870class _BracketSplitComponent(Enum): 

871 head = auto() 

872 body = auto() 

873 tail = auto() 

874 

875 

876def left_hand_split( 

877 line: Line, _features: Collection[Feature], mode: Mode 

878) -> Iterator[Line]: 

879 """Split line into many lines, starting with the first matching bracket pair. 

880 

881 Note: this usually looks weird, only use this for function definitions. 

882 Prefer RHS otherwise. This is why this function is not symmetrical with 

883 :func:`right_hand_split` which also handles optional parentheses. 

884 """ 

885 for leaf_type in [token.LPAR, token.LSQB]: 

886 tail_leaves: list[Leaf] = [] 

887 body_leaves: list[Leaf] = [] 

888 head_leaves: list[Leaf] = [] 

889 current_leaves = head_leaves 

890 matching_bracket: Leaf | None = None 

891 depth = 0 

892 for index, leaf in enumerate(line.leaves): 

893 if index == 2 and leaf.type == token.LSQB: 

894 # A [ at index 2 means this is a type param, so start 

895 # tracking the depth 

896 depth += 1 

897 elif depth > 0: 

898 if leaf.type == token.LSQB: 

899 depth += 1 

900 elif leaf.type == token.RSQB: 

901 depth -= 1 

902 if ( 

903 current_leaves is body_leaves 

904 and leaf.type in CLOSING_BRACKETS 

905 and leaf.opening_bracket is matching_bracket 

906 and isinstance(matching_bracket, Leaf) 

907 # If the code is still on LPAR and we are inside a type 

908 # param, ignore the match since this is searching 

909 # for the function arguments 

910 and not (leaf_type == token.LPAR and depth > 0) 

911 ): 

912 ensure_visible(leaf) 

913 ensure_visible(matching_bracket) 

914 current_leaves = tail_leaves if body_leaves else head_leaves 

915 current_leaves.append(leaf) 

916 if current_leaves is head_leaves: 

917 if leaf.type == leaf_type and ( 

918 not (leaf_type == token.LPAR and depth > 0) 

919 ): 

920 matching_bracket = leaf 

921 current_leaves = body_leaves 

922 if matching_bracket and tail_leaves: 

923 break 

924 if not matching_bracket or not tail_leaves: 

925 raise CannotSplit("No brackets found") 

926 

927 head = bracket_split_build_line( 

928 head_leaves, line, matching_bracket, component=_BracketSplitComponent.head 

929 ) 

930 body = bracket_split_build_line( 

931 body_leaves, line, matching_bracket, component=_BracketSplitComponent.body 

932 ) 

933 tail = bracket_split_build_line( 

934 tail_leaves, line, matching_bracket, component=_BracketSplitComponent.tail 

935 ) 

936 bracket_split_succeeded_or_raise(head, body, tail) 

937 for result in (head, body, tail): 

938 if result: 

939 yield result 

940 

941 

942def right_hand_split( 

943 line: Line, 

944 mode: Mode, 

945 features: Collection[Feature] = (), 

946 omit: Collection[LeafID] = (), 

947) -> Iterator[Line]: 

948 """Split line into many lines, starting with the last matching bracket pair. 

949 

950 If the split was by optional parentheses, attempt splitting without them, too. 

951 `omit` is a collection of closing bracket IDs that shouldn't be considered for 

952 this split. 

953 

954 Note: running this function modifies `bracket_depth` on the leaves of `line`. 

955 """ 

956 rhs_result = _first_right_hand_split(line, omit=omit) 

957 yield from _maybe_split_omitting_optional_parens( 

958 rhs_result, line, mode, features=features, omit=omit 

959 ) 

960 

961 

962def _first_right_hand_split( 

963 line: Line, 

964 omit: Collection[LeafID] = (), 

965) -> RHSResult: 

966 """Split the line into head, body, tail starting with the last bracket pair. 

967 

968 Note: this function should not have side effects. It's relied upon by 

969 _maybe_split_omitting_optional_parens to get an opinion whether to prefer 

970 splitting on the right side of an assignment statement. 

971 """ 

972 tail_leaves: list[Leaf] = [] 

973 body_leaves: list[Leaf] = [] 

974 head_leaves: list[Leaf] = [] 

975 current_leaves = tail_leaves 

976 opening_bracket: Leaf | None = None 

977 closing_bracket: Leaf | None = None 

978 for leaf in reversed(line.leaves): 

979 if current_leaves is body_leaves: 

980 if leaf is opening_bracket: 

981 current_leaves = head_leaves if body_leaves else tail_leaves 

982 current_leaves.append(leaf) 

983 if current_leaves is tail_leaves: 

984 if leaf.type in CLOSING_BRACKETS and id(leaf) not in omit: 

985 opening_bracket = leaf.opening_bracket 

986 closing_bracket = leaf 

987 current_leaves = body_leaves 

988 if not (opening_bracket and closing_bracket and head_leaves): 

989 # If there is no opening or closing_bracket that means the split failed and 

990 # all content is in the tail. Otherwise, if `head_leaves` are empty, it means 

991 # the matching `opening_bracket` wasn't available on `line` anymore. 

992 raise CannotSplit("No brackets found") 

993 

994 tail_leaves.reverse() 

995 body_leaves.reverse() 

996 head_leaves.reverse() 

997 

998 body: Line | None = None 

999 if ( 

1000 Preview.hug_parens_with_braces_and_square_brackets in line.mode 

1001 and tail_leaves[0].value 

1002 and tail_leaves[0].opening_bracket is head_leaves[-1] 

1003 ): 

1004 inner_body_leaves = list(body_leaves) 

1005 hugged_opening_leaves: list[Leaf] = [] 

1006 hugged_closing_leaves: list[Leaf] = [] 

1007 is_unpacking = body_leaves[0].type in [token.STAR, token.DOUBLESTAR] 

1008 unpacking_offset: int = 1 if is_unpacking else 0 

1009 while ( 

1010 len(inner_body_leaves) >= 2 + unpacking_offset 

1011 and inner_body_leaves[-1].type in CLOSING_BRACKETS 

1012 and inner_body_leaves[-1].opening_bracket 

1013 is inner_body_leaves[unpacking_offset] 

1014 ): 

1015 if unpacking_offset: 

1016 hugged_opening_leaves.append(inner_body_leaves.pop(0)) 

1017 unpacking_offset = 0 

1018 hugged_opening_leaves.append(inner_body_leaves.pop(0)) 

1019 hugged_closing_leaves.insert(0, inner_body_leaves.pop()) 

1020 

1021 if hugged_opening_leaves and inner_body_leaves: 

1022 inner_body = bracket_split_build_line( 

1023 inner_body_leaves, 

1024 line, 

1025 hugged_opening_leaves[-1], 

1026 component=_BracketSplitComponent.body, 

1027 ) 

1028 if ( 

1029 line.mode.magic_trailing_comma 

1030 and inner_body_leaves[-1].type == token.COMMA 

1031 ): 

1032 should_hug = True 

1033 else: 

1034 line_length = line.mode.line_length - sum( 

1035 len(str(leaf)) 

1036 for leaf in hugged_opening_leaves + hugged_closing_leaves 

1037 ) 

1038 if is_line_short_enough( 

1039 inner_body, mode=replace(line.mode, line_length=line_length) 

1040 ): 

1041 # Do not hug if it fits on a single line. 

1042 should_hug = False 

1043 else: 

1044 should_hug = True 

1045 if should_hug: 

1046 body_leaves = inner_body_leaves 

1047 head_leaves.extend(hugged_opening_leaves) 

1048 tail_leaves = hugged_closing_leaves + tail_leaves 

1049 body = inner_body # No need to re-calculate the body again later. 

1050 

1051 head = bracket_split_build_line( 

1052 head_leaves, line, opening_bracket, component=_BracketSplitComponent.head 

1053 ) 

1054 if body is None: 

1055 body = bracket_split_build_line( 

1056 body_leaves, line, opening_bracket, component=_BracketSplitComponent.body 

1057 ) 

1058 tail = bracket_split_build_line( 

1059 tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail 

1060 ) 

1061 bracket_split_succeeded_or_raise(head, body, tail) 

1062 return RHSResult(head, body, tail, opening_bracket, closing_bracket) 

1063 

1064 

1065def _maybe_split_omitting_optional_parens( 

1066 rhs: RHSResult, 

1067 line: Line, 

1068 mode: Mode, 

1069 features: Collection[Feature] = (), 

1070 omit: Collection[LeafID] = (), 

1071) -> Iterator[Line]: 

1072 if ( 

1073 Feature.FORCE_OPTIONAL_PARENTHESES not in features 

1074 # the opening bracket is an optional paren 

1075 and rhs.opening_bracket.type == token.LPAR 

1076 and not rhs.opening_bracket.value 

1077 # the closing bracket is an optional paren 

1078 and rhs.closing_bracket.type == token.RPAR 

1079 and not rhs.closing_bracket.value 

1080 # it's not an import (optional parens are the only thing we can split on 

1081 # in this case; attempting a split without them is a waste of time) 

1082 and not line.is_import 

1083 # and we can actually remove the parens 

1084 and can_omit_invisible_parens(rhs, mode.line_length) 

1085 ): 

1086 omit = {id(rhs.closing_bracket), *omit} 

1087 try: 

1088 # The RHSResult Omitting Optional Parens. 

1089 rhs_oop = _first_right_hand_split(line, omit=omit) 

1090 if _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode): 

1091 yield from _maybe_split_omitting_optional_parens( 

1092 rhs_oop, line, mode, features=features, omit=omit 

1093 ) 

1094 return 

1095 

1096 except CannotSplit as e: 

1097 # For chained assignments we want to use the previous successful split 

1098 if line.is_chained_assignment: 

1099 pass 

1100 

1101 elif ( 

1102 not can_be_split(rhs.body) 

1103 and not is_line_short_enough(rhs.body, mode=mode) 

1104 and not ( 

1105 Preview.wrap_long_dict_values_in_parens 

1106 and rhs.opening_bracket.parent 

1107 and rhs.opening_bracket.parent.parent 

1108 and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker 

1109 ) 

1110 and not ( 

1111 rhs.opening_bracket.parent 

1112 and rhs.opening_bracket.parent.parent 

1113 and rhs.opening_bracket.parent.parent.type == syms.case_block 

1114 ) 

1115 ): 

1116 raise CannotSplit( 

1117 "Splitting failed, body is still too long and can't be split." 

1118 ) from e 

1119 

1120 elif ( 

1121 rhs.head.contains_multiline_strings() 

1122 or rhs.tail.contains_multiline_strings() 

1123 ): 

1124 raise CannotSplit( 

1125 "The current optional pair of parentheses is bound to fail to" 

1126 " satisfy the splitting algorithm because the head or the tail" 

1127 " contains multiline strings which by definition never fit one" 

1128 " line." 

1129 ) from e 

1130 

1131 ensure_visible(rhs.opening_bracket) 

1132 ensure_visible(rhs.closing_bracket) 

1133 for result in (rhs.head, rhs.body, rhs.tail): 

1134 if result: 

1135 yield result 

1136 

1137 

1138def _prefer_split_rhs_oop_over_rhs( 

1139 rhs_oop: RHSResult, rhs: RHSResult, mode: Mode 

1140) -> bool: 

1141 """ 

1142 Returns whether we should prefer the result from a split omitting optional parens 

1143 (rhs_oop) over the original (rhs). 

1144 """ 

1145 # contains unsplittable type ignore 

1146 if ( 

1147 rhs_oop.head.contains_unsplittable_type_ignore() 

1148 or rhs_oop.body.contains_unsplittable_type_ignore() 

1149 or rhs_oop.tail.contains_unsplittable_type_ignore() 

1150 ): 

1151 return True 

1152 

1153 # Retain optional parens around dictionary values 

1154 if ( 

1155 Preview.wrap_long_dict_values_in_parens 

1156 and rhs.opening_bracket.parent 

1157 and rhs.opening_bracket.parent.parent 

1158 and rhs.opening_bracket.parent.parent.type == syms.dictsetmaker 

1159 and rhs.body.bracket_tracker.delimiters 

1160 ): 

1161 # Unless the split is inside the key 

1162 return any(leaf.type == token.COLON for leaf in rhs_oop.tail.leaves) 

1163 

1164 # the split is right after `=` 

1165 if not (len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL): 

1166 return True 

1167 

1168 # the left side of assignment contains brackets 

1169 if not any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]): 

1170 return True 

1171 

1172 # the left side of assignment is short enough (the -1 is for the ending optional 

1173 # paren) 

1174 if not is_line_short_enough( 

1175 rhs.head, mode=replace(mode, line_length=mode.line_length - 1) 

1176 ): 

1177 return True 

1178 

1179 # the left side of assignment won't explode further because of magic trailing comma 

1180 if rhs.head.magic_trailing_comma is not None: 

1181 return True 

1182 

1183 # If we have multiple targets, we prefer more `=`s on the head vs pushing them to 

1184 # the body 

1185 rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL) 

1186 rhs_oop_head_equal_count = [leaf.type for leaf in rhs_oop.head.leaves].count( 

1187 token.EQUAL 

1188 ) 

1189 if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count: 

1190 return False 

1191 

1192 has_closing_bracket_after_assign = False 

1193 for leaf in reversed(rhs_oop.head.leaves): 

1194 if leaf.type == token.EQUAL: 

1195 break 

1196 if leaf.type in CLOSING_BRACKETS: 

1197 has_closing_bracket_after_assign = True 

1198 break 

1199 return ( 

1200 # contains matching brackets after the `=` (done by checking there is a 

1201 # closing bracket) 

1202 has_closing_bracket_after_assign 

1203 or ( 

1204 # the split is actually from inside the optional parens (done by checking 

1205 # the first line still contains the `=`) 

1206 any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves) 

1207 # the first line is short enough 

1208 and is_line_short_enough(rhs_oop.head, mode=mode) 

1209 ) 

1210 ) 

1211 

1212 

1213def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None: 

1214 """Raise :exc:`CannotSplit` if the last left- or right-hand split failed. 

1215 

1216 Do nothing otherwise. 

1217 

1218 A left- or right-hand split is based on a pair of brackets. Content before 

1219 (and including) the opening bracket is left on one line, content inside the 

1220 brackets is put on a separate line, and finally content starting with and 

1221 following the closing bracket is put on a separate line. 

1222 

1223 Those are called `head`, `body`, and `tail`, respectively. If the split 

1224 produced the same line (all content in `head`) or ended up with an empty `body` 

1225 and the `tail` is just the closing bracket, then it's considered failed. 

1226 """ 

1227 tail_len = len(str(tail).strip()) 

1228 if not body: 

1229 if tail_len == 0: 

1230 raise CannotSplit("Splitting brackets produced the same line") 

1231 

1232 elif tail_len < 3: 

1233 raise CannotSplit( 

1234 f"Splitting brackets on an empty body to save {tail_len} characters is" 

1235 " not worth it" 

1236 ) 

1237 

1238 

1239def _ensure_trailing_comma( 

1240 leaves: list[Leaf], original: Line, opening_bracket: Leaf 

1241) -> bool: 

1242 if not leaves: 

1243 return False 

1244 # Ensure a trailing comma for imports 

1245 if original.is_import: 

1246 return True 

1247 # ...and standalone function arguments 

1248 if not original.is_def: 

1249 return False 

1250 if opening_bracket.value != "(": 

1251 return False 

1252 # Don't add commas if we already have any commas 

1253 if any( 

1254 leaf.type == token.COMMA and not is_part_of_annotation(leaf) for leaf in leaves 

1255 ): 

1256 return False 

1257 

1258 # Find a leaf with a parent (comments don't have parents) 

1259 leaf_with_parent = next((leaf for leaf in leaves if leaf.parent), None) 

1260 if leaf_with_parent is None: 

1261 return True 

1262 # Don't add commas inside parenthesized return annotations 

1263 if get_annotation_type(leaf_with_parent) == "return": 

1264 return False 

1265 # Don't add commas inside PEP 604 unions 

1266 if ( 

1267 leaf_with_parent.parent 

1268 and leaf_with_parent.parent.next_sibling 

1269 and leaf_with_parent.parent.next_sibling.type == token.VBAR 

1270 ): 

1271 return False 

1272 return True 

1273 

1274 

1275def bracket_split_build_line( 

1276 leaves: list[Leaf], 

1277 original: Line, 

1278 opening_bracket: Leaf, 

1279 *, 

1280 component: _BracketSplitComponent, 

1281) -> Line: 

1282 """Return a new line with given `leaves` and respective comments from `original`. 

1283 

1284 If it's the head component, brackets will be tracked so trailing commas are 

1285 respected. 

1286 

1287 If it's the body component, the result line is one-indented inside brackets and as 

1288 such has its first leaf's prefix normalized and a trailing comma added when 

1289 expected. 

1290 """ 

1291 result = Line(mode=original.mode, depth=original.depth) 

1292 if component is _BracketSplitComponent.body: 

1293 result.inside_brackets = True 

1294 result.depth += 1 

1295 if _ensure_trailing_comma(leaves, original, opening_bracket): 

1296 for i in range(len(leaves) - 1, -1, -1): 

1297 if leaves[i].type == STANDALONE_COMMENT: 

1298 continue 

1299 

1300 if leaves[i].type != token.COMMA: 

1301 new_comma = Leaf(token.COMMA, ",") 

1302 leaves.insert(i + 1, new_comma) 

1303 break 

1304 

1305 leaves_to_track: set[LeafID] = set() 

1306 if component is _BracketSplitComponent.head: 

1307 leaves_to_track = get_leaves_inside_matching_brackets(leaves) 

1308 # Populate the line 

1309 for leaf in leaves: 

1310 result.append( 

1311 leaf, 

1312 preformatted=True, 

1313 track_bracket=id(leaf) in leaves_to_track, 

1314 ) 

1315 for comment_after in original.comments_after(leaf): 

1316 result.append(comment_after, preformatted=True) 

1317 if component is _BracketSplitComponent.body and should_split_line( 

1318 result, opening_bracket 

1319 ): 

1320 result.should_split_rhs = True 

1321 return result 

1322 

1323 

1324def dont_increase_indentation(split_func: Transformer) -> Transformer: 

1325 """Normalize prefix of the first leaf in every line returned by `split_func`. 

1326 

1327 This is a decorator over relevant split functions. 

1328 """ 

1329 

1330 @wraps(split_func) 

1331 def split_wrapper( 

1332 line: Line, features: Collection[Feature], mode: Mode 

1333 ) -> Iterator[Line]: 

1334 for split_line in split_func(line, features, mode): 

1335 split_line.leaves[0].prefix = "" 

1336 yield split_line 

1337 

1338 return split_wrapper 

1339 

1340 

1341def _get_last_non_comment_leaf(line: Line) -> int | None: 

1342 for leaf_idx in range(len(line.leaves) - 1, 0, -1): 

1343 if line.leaves[leaf_idx].type != STANDALONE_COMMENT: 

1344 return leaf_idx 

1345 return None 

1346 

1347 

1348def _can_add_trailing_comma(leaf: Leaf, features: Collection[Feature]) -> bool: 

1349 if is_vararg(leaf, within={syms.typedargslist}): 

1350 return Feature.TRAILING_COMMA_IN_DEF in features 

1351 if is_vararg(leaf, within={syms.arglist, syms.argument}): 

1352 return Feature.TRAILING_COMMA_IN_CALL in features 

1353 return True 

1354 

1355 

1356def _safe_add_trailing_comma(safe: bool, delimiter_priority: int, line: Line) -> Line: 

1357 if ( 

1358 safe 

1359 and delimiter_priority == COMMA_PRIORITY 

1360 and line.leaves[-1].type != token.COMMA 

1361 and line.leaves[-1].type != STANDALONE_COMMENT 

1362 ): 

1363 new_comma = Leaf(token.COMMA, ",") 

1364 line.append(new_comma) 

1365 return line 

1366 

1367 

1368MIGRATE_COMMENT_DELIMITERS = {STRING_PRIORITY, COMMA_PRIORITY} 

1369 

1370 

1371def _can_defer_lone_comparator_to_rhs(line: Line, mode: Mode) -> bool: 

1372 """Return True if the lone comparator on `line` can defer to right_hand_split. 

1373 

1374 Caller has already established exactly one delimiter at 

1375 `COMPARATOR_PRIORITY`. We defer only when: 

1376 

1377 - the LHS up to the comparator has no opening brackets, so the existing 

1378 "break before the comparator" wouldn't produce a balanced two-sided 

1379 split anyway, and 

1380 - `right_hand_split` would produce a head that fits in the line length, 

1381 so we don't strand `if t` on its own line just to push it back onto an 

1382 overflowing single line when the RHS bracket can't be exploded 

1383 usefully (e.g. an empty `decode()` paren). 

1384 """ 

1385 past_comparator = False 

1386 for leaf in line.leaves: 

1387 if leaf.type in OPENING_BRACKETS and not past_comparator: 

1388 return False 

1389 if not past_comparator and ( 

1390 line.bracket_tracker.delimiters.get(id(leaf)) == COMPARATOR_PRIORITY 

1391 ): 

1392 past_comparator = True 

1393 try: 

1394 rhs = _first_right_hand_split(line) 

1395 except CannotSplit: 

1396 return False 

1397 return is_line_short_enough(rhs.head, mode=mode) 

1398 

1399 

1400@dont_increase_indentation 

1401def delimiter_split( 

1402 line: Line, features: Collection[Feature], mode: Mode 

1403) -> Iterator[Line]: 

1404 """Split according to delimiters of the highest priority. 

1405 

1406 If the appropriate Features are given, the split will add trailing commas 

1407 also in function signatures and calls that contain `*` and `**`. 

1408 """ 

1409 if len(line.leaves) == 0: 

1410 raise CannotSplit("Line empty") from None 

1411 last_leaf = line.leaves[-1] 

1412 

1413 bt = line.bracket_tracker 

1414 try: 

1415 delimiter_priority = bt.max_delimiter_priority(exclude={id(last_leaf)}) 

1416 except ValueError: 

1417 raise CannotSplit("No delimiters found") from None 

1418 

1419 if ( 

1420 delimiter_priority == DOT_PRIORITY 

1421 and bt.delimiter_count_with_priority(delimiter_priority) == 1 

1422 ): 

1423 raise CannotSplit("Splitting a single attribute from its owner looks wrong") 

1424 

1425 if ( 

1426 Preview.hug_comparator in mode 

1427 and delimiter_priority == COMPARATOR_PRIORITY 

1428 and bt.delimiter_count_with_priority(delimiter_priority) == 1 

1429 and _can_defer_lone_comparator_to_rhs(line, mode) 

1430 ): 

1431 raise CannotSplit("Bracketed RHS will explode via right_hand_split") 

1432 

1433 current_line = Line( 

1434 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1435 ) 

1436 lowest_depth = sys.maxsize 

1437 trailing_comma_safe = True 

1438 

1439 def append_to_line(leaf: Leaf) -> Iterator[Line]: 

1440 """Append `leaf` to current line or to new line if appending impossible.""" 

1441 nonlocal current_line 

1442 try: 

1443 current_line.append_safe(leaf, preformatted=True) 

1444 except ValueError: 

1445 yield current_line 

1446 

1447 current_line = Line( 

1448 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1449 ) 

1450 current_line.append(leaf) 

1451 

1452 def append_comments(leaf: Leaf) -> Iterator[Line]: 

1453 for comment_after in line.comments_after(leaf): 

1454 yield from append_to_line(comment_after) 

1455 

1456 last_non_comment_leaf = _get_last_non_comment_leaf(line) 

1457 for leaf_idx, leaf in enumerate(line.leaves): 

1458 yield from append_to_line(leaf) 

1459 

1460 previous_priority = leaf_idx > 0 and bt.delimiters.get( 

1461 id(line.leaves[leaf_idx - 1]) 

1462 ) 

1463 if ( 

1464 previous_priority != delimiter_priority 

1465 or delimiter_priority in MIGRATE_COMMENT_DELIMITERS 

1466 ): 

1467 yield from append_comments(leaf) 

1468 

1469 lowest_depth = min(lowest_depth, leaf.bracket_depth) 

1470 if trailing_comma_safe and leaf.bracket_depth == lowest_depth: 

1471 trailing_comma_safe = _can_add_trailing_comma(leaf, features) 

1472 

1473 if last_leaf.type == STANDALONE_COMMENT and leaf_idx == last_non_comment_leaf: 

1474 current_line = _safe_add_trailing_comma( 

1475 trailing_comma_safe, delimiter_priority, current_line 

1476 ) 

1477 

1478 leaf_priority = bt.delimiters.get(id(leaf)) 

1479 if leaf_priority == delimiter_priority: 

1480 if ( 

1481 leaf_idx + 1 < len(line.leaves) 

1482 and delimiter_priority not in MIGRATE_COMMENT_DELIMITERS 

1483 ): 

1484 yield from append_comments(line.leaves[leaf_idx + 1]) 

1485 

1486 yield current_line 

1487 current_line = Line( 

1488 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1489 ) 

1490 

1491 if current_line: 

1492 current_line = _safe_add_trailing_comma( 

1493 trailing_comma_safe, delimiter_priority, current_line 

1494 ) 

1495 yield current_line 

1496 

1497 

1498@dont_increase_indentation 

1499def standalone_comment_split( 

1500 line: Line, features: Collection[Feature], mode: Mode 

1501) -> Iterator[Line]: 

1502 """Split standalone comments from the rest of the line.""" 

1503 if not line.contains_standalone_comments(): 

1504 raise CannotSplit("Line does not have any standalone comments") 

1505 

1506 current_line = Line( 

1507 mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1508 ) 

1509 

1510 def append_to_line(leaf: Leaf) -> Iterator[Line]: 

1511 """Append `leaf` to current line or to new line if appending impossible.""" 

1512 nonlocal current_line 

1513 try: 

1514 current_line.append_safe(leaf, preformatted=True) 

1515 except ValueError: 

1516 yield current_line 

1517 

1518 current_line = Line( 

1519 line.mode, depth=line.depth, inside_brackets=line.inside_brackets 

1520 ) 

1521 current_line.append(leaf) 

1522 

1523 for leaf in line.leaves: 

1524 yield from append_to_line(leaf) 

1525 

1526 for comment_after in line.comments_after(leaf): 

1527 yield from append_to_line(comment_after) 

1528 

1529 if current_line: 

1530 yield current_line 

1531 

1532 

1533def normalize_invisible_parens( 

1534 node: Node, parens_after: set[str], *, mode: Mode, features: Collection[Feature] 

1535) -> None: 

1536 """Make existing optional parentheses invisible or create new ones. 

1537 

1538 `parens_after` is a set of string leaf values immediately after which parens 

1539 should be put. 

1540 

1541 Standardizes on visible parentheses for single-element tuples, and keeps 

1542 existing visible parentheses for other tuples and generator expressions. 

1543 """ 

1544 for pc in list_comments(node.prefix, is_endmarker=False, mode=mode): 

1545 if contains_fmt_directive(pc.value, FMT_OFF): 

1546 # This `node` has a prefix with `# fmt: off`, don't mess with parens. 

1547 return 

1548 

1549 # The multiple context managers grammar has a different pattern, thus this is 

1550 # separate from the for-loop below. This possibly wraps them in invisible parens, 

1551 # and later will be removed in remove_with_parens when needed. 

1552 if node.type == syms.with_stmt: 

1553 _maybe_wrap_cms_in_parens(node, mode, features) 

1554 

1555 check_lpar = False 

1556 for index, child in enumerate(list(node.children)): 

1557 # Fixes a bug where invisible parens are not properly stripped from 

1558 # assignment statements that contain type annotations. 

1559 if isinstance(child, Node) and child.type == syms.annassign: 

1560 normalize_invisible_parens( 

1561 child, parens_after=parens_after, mode=mode, features=features 

1562 ) 

1563 

1564 # Fixes a bug where invisible parens are not properly wrapped around 

1565 # case blocks. 

1566 if isinstance(child, Node) and child.type == syms.case_block: 

1567 normalize_invisible_parens( 

1568 child, parens_after={"case"}, mode=mode, features=features 

1569 ) 

1570 

1571 # Add parentheses around if guards in case blocks 

1572 if isinstance(child, Node) and child.type == syms.guard: 

1573 normalize_invisible_parens( 

1574 child, parens_after={"if"}, mode=mode, features=features 

1575 ) 

1576 

1577 # Add parentheses around long tuple unpacking in assignments. 

1578 if ( 

1579 index == 0 

1580 and isinstance(child, Node) 

1581 and child.type == syms.testlist_star_expr 

1582 ): 

1583 check_lpar = True 

1584 

1585 if ( 

1586 index == 0 

1587 and isinstance(child, Node) 

1588 and child.type == syms.atom 

1589 and node.type == syms.expr_stmt 

1590 and not _atom_has_magic_trailing_comma(child, mode) 

1591 and not _is_atom_multiline(child) 

1592 ): 

1593 if maybe_make_parens_invisible_in_atom( 

1594 child, 

1595 parent=node, 

1596 mode=mode, 

1597 features=features, 

1598 remove_brackets_around_comma=True, 

1599 allow_star_expr=True, 

1600 ): 

1601 wrap_in_parentheses(node, child, visible=False) 

1602 

1603 if check_lpar: 

1604 if ( 

1605 child.type == syms.atom 

1606 and node.type == syms.for_stmt 

1607 and isinstance(child.prev_sibling, Leaf) 

1608 and child.prev_sibling.type == token.NAME 

1609 and child.prev_sibling.value == "for" 

1610 ): 

1611 if maybe_make_parens_invisible_in_atom( 

1612 child, 

1613 parent=node, 

1614 mode=mode, 

1615 features=features, 

1616 remove_brackets_around_comma=True, 

1617 ): 

1618 wrap_in_parentheses(node, child, visible=False) 

1619 elif isinstance(child, Node) and node.type == syms.with_stmt: 

1620 remove_with_parens(child, node, mode=mode, features=features) 

1621 elif child.type == syms.atom and not ( 

1622 "in" in parens_after 

1623 and len(child.children) == 3 

1624 and is_lpar_token(child.children[0]) 

1625 and is_rpar_token(child.children[-1]) 

1626 and child.children[1].type == syms.test 

1627 ): 

1628 if maybe_make_parens_invisible_in_atom( 

1629 child, parent=node, mode=mode, features=features 

1630 ): 

1631 wrap_in_parentheses(node, child, visible=False) 

1632 elif is_one_tuple(child): 

1633 wrap_in_parentheses(node, child, visible=True) 

1634 elif node.type == syms.import_from: 

1635 _normalize_import_from(node, child, index) 

1636 break 

1637 elif ( 

1638 index == 1 

1639 and child.type == token.STAR 

1640 and node.type == syms.except_clause 

1641 ): 

1642 # In except* (PEP 654), the star is actually part of 

1643 # of the keyword. So we need to skip the insertion of 

1644 # invisible parentheses to work more precisely. 

1645 continue 

1646 

1647 elif ( 

1648 isinstance(child, Leaf) 

1649 and child.next_sibling is not None 

1650 and child.next_sibling.type == token.COLON 

1651 and child.value == "case" 

1652 ): 

1653 # A special patch for "case case:" scenario, the second occurrence 

1654 # of case will be not parsed as a Python keyword. 

1655 break 

1656 

1657 elif isinstance(child, Node) and child.type == syms.guard: 

1658 # Guard nodes handle their own inner wrapping. Wrapping the guard 

1659 # itself can produce invalid output when the case pattern splits. 

1660 pass 

1661 

1662 elif not is_multiline_string(child): 

1663 if ( 

1664 Preview.fix_if_guard_explosion_in_case_statement in mode 

1665 and node.type == syms.guard 

1666 ): 

1667 mock_line = Line(mode=mode) 

1668 for leaf in child.leaves(): 

1669 mock_line.append(leaf) 

1670 # If it's a guard AND it's short, we DON'T wrap 

1671 if not is_line_short_enough(mock_line, mode=mode): 

1672 wrap_in_parentheses(node, child, visible=False) 

1673 else: 

1674 wrap_in_parentheses(node, child, visible=False) 

1675 

1676 comma_check = child.type == token.COMMA 

1677 

1678 check_lpar = isinstance(child, Leaf) and ( 

1679 child.value in parens_after or comma_check 

1680 ) 

1681 

1682 

1683def _normalize_import_from(parent: Node, child: LN, index: int) -> None: 

1684 # "import from" nodes store parentheses directly as part of 

1685 # the statement 

1686 if is_lpar_token(child): 

1687 assert is_rpar_token(parent.children[-1]) 

1688 # make parentheses invisible 

1689 child.value = "" 

1690 parent.children[-1].value = "" 

1691 elif child.type != token.STAR: 

1692 # insert invisible parentheses 

1693 parent.insert_child(index, Leaf(token.LPAR, "")) 

1694 parent.append_child(Leaf(token.RPAR, "")) 

1695 

1696 

1697def remove_await_parens(node: Node, mode: Mode, features: Collection[Feature]) -> None: 

1698 if node.children[0].type == token.AWAIT and len(node.children) > 1: 

1699 if ( 

1700 node.children[1].type == syms.atom 

1701 and node.children[1].children[0].type == token.LPAR 

1702 ): 

1703 if maybe_make_parens_invisible_in_atom( 

1704 node.children[1], 

1705 parent=node, 

1706 mode=mode, 

1707 features=features, 

1708 remove_brackets_around_comma=True, 

1709 ): 

1710 wrap_in_parentheses(node, node.children[1], visible=False) 

1711 

1712 # Since await is an expression we shouldn't remove 

1713 # brackets in cases where this would change 

1714 # the AST due to operator precedence. 

1715 # Therefore we only aim to remove brackets around 

1716 # power nodes that aren't also await expressions themselves. 

1717 # https://peps.python.org/pep-0492/#updated-operator-precedence-table 

1718 # N.B. We've still removed any redundant nested brackets though :) 

1719 opening_bracket = cast(Leaf, node.children[1].children[0]) 

1720 closing_bracket = cast(Leaf, node.children[1].children[-1]) 

1721 bracket_contents = node.children[1].children[1] 

1722 if isinstance(bracket_contents, Node) and ( 

1723 bracket_contents.type != syms.power 

1724 or bracket_contents.children[0].type == token.AWAIT 

1725 or any( 

1726 isinstance(child, Leaf) and child.type == token.DOUBLESTAR 

1727 for child in bracket_contents.children 

1728 ) 

1729 ): 

1730 ensure_visible(opening_bracket) 

1731 ensure_visible(closing_bracket) 

1732 

1733 

1734def _maybe_wrap_cms_in_parens( 

1735 node: Node, mode: Mode, features: Collection[Feature] 

1736) -> None: 

1737 """When enabled and safe, wrap the multiple context managers in invisible parens. 

1738 

1739 It is only safe when `features` contain Feature.PARENTHESIZED_CONTEXT_MANAGERS. 

1740 """ 

1741 if ( 

1742 Feature.PARENTHESIZED_CONTEXT_MANAGERS not in features 

1743 or len(node.children) <= 2 

1744 # If it's an atom, it's already wrapped in parens. 

1745 or node.children[1].type == syms.atom 

1746 ): 

1747 return 

1748 colon_index: int | None = None 

1749 for i in range(2, len(node.children)): 

1750 if node.children[i].type == token.COLON: 

1751 colon_index = i 

1752 break 

1753 if colon_index is not None: 

1754 lpar = Leaf(token.LPAR, "") 

1755 rpar = Leaf(token.RPAR, "") 

1756 context_managers = node.children[1:colon_index] 

1757 for child in context_managers: 

1758 child.remove() 

1759 # After wrapping, the with_stmt will look like this: 

1760 # with_stmt 

1761 # NAME 'with' 

1762 # atom 

1763 # LPAR '' 

1764 # testlist_gexp 

1765 # ... <-- context_managers 

1766 # /testlist_gexp 

1767 # RPAR '' 

1768 # /atom 

1769 # COLON ':' 

1770 new_child = Node( 

1771 syms.atom, [lpar, Node(syms.testlist_gexp, context_managers), rpar] 

1772 ) 

1773 node.insert_child(1, new_child) 

1774 

1775 

1776def remove_with_parens( 

1777 node: Node, parent: Node, mode: Mode, features: Collection[Feature] 

1778) -> None: 

1779 """Recursively hide optional parens in `with` statements.""" 

1780 # Removing all unnecessary parentheses in with statements in one pass is a tad 

1781 # complex as different variations of bracketed statements result in pretty 

1782 # different parse trees: 

1783 # 

1784 # with (open("file")) as f: # this is an asexpr_test 

1785 # ... 

1786 # 

1787 # with (open("file") as f): # this is an atom containing an 

1788 # ... # asexpr_test 

1789 # 

1790 # with (open("file")) as f, (open("file")) as f: # this is asexpr_test, COMMA, 

1791 # ... # asexpr_test 

1792 # 

1793 # with (open("file") as f, open("file") as f): # an atom containing a 

1794 # ... # testlist_gexp which then 

1795 # # contains multiple asexpr_test(s) 

1796 if node.type == syms.atom: 

1797 if maybe_make_parens_invisible_in_atom( 

1798 node, 

1799 parent=parent, 

1800 mode=mode, 

1801 features=features, 

1802 remove_brackets_around_comma=True, 

1803 ): 

1804 wrap_in_parentheses(parent, node, visible=False) 

1805 if isinstance(node.children[1], Node): 

1806 remove_with_parens(node.children[1], node, mode=mode, features=features) 

1807 elif node.type == syms.testlist_gexp: 

1808 for child in node.children: 

1809 if isinstance(child, Node): 

1810 remove_with_parens(child, node, mode=mode, features=features) 

1811 elif node.type == syms.asexpr_test and not any( 

1812 leaf.type == token.COLONEQUAL for leaf in node.leaves() 

1813 ): 

1814 if maybe_make_parens_invisible_in_atom( 

1815 node.children[0], 

1816 parent=node, 

1817 mode=mode, 

1818 features=features, 

1819 remove_brackets_around_comma=True, 

1820 ): 

1821 wrap_in_parentheses(node, node.children[0], visible=False) 

1822 

1823 

1824def _atom_has_magic_trailing_comma(node: LN, mode: Mode) -> bool: 

1825 """Check if an atom node has a magic trailing comma. 

1826 

1827 Returns True for single-element tuples with trailing commas like (a,), 

1828 which should be preserved to maintain their tuple type. 

1829 """ 

1830 if not mode.magic_trailing_comma: 

1831 return False 

1832 

1833 return is_one_tuple(node) 

1834 

1835 

1836def _is_atom_multiline(node: LN) -> bool: 

1837 """Check if an atom node is multiline (indicating intentional formatting).""" 

1838 if not isinstance(node, Node) or len(node.children) < 3: 

1839 return False 

1840 

1841 # Check the middle child (between LPAR and RPAR) for newlines in its subtree 

1842 # The first child's prefix contains blank lines/comments before the opening paren 

1843 middle = node.children[1] 

1844 for child in middle.pre_order(): 

1845 if isinstance(child, Leaf) and "\n" in child.prefix: 

1846 return True 

1847 

1848 return False 

1849 

1850 

1851def maybe_make_parens_invisible_in_atom( 

1852 node: LN, 

1853 parent: LN, 

1854 mode: Mode, 

1855 features: Collection[Feature], 

1856 remove_brackets_around_comma: bool = False, 

1857 allow_star_expr: bool = False, 

1858) -> bool: 

1859 """If it's safe, make the parens in the atom `node` invisible, recursively. 

1860 Additionally, remove repeated, adjacent invisible parens from the atom `node` 

1861 as they are redundant. 

1862 

1863 Returns whether the node should itself be wrapped in invisible parentheses. 

1864 """ 

1865 if ( 

1866 node.type not in (syms.atom, syms.expr) 

1867 or is_empty_tuple(node) 

1868 or is_one_tuple(node) 

1869 or (is_tuple(node) and parent.type == syms.asexpr_test) 

1870 or ( 

1871 is_tuple(node) 

1872 and parent.type == syms.with_stmt 

1873 and has_sibling_with_type(node, token.COMMA) 

1874 ) 

1875 or (is_yield(node) and parent.type != syms.expr_stmt) 

1876 or ( 

1877 # This condition tries to prevent removing non-optional brackets 

1878 # around a tuple, however, can be a bit overzealous so we provide 

1879 # and option to skip this check for `for` and `with` statements. 

1880 not remove_brackets_around_comma 

1881 and max_delimiter_priority_in_atom(node) >= COMMA_PRIORITY 

1882 # Remove parentheses around multiple exception types in except and 

1883 # except* without as. See PEP 758 for details. 

1884 and not ( 

1885 Feature.UNPARENTHESIZED_EXCEPT_TYPES in features 

1886 # is a tuple 

1887 and is_tuple(node) 

1888 # has a parent node 

1889 and node.parent is not None 

1890 # parent is an except clause 

1891 and node.parent.type == syms.except_clause 

1892 # is not immediately followed by as clause 

1893 and not ( 

1894 node.next_sibling is not None 

1895 and is_name_token(node.next_sibling) 

1896 and node.next_sibling.value == "as" 

1897 ) 

1898 ) 

1899 ) 

1900 or is_tuple_containing_walrus(node) 

1901 or (not allow_star_expr and is_tuple_containing_star(node)) 

1902 or is_generator(node) 

1903 ): 

1904 return False 

1905 

1906 if is_walrus_assignment(node): 

1907 if parent.type in [ 

1908 syms.annassign, 

1909 syms.expr_stmt, 

1910 syms.assert_stmt, 

1911 syms.return_stmt, 

1912 syms.except_clause, 

1913 syms.funcdef, 

1914 syms.with_stmt, 

1915 syms.testlist_gexp, 

1916 syms.tname, 

1917 # these ones aren't useful to end users, but they do please fuzzers 

1918 syms.for_stmt, 

1919 syms.del_stmt, 

1920 syms.for_stmt, 

1921 ]: 

1922 return False 

1923 

1924 first = node.children[0] 

1925 last = node.children[-1] 

1926 if is_lpar_token(first) and is_rpar_token(last): 

1927 middle = node.children[1] 

1928 # make parentheses invisible 

1929 if ( 

1930 # If the prefix of `middle` includes a type comment with 

1931 # ignore annotation, then we do not remove the parentheses 

1932 not is_type_ignore_comment_string(middle.prefix.strip(), mode=mode) 

1933 ): 

1934 first.value = "" 

1935 last.value = "" 

1936 maybe_make_parens_invisible_in_atom( 

1937 middle, 

1938 parent=parent, 

1939 mode=mode, 

1940 features=features, 

1941 remove_brackets_around_comma=remove_brackets_around_comma, 

1942 ) 

1943 

1944 if is_atom_with_invisible_parens(middle): 

1945 # Strip the invisible parens from `middle` by replacing 

1946 # it with the child in-between the invisible parens 

1947 middle.replace(middle.children[1]) 

1948 

1949 if middle.children[0].prefix.strip(): 

1950 # Preserve comments before first paren 

1951 middle.children[1].prefix = ( 

1952 middle.children[0].prefix + middle.children[1].prefix 

1953 ) 

1954 

1955 if middle.children[-1].prefix.strip(): 

1956 # Preserve comments before last paren 

1957 last.prefix = middle.children[-1].prefix + last.prefix 

1958 

1959 return False 

1960 

1961 return True 

1962 

1963 

1964def should_split_line(line: Line, opening_bracket: Leaf) -> bool: 

1965 """Should `line` be immediately split with `delimiter_split()` after RHS?""" 

1966 

1967 if not (opening_bracket.parent and opening_bracket.value in "[{("): 

1968 return False 

1969 

1970 # We're essentially checking if the body is delimited by commas and there's more 

1971 # than one of them (we're excluding the trailing comma and if the delimiter priority 

1972 # is still commas, that means there's more). 

1973 exclude = set() 

1974 trailing_comma = False 

1975 try: 

1976 last_leaf = line.leaves[-1] 

1977 if last_leaf.type == token.COMMA: 

1978 trailing_comma = True 

1979 exclude.add(id(last_leaf)) 

1980 max_priority = line.bracket_tracker.max_delimiter_priority(exclude=exclude) 

1981 except (IndexError, ValueError): 

1982 return False 

1983 

1984 return max_priority == COMMA_PRIORITY and ( 

1985 (line.mode.magic_trailing_comma and trailing_comma) 

1986 # always explode imports 

1987 or opening_bracket.parent.type in {syms.atom, syms.import_from} 

1988 ) 

1989 

1990 

1991def generate_trailers_to_omit(line: Line, line_length: int) -> Iterator[set[LeafID]]: 

1992 """Generate sets of closing bracket IDs that should be omitted in a RHS. 

1993 

1994 Brackets can be omitted if the entire trailer up to and including 

1995 a preceding closing bracket fits in one line. 

1996 

1997 Yielded sets are cumulative (contain results of previous yields, too). First 

1998 set is empty, unless the line should explode, in which case bracket pairs until 

1999 the one that needs to explode are omitted. 

2000 """ 

2001 

2002 omit: set[LeafID] = set() 

2003 if not line.magic_trailing_comma: 

2004 yield omit 

2005 

2006 length = 4 * line.depth 

2007 opening_bracket: Leaf | None = None 

2008 closing_bracket: Leaf | None = None 

2009 inner_brackets: set[LeafID] = set() 

2010 for index, leaf, leaf_length in line.enumerate_with_length(is_reversed=True): 

2011 length += leaf_length 

2012 if length > line_length: 

2013 break 

2014 

2015 has_inline_comment = leaf_length > len(leaf.value) + len(leaf.prefix) 

2016 if leaf.type == STANDALONE_COMMENT or has_inline_comment: 

2017 break 

2018 

2019 if opening_bracket: 

2020 if leaf is opening_bracket: 

2021 opening_bracket = None 

2022 elif leaf.type in CLOSING_BRACKETS: 

2023 prev = line.leaves[index - 1] if index > 0 else None 

2024 if ( 

2025 prev 

2026 and prev.type == token.COMMA 

2027 and leaf.opening_bracket is not None 

2028 and not is_one_sequence_between( 

2029 leaf.opening_bracket, leaf, line.leaves 

2030 ) 

2031 ): 

2032 # Never omit bracket pairs with trailing commas. 

2033 # We need to explode on those. 

2034 break 

2035 

2036 inner_brackets.add(id(leaf)) 

2037 elif leaf.type in CLOSING_BRACKETS: 

2038 prev = line.leaves[index - 1] if index > 0 else None 

2039 if prev and prev.type in OPENING_BRACKETS: 

2040 # Empty brackets would fail a split so treat them as "inner" 

2041 # brackets (e.g. only add them to the `omit` set if another 

2042 # pair of brackets was good enough. 

2043 inner_brackets.add(id(leaf)) 

2044 continue 

2045 

2046 if closing_bracket: 

2047 omit.add(id(closing_bracket)) 

2048 omit.update(inner_brackets) 

2049 inner_brackets.clear() 

2050 yield omit 

2051 

2052 if ( 

2053 prev 

2054 and prev.type == token.COMMA 

2055 and leaf.opening_bracket is not None 

2056 and not is_one_sequence_between(leaf.opening_bracket, leaf, line.leaves) 

2057 ): 

2058 # Never omit bracket pairs with trailing commas. 

2059 # We need to explode on those. 

2060 break 

2061 

2062 if leaf.value: 

2063 opening_bracket = leaf.opening_bracket 

2064 closing_bracket = leaf 

2065 

2066 

2067def _over_length_only_due_to_subscript_comment(line: Line, mode: Mode) -> bool: 

2068 """Return True if `line` only exceeds `mode.line_length` because of an inline 

2069 comment attached to a subscript opening bracket (`[`). 

2070 

2071 This is the shape produced by the original of the issue #4733 reproducer: 

2072 a comment inside the annotation's subscript brackets renders at the end of 

2073 the head line after Black splits the statement, pushing it past the limit. 

2074 Taking the FORCE_OPTIONAL_PARENTHESES "second opinion" in that case wraps 

2075 the annotation in extra parens and migrates the comment outside the 

2076 subscript, which then oscillates on the next formatter pass. 

2077 """ 

2078 if not line.leaves: 

2079 return False 

2080 # The over-length must be caused entirely by a trailing comment. 

2081 indent = " " * line.depth 

2082 leaves_iter = iter(line.leaves) 

2083 first = next(leaves_iter) 

2084 text_without_comments = f"{first.prefix}{indent}{first.value}" 

2085 text_without_comments += "".join(str(leaf) for leaf in leaves_iter) 

2086 if str_width(text_without_comments) > mode.line_length: 

2087 return False 

2088 # And the comment must be attached to a subscript opening bracket. 

2089 for leaf_id, comments in line.comments.items(): 

2090 if not comments: 

2091 continue 

2092 leaf = next((lf for lf in line.leaves if id(lf) == leaf_id), None) 

2093 if leaf is None or leaf.type != token.LSQB: 

2094 return False 

2095 return True 

2096 

2097 

2098def run_transformer( 

2099 line: Line, 

2100 transform: Transformer, 

2101 mode: Mode, 

2102 features: Collection[Feature], 

2103 *, 

2104 line_str: str = "", 

2105) -> list[Line]: 

2106 if not line_str: 

2107 line_str = line_to_string(line) 

2108 result: list[Line] = [] 

2109 for transformed_line in transform(line, features, mode): 

2110 if str(transformed_line).strip("\n") == line_str: 

2111 raise CannotTransform("Line transformer returned an unchanged result") 

2112 

2113 result.extend(transform_line(transformed_line, mode=mode, features=features)) 

2114 

2115 features_set = set(features) 

2116 if ( 

2117 Feature.FORCE_OPTIONAL_PARENTHESES in features_set 

2118 or transform.__class__.__name__ != "rhs" 

2119 or not line.bracket_tracker.invisible 

2120 or any(bracket.value for bracket in line.bracket_tracker.invisible) 

2121 or line.contains_multiline_strings() 

2122 or result[0].contains_uncollapsable_type_comments() 

2123 or result[0].contains_unsplittable_type_ignore() 

2124 or is_line_short_enough(result[0], mode=mode) 

2125 # result[0] only exceeds the length because of a comment attached to a 

2126 # subscript opening bracket. Taking the FORCE_OPTIONAL_PARENTHESES 

2127 # "second opinion" wraps the annotation in extra invisible parens and 

2128 # migrates the comment outside the subscript, which then oscillates with 

2129 # a deeper-bracket split on the next formatter pass (issue #4733). 

2130 or _over_length_only_due_to_subscript_comment(result[0], mode) 

2131 # If any leaves have no parents (which _can_ occur since 

2132 # `transform(line)` potentially destroys the line's underlying node 

2133 # structure), then we can't proceed. Doing so would cause the below 

2134 # call to `append_leaves()` to fail. 

2135 or any(leaf.parent is None for leaf in line.leaves) 

2136 ): 

2137 return result 

2138 

2139 line_copy = line.clone() 

2140 append_leaves(line_copy, line, line.leaves) 

2141 features_fop = features_set | {Feature.FORCE_OPTIONAL_PARENTHESES} 

2142 second_opinion = run_transformer( 

2143 line_copy, transform, mode, features_fop, line_str=line_str 

2144 ) 

2145 if all(is_line_short_enough(ln, mode=mode) for ln in second_opinion): 

2146 result = second_opinion 

2147 return result