jaclang 0.8.0__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of jaclang might be problematic. Click here for more details.

Files changed (124) hide show
  1. jaclang/__init__.py +6 -0
  2. jaclang/cli/cli.py +23 -50
  3. jaclang/compiler/codeinfo.py +0 -1
  4. jaclang/compiler/jac.lark +14 -22
  5. jaclang/compiler/larkparse/jac_parser.py +2 -2
  6. jaclang/compiler/parser.py +378 -531
  7. jaclang/compiler/passes/main/__init__.py +0 -14
  8. jaclang/compiler/passes/main/annex_pass.py +2 -8
  9. jaclang/compiler/passes/main/cfg_build_pass.py +39 -13
  10. jaclang/compiler/passes/main/def_impl_match_pass.py +14 -13
  11. jaclang/compiler/passes/main/def_use_pass.py +4 -7
  12. jaclang/compiler/passes/main/import_pass.py +6 -14
  13. jaclang/compiler/passes/main/inheritance_pass.py +2 -2
  14. jaclang/compiler/passes/main/pyast_gen_pass.py +428 -799
  15. jaclang/compiler/passes/main/pyast_load_pass.py +115 -311
  16. jaclang/compiler/passes/main/pyjac_ast_link_pass.py +8 -7
  17. jaclang/compiler/passes/main/sym_tab_build_pass.py +3 -3
  18. jaclang/compiler/passes/main/sym_tab_link_pass.py +6 -9
  19. jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/action/actions.jac +1 -5
  20. jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/main.jac +1 -8
  21. jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +5 -9
  22. jaclang/compiler/passes/main/tests/test_decl_impl_match_pass.py +7 -8
  23. jaclang/compiler/passes/main/tests/test_import_pass.py +5 -18
  24. jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +2 -6
  25. jaclang/compiler/passes/main/tests/test_sub_node_pass.py +1 -3
  26. jaclang/compiler/passes/main/tests/test_sym_tab_link_pass.py +20 -17
  27. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +425 -216
  28. jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -0
  29. jaclang/compiler/passes/tool/tests/fixtures/archetype_frmt.jac +14 -0
  30. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +5 -4
  31. jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +6 -0
  32. jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +3 -3
  33. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +9 -0
  34. jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +18 -3
  35. jaclang/compiler/passes/tool/tests/test_unparse_validate.py +2 -2
  36. jaclang/compiler/program.py +22 -66
  37. jaclang/compiler/tests/fixtures/fam.jac +2 -2
  38. jaclang/compiler/tests/fixtures/pkg_import_lib/__init__.jac +1 -0
  39. jaclang/compiler/tests/fixtures/pkg_import_lib/sub/__init__.jac +1 -0
  40. jaclang/compiler/tests/fixtures/pkg_import_lib/sub/helper.jac +3 -0
  41. jaclang/compiler/tests/fixtures/pkg_import_lib/tools.jac +3 -0
  42. jaclang/compiler/tests/fixtures/pkg_import_lib_py/__init__.py +5 -0
  43. jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/__init__.py +3 -0
  44. jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/helper.jac +3 -0
  45. jaclang/compiler/tests/fixtures/pkg_import_lib_py/tools.jac +3 -0
  46. jaclang/compiler/tests/fixtures/pkg_import_main.jac +10 -0
  47. jaclang/compiler/tests/fixtures/pkg_import_main_py.jac +11 -0
  48. jaclang/compiler/tests/test_importer.py +30 -13
  49. jaclang/compiler/tests/test_parser.py +1 -0
  50. jaclang/compiler/unitree.py +488 -320
  51. jaclang/langserve/__init__.jac +1 -0
  52. jaclang/langserve/engine.jac +503 -0
  53. jaclang/langserve/sem_manager.jac +309 -0
  54. jaclang/langserve/server.jac +201 -0
  55. jaclang/langserve/tests/server_test/test_lang_serve.py +139 -48
  56. jaclang/langserve/tests/server_test/utils.py +35 -6
  57. jaclang/langserve/tests/session.jac +294 -0
  58. jaclang/langserve/tests/test_sem_tokens.py +2 -2
  59. jaclang/langserve/tests/test_server.py +8 -7
  60. jaclang/langserve/utils.jac +51 -30
  61. jaclang/runtimelib/archetype.py +128 -6
  62. jaclang/runtimelib/builtin.py +17 -14
  63. jaclang/runtimelib/importer.py +51 -76
  64. jaclang/runtimelib/machine.py +469 -305
  65. jaclang/runtimelib/meta_importer.py +86 -0
  66. jaclang/runtimelib/tests/fixtures/graph_purger.jac +24 -26
  67. jaclang/runtimelib/tests/fixtures/other_root_access.jac +25 -16
  68. jaclang/runtimelib/tests/fixtures/traversing_save.jac +7 -5
  69. jaclang/runtimelib/tests/test_jaseci.py +3 -1
  70. jaclang/runtimelib/utils.py +3 -3
  71. jaclang/tests/fixtures/arch_rel_import_creation.jac +23 -23
  72. jaclang/tests/fixtures/async_ability.jac +43 -10
  73. jaclang/tests/fixtures/async_function.jac +18 -0
  74. jaclang/tests/fixtures/async_walker.jac +17 -12
  75. jaclang/tests/fixtures/backward_edge_visit.jac +31 -0
  76. jaclang/tests/fixtures/builtin_printgraph.jac +85 -0
  77. jaclang/tests/fixtures/builtin_printgraph_json.jac +21 -0
  78. jaclang/tests/fixtures/builtin_printgraph_mermaid.jac +16 -0
  79. jaclang/tests/fixtures/chandra_bugs2.jac +20 -13
  80. jaclang/tests/fixtures/concurrency.jac +1 -1
  81. jaclang/tests/fixtures/create_dynamic_archetype.jac +25 -28
  82. jaclang/tests/fixtures/deep/deeper/deep_outer_import.jac +7 -4
  83. jaclang/tests/fixtures/deep/deeper/snd_lev.jac +2 -2
  84. jaclang/tests/fixtures/deep/deeper/snd_lev_dup.jac +6 -0
  85. jaclang/tests/fixtures/deep/one_lev.jac +2 -2
  86. jaclang/tests/fixtures/deep/one_lev_dup.jac +4 -3
  87. jaclang/tests/fixtures/dynamic_archetype.jac +19 -12
  88. jaclang/tests/fixtures/edge_ability.jac +49 -0
  89. jaclang/tests/fixtures/foo.jac +14 -22
  90. jaclang/tests/fixtures/guess_game.jac +1 -1
  91. jaclang/tests/fixtures/here_usage_error.jac +21 -0
  92. jaclang/tests/fixtures/here_visitor_usage.jac +21 -0
  93. jaclang/tests/fixtures/jac_from_py.py +1 -1
  94. jaclang/tests/fixtures/jp_importer.jac +6 -6
  95. jaclang/tests/fixtures/jp_importer_auto.jac +5 -3
  96. jaclang/tests/fixtures/node_del.jac +30 -36
  97. jaclang/tests/fixtures/unicode_strings.jac +24 -0
  98. jaclang/tests/fixtures/visit_traversal.jac +47 -0
  99. jaclang/tests/fixtures/walker_update.jac +5 -7
  100. jaclang/tests/test_cli.py +12 -7
  101. jaclang/tests/test_language.py +218 -145
  102. jaclang/tests/test_reference.py +9 -4
  103. jaclang/tests/test_typecheck.py +13 -26
  104. jaclang/utils/helpers.py +14 -6
  105. jaclang/utils/lang_tools.py +9 -8
  106. jaclang/utils/module_resolver.py +23 -0
  107. jaclang/utils/tests/test_lang_tools.py +2 -1
  108. jaclang/utils/treeprinter.py +3 -4
  109. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/METADATA +4 -3
  110. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/RECORD +112 -94
  111. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/WHEEL +1 -1
  112. jaclang/compiler/passes/main/tests/fixtures/main_err.jac +0 -6
  113. jaclang/compiler/passes/main/tests/fixtures/second_err.jac +0 -4
  114. jaclang/compiler/passes/tool/tests/fixtures/corelib.jac +0 -644
  115. jaclang/compiler/passes/tool/tests/test_doc_ir_gen_pass.py +0 -29
  116. jaclang/langserve/__init__.py +0 -1
  117. jaclang/langserve/engine.py +0 -553
  118. jaclang/langserve/sem_manager.py +0 -383
  119. jaclang/langserve/server.py +0 -167
  120. jaclang/langserve/tests/session.py +0 -255
  121. jaclang/tests/fixtures/builtin_dotgen.jac +0 -42
  122. jaclang/tests/fixtures/builtin_dotgen_json.jac +0 -21
  123. jaclang/tests/fixtures/deep/deeper/__init__.jac +0 -1
  124. {jaclang-0.8.0.dist-info → jaclang-0.8.2.dist-info}/entry_points.txt +0 -0
@@ -3,7 +3,7 @@
3
3
  This is a pass for generating DocIr for Jac code.
4
4
  """
5
5
 
6
- from typing import List, Optional
6
+ from typing import List, Optional, Sequence
7
7
 
8
8
  import jaclang.compiler.passes.tool.doc_ir as doc
9
9
  import jaclang.compiler.unitree as uni
@@ -78,26 +78,6 @@ class DocIRGenPass(UniPass):
78
78
 
79
79
  return self.concat(result)
80
80
 
81
- def _strip_trailing_ws(self, part: doc.DocType) -> doc.DocType:
82
- """Recursively strip trailing whitespace from a Doc node."""
83
- if isinstance(part, doc.Concat) and part.parts:
84
- while (
85
- part.parts
86
- and isinstance(part.parts[-1], doc.Text)
87
- and getattr(part.parts[-1], "text", "") == " "
88
- ):
89
- part.parts.pop()
90
- if part.parts:
91
- part.parts[-1] = self._strip_trailing_ws(part.parts[-1])
92
- elif isinstance(part, (doc.Group, doc.Indent, doc.Align)):
93
- part.contents = self._strip_trailing_ws(part.contents)
94
- return part
95
-
96
- def finalize(self, parts: List[doc.DocType], group: bool = True) -> doc.DocType:
97
- """Concat parts and remove trailing whitespace before grouping."""
98
- result = self._strip_trailing_ws(self.concat(parts))
99
- return self.group(result) if group else result
100
-
101
81
  def is_one_line(self, node: uni.UniNode) -> bool:
102
82
  """Check if the node is a one line node."""
103
83
  kid = [i for i in node.kid if not isinstance(i, uni.CommentToken)]
@@ -139,14 +119,15 @@ class DocIRGenPass(UniPass):
139
119
  """Exit import node."""
140
120
  parts: list[doc.DocType] = []
141
121
  for i in node.kid:
142
- if isinstance(i, uni.SubNodeList) and i.items:
143
- parts.append(
144
- self.indent(self.concat([self.tight_line(), i.gen.doc_ir]))
145
- )
146
- parts.append(self.line())
122
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
123
+ parts.pop()
124
+ parts.append(i.gen.doc_ir)
125
+ parts.append(self.space())
147
126
  elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
148
127
  parts.pop()
149
128
  parts.append(i.gen.doc_ir)
129
+ elif isinstance(i, uni.Token) and i.name == Tok.RBRACE:
130
+ parts.append(i.gen.doc_ir)
150
131
  else:
151
132
  parts.append(i.gen.doc_ir)
152
133
  parts.append(self.space())
@@ -179,13 +160,45 @@ class DocIRGenPass(UniPass):
179
160
  def exit_archetype(self, node: uni.Archetype) -> None:
180
161
  """Generate DocIR for archetypes."""
181
162
  parts: list[doc.DocType] = []
163
+ body_parts: list[doc.DocType] = []
164
+ prev_item = None
165
+ in_body = False
182
166
  for i in node.kid:
183
- if i in [node.doc, node.decorators]:
167
+ if (node.doc and i is node.doc) or (
168
+ node.decorators and i in node.decorators
169
+ ):
184
170
  parts.append(i.gen.doc_ir)
185
171
  parts.append(self.hard_line())
186
172
  elif i == node.name:
187
173
  parts.append(i.gen.doc_ir)
188
174
  parts.append(self.space())
175
+ elif isinstance(i, uni.Token) and i.name == Tok.LBRACE:
176
+ parts.append(i.gen.doc_ir)
177
+ elif isinstance(i, uni.Token) and i.name == Tok.LPAREN:
178
+ parts.pop()
179
+ parts.append(i.gen.doc_ir)
180
+ elif isinstance(i, uni.Token) and i.name == Tok.RPAREN:
181
+ parts.pop()
182
+ parts.append(i.gen.doc_ir)
183
+ parts.append(self.space())
184
+ elif isinstance(node.body, Sequence) and i in node.body:
185
+ if not in_body:
186
+ body_parts.append(self.hard_line())
187
+ if (prev_item and type(prev_item) is not type(i)) or (
188
+ prev_item and not self.is_one_line(prev_item)
189
+ ):
190
+ body_parts.append(self.hard_line())
191
+ body_parts.append(i.gen.doc_ir)
192
+ body_parts.append(self.hard_line())
193
+ prev_item = i
194
+ in_body = True
195
+ elif in_body:
196
+ in_body = False
197
+ body_parts.pop()
198
+ parts.append(self.indent(self.concat(body_parts)))
199
+ parts.append(self.hard_line())
200
+ parts.append(i.gen.doc_ir)
201
+ parts.append(self.space())
189
202
  elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
190
203
  parts.pop()
191
204
  parts.append(i.gen.doc_ir)
@@ -193,43 +206,85 @@ class DocIRGenPass(UniPass):
193
206
  else:
194
207
  parts.append(i.gen.doc_ir)
195
208
  parts.append(self.space())
196
- node.gen.doc_ir = self.finalize(parts)
209
+
210
+ node.gen.doc_ir = self.group(self.concat(parts))
197
211
 
198
212
  def exit_ability(self, node: uni.Ability) -> None:
199
213
  """Generate DocIR for abilities."""
200
214
  parts: list[doc.DocType] = []
215
+ body_parts: list[doc.DocType] = []
216
+ in_body = False
201
217
  for i in node.kid:
202
- if i in [node.doc, node.decorators]:
218
+ if i == node.doc or (node.decorators and i in node.decorators):
203
219
  parts.append(i.gen.doc_ir)
204
220
  parts.append(self.hard_line())
205
221
  elif i == node.name_ref:
206
222
  parts.append(i.gen.doc_ir)
207
223
  if not isinstance(node.signature, uni.FuncSignature):
208
224
  parts.append(self.space())
225
+ elif isinstance(node.body, Sequence) and i in node.body:
226
+ if not in_body:
227
+ parts.pop()
228
+ body_parts.append(self.hard_line())
229
+ body_parts.append(i.gen.doc_ir)
230
+ body_parts.append(self.hard_line())
231
+ in_body = True
232
+ elif in_body:
233
+ in_body = False
234
+ body_parts.pop()
235
+ parts.append(self.indent(self.concat(body_parts)))
236
+ parts.append(self.hard_line())
237
+ parts.append(i.gen.doc_ir)
238
+ parts.append(self.space())
209
239
  elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
210
240
  parts.pop()
211
241
  parts.append(i.gen.doc_ir)
242
+ parts.append(self.space())
212
243
  else:
213
244
  parts.append(i.gen.doc_ir)
214
- if not isinstance(i, uni.CommentToken):
215
- parts.append(self.space())
216
- node.gen.doc_ir = self.finalize(parts)
245
+ parts.append(self.space())
246
+ node.gen.doc_ir = self.group(self.concat(parts))
217
247
 
218
248
  def exit_func_signature(self, node: uni.FuncSignature) -> None:
219
249
  """Generate DocIR for function signatures."""
220
250
  parts: list[doc.DocType] = []
251
+ indent_parts: list[doc.DocType] = []
252
+ in_params = False
253
+ has_parens = False
221
254
  for i in node.kid:
222
- if isinstance(i, uni.Token) and i.name == Tok.LPAREN:
255
+ if isinstance(i, uni.Token) and i.name == Tok.LPAREN and node.params:
256
+ in_params = True
223
257
  parts.append(i.gen.doc_ir)
224
- elif i == node.params:
258
+ elif isinstance(i, uni.Token) and i.name == Tok.RPAREN and node.params:
259
+ in_params = False
260
+ has_parens = True
225
261
  parts.append(
226
- self.indent(self.concat([self.tight_line(), i.gen.doc_ir]))
262
+ self.indent(self.concat([self.tight_line(), *indent_parts]))
227
263
  )
228
264
  parts.append(self.tight_line())
265
+ parts.append(i.gen.doc_ir)
266
+ parts.append(self.space())
267
+ elif isinstance(i, uni.Token) and i.name == Tok.RPAREN:
268
+ parts.pop()
269
+ parts.append(i.gen.doc_ir)
270
+ parts.append(self.space())
271
+ elif in_params:
272
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
273
+ indent_parts.append(i.gen.doc_ir)
274
+ indent_parts.append(self.space())
275
+ else:
276
+ indent_parts.append(i.gen.doc_ir)
229
277
  else:
278
+ if (
279
+ isinstance(i, uni.Token)
280
+ and i.name == Tok.RETURN_HINT
281
+ and not has_parens
282
+ ):
283
+ parts.append(self.space())
230
284
  parts.append(i.gen.doc_ir)
231
285
  parts.append(self.space())
232
- node.gen.doc_ir = self.finalize(parts)
286
+ parts.pop()
287
+ node.gen.doc_ir = self.group(self.concat(parts))
233
288
 
234
289
  def exit_param_var(self, node: uni.ParamVar) -> None:
235
290
  """Generate DocIR for parameter variables."""
@@ -241,51 +296,140 @@ class DocIRGenPass(UniPass):
241
296
  parts.append(self.space())
242
297
  else:
243
298
  parts.append(i.gen.doc_ir)
244
- node.gen.doc_ir = self.finalize(parts)
299
+ node.gen.doc_ir = self.group(self.concat(parts))
245
300
 
246
301
  def exit_type_ref(self, node: uni.TypeRef) -> None:
247
302
  """Generate DocIR for type references."""
248
303
  parts: list[doc.DocType] = []
249
304
  for i in node.kid:
250
305
  parts.append(i.gen.doc_ir)
251
- node.gen.doc_ir = self.finalize(parts)
306
+ node.gen.doc_ir = self.group(self.concat(parts))
252
307
 
253
308
  def exit_assignment(self, node: uni.Assignment) -> None:
254
309
  """Generate DocIR for assignments."""
310
+ lhs_parts: list[doc.DocType] = []
311
+ rhs_parts: list[doc.DocType] = []
312
+ eq_tok: Optional[doc.DocType] = None
313
+ seen_eq = False
314
+
315
+ for i in node.kid:
316
+ if isinstance(i, uni.Token) and i.name == Tok.KW_LET:
317
+ lhs_parts.append(i.gen.doc_ir)
318
+ lhs_parts.append(self.space())
319
+ elif isinstance(i, uni.Token) and i.name == Tok.EQ and not seen_eq:
320
+ eq_tok = i.gen.doc_ir
321
+ seen_eq = True
322
+ elif seen_eq:
323
+ rhs_parts.append(i.gen.doc_ir)
324
+ else:
325
+ if i == node.aug_op:
326
+ lhs_parts.append(self.space())
327
+ lhs_parts.append(i.gen.doc_ir)
328
+ if i == node.aug_op:
329
+ lhs_parts.append(self.space())
330
+
331
+ if eq_tok is not None:
332
+ rhs_concat = self.concat(rhs_parts)
333
+ node.gen.doc_ir = self.group(
334
+ self.concat(
335
+ [
336
+ *lhs_parts,
337
+ self.space(),
338
+ eq_tok,
339
+ self.indent(self.concat([self.line(), rhs_concat])),
340
+ ]
341
+ )
342
+ )
343
+ else:
344
+ node.gen.doc_ir = self.group(self.concat(lhs_parts + rhs_parts))
345
+
346
+ def exit_if_stmt(self, node: uni.IfStmt) -> None:
347
+ """Generate DocIR for if statements."""
255
348
  parts: list[doc.DocType] = []
349
+ body_parts: list[doc.DocType] = []
350
+ in_body = False
256
351
  for i in node.kid:
257
- if isinstance(i, uni.Token) and i.name == Tok.SEMI:
352
+ if isinstance(node.body, Sequence) and i in node.body:
353
+ if not in_body:
354
+ parts.pop()
355
+ body_parts.append(self.hard_line())
356
+ body_parts.append(i.gen.doc_ir)
357
+ body_parts.append(self.hard_line())
358
+ in_body = True
359
+ elif in_body:
360
+ in_body = False
361
+ body_parts.pop()
362
+ parts.append(self.indent(self.concat(body_parts)))
363
+ parts.append(self.hard_line())
364
+ parts.append(i.gen.doc_ir)
365
+ parts.append(self.space())
366
+ elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
258
367
  parts.pop()
259
368
  parts.append(i.gen.doc_ir)
260
369
  parts.append(self.space())
261
370
  else:
262
371
  parts.append(i.gen.doc_ir)
263
372
  parts.append(self.space())
264
- node.gen.doc_ir = self.finalize(parts)
265
-
266
- def exit_if_stmt(self, node: uni.IfStmt) -> None:
267
- """Generate DocIR for if statements."""
268
- parts: list[doc.DocType] = []
269
- for i in node.kid:
270
- parts.append(i.gen.doc_ir)
271
- parts.append(self.space())
272
- node.gen.doc_ir = self.finalize(parts)
373
+ parts.pop()
374
+ node.gen.doc_ir = self.group(self.concat(parts))
273
375
 
274
376
  def exit_else_if(self, node: uni.ElseIf) -> None:
275
377
  """Generate DocIR for else if statements."""
276
378
  parts: list[doc.DocType] = []
379
+ body_parts: list[doc.DocType] = []
380
+ in_body = False
277
381
  for i in node.kid:
278
- parts.append(i.gen.doc_ir)
279
- parts.append(self.space())
280
- node.gen.doc_ir = self.finalize(parts)
382
+ if isinstance(node.body, Sequence) and i in node.body:
383
+ if not in_body:
384
+ parts.pop()
385
+ body_parts.append(self.hard_line())
386
+ body_parts.append(i.gen.doc_ir)
387
+ body_parts.append(self.hard_line())
388
+ in_body = True
389
+ elif in_body:
390
+ in_body = False
391
+ body_parts.pop()
392
+ parts.append(self.indent(self.concat(body_parts)))
393
+ parts.append(self.hard_line())
394
+ parts.append(i.gen.doc_ir)
395
+ parts.append(self.space())
396
+ elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
397
+ parts.pop()
398
+ parts.append(i.gen.doc_ir)
399
+ parts.append(self.space())
400
+ else:
401
+ parts.append(i.gen.doc_ir)
402
+ parts.append(self.space())
403
+ node.gen.doc_ir = self.group(self.concat(parts))
281
404
 
282
405
  def exit_else_stmt(self, node: uni.ElseStmt) -> None:
283
406
  """Generate DocIR for else statements."""
284
407
  parts: list[doc.DocType] = []
408
+ body_parts: list[doc.DocType] = []
409
+ in_body = False
285
410
  for i in node.kid:
286
- parts.append(i.gen.doc_ir)
287
- parts.append(self.space())
288
- node.gen.doc_ir = self.finalize(parts)
411
+ if isinstance(node.body, Sequence) and i in node.body:
412
+ if not in_body:
413
+ parts.pop()
414
+ body_parts.append(self.hard_line())
415
+ body_parts.append(i.gen.doc_ir)
416
+ body_parts.append(self.hard_line())
417
+ in_body = True
418
+ elif in_body:
419
+ in_body = False
420
+ body_parts.pop()
421
+ parts.append(self.indent(self.concat(body_parts)))
422
+ parts.append(self.hard_line())
423
+ parts.append(i.gen.doc_ir)
424
+ parts.append(self.space())
425
+ elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
426
+ parts.pop()
427
+ parts.append(i.gen.doc_ir)
428
+ parts.append(self.space())
429
+ else:
430
+ parts.append(i.gen.doc_ir)
431
+ parts.append(self.space())
432
+ node.gen.doc_ir = self.group(self.concat(parts))
289
433
 
290
434
  def exit_binary_expr(self, node: uni.BinaryExpr) -> None:
291
435
  """Generate DocIR for binary expressions."""
@@ -293,23 +437,14 @@ class DocIRGenPass(UniPass):
293
437
  for i in node.kid:
294
438
  parts.append(i.gen.doc_ir)
295
439
  parts.append(self.space())
296
- node.gen.doc_ir = self.finalize(parts)
440
+ parts.pop()
441
+ node.gen.doc_ir = self.group(self.concat(parts))
297
442
 
298
443
  def exit_expr_stmt(self, node: uni.ExprStmt) -> None:
299
444
  """Generate DocIR for expression statements."""
300
445
  parts: list[doc.DocType] = []
301
- is_fstring = (
302
- node.parent
303
- and isinstance(node.parent, uni.SubNodeList)
304
- and node.parent.parent
305
- and isinstance(node.parent.parent, uni.FString)
306
- )
307
446
  for i in node.kid:
308
- if is_fstring:
309
- parts.append(self.text("{"))
310
447
  parts.append(i.gen.doc_ir)
311
- if is_fstring:
312
- parts.append(self.text("}"))
313
448
  node.gen.doc_ir = self.group(self.concat(parts))
314
449
 
315
450
  def exit_concurrent_expr(self, node: uni.ConcurrentExpr) -> None:
@@ -318,7 +453,7 @@ class DocIRGenPass(UniPass):
318
453
  for i in node.kid:
319
454
  parts.append(i.gen.doc_ir)
320
455
  parts.append(self.space())
321
- node.gen.doc_ir = self.finalize(parts)
456
+ node.gen.doc_ir = self.group(self.concat(parts))
322
457
 
323
458
  def exit_return_stmt(self, node: uni.ReturnStmt) -> None:
324
459
  """Generate DocIR for return statements."""
@@ -330,13 +465,32 @@ class DocIRGenPass(UniPass):
330
465
  else:
331
466
  parts.append(i.gen.doc_ir)
332
467
  parts.append(self.space())
333
- node.gen.doc_ir = self.finalize(parts)
468
+ node.gen.doc_ir = self.group(self.concat(parts))
334
469
 
335
470
  def exit_func_call(self, node: uni.FuncCall) -> None:
336
471
  """Generate DocIR for function calls."""
337
472
  parts: list[doc.DocType] = []
473
+ indent_parts: list[doc.DocType] = []
474
+ in_params = False
338
475
  for i in node.kid:
339
- parts.append(i.gen.doc_ir)
476
+ if isinstance(i, uni.Token) and i.name == Tok.LPAREN and node.params:
477
+ in_params = True
478
+ parts.append(i.gen.doc_ir)
479
+ elif isinstance(i, uni.Token) and i.name == Tok.RPAREN and node.params:
480
+ in_params = False
481
+ parts.append(
482
+ self.indent(self.concat([self.tight_line(), *indent_parts]))
483
+ )
484
+ parts.append(self.tight_line())
485
+ parts.append(i.gen.doc_ir)
486
+ elif in_params:
487
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
488
+ indent_parts.append(i.gen.doc_ir)
489
+ indent_parts.append(self.space())
490
+ else:
491
+ indent_parts.append(i.gen.doc_ir)
492
+ else:
493
+ parts.append(i.gen.doc_ir)
340
494
  node.gen.doc_ir = self.group(self.concat(parts))
341
495
 
342
496
  def exit_atom_trailer(self, node: uni.AtomTrailer) -> None:
@@ -350,8 +504,24 @@ class DocIRGenPass(UniPass):
350
504
  """Generate DocIR for list values."""
351
505
  parts: list[doc.DocType] = []
352
506
  for i in node.kid:
353
- parts.append(i.gen.doc_ir)
354
- node.gen.doc_ir = self.group(self.concat(parts))
507
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
508
+ parts.append(i.gen.doc_ir)
509
+ parts.append(self.space())
510
+ else:
511
+ parts.append(i.gen.doc_ir)
512
+ not_broke = self.concat(parts)
513
+ parts = []
514
+ for i in node.kid:
515
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
516
+ parts.append(i.gen.doc_ir)
517
+ parts.append(self.hard_line())
518
+ elif isinstance(i, uni.Token) and i.name == Tok.LSQUARE:
519
+ parts.append(self.hard_line())
520
+ parts.append(i.gen.doc_ir)
521
+ else:
522
+ parts.append(i.gen.doc_ir)
523
+ broke = self.concat(parts)
524
+ node.gen.doc_ir = self.group(self.if_break(broke, not_broke))
355
525
 
356
526
  def exit_dict_val(self, node: uni.DictVal) -> None:
357
527
  """Generate DocIR for dictionary values."""
@@ -363,7 +533,7 @@ class DocIRGenPass(UniPass):
363
533
  else:
364
534
  parts.append(i.gen.doc_ir)
365
535
  parts.append(self.space())
366
- node.gen.doc_ir = self.finalize(parts)
536
+ node.gen.doc_ir = self.group(self.concat(parts))
367
537
 
368
538
  def exit_k_v_pair(self, node: uni.KVPair) -> None:
369
539
  """Generate DocIR for key-value pairs."""
@@ -371,7 +541,8 @@ class DocIRGenPass(UniPass):
371
541
  for i in node.kid:
372
542
  parts.append(i.gen.doc_ir)
373
543
  parts.append(self.space())
374
- node.gen.doc_ir = self.finalize(parts)
544
+ parts.pop()
545
+ node.gen.doc_ir = self.group(self.concat(parts))
375
546
 
376
547
  def exit_has_var(self, node: uni.HasVar) -> None:
377
548
  """Generate DocIR for has variable declarations."""
@@ -395,16 +566,14 @@ class DocIRGenPass(UniPass):
395
566
  if i == node.doc:
396
567
  parts.append(i.gen.doc_ir)
397
568
  parts.append(self.hard_line())
398
- elif (
399
- isinstance(i, uni.SubNodeList)
400
- and i == node.vars
401
- and isinstance(i.gen.doc_ir, doc.Concat)
402
- ):
403
- parts.append(self.align(i.gen.doc_ir))
569
+ elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
570
+ parts.pop()
571
+ parts.append(i.gen.doc_ir)
572
+ parts.append(self.space())
404
573
  else:
405
574
  parts.append(i.gen.doc_ir)
406
575
  parts.append(self.space())
407
- node.gen.doc_ir = self.finalize(parts)
576
+ node.gen.doc_ir = self.group(self.concat(parts))
408
577
 
409
578
  def exit_while_stmt(self, node: uni.WhileStmt) -> None:
410
579
  """Generate DocIR for while statements."""
@@ -412,7 +581,7 @@ class DocIRGenPass(UniPass):
412
581
  for i in node.kid:
413
582
  parts.append(i.gen.doc_ir)
414
583
  parts.append(self.space())
415
- node.gen.doc_ir = self.finalize(parts)
584
+ node.gen.doc_ir = self.group(self.concat(parts))
416
585
 
417
586
  def exit_in_for_stmt(self, node: uni.InForStmt) -> None:
418
587
  """Generate DocIR for for-in statements."""
@@ -420,7 +589,7 @@ class DocIRGenPass(UniPass):
420
589
  for i in node.kid:
421
590
  parts.append(i.gen.doc_ir)
422
591
  parts.append(self.space())
423
- node.gen.doc_ir = self.finalize(parts)
592
+ node.gen.doc_ir = self.group(self.concat(parts))
424
593
 
425
594
  def exit_iter_for_stmt(self, node: uni.IterForStmt) -> None:
426
595
  """Generate DocIR for iterative for statements."""
@@ -428,7 +597,7 @@ class DocIRGenPass(UniPass):
428
597
  for i in node.kid:
429
598
  parts.append(i.gen.doc_ir)
430
599
  parts.append(self.space())
431
- node.gen.doc_ir = self.finalize(parts)
600
+ node.gen.doc_ir = self.group(self.concat(parts))
432
601
 
433
602
  def exit_try_stmt(self, node: uni.TryStmt) -> None:
434
603
  """Generate DocIR for try statements."""
@@ -436,7 +605,7 @@ class DocIRGenPass(UniPass):
436
605
  for i in node.kid:
437
606
  parts.append(i.gen.doc_ir)
438
607
  parts.append(self.space())
439
- node.gen.doc_ir = self.finalize(parts)
608
+ node.gen.doc_ir = self.group(self.concat(parts))
440
609
 
441
610
  def exit_except(self, node: uni.Except) -> None:
442
611
  """Generate DocIR for except clauses."""
@@ -444,7 +613,7 @@ class DocIRGenPass(UniPass):
444
613
  for i in node.kid:
445
614
  parts.append(i.gen.doc_ir)
446
615
  parts.append(self.space())
447
- node.gen.doc_ir = self.finalize(parts)
616
+ node.gen.doc_ir = self.group(self.concat(parts))
448
617
 
449
618
  def exit_finally_stmt(self, node: uni.FinallyStmt) -> None:
450
619
  """Generate DocIR for finally statements."""
@@ -452,14 +621,30 @@ class DocIRGenPass(UniPass):
452
621
  for i in node.kid:
453
622
  parts.append(i.gen.doc_ir)
454
623
  parts.append(self.space())
455
- node.gen.doc_ir = self.finalize(parts)
624
+ node.gen.doc_ir = self.group(self.concat(parts))
456
625
 
457
626
  def exit_tuple_val(self, node: uni.TupleVal) -> None:
458
627
  """Generate DocIR for tuple values."""
459
628
  parts: list[doc.DocType] = []
460
629
  for i in node.kid:
461
- parts.append(i.gen.doc_ir)
462
- node.gen.doc_ir = self.group(self.concat(parts))
630
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
631
+ parts.append(i.gen.doc_ir)
632
+ parts.append(self.space())
633
+ else:
634
+ parts.append(i.gen.doc_ir)
635
+ not_broke = self.concat(parts)
636
+ parts = []
637
+ for i in node.kid:
638
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
639
+ parts.append(i.gen.doc_ir)
640
+ parts.append(self.hard_line())
641
+ elif isinstance(i, uni.Token) and i.name == Tok.LPAREN:
642
+ parts.append(self.hard_line())
643
+ parts.append(i.gen.doc_ir)
644
+ else:
645
+ parts.append(i.gen.doc_ir)
646
+ broke = self.concat(parts)
647
+ node.gen.doc_ir = self.group(self.if_break(broke, not_broke))
463
648
 
464
649
  def exit_multi_string(self, node: uni.MultiString) -> None:
465
650
  """Generate DocIR for multiline strings."""
@@ -472,12 +657,7 @@ class DocIRGenPass(UniPass):
472
657
  """Generate DocIR for set values."""
473
658
  parts: list[doc.DocType] = []
474
659
  for i in node.kid:
475
- if isinstance(i, uni.Token):
476
- parts.append(i.gen.doc_ir)
477
- elif isinstance(i, uni.SubNodeList):
478
- parts.append(self.if_break(self.line(), self.space()))
479
- parts.append(i.gen.doc_ir)
480
- parts.append(self.if_break(self.line(), self.space()))
660
+ parts.append(i.gen.doc_ir)
481
661
 
482
662
  node.gen.doc_ir = self.group(self.concat(parts))
483
663
 
@@ -487,7 +667,8 @@ class DocIRGenPass(UniPass):
487
667
  for i in node.kid:
488
668
  parts.append(i.gen.doc_ir)
489
669
  parts.append(self.space())
490
- node.gen.doc_ir = self.finalize(parts)
670
+ parts.pop()
671
+ node.gen.doc_ir = self.group(self.concat(parts))
491
672
 
492
673
  def exit_list_compr(self, node: uni.ListCompr) -> None:
493
674
  """Generate DocIR for list comprehensions."""
@@ -495,7 +676,8 @@ class DocIRGenPass(UniPass):
495
676
  for i in node.kid:
496
677
  parts.append(i.gen.doc_ir)
497
678
  parts.append(self.space())
498
- node.gen.doc_ir = self.finalize(parts)
679
+ parts.pop()
680
+ node.gen.doc_ir = self.group(self.concat(parts))
499
681
 
500
682
  def exit_inner_compr(self, node: uni.InnerCompr) -> None:
501
683
  """Generate DocIR for inner comprehension clauses."""
@@ -503,7 +685,8 @@ class DocIRGenPass(UniPass):
503
685
  for i in node.kid:
504
686
  parts.append(i.gen.doc_ir)
505
687
  parts.append(self.space())
506
- node.gen.doc_ir = self.finalize(parts)
688
+ parts.pop()
689
+ node.gen.doc_ir = self.group(self.concat(parts))
507
690
 
508
691
  def exit_f_string(self, node: uni.FString) -> None:
509
692
  """Generate DocIR for formatted strings."""
@@ -526,7 +709,7 @@ class DocIRGenPass(UniPass):
526
709
  else:
527
710
  parts.append(i.gen.doc_ir)
528
711
  parts.append(self.space())
529
-
712
+ parts.pop()
530
713
  node.gen.doc_ir = self.group(self.concat(parts))
531
714
 
532
715
  def exit_bool_expr(self, node: uni.BoolExpr) -> None:
@@ -539,7 +722,6 @@ class DocIRGenPass(UniPass):
539
722
  else:
540
723
  parts.append(i.gen.doc_ir)
541
724
  parts.append(self.line()) # Potential break
542
-
543
725
  node.gen.doc_ir = self.group(self.concat(parts))
544
726
 
545
727
  def exit_unary_expr(self, node: uni.UnaryExpr) -> None:
@@ -605,7 +787,8 @@ class DocIRGenPass(UniPass):
605
787
  for i in node.kid:
606
788
  parts.append(i.gen.doc_ir)
607
789
  parts.append(self.space())
608
- node.gen.doc_ir = self.finalize(parts)
790
+ parts.pop()
791
+ node.gen.doc_ir = self.group(self.concat(parts))
609
792
 
610
793
  def exit_gen_compr(self, node: uni.GenCompr) -> None:
611
794
  """Generate DocIR for generator comprehensions."""
@@ -613,7 +796,8 @@ class DocIRGenPass(UniPass):
613
796
  for i in node.kid:
614
797
  parts.append(i.gen.doc_ir)
615
798
  parts.append(self.space())
616
- node.gen.doc_ir = self.finalize(parts)
799
+ parts.pop()
800
+ node.gen.doc_ir = self.group(self.concat(parts))
617
801
 
618
802
  def exit_set_compr(self, node: uni.SetCompr) -> None:
619
803
  """Generate DocIR for set comprehensions."""
@@ -621,7 +805,8 @@ class DocIRGenPass(UniPass):
621
805
  for i in node.kid:
622
806
  parts.append(i.gen.doc_ir)
623
807
  parts.append(self.space())
624
- node.gen.doc_ir = self.finalize(parts)
808
+ parts.pop()
809
+ node.gen.doc_ir = self.group(self.concat(parts))
625
810
 
626
811
  def exit_dict_compr(self, node: uni.DictCompr) -> None:
627
812
  """Generate DocIR for dictionary comprehensions."""
@@ -632,14 +817,15 @@ class DocIRGenPass(UniPass):
632
817
  else:
633
818
  parts.append(i.gen.doc_ir)
634
819
  parts.append(self.space())
635
- node.gen.doc_ir = self.finalize(parts)
820
+ parts.pop()
821
+ node.gen.doc_ir = self.group(self.concat(parts))
636
822
 
637
823
  def exit_k_w_pair(self, node: uni.KWPair) -> None:
638
824
  """Generate DocIR for keyword arguments."""
639
825
  parts: list[doc.DocType] = []
640
826
  for i in node.kid:
641
827
  parts.append(i.gen.doc_ir)
642
- node.gen.doc_ir = self.finalize(parts)
828
+ node.gen.doc_ir = self.group(self.concat(parts))
643
829
 
644
830
  def exit_await_expr(self, node: uni.AwaitExpr) -> None:
645
831
  """Generate DocIR for await expressions."""
@@ -647,7 +833,8 @@ class DocIRGenPass(UniPass):
647
833
  for i in node.kid:
648
834
  parts.append(i.gen.doc_ir)
649
835
  parts.append(self.space())
650
- node.gen.doc_ir = self.finalize(parts)
836
+ parts.pop()
837
+ node.gen.doc_ir = self.group(self.concat(parts))
651
838
 
652
839
  def exit_yield_expr(self, node: uni.YieldExpr) -> None:
653
840
  """Generate DocIR for yield expressions."""
@@ -655,7 +842,8 @@ class DocIRGenPass(UniPass):
655
842
  for i in node.kid:
656
843
  parts.append(i.gen.doc_ir)
657
844
  parts.append(self.space())
658
- node.gen.doc_ir = self.finalize(parts)
845
+ parts.pop()
846
+ node.gen.doc_ir = self.group(self.concat(parts))
659
847
 
660
848
  def exit_ctrl_stmt(self, node: uni.CtrlStmt) -> None:
661
849
  """Generate DocIR for control statements (break, continue, skip)."""
@@ -670,7 +858,8 @@ class DocIRGenPass(UniPass):
670
858
  for i in node.kid:
671
859
  parts.append(i.gen.doc_ir)
672
860
  parts.append(self.space())
673
- node.gen.doc_ir = self.finalize(parts)
861
+ parts.pop()
862
+ node.gen.doc_ir = self.group(self.concat(parts))
674
863
 
675
864
  def exit_disengage_stmt(self, node: uni.DisengageStmt) -> None:
676
865
  """Generate DocIR for disengage statements."""
@@ -685,7 +874,8 @@ class DocIRGenPass(UniPass):
685
874
  for i in node.kid:
686
875
  parts.append(i.gen.doc_ir)
687
876
  parts.append(self.space())
688
- node.gen.doc_ir = self.finalize(parts)
877
+ parts.pop()
878
+ node.gen.doc_ir = self.group(self.concat(parts))
689
879
 
690
880
  def exit_assert_stmt(self, node: uni.AssertStmt) -> None:
691
881
  """Generate DocIR for assert statements."""
@@ -693,7 +883,8 @@ class DocIRGenPass(UniPass):
693
883
  for i in node.kid:
694
884
  parts.append(i.gen.doc_ir)
695
885
  parts.append(self.space())
696
- node.gen.doc_ir = self.finalize(parts)
886
+ parts.pop()
887
+ node.gen.doc_ir = self.group(self.concat(parts))
697
888
 
698
889
  def exit_raise_stmt(self, node: uni.RaiseStmt) -> None:
699
890
  """Generate DocIR for raise statements."""
@@ -701,7 +892,8 @@ class DocIRGenPass(UniPass):
701
892
  for i in node.kid:
702
893
  parts.append(i.gen.doc_ir)
703
894
  parts.append(self.space())
704
- node.gen.doc_ir = self.finalize(parts)
895
+ parts.pop()
896
+ node.gen.doc_ir = self.group(self.concat(parts))
705
897
 
706
898
  def exit_global_vars(self, node: uni.GlobalVars) -> None:
707
899
  """Generate DocIR for global variables."""
@@ -717,26 +909,45 @@ class DocIRGenPass(UniPass):
717
909
  else:
718
910
  parts.append(i.gen.doc_ir)
719
911
  parts.append(self.space())
720
- node.gen.doc_ir = self.finalize(parts)
912
+ node.gen.doc_ir = self.group(self.concat(parts))
721
913
 
722
914
  def exit_module_code(self, node: uni.ModuleCode) -> None:
723
915
  """Generate DocIR for module code."""
724
916
  parts: list[doc.DocType] = []
917
+ body_parts: list[doc.DocType] = []
918
+ in_body = False
725
919
  for i in node.kid:
726
- if i == node.doc:
920
+ if node.doc and i is node.doc:
727
921
  parts.append(i.gen.doc_ir)
728
922
  parts.append(self.hard_line())
729
- elif isinstance(i, uni.Token):
923
+ elif i == node.name:
924
+ parts.append(i.gen.doc_ir)
925
+ parts.append(self.space())
926
+ elif isinstance(node.body, Sequence) and i in node.body:
927
+ if not in_body:
928
+ parts.pop()
929
+ body_parts.append(self.hard_line())
930
+ body_parts.append(i.gen.doc_ir)
931
+ body_parts.append(self.hard_line())
932
+ in_body = True
933
+ elif in_body:
934
+ in_body = False
935
+ body_parts.pop()
936
+ parts.append(self.indent(self.concat(body_parts)))
937
+ parts.append(self.hard_line())
938
+ parts.append(i.gen.doc_ir)
939
+ parts.append(self.space())
940
+ elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
941
+ parts.pop()
730
942
  parts.append(i.gen.doc_ir)
731
943
  parts.append(self.space())
732
944
  else:
733
- parts.append(self.concat([i.gen.doc_ir]))
734
-
945
+ parts.append(i.gen.doc_ir)
946
+ parts.append(self.space())
735
947
  node.gen.doc_ir = self.group(self.concat(parts))
736
948
 
737
949
  def exit_global_stmt(self, node: uni.GlobalStmt) -> None:
738
950
  """Generate DocIR for global statements."""
739
- # node.kid is [GLOBAL_OP_token, name_list_SubNodeList, SEMI_token]
740
951
  parts: list[doc.DocType] = []
741
952
  for i in node.kid:
742
953
  parts.append(i.gen.doc_ir)
@@ -748,7 +959,7 @@ class DocIRGenPass(UniPass):
748
959
  for i in node.kid:
749
960
  parts.append(i.gen.doc_ir)
750
961
  parts.append(self.space())
751
- node.gen.doc_ir = self.finalize(parts)
962
+ node.gen.doc_ir = self.group(self.concat(parts))
752
963
 
753
964
  def exit_visit_stmt(self, node: uni.VisitStmt) -> None:
754
965
  """Generate DocIR for visit statements."""
@@ -761,7 +972,7 @@ class DocIRGenPass(UniPass):
761
972
  else:
762
973
  parts.append(i.gen.doc_ir)
763
974
  parts.append(self.space())
764
- node.gen.doc_ir = self.finalize(parts)
975
+ node.gen.doc_ir = self.group(self.concat(parts))
765
976
 
766
977
  def exit_ignore_stmt(self, node: uni.IgnoreStmt) -> None:
767
978
  """Generate DocIR for ignore statements."""
@@ -769,7 +980,7 @@ class DocIRGenPass(UniPass):
769
980
  for i in node.kid:
770
981
  parts.append(i.gen.doc_ir)
771
982
  parts.append(self.space())
772
- node.gen.doc_ir = self.finalize(parts)
983
+ node.gen.doc_ir = self.group(self.concat(parts))
773
984
 
774
985
  def exit_connect_op(self, node: uni.ConnectOp) -> None:
775
986
  """Generate DocIR for connect operator."""
@@ -777,7 +988,8 @@ class DocIRGenPass(UniPass):
777
988
  for i in node.kid:
778
989
  parts.append(i.gen.doc_ir)
779
990
  parts.append(self.space())
780
- node.gen.doc_ir = self.finalize(parts)
991
+ parts.pop()
992
+ node.gen.doc_ir = self.group(self.concat(parts))
781
993
 
782
994
  def exit_disconnect_op(self, node: uni.DisconnectOp) -> None:
783
995
  """Generate DocIR for disconnect operator."""
@@ -785,7 +997,8 @@ class DocIRGenPass(UniPass):
785
997
  for i in node.kid:
786
998
  parts.append(i.gen.doc_ir)
787
999
  parts.append(self.space())
788
- node.gen.doc_ir = self.finalize(parts)
1000
+ parts.pop()
1001
+ node.gen.doc_ir = self.group(self.concat(parts))
789
1002
 
790
1003
  def exit_compare_expr(self, node: uni.CompareExpr) -> None:
791
1004
  """Generate DocIR for comparison expressions."""
@@ -793,7 +1006,8 @@ class DocIRGenPass(UniPass):
793
1006
  for i in node.kid:
794
1007
  parts.append(i.gen.doc_ir)
795
1008
  parts.append(self.space())
796
- node.gen.doc_ir = self.finalize(parts)
1009
+ parts.pop()
1010
+ node.gen.doc_ir = self.group(self.concat(parts))
797
1011
 
798
1012
  def exit_atom_unit(self, node: uni.AtomUnit) -> None:
799
1013
  """Generate DocIR for atom units (parenthesized expressions)."""
@@ -815,7 +1029,8 @@ class DocIRGenPass(UniPass):
815
1029
  parts.append(i.gen.doc_ir)
816
1030
  parts.append(self.space())
817
1031
  prev_item = i
818
- node.gen.doc_ir = self.finalize(parts)
1032
+ parts.pop()
1033
+ node.gen.doc_ir = self.group(self.concat(parts))
819
1034
 
820
1035
  def exit_expr_as_item(self, node: uni.ExprAsItem) -> None:
821
1036
  """Generate DocIR for expression as item nodes."""
@@ -823,7 +1038,7 @@ class DocIRGenPass(UniPass):
823
1038
  for i in node.kid:
824
1039
  parts.append(i.gen.doc_ir)
825
1040
  parts.append(self.space())
826
- node.gen.doc_ir = self.finalize(parts)
1041
+ node.gen.doc_ir = self.group(self.concat(parts))
827
1042
 
828
1043
  def exit_filter_compr(self, node: uni.FilterCompr) -> None:
829
1044
  """Generate DocIR for filter comprehensions."""
@@ -831,7 +1046,7 @@ class DocIRGenPass(UniPass):
831
1046
  for i in node.kid:
832
1047
  parts.append(i.gen.doc_ir)
833
1048
  parts.append(self.space())
834
- node.gen.doc_ir = self.finalize(parts)
1049
+ node.gen.doc_ir = self.group(self.concat(parts))
835
1050
 
836
1051
  def exit_assign_compr(self, node: uni.AssignCompr) -> None:
837
1052
  """Generate DocIR for assignment comprehensions."""
@@ -839,7 +1054,7 @@ class DocIRGenPass(UniPass):
839
1054
  for i in node.kid:
840
1055
  parts.append(i.gen.doc_ir)
841
1056
  parts.append(self.space())
842
- node.gen.doc_ir = self.finalize(parts)
1057
+ node.gen.doc_ir = self.group(self.concat(parts))
843
1058
 
844
1059
  def exit_py_inline_code(self, node: uni.PyInlineCode) -> None:
845
1060
  """Generate DocIR for Python inline code blocks."""
@@ -856,7 +1071,7 @@ class DocIRGenPass(UniPass):
856
1071
  else:
857
1072
  parts.append(i.gen.doc_ir)
858
1073
  parts.append(self.space())
859
- node.gen.doc_ir = self.finalize(parts)
1074
+ node.gen.doc_ir = self.group(self.concat(parts))
860
1075
 
861
1076
  def exit_test(self, node: uni.Test) -> None:
862
1077
  """Generate DocIR for test nodes."""
@@ -872,7 +1087,7 @@ class DocIRGenPass(UniPass):
872
1087
  else:
873
1088
  parts.append(i.gen.doc_ir)
874
1089
  parts.append(self.space())
875
- node.gen.doc_ir = self.finalize(parts)
1090
+ node.gen.doc_ir = self.group(self.concat(parts))
876
1091
 
877
1092
  def exit_check_stmt(self, node: uni.CheckStmt) -> None:
878
1093
  """Generate DocIR for check statements."""
@@ -882,7 +1097,7 @@ class DocIRGenPass(UniPass):
882
1097
  parts.pop()
883
1098
  parts.append(i.gen.doc_ir)
884
1099
  parts.append(self.space())
885
- node.gen.doc_ir = self.finalize(parts)
1100
+ node.gen.doc_ir = self.group(self.concat(parts))
886
1101
 
887
1102
  def exit_match_stmt(self, node: uni.MatchStmt) -> None:
888
1103
  """Generate DocIR for match statements."""
@@ -902,7 +1117,7 @@ class DocIRGenPass(UniPass):
902
1117
  else:
903
1118
  parts.append(i.gen.doc_ir)
904
1119
  parts.append(self.space())
905
- node.gen.doc_ir = self.finalize(parts)
1120
+ node.gen.doc_ir = self.group(self.concat(parts))
906
1121
 
907
1122
  def exit_match_case(self, node: uni.MatchCase) -> None:
908
1123
  """Generate DocIR for match cases."""
@@ -919,7 +1134,7 @@ class DocIRGenPass(UniPass):
919
1134
  parts.append(i.gen.doc_ir)
920
1135
  parts.append(self.space())
921
1136
  parts.append(self.indent(self.concat([self.hard_line()] + indent_parts)))
922
- node.gen.doc_ir = self.finalize(parts)
1137
+ node.gen.doc_ir = self.group(self.concat(parts))
923
1138
 
924
1139
  def exit_match_value(self, node: uni.MatchValue) -> None:
925
1140
  """Generate DocIR for match value patterns."""
@@ -927,7 +1142,8 @@ class DocIRGenPass(UniPass):
927
1142
  for i in node.kid:
928
1143
  parts.append(i.gen.doc_ir)
929
1144
  parts.append(self.space())
930
- node.gen.doc_ir = self.finalize(parts)
1145
+ parts.pop()
1146
+ node.gen.doc_ir = self.group(self.concat(parts))
931
1147
 
932
1148
  def exit_match_singleton(self, node: uni.MatchSingleton) -> None:
933
1149
  """Generate DocIR for match singleton patterns."""
@@ -935,7 +1151,8 @@ class DocIRGenPass(UniPass):
935
1151
  for i in node.kid:
936
1152
  parts.append(i.gen.doc_ir)
937
1153
  parts.append(self.space())
938
- node.gen.doc_ir = self.finalize(parts)
1154
+ parts.pop()
1155
+ node.gen.doc_ir = self.group(self.concat(parts))
939
1156
 
940
1157
  def exit_match_sequence(self, node: uni.MatchSequence) -> None:
941
1158
  """Generate DocIR for match sequence patterns."""
@@ -943,7 +1160,8 @@ class DocIRGenPass(UniPass):
943
1160
  for i in node.kid:
944
1161
  parts.append(i.gen.doc_ir)
945
1162
  parts.append(self.space())
946
- node.gen.doc_ir = self.finalize(parts)
1163
+ parts.pop()
1164
+ node.gen.doc_ir = self.group(self.concat(parts))
947
1165
 
948
1166
  def exit_match_mapping(self, node: uni.MatchMapping) -> None:
949
1167
  """Generate DocIR for match mapping patterns."""
@@ -951,7 +1169,8 @@ class DocIRGenPass(UniPass):
951
1169
  for i in node.kid:
952
1170
  parts.append(i.gen.doc_ir)
953
1171
  parts.append(self.space())
954
- node.gen.doc_ir = self.finalize(parts)
1172
+ parts.pop()
1173
+ node.gen.doc_ir = self.group(self.concat(parts))
955
1174
 
956
1175
  def exit_match_or(self, node: uni.MatchOr) -> None:
957
1176
  """Generate DocIR for match OR patterns."""
@@ -959,7 +1178,8 @@ class DocIRGenPass(UniPass):
959
1178
  for i in node.kid:
960
1179
  parts.append(i.gen.doc_ir)
961
1180
  parts.append(self.space())
962
- node.gen.doc_ir = self.finalize(parts)
1181
+ parts.pop()
1182
+ node.gen.doc_ir = self.group(self.concat(parts))
963
1183
 
964
1184
  def exit_match_as(self, node: uni.MatchAs) -> None:
965
1185
  """Generate DocIR for match AS patterns."""
@@ -967,7 +1187,8 @@ class DocIRGenPass(UniPass):
967
1187
  for i in node.kid:
968
1188
  parts.append(i.gen.doc_ir)
969
1189
  parts.append(self.space())
970
- node.gen.doc_ir = self.finalize(parts)
1190
+ parts.pop()
1191
+ node.gen.doc_ir = self.group(self.concat(parts))
971
1192
 
972
1193
  def exit_match_wild(self, node: uni.MatchWild) -> None:
973
1194
  """Generate DocIR for match wildcard patterns."""
@@ -975,7 +1196,8 @@ class DocIRGenPass(UniPass):
975
1196
  for i in node.kid:
976
1197
  parts.append(i.gen.doc_ir)
977
1198
  parts.append(self.space())
978
- node.gen.doc_ir = self.finalize(parts)
1199
+ parts.pop()
1200
+ node.gen.doc_ir = self.group(self.concat(parts))
979
1201
 
980
1202
  def exit_match_star(self, node: uni.MatchStar) -> None:
981
1203
  """Generate DocIR for match star patterns (e.g., *args, **kwargs)."""
@@ -983,7 +1205,8 @@ class DocIRGenPass(UniPass):
983
1205
  for i in node.kid:
984
1206
  parts.append(i.gen.doc_ir)
985
1207
  parts.append(self.space())
986
- node.gen.doc_ir = self.finalize(parts)
1208
+ parts.pop()
1209
+ node.gen.doc_ir = self.group(self.concat(parts))
987
1210
 
988
1211
  def exit_match_k_v_pair(self, node: uni.MatchKVPair) -> None:
989
1212
  """Generate DocIR for match key-value pairs."""
@@ -994,21 +1217,27 @@ class DocIRGenPass(UniPass):
994
1217
  else:
995
1218
  parts.append(i.gen.doc_ir)
996
1219
  parts.append(self.space())
997
- node.gen.doc_ir = self.finalize(parts)
1220
+ parts.pop()
1221
+ node.gen.doc_ir = self.group(self.concat(parts))
998
1222
 
999
1223
  def exit_match_arch(self, node: uni.MatchArch) -> None:
1000
1224
  """Generate DocIR for match architecture patterns."""
1001
1225
  parts: list[doc.DocType] = []
1002
1226
  for i in node.kid:
1003
- parts.append(i.gen.doc_ir)
1004
- parts.append(self.space())
1005
- node.gen.doc_ir = self.finalize(parts)
1227
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
1228
+ parts.append(i.gen.doc_ir)
1229
+ parts.append(self.space())
1230
+ else:
1231
+ parts.append(i.gen.doc_ir)
1232
+ node.gen.doc_ir = self.group(self.concat(parts))
1006
1233
 
1007
1234
  def exit_enum(self, node: uni.Enum) -> None:
1008
1235
  """Generate DocIR for enum declarations."""
1009
1236
  parts: list[doc.DocType] = []
1010
1237
  for i in node.kid:
1011
- if i in [node.doc, node.decorators]:
1238
+ if (node.doc and i is node.doc) or (
1239
+ node.decorators and i in node.decorators
1240
+ ):
1012
1241
  parts.append(i.gen.doc_ir)
1013
1242
  parts.append(self.hard_line())
1014
1243
  elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
@@ -1018,93 +1247,74 @@ class DocIRGenPass(UniPass):
1018
1247
  else:
1019
1248
  parts.append(i.gen.doc_ir)
1020
1249
  parts.append(self.space())
1021
- node.gen.doc_ir = self.finalize(parts)
1250
+ node.gen.doc_ir = self.group(self.concat(parts))
1022
1251
 
1023
1252
  def exit_sub_tag(self, node: uni.SubTag) -> None:
1024
1253
  """Generate DocIR for sub-tag nodes."""
1025
- parts: list[doc.DocType] = []
1026
- for i in node.kid:
1027
- parts.append(i.gen.doc_ir)
1028
- parts.append(self.space())
1029
- node.gen.doc_ir = self.finalize(parts)
1254
+ before_colon: list[doc.DocType] = []
1255
+ after_colon: list[doc.DocType] = []
1256
+ seen_colon = False
1030
1257
 
1031
- def exit_sub_node_list(self, node: uni.SubNodeList) -> None:
1032
- """Generate DocIR for a SubNodeList."""
1033
- parts: list[doc.DocType] = []
1034
- indent_parts: list[doc.DocType] = []
1035
- prev_item: Optional[uni.UniNode] = None
1036
- in_codeblock = node.delim and node.delim.name == Tok.WS
1037
- in_archetype = (
1038
- node.parent
1039
- and isinstance(node.parent, (uni.Archetype, uni.Enum))
1040
- and node == node.parent.body
1041
- )
1042
- is_assignment = node.delim and node.delim.name == Tok.EQ
1043
1258
  for i in node.kid:
1044
- if i == node.left_enc:
1045
- parts.append(i.gen.doc_ir)
1046
- indent_parts.append(self.hard_line())
1047
- elif i == node.right_enc:
1048
- if in_codeblock:
1049
- indent_parts.pop()
1050
- parts.append(self.indent(self.concat(indent_parts)))
1051
- if prev_item != node.left_enc:
1052
- parts.append(self.line())
1053
- parts.append(i.gen.doc_ir)
1054
- elif (
1055
- isinstance(i, uni.Token)
1056
- and node.delim
1057
- and i.name == node.delim.name
1058
- and i.name not in [Tok.DOT, Tok.DECOR_OP]
1059
- ):
1060
- indent_parts.append(i.gen.doc_ir)
1061
- indent_parts.append(self.line())
1259
+ if isinstance(i, uni.Token) and i.name == Tok.COLON and not seen_colon:
1260
+ colon_tok = i.gen.doc_ir
1261
+ seen_colon = True
1262
+ elif seen_colon:
1263
+ after_colon.append(i.gen.doc_ir)
1062
1264
  else:
1063
- if (
1064
- in_codeblock
1065
- and prev_item
1066
- and self.has_gap(prev_item, i)
1067
- and isinstance(i, uni.CommentToken)
1068
- and (
1069
- not in_archetype
1070
- or not prev_item
1071
- or not (
1072
- self.is_one_line(prev_item) and type(prev_item) is type(i)
1073
- )
1074
- )
1075
- ) or (
1076
- in_archetype
1077
- and prev_item not in [None, node.left_enc, node.right_enc]
1078
- and (
1079
- type(prev_item) is not type(i)
1080
- or (prev_item and not self.is_one_line(prev_item))
1081
- )
1082
- ):
1083
- indent_parts.append(self.hard_line())
1084
- indent_parts.append(i.gen.doc_ir)
1085
- if in_codeblock:
1086
- indent_parts.append(self.hard_line())
1087
- elif is_assignment:
1088
- indent_parts.append(self.space())
1089
- prev_item = i
1090
- if is_assignment:
1091
- indent_parts.pop()
1092
- node.gen.doc_ir = self.concat(parts) if parts else self.concat(indent_parts)
1265
+ before_colon.append(i.gen.doc_ir)
1266
+
1267
+ if seen_colon:
1268
+ flat = self.concat([*before_colon, colon_tok, self.space(), *after_colon])
1269
+ broke = self.concat(
1270
+ [
1271
+ *before_colon,
1272
+ colon_tok,
1273
+ self.indent(self.concat([self.line(), *after_colon])),
1274
+ ]
1275
+ )
1276
+ node.gen.doc_ir = self.group(self.if_break(broke, flat))
1277
+ else:
1278
+ node.gen.doc_ir = self.concat(before_colon + after_colon)
1093
1279
 
1094
1280
  def exit_impl_def(self, node: uni.ImplDef) -> None:
1095
1281
  """Generate DocIR for implementation definitions."""
1096
1282
  parts: list[doc.DocType] = []
1283
+ body_parts: list[doc.DocType] = []
1284
+ in_body = False
1097
1285
  for i in node.kid:
1098
- if i in [node.doc, node.decorators]:
1286
+ if i == node.doc or (node.decorators and i in node.decorators):
1099
1287
  parts.append(i.gen.doc_ir)
1100
1288
  parts.append(self.hard_line())
1101
- elif i == node.target:
1289
+ elif i in node.target:
1290
+ parts.append(i.gen.doc_ir)
1291
+ elif (
1292
+ in_body
1293
+ or isinstance(node.body, Sequence)
1294
+ and node.body
1295
+ and i == node.body[0]
1296
+ ):
1297
+ if not in_body:
1298
+ parts.pop()
1299
+ body_parts.append(self.hard_line())
1300
+ if isinstance(i, uni.Token) and i.name == Tok.COMMA:
1301
+ body_parts.pop()
1302
+ body_parts.append(i.gen.doc_ir)
1303
+ body_parts.append(self.hard_line())
1304
+ in_body = True
1305
+ if in_body and isinstance(node.body, Sequence) and i == node.body[-1]:
1306
+ in_body = False
1307
+ body_parts.pop()
1308
+ parts.append(self.indent(self.concat(body_parts)))
1309
+ parts.append(self.hard_line())
1310
+ elif isinstance(i, uni.Token) and i.name == Tok.SEMI:
1311
+ parts.pop()
1102
1312
  parts.append(i.gen.doc_ir)
1103
1313
  parts.append(self.space())
1104
1314
  else:
1105
1315
  parts.append(i.gen.doc_ir)
1106
1316
  parts.append(self.space())
1107
- node.gen.doc_ir = self.finalize(parts)
1317
+ node.gen.doc_ir = self.group(self.concat(parts))
1108
1318
 
1109
1319
  def exit_event_signature(self, node: uni.EventSignature) -> None:
1110
1320
  """Generate DocIR for event signatures."""
@@ -1112,7 +1322,8 @@ class DocIRGenPass(UniPass):
1112
1322
  for i in node.kid:
1113
1323
  parts.append(i.gen.doc_ir)
1114
1324
  parts.append(self.space())
1115
- node.gen.doc_ir = self.finalize(parts)
1325
+ parts.pop()
1326
+ node.gen.doc_ir = self.group(self.concat(parts))
1116
1327
 
1117
1328
  def exit_typed_ctx_block(self, node: uni.TypedCtxBlock) -> None:
1118
1329
  """Generate DocIR for typed context blocks."""
@@ -1154,9 +1365,7 @@ class DocIRGenPass(UniPass):
1154
1365
  is_escaped_curly = (
1155
1366
  node.lit_value in ["{", "}"]
1156
1367
  and node.parent
1157
- and isinstance(node.parent, uni.SubNodeList)
1158
- and node.parent.parent
1159
- and isinstance(node.parent.parent, uni.FString)
1368
+ and isinstance(node.parent, uni.FString)
1160
1369
  )
1161
1370
 
1162
1371
  if "\n" in node.value: