batch-impl 0.7.2

A proc-macro library for batch generating trait impls with a powerful DSL
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
# Developer Changelog

> Internal implementation details, refactoring, testing, CI; user-visible features are covered in `CHANGELOG.md`.
>
> English docs are the release artifact, translated from the development Chinese docs in
> `docs/zh-CN/` right before publishing.

## 0.7.2 (2026-08-14)

- **`@` reference diagnostics in user language + type-position validation** (`codegen/fresh.rs` + `where_at.rs`): the `@g_i` out-of-range error no longer leaks the `_Param_{g}_{i}_BatchGen_` protocol name — the displayed `@{}_{}` is derived from the parsed (g, pos) pair (single authority, the wording cannot drift); new `validate_at_refs`: dangling `@N` (index < fresh count) / `@g_i` (group membership) references in the target type / trait args previously leaked the reserved name through the sweep as raw rustc E0412 — now validated against the impl's declared fresh set, the same rule as the where side; `at_group_out_of_range` / `at_num_out_of_range` constructors shared by where and type positions
- **Tests**: dsl `at_refs_in_target_type` (`(()^2)^Box<@0>` / `@0_1` positives, locking out false positives); 2 new ui fixtures (at_num_in_type / at_group_in_type) locking the user-language wording
- **`batch_preview!` expansion preview** (`entry/preview.rs`): the real pipeline (`prepare_attr_expansion` + `collect_spec_leaves` shared refactor — the preview and the three entries share one preprocessing/parse path) → one item per line rendered into the `compile_error!` diagnostic channel (the only stable terminal channel) — trait + impls one item per line, DSL errors surface as-is; preview-only `^`/`-` associativity miswrite notes (`ONE_ARITY_CONTAINERS` table + target-type recursion, `Box<Vec, u32>` → suggests `Box^Vec^u32` with the `A^B-C` = `A-B-C` identity); zero heuristics on the compiler path
- **driver/entry refactor**: `collect_spec_leaves` extracted from `parse_batch_trait_entry` (parse/expand/error-aggregation single authority, shared by the three entries and the preview); `prepare_attr_expansion` → `PreparedAttr` extracted from `expand_attr_macro` (preprocessing once, rendering deferred); behavior-equivalent, all tests green
- **Generator-splat declaration hoisting in trait args** (`codegen/impl_parts.rs`): the WithTrait arm of `extract_impl_parts` previously dropped the declaration returned by `flat_splat_params` ("Declarations are dropped here") — `Conv<*()^2> X` leaked fresh names as raw E0412; the declaration now joins the impl generics, the same rule as the generic-arg position; the stale "acknowledged oddity" comment in `parse/generic.rs` corrected (measured: `Foo<*(()^N)>` has worked since the structural refactor)
- **Generator in the declaration position: targeted error** (`parse/primary.rs` + `ast/types_visit.rs::contains_generator`): `<*()^N>` / `<*(()^N)>` fresh declarations have no carrier (the declaration position IS the carrier), previously rendered `impl <<P0,..> *(P0,..)>` garbage — parse-layer error suggesting `T^()^2`; dsl `gen_splat_trait_args_hoist` (trait-arg hoisting + the `*(()^3)` parenthesized form) + ui fixture decl_generator_splat
- **`#blanket` by-value receiver fix + doc note** (`directives/blanket.rs`): the forward's deref count dispatches on the receiver kind — `&self`/`&mut self` use depth+1 (`**self`, through the reference and the wrapper layers), by-value `self` IS the wrapper and uses depth (`*self`) — the uniform `**self` dereferenced the inner type one layer too deep for by-value methods (E0614, Box probe verified); the doc note stays: moving out of shared wrappers (`&`/`Rc`) cannot type-check, a non-empty by-value selection injects `#[doc]` per spec (the attr rides the existing `WithAttr` → `ImplParts.attrs` channel, zero new machinery); dsl `blanket_by_value_receiver` (`Box::new(9u8).consume()` exercises the by-value forward)
- **`TyWithAttr::apply` inner-preservation fix** (`apply/apply_tuple.rs`): with an inner already attached, the operator applies to the inner (`#[attr] Box^u8` = `#[attr] Box<u8>`), previously `TyWithAttr(self.0, o.into())` silently replaced it — a pre-existing bug exposed by the `#[doc]` injection; dsl regression `attr_wrapper_chain`
- **Open-extension protocol convergence (docs)**: the in-impl `T {m!{...}}` (no `!`, associated items) is deprecated and kept for compatibility, the top-level `{! m!{...}}` four-segment protocol is the only recommended shape — tutorial §7.5 convergence note, `directive_open.md`/`batch_preprocess_test.md` crate docs synced, architecture attachment semantics "top-level only"
- **Syntax-freeze commitment (docs)**: the `@N` stability commitment extended to the whole surface — README "Syntax-freeze commitment (0.7.2)" section, architecture extension-guidelines freeze clause, tutorial §6.4 marks `@g_i`/`@all_fresh`/`@N..M` as power-user tier; future releases only add / diagnose / document, changing existing semantics = a deliberate breaking release
- **Attribute-macro custom `@` constants** (`consts/ctx.rs` + `entry/mod.rs`): `ConstCtx::Attribute` gains `user_table` — `prepare_attr_expansion` calls `collect_user_consts` after path-prefix parsing to collect the leading `@name=value;` section (same rule/validation as `batch_trait!`: reserved names, built-in collisions, cycles, forward refs); the `try_expand_at` definition-branch message unified (both entries carry a table, no more branching); the unknown-constant message uniformly gains the "defined before the reference" suffix (const_unknown.stderr updated); dsl `attr_custom_consts` (chained refs + DSL expression values) + ui `const_def_position` (non-leading definition errors)

## 0.7.1 (2026-08-13)

- **Fallback validation**(`parse::generic::primitive`):stray `;`/`=`/`@`/`#` 与相邻类型片段(`A B`/`Vec<T>U`/`[A B]`)定向报错——不再渲染非法 Rust;排除路径/range/泛型/fn/dyn/lifetime 名(不误伤 `Vec<u32>`/`a::b`/`0..3`/`dyn Trait`/`&'a T`)
- **`parse_function` at_end**:fn 参数列表后残留 + `(<T: Bound>)` 元组生成器声明处理
- **blanket 返回 `Self`/`Self::Assoc` 拒绝**:朴素 `(**self)` 委托匹配不上 wrapper 的 `Self`——定向报错并建议 `#name{...}`
- **`MAX_NEST_DEPTH` 上移 util + `depth_err` 合并**:三处递归 walker 统一到 `util::MAX_NEST_DEPTH` + 统一构造诊断
- **`generate_impl` 拆分**(codegen/mod + where_at + impl_parts):impl 泛型名/继承提取共用,行为等价
- **passthrough 一致性测试 + 探针转回归**:`bracket_is_passthrough` 四递归入口一致性 + 4 个新 ui fixture + adjacent_types
- **Diagnostic hardening (extended)** (`parse::generic::primitive` + directive system): empty binding/bound values (`Item =` / `T:`), non-integer type literals (`1.5`/`"hi"`), non-integer range endpoints (`1..x`/`A..B`), malformed array lengths (`[u8; 3; 4]`/`[u8;]`), `+`/`?`/`.` at a type start, typo suggestions for unknown directives (Levenshtein ≤ 2, `#delgate`→`#delegate`), and a parse_group transparent-group guard — all targeted errors. **Known leftover**: an empty bound in a generic declaration / trait arg (`<T:>`) still loses its `:` during angle-collect (rustc E0425 fallback, see the ui `binding_bound_empty` comment)
- **Structure**: directive dispatch (`expand_directive`/`expand_fill`/`expand_delegate`/`expand_single`/`expand_many`/`levenshtein`) moved from `preprocess/mod.rs` into `directives/dispatch.rs` — `preprocess/mod.rs` 412→179 lines, `directives/` is now the real directive-system entry
- **Docs (equivalent shorthands)**: `#fill([foo]){body}` ≡ `#foo{body}`, `where{predicates} {code block}` ≡ bare `where predicates {code block}` — written into tutorial §7.2/§8.2 and the README feature table (measured: stable 1.97.1 has no proc-macro warning channel — `proc_macro_diagnostic` E0658 — so docs education chosen over runtime warnings)
- **Single-source dedup (P0)**: the Cartesian-product algorithm lived in three copies (`apply::apply_tuple::pow_cartesian` + the Tuple/Generic arms of `ast::types_visit`) — unified into a generic `util::cartesian<T>`, one authority for N-way Cartesian expansion
- **Chained `.into()` (P1)**: 13 usage-site `Box::new(x)` / `Some(x.into())` wrappers became `.into()` (`From<T> for Box<Ty>` / `From<Ty> for Option<Box<Ty>>` already exist; the definition site keeps `Box::new` to avoid recursion)
- **FP accumulators (P2)**: 5 `for`+`push` accumulators became `fold`/`map`/`from_fn` (`render_impl`/`instantiate_combo`/`parse_list`/`fold_splat_elems`/`expand_splat_elems`); `flat_splat_params` keeps its `for` (a fold closure would be longer — conciseness wins)
- **Long-function split (P3)**: `resolve_where_at` extracted `emit_fresh_predicates` + `parse_fresh_range`; `primitive` extracted 4 `validate_*`; `parse_group` extracted `parse_array_group`; `try_expand_at` left as-is (already pure chained short-circuit — splitting only adds boilerplate)
- **Typo-guard dedup**: `check_builtin_typo` extracted the Levenshtein guard (two verbatim copies in one file)
- **Merge-verification outcome**: the audit's suggested `generic_param_names`×4, `@`-reference×5 and `range`×2 were each verified and **not merged — different semantics** (e.g. blanket needs the full `const N: usize` declaration while `generic_param_names` yields bare names — a naive reuse would E0747) — "looks alike" ≠ "same semantics"; don't unify for its own sake

## 0.7.0 (2026-08-10)

### Trait generic args substitute in directive bodies + codegen postprocess layer

- **New capability**: a spec-level trait segment with concrete args
  (`Conv<bool> [Pair<A, A>, Pair<B, B>] #conv{...}`) now substitutes the
  trait's generic params in directive-copied bodies — `fn conv(value: T)`
  becomes `fn conv(value: bool)` in the generated impl (previously the raw
  `T` leaked into the impl, E0425). Works for `#[batch_impl]` and
  `#[batch_impl_only]`; the trait definition is the source of param names.
- **Codegen postprocess layer** (`codegen/postprocess.rs`): trait generic
  substitution moved out of preprocess (which no longer threads a param map
  through `expand_tokens`/`expand_directive`/`build_from_item`) into a
  postprocess over `ImplParts` — it pairs `ImplParts::trait_generic_names`
  (the concrete args) with the entry trait's type/const param names
  (threaded via `run_pipeline` → `parse_batch_trait_entry` → `generate_impl`)
  and rewrites the body (fn signature + user code block). Lifetime args
  (`'static`) and lifetime params are excluded — bodies reference their own
  impl lifetimes. This joins `sweep_fresh_names` as the "codegen
  postprocess" concept: complex token rewrites after extraction, where
  `ImplParts` carries all needed context.
- Tests: `trait_generic_args` (dsl) — trait generic substitution with a
  real (non-discarded) trait, verifying the impl compiles and the method is
  referenceable; `trait_generic_args_to_impl_generic` — the arg points at an
  impl generic (`<U>A<U>()` → `fn foo(_: U)`).
- **Fixed edge (trait segment + right splat)**: `Conv<bool> Pair^*(A, B)`
  previously misparsed to `Pair<A<B>>`; the splat-deferred-expansion
  refactor (below) keeps `*(A,B)` whole through parse/apply and expands it
  only in codegen — the same input now produces `Pair<A, B>` (verified by
  dsl `splat_scenarios`'s `assert_cv::<Pair<SplatA, SplatB>>()`). The
  array-splat alternative `Pair^[*(A),*(B)]^2` still works as before.

### Splat expansion deferred to codegen (parse/apply/expand keep `*()`/`*[]` whole)

- **Principle (user-confirmed)**: a splat (`*(...)` / `*[...]`) is a *whole*
  unit through parse/apply/expand — it only flattens into its elements in
  the codegen postprocess. Previously the apply layer flattened right-splat
  operands (`T^*(A,B)` → flat `T-A-B-...` chain), which misparsed in
  combinations with trait segments (`Conv<bool> Pair^*(A,B)` → `Pair<A<B>>`)
  and with trailing code blocks (`Pair^*(A,B) {body}` → `Pair<*const (A,B)>`
  via the rest-parse path).
- **Where splats flatten now** (single expansion point, codegen):
  - `expand_splat_elems` (Ty structure): splat elements inside `TyTuple`
    flatten with fresh declarations hoisted — `(A, *(B,C))` → `(A,B,C)`,
    `(*(()^3))` → `<P0,P1,P2>(P0,P1,P2)`. Runs before `hoist_type_params`.
  - Generic-arg and trait-arg splats flatten in the same pass via
    `expand_tp` (TyTypeParam params are now `Box<Ty>`, so splats stay
    structural): `T<*(A,B)>` → `T<A,B>`, `Map<*(K,V)>` → `Map<K,V>`
    (nested splats recurse), `Conv<*(A,B)> X` → `impl Conv<A,B> for X`
    (trait-path splats expand in `extract_impl_parts`, where the trait
    args are rendered). The former token-level `expand_splats` pass is
    gone — bodies never pass through any expander, so `a * b` inside a fn
    stays multiplication; `*const T` / `*mut T` stay raw pointers.
  - Spec-list splats (`[*(A),*(B)]`, `*[Vec,Box]^T`) still flatten in the
    expand phase (`TyKind::Splat` → `Expand::Many`) — that is impl-list
    generation, not type-structure expansion.
  - Generic-arg splats need no parser special case — the chunk falls
    through the default path, survives as a single `*(a,b)` arg and
    expands structurally (`expand_tp`) — `Foo<*(a,b)>` → `Foo<a,b>` (the
    dedicated Splat-arg branch and `contains_generator` were deleted; ui
    `gen_splat_arg` removed). A generator splat there (`Foo<*(()^N)>` /
    `<*()^3>`) survives as a raw arg and rustc reports the missing
    declaration — acknowledged oddity, no dedicated diagnostic.
  - **Splat pow inside generic args** (`Frac<*(*@u*)^2>`): the pow result
    (`TyArray([*(u8,u8), ...])`) enters params and distributes in `expand`'s
    generic branch — one impl per pair (36 total, equivalent to the
    right-splat chain `Frac^*(*@u*)^2`). **Array-arg distribution unified
    into one path** (user principle: "a rule that doesn't apply universally
    isn't a rule"): literals (`T<[A,B]>`), constants (`T<@u*>` →
    `[u8,...]`) and pow results all reach params as a `TyArray` and
    distribute in that same `expand` branch — the parse-time `has_array_arg`
    and `split_arg_candidates` were deleted (dsl `splat_pow_arg`; nested
    `[[A,B],C]` went from recursive flatten-to-leaves to one-layer
    distribution, consistent with splat's one-layer expansion).
  - **Container rule** (`parse_group`): a group whose content is a lone
    splat parses as the container holding the splat as one element —
    `(*(a,b))` = `( *(a,b) )` (tuple), `[*(a,b)]` = `[ *(a,b) ]` (array);
    the splat element expands only in codegen (rendered `(a, b)` /
    `[a, b]`), so the tail-comma forms and the bare forms share one code
    path (`lone_splat` gates the parse_list path; the former per-delimiter
    `TyKind::Splat` special-case branches were deleted). `(a)` stays a
    transparent group, `[a]` a slice.
  - **Concrete-type args reject bindings/bounds** (user ruling: "if it has
    `Item = u32` it's a trait"): `parse_angle_bracket_contents` gained an
    `allow_special` gate — bindings (`Item = u32`) and bounds (`T: Clone`)
    are valid only on a trait path (`Conv<Item = u32> X`) or in a generic
    declaration (`<T: Clone> Foo`); a concrete type's args hitting `=`/`:`
    now error with a targeted message (previously the bound was silently
    dropped and a struct binding rendered invalid code). Added
    `compile_error_ty` (type-position form without the trailing `;` — a
    semicolon inside generic args is a syntax error). Two latent bugs fixed
    along the way: `scan_stop` skips `..=` (the range operator's `=` is not
    a binding separator — `Vec<@0..=2>` was being misread as a binding), and
    `@N..M` range refs in type position now error with a targeted message
    (where-predicate-only; ui `concrete_binding`/`concrete_bound`, and
    `at_range_in_type` snapshot updated).
  - **Where-predicate constraint**: a bare splat as a predicate subject
    (`where{*(A,B): Trait}`) is rejected in codegen with a clear message —
    a predicate is a constraint, not a parameter list, so a structural
    expander would emit illegal `A, B: Trait`. Tuple predicates
    (`(*(A,B)): Trait`) and splats inside a predicate
    (`X: Trait<*(A,B)>`) stay legal (ui `where_splat_bad`).
- **Splat survival unchanged**: `Pair^[*(A),*(B)]^2` still repeats each
  element (`[Pair<A,A>, Pair<B,B>]`); splat pow (`*(A,B)^2` Cartesian) and
  left-splat append/distribute (`*[...]^T`, `*(...)^T`) keep working in
  `TySplat::apply_help`.
- `TySplat::Tuple` renders as `*(A,B)` (was `(*(A,B))`) — the outer parens
  were only needed by the old parse-time consumption; the codegen expander
  matches the bare marker.
- **Generator args in `<>`**: `flat_splat_params` (the shared splat
  flattener) now also hoists `WithType` (fresh-generator) params — `()^N`
  keeps its inner tuple as one arg (`T<()^2>` = `impl<P0,P1> T for
  T<(P0,P1)>`), while a splat re-wrap (`*()^N`) flattens (`T<*()^2>` =
  `impl<P0,P1> T for T<P0,P1>`). Previously `Pair<()^2>` / `Pair<*()^2>`
  leaked the declaration into the args and failed to compile. Tests: dsl
  `gen_args_in_angle`.
- **`TyTypeParam` is fully Ty-typed now**: `params` is `Vec<(Box<Ty>,
  Option<Ty>)>` and `bindings` `Vec<(Box<Ty>, Box<Ty>)>` — every element is
  a `Ty`, with non-type tokens (parameter names, `const N`, lifetimes,
  numeric const args, binding names) riding in a `TyPrimitive` wrapper. This
  makes generic args structural: `T<Map<K,V>>` stays
  `TyGeneric(T, [TyGeneric(Map, [K,V])])`, splat args (`T<*(A,B)>`) survive
  as `TySplat` and flatten in codegen (`expand_tp`), and `@N` still resolves
  before parse. Render / extraction / apply treat params uniformly as
  structured types; the declaration-vs-argument distinction still lives in
  the render function used (`params_to_tokens` vs
  `params_to_tokens_no_base`).
- `consume_splats` (parse-time splat flattening in `parse_group`) deleted;
  `(a, *(b,c))` and `(*(a,b))` now keep their splat until codegen.
- Tests: existing splat suite (SplatArgs / SplatConcat / SplatGen /
  SplatGenFlat / SplatSurvival / SplatLeft / trailing-comma / middle-empty /
  idempotent) all pass unchanged; new dsl `SplatGenericArg`
  (`SplatMap<*(A,B)>` → `SplatMap<A,B>`) and `assert_cv` (trait segment +
  right splat) cover the deferred-expansion paths.
- **Splat survival (array elements)**: array/list elements that are splats
  are now KEPT until consumption instead of being flattened at parse time
  (`parse_atom.rs` no longer calls `consume_splats` for `[...]` lists or a
  lone `[*(...)]` element) — a splat lives until apply-right or codegen, so
  `[*(A),*(B)]^2` repeats each element (`[*(A,A),*(B,B)]`) and
  `Pair^[*(SplatA),*(SplatB)]^2` = `[Pair<SplatA,SplatA>,
  Pair<SplatB,SplatB>]` (splat pow drives both generic positions). Bare
  arrays/slices (`[u8]`, `[u8; 3]`) and no-right-operand targets
  (`[a, *[b,c]]` = `[a,b,c]`) are unchanged (codegen flattens at the end).
  With a right operand, a kept splat element follows its own splat
  semantics (matching standalone splats): `[A,B,C]^D` = `[A^D, B^D, C^D]`
  (bare list: distribute), `[A,*(B,C)]^D` = `[A^D, *(B,C,D)]` =
  `[A^D, B, C, D]` (tuple splat: append), `[A,*[B,C]]^D` =
  `[A^D, *[B^D,C^D]]` = `[A^D, B^D, C^D]` (array splat: distribute),
  `[*(A)]^2` = `[*(A,A)]` = `[A, A]` (pow: repeat). Use a bare list
  `[A,B,C]^D` for plain distribution.
  Tuples still flatten splats at parse (unchanged scope). Test: dsl
  `SplatSurvival`.
- **Not diagnosed (by design)**: a *function* generic param colliding with
  the substituted trait arg (`fn foo<U>(_: T)` inside `impl<U> A<U>`) is
  Rust's own generic-shadowing ban — `E0403` already points at both `U`s
  (the spec's `<U>` and the fn's `<U>`). The macro emits legal code once
  the user renames; no postprocess check is added (language-level rule,
  rustc's diagnostic is already precise).

### Core restructure: codegen split + fresh-name protocol unification

- `codegen/` split from a 672-line monolith into four files, all under the
  per-file budget: `mod.rs` (generate_impl + assembly, 242 lines),
  `top_level.rs` (top-level macro injection — spec-body merge + macro-input
  rewrite), `fresh.rs` (fresh-name sweeping), `where_at.rs` (`@` where
  predicate resolution);
- **Fresh-name protocol unified** in `ast/fresh.rs`: the reserved
  `_Param_*_BatchGen_` pattern (prefix/suffix constants) plus the
  generate/construct/parse trio — `fresh_param` (apply layer mints
  `_Param_{g}_{i}_`), `at_ref_name` (parse layer turns `@N`/`@g_i` into
  names), `parse_grouped_fresh` (codegen layer identifies the grouped form)
  — previously scattered across `ast/types.rs`, `parse/mod.rs`, and
  `codegen/mod.rs`; the three layers now share one protocol source and
  cannot drift apart.

### Splat (`*` prefix)

- New `*[...]` / `*(...)` splat: flattens a container/generator into the
  enclosing list — spliced inside tuples/arrays (`[a, *[d,e,f]]` = `[a,d,e,f]`),
  flat-append as a `^`/`-` right operand (`T^*(A,B)` ≡ `T-A-B`), multi-arg as a
  generic argument (`Foo<*(a,b)>` = `Foo<a,b>`), idempotent nested
  (`*(*[a,b])` = `[a,b]`), empty no-op (`[a, *()]` = `[a]`);
- **Source-driven left semantics**: `TySplat` is an enum mirroring its parse
  delimiter — `TySplat::Array` distributes `^T` (`*[A^T,B^T]` — set, mirrors
  `TyArray`, re-wrapped so right-splat chains can flatten into a container),
  `TySplat::Tuple` appends (`*(A,B,...,T)` — list, mirrors `TyTuple`,
  re-wrapped); **`^N` pow on a splat re-wraps each Cartesian combo into a
  splat**: `*(A,B)^2` = `[*(A,A),*(A,B),*(B,A),*(B,B)]` — each combo is a
  param-position list a right-splat chain flattens into the container
  (`A^*(*@u*)^2` = `A<u8,u8>`/`A<u8,u16>`/... — repeat-list shorthand for
  `A<@u*,@u*>`; a lone `*(A,B)^2` target flattens to duplicates, E0119 —
  use `(A,B)^2` for tuple impls); **splat expands ONE layer**: tuples are
  types and stay intact (`*((a,b),)` = one `(a,b)` impl; `*(a,(b,c))` keeps
  `(b,c)`), while arrays / nested splats / generators / groups flatten;
  `*()^N` (empty splat) re-wraps its fresh tuple into the splat so a
  carrier appends the params (`T^*()^2` = `<A,B>T<A,B>`; a bare `*()^N` as a
  lone target is rejected by rustc — its multiple impls share one generic
  declaration while each uses only one param, E0207); a bare `*()` as a lone
  target yields **no impls** (empty list, no elements); the left-operand
  `apply_help` **delegates to `TyArray`/`TyTuple::apply_help`** and re-wraps
  the result into the matching splat variant (a splat stays a splat until
  consumed) — no duplicated distribution/append logic. Right operands and
  container collection flatten regardless of variant (per user decision —
  "that's the point of `*`");
- `*const`/`*mut` raw pointers unaffected (disambiguated by the following
  token); bare `*u8` errors with a targeted message (ui: `star_misuse`);
  a generator splat as a generic argument errors — its fresh declaration has
  nowhere to live (ui: `gen_splat_arg`);
- Right-splat branch collapsed from three match arms (tuple concat / generator
  recurse / chain) to one flat chain — tuples concat via their own apply_help,
  generators recurse through `TyWithType::apply_help` keeping the declaration
  (a prior version unwrapped `*wt.1` and dropped the decl — E0425).

### Style unification + `Apply` trait-ification

- All `Ty*` subtypes now implement the `Apply` trait (17 impls) — `apply_help`
  became a trait method; internal recursion calls `.apply()` (full
  right-operand dispatch), never `apply_help` directly. `TySplat::apply_help`
  is now pure delegation: `TySplat::Array(a) => a.apply(o)` /
  `TySplat::Tuple(t) => t.apply(o)`, then re-wraps the returned container
  (splat stays a splat until consumption); `*()^N` keeps its splat shape via
  the `WithType` passthrough (`*()^2` = `<A,B>*(A,B)`).
- Constructor style unified: `Ty::new(span, TyKind::X(sub))` nesting removed
  crate-wide (`Ty::new` deleted — 49 call sites → `X(...).to_ty().with_span(...)`;
  passthrough uses `Ty { span, kind }`); value-site wrappers use `val.into()`
  (`Some(Box::new(x))` → `x.into()` via `From<Ty> for Option<Box<Ty>>`; the one
  `Box::new` left is inside `From<$t> for Box<Ty>` — the definition site, the
  only way to build a Box; bare `value.into()` there recurses into the impl
  itself, verified E0119-style stack overflow); type annotations moved to the
  right-hand side (`collect::<Vec<T>>()`, `parse::<usize>()`, `s.parse::<TS2>()`)
  except mandatory ones (empty `vec![]`, `parse_quote!`).
- Parse layer split: `parse/mod.rs` (582 lines) → `chain.rs` (operator
  climbing) / `primary.rs` (atoms, generic args, splats) / `trailing.rs`
  (body/where split, wrapper attach) + `mod.rs` (119); all parse files ≤ 350.
- Docs: `#fill` single-item preference emphasized in the tutorial
  (`#name{body}` over `#fill(name){body}` — verified no single-item `#fill`
  anywhere in the repo); splat position survey: 8 allowed sites, 4 macro-level
  errors (directive args / `@` defs / bare `*` / generator-arg), 2 rustc
  fallbacks (where predicates / generic decls).

## 0.6.7 (2026-08-08)

### Fresh-system rework: grouped generation + per-impl sweep; `@N` pure construction

- **Grouped fresh generation**: fresh params are generated as
  `_Param_{g}_{i}_BatchGen_` (group = the generating site within the spec —
  per-spec/per-segment group counter, DSL-local; position = the site's own
  index). The codegen **sweeper** renumbers every impl's fresh params to
  `_Param_0..N_BatchGen_` in (group, position) order before rendering — which
  is the target type's document order;
- **Per-impl numbering fixes unit drift**: each impl sweeps independently, so
  `@N` always refers to *that* impl's N-th fresh — usable across specs and
  range-generated impls (`()^1..=3 where{@0: Clone}` and
  `(()^2, ()^2 where{@0: Clone})` previously errored "out of range" on the
  later units because the counter continued across them; now every unit's
  fresh starts at 0). `@N` is a pure construction (`@N` → `_Param_{N}_BatchGen_`)
  that always matches the swept name — no lookup needed;
- **Combination scenarios** (`()^3-()^3`): `@0` is the left tuple's first
  element (document order — previously the declaration-order first, since
  hoisting declared the nested tuple first — Breaking);
- **Target-type `@N` channel**: `@N` position references are resolved at the
  type-domain boundary (`parse_operand`, plus `resolve_at_refs` for flat
  angle-group chunks such as `Box<@0>`) into the fresh name. Blanket's
  wrapper `@0` position marker is no longer replaced by blanket itself
  (`replace_at0` removed; `has_at0` keeps only the position decision — with
  `@0` the wrapper is emitted as-is and the parse layer resolves the marker);
- **Declaration order = document order**: when both operands of an apply
  carry generic declarations (fresh-fresh chains like `()^3-()^3`), params
  merge left-first so hoisting collects them in the target type's document
  order; the inner type takes only the left's inner part (otherwise hoisting
  collects the left declaration twice — E0403);
- Marked-placement evaluation (decision): a "generic-name placeholder
  resolved by codegen" system was considered and rejected — grouped names +
  per-impl sweep reach predictability without a marker token system (which
  would need a marker/`@N` distinction rule, parse-time substitution, and
  Ty-tree token survival);
- **`@g_i` grouped references enabled**: `@0_1` (a literal with an underscore)
  resolves to the grouped name `_Param_{g}_{i}_BatchGen_` in the target type
  (parse layer) and where predicates (impl-group match, erroring "no group g
  position i" when the impl has no such group) — stable across array-dispatch
  impls, where `@N`'s document-order meaning shifts; the sweeper renumbers
  the reference along with the generated names;
- **`@N` stability commitment**: the numbering semantics were revised across
  0.6.4 → 0.6.7 (generic-param-family era → `@N` semantic fix → per-impl
  numbering + document order + target-type channel). The current mechanism
  (per-impl sweep to `_Param_0..N_BatchGen_`, `@N` = pure construction) is
  considered **final** — any future change is a deliberate breaking release.

### Preprocess directory restructure + documentation pass

- `preprocess/` reorganized into two sub-folders: **`directives/`** (the `#`
  directive system: name_list / trait_items / delegate_args / blanket /
  blanket_wrappers + mod.rs entry) and **`consts/`** (the `@` constant
  system: table / expand / ctx + mod.rs entry) — the flat 12-file layout was
  grouping two unrelated concerns; `preprocess/mod.rs` now declares 5 modules
  with glob re-exports; internal paths updated
  (`crate::preprocess::consts::ctx::ConstCtx` etc.);
- tutorial: the `@` macro-meta layer is now introduced as **three
  dimensions** (constants / selectors / positional references) with a
  composition note; added `@N` vs `@g_i` selection guidance, the `@N`
  stability commitment, and a learning-cost note (start with `@u*` /
  `@all_methods` / `@0`; reach for grouped/batch/range references only when a
  predicate must name a specific fresh);
- README: the intro now states the crate's layered positioning — batch impl
  generator with a pluggable codegen protocol (macro-meta layer + open
  directive system below the "one line" story);
- architecture.md module graph updated for the new preprocess layout.
- New dsl tests: `at_refs_numbered_match_in_join` (u8-only `Marker` bound verifies `@0` = document-order first fresh in a join) and `at_refs_across_generation_units` (range lengths + multiple specs).
### Top-level macro injection (`{! ...}`)

- **Open extension is now top-level**: `#cmd(args){body}` expands to
  `{ ! name!{(args){body} trait_def} }` — the `!` marks top-level emission:
  codegen strips it, prepends the spec body (the target type, rendered as
  one Brace group) to the macro input (4-segment protocol
  `{spec}(args){body}trait`), and emits the call at top level — the
  user macro generates arbitrary items (typically its own impl); batch-impl
  generates no impl in this mode (Breaking: open-extension macros must now
  parse 4 segments and emit complete items, not associated items);
- **`T {! m!{...}}` attach form**: the same top-level protocol with a
  user-written macro input; `T {m!{...}}` (no `!`) keeps the legacy in-impl
  form (associated items — the user writes the full input including the
  trait). At most one `{!}` per spec and it must be the last block — a
  following `{...}` block errors (under the current block order either at
  walk_top_level's "must be the last block" or at the rustc layer via the
  top-level path; ui fixtures `top_level_block_not_last` /
  `top_level_manual_not_last`);
- **Guards**: a standalone `{! ...}` (no attached type) errors "needs an
  attached type" instead of emitting invalid Rust (ui fixture
  `top_level_without_attach`); an empty `{! }` (no macro call) errors "must
  contain a macro call"; `walk_top_level` tracks whether a `{!}` was
  found inside a plain block vs. outside it, so a future block-order change
  cannot misreport preceding blocks;
- `batch_preprocess_test` (test macro) gained the dual protocol: the
  4-segment top-level form emits `impl Trait for {spec}`; the 3-segment
  in-impl form emits associated fn definitions.

### `@all_fresh` / `@N..M` batch references (where predicates)

- `@all_fresh: Bound` expands to one predicate per fresh generic
  (`_Param_0_: Bound, _Param_1_: Bound, ...`); errors when the impl has no
  fresh generics or the expansion exceeds `MAX_EXPAND`;
- `@N..M` / `@N..=M` expand a contiguous fresh range (`@0..=2: Clone`
  bounds the first three); out-of-range and > MAX_EXPAND predicates error,
  and an empty range (`@0..0`) errors instead of silently expanding to
  nothing; the constant stage passes `@all_fresh` through (where-only
  selector);
- A range reference in a type (`Vec<@0..=2>`) errors at the parse layer
  with a targeted message (ui fixture `at_range_in_type`);
- **Where-group predicate splitting**: a `where{...}` group is now split
  into predicates at depth-0 commas (`extract_impl_parts`), so an
  `@all_fresh` / `@N..M` expansion cannot swallow the following predicates
  in the same group (`where{@all_fresh: Clone, @0..=2: Copy}` previously
  leaked `@0..=2` into the rendered where clause — locked by dsl test
  `at_all_fresh_with_range_same_group`).

### Error aggregation

- The driver now collects every spec's error (recursing into nested
  wrappers via `map_children` — `Box<@0..=2>` carries its error in the type
  params) and reports them all at once; the old behavior stopped at the
  first error. When any error exists only the errors are emitted — no
  partial impls (ui fixture `error_aggregation`).


## 0.6.6 (2026-08-07)

### Tuple/fn syntax-boundary fixes (`(T)^2 = T^2`)

- `(T)^2 = T^2` confirmed: a group strips before `^N`, which for a plain type
  is a const-generic argument (`(u8)^2 = u8<2>`); TyGroup restored to
  strip-then-apply (tuple generation needs `(T,)^N`);
- `(<T>)^2` rejected (a `<` right after `(` is not a legal type) — locked by
  ui fixture `group_angle_bare`;
- Number/range rendering uses unsuffixed literals: `u8<2>` / `[u8; 3]`
  (was `u8<2usize>` / `[u8; 3usize]`);
- Tutorial fixes: fn-arrow note (`fn(A,B)-C` = `fn(A,B)->C`, `->` is not a
  DSL operator), tuple note block (`(T)` is a group, `(<u8>)` is invalid,
  `(<Clone>)^N` unsupported).

### Input-validation guards (evaluator findings)

- `expand_consts` gained a 128-level nesting guard (mirrors angle_collect):
  deeply nested `[[[...]]]` previously overflowed the stack (reproduced at
  4000 levels), now errors with "nesting depth exceeds 128 levels";
- `check_value_refs` (the sibling recursive validator of constant-value
  references) gained the same 128-level nesting guard — deeply nested
  constant values no longer overflow the stack (ui fixture
  const_value_deep_nesting);
- `#blanket` `:N` depth capped at 128: `Box:999999` previously expanded a
  million-deref delegation body and overflowed rustc, now errors with
  "deref depth must be ≤ 128";
- batch_trait! constant definitions reject reserved names at the
  definition site: the `@all_*` prefix (`@all_methods = ...` previously
  passed the definition and failed at the use site, now errors with
  "reserved `@all_*` selector") and the bare `@all`;
- `#blanket` `Box:` (empty depth after the colon) previously passed
  silently (the `:` leaked into the type), now errors at the DSL layer
  ("after `:` must come a number");
- New ui fixtures ×5: const_reserved_all / blanket_bad_empty_depth /
  blanket_bad_huge_depth / nested_bracket_too_deep /
  const_value_deep_nesting.

### `#delegate` param-pattern fixes (evaluator finding)

- **Pattern kept + expression rebuild**: a non-`_` parameter pattern (e.g.
  `(a, b)`) stays in the generated signature, and the delegation call uses
  the pattern's token stream directly as an expression — `(a, b)` binds
  `a`/`b` and `(a, b)` rebuilds the tuple for the target (`[x, y]`,
  `Foo { x }`, `&x` work the same way);
- **Recursive non-forwardable detection (`pat_is_forwardable`)**: `ref x`
  (`by_ref`), guards / `x @ pat` (`subpat`), `_`, and nested forms such as
  `(ref x, ref y)` are all detected recursively and fall back to `arg0`, …
  renaming (signature and call together, parsed via syn::Pat::parse_single);
  forwardable patterns (`(a, b)` / `[x, y]` / `Foo { x }` / `&x`) keep their
  signature and are forwarded by using the pattern tokens directly as an
  expression (rebuild);
- `collect_call_args` now returns `Vec<TokenStream>`: Ident → name,
  forwardable pattern → `quote!(#pat)` used directly as an expression;
- `build_from_item_sig`: signature-override variant (needed for the
  fallback rename to reach the generated signature);
- New dsl tests delegate_wildcard_param / delegate_tuple_pattern /
  delegate_ref_nested_pattern;
- Removed the stale ui fixture delegate_pattern_arg (delegate no longer
  rejects pattern params).


### Depth-guard hardening (evaluator holes E / D)

- **Guard moved earlier**: the `depth + 1` check now runs before
  `stream()`/collect in both consts.rs and consts_expand.rs, so it fires
  before the subtree is materialized;
- **Measured clarification**: the 20000-level default-stack crash happens
  while rustc parses the macro argument (the macro's first line never
  runs — verified by an `[entry] start` trace); once RUST_MIN_STACK lets
  parsing through, the 128-level guard intercepts gracefully.
  proc-macro2's Group Clone is Rc-shared (shallow) — the "into_iter deep
  copies the whole tree" mechanism does not hold; the real ceiling for
  deeply nested input is rustc's macro-thread stack, which a macro cannot
  intercept;
- **Pat::Type** (type-ascription patterns `x: u32`) cannot appear in an
  expression position → falls back to `arg{i}` naming (hole D).

### `@0` position marker in blanket wrappers

- A wrapper's main part (minus where / `:N`) **with `@0`** treats `@0` as the
  target T's position — emitted as-is with `@0` replaced by the fresh
  generic, so T can sit anywhere (`(u32, @0)` → `(u32, T)`);
- **Without `@0`** → `part^T` (target appended last, unchanged);
- has_at0 / replace_at0 helpers (recursing into groups); dsl tests
  blanket_at0_position / blanket_at0_const_generic (custom Deref +
  `<const N: usize>` generics, coexisting with the user's `N`).

### Directive documentation placeholders

- Six new empty `#[proc_macro]` placeholder macros (doc-only symbols):
  batch_impl_delegate / batch_impl_fill / batch_impl_blanket /
  batch_impl_name (`#name{body}` fill-by-name) / batch_impl_open (open
  extension) / batch_impl_consts (`@` constant system) — directive docs
  become hoverable/searchable rustdoc symbols instead of unreachable
  tokens inside macro arguments;
- **Measured**: a proc-macro crate cannot export plain `pub fn` (E0753),
  so the placeholders are empty `#[proc_macro]` macros
  (`batch_impl_delegate!{}` expands to nothing; docs render normally);
- Each placeholder ships a compilable doctest example (57 doctests pass).

## 0.6.5 (2026-08-06)


### Documentation rules (author wording + no in-dev marker)

- Documents call the DSL authors **"author"** and the library users **"user"**
  (project-owner semantics: design intent / decisions / "pointed out" use author;
  library-user semantics: "user-defined constants", "user generics",
  "user-visible" keep user); code identifiers (user_table etc.) and source
  error messages unchanged;
- Version headers carry no "in development" marker (prevents missed updates).

### `#cmd[args]{body}` equivalent syntax + blanket `@0` unified into codegen

- `#cmd[args]{body}` (bracket arguments) confirmed and advertised:
  equivalent to `(args)` (the `_` branch covers both); error messages
  updated to `(args)` or `[args]`; the tutorial's directive chapter notes
  both forms (brackets are clearer when the arguments contain parentheses);
  the ui fixture `directive_bad_follow` snapshot regenerated;
- **blanket's `@0` unified into codegen**: `resolve_target_predicates` drops
  its @0 replacement branch — `@0`/`@N` are kept as-is into the spec and
  resolved uniformly by codegen's `resolve_where_at` (a blanket's fresh
  generic is the only fresh one, so `@0` indexes correctly); the
  preprocessor keeps only the `@trait` replacement (only the preprocessor
  knows the trait path); architecturally, "`@N` is the only codegen marker"
  now holds for blanket wrapper where clauses too;
- Verified: fmt/clippy -D warnings clean, lib 10 / dsl 51 / regression 26 /
  ui 34 fixtures / doctest 50 all green.

### Punct helpers unified + directive system cleanup (#blanket split)

- In-crate punct helpers (`is_punct` pre-existing + new `is_punct_at` /
  `is_joint_punct_at`): replace the scattered `matches!(...Punct(p)...)`
  standalone expressions (inside consts/consts_expand/path_prefix/
  where_process/scan), keeping the pattern itself where a match-arm guard,
  slice destructuring, or binding is needed;
- `#blanket` split: blanket.rs 401 → `blanket.rs` (249: doc + expand_blanket +
  resolve_target_predicates + new trait_with_args) + new `blanket_wrappers.rs`
  (160: BlanketWrapper + parse_blanket_wrappers) — all ≤350;
- The t_bound/trait_part duplication in blanket.rs converges into
  `trait_with_args` (trait path + manual angle group; blanket output is no
  longer paired by angle_collect);
- Verified: fmt/clippy -D warnings clean, lib 10 / dsl 51 / regression 26 /
  ui 33 fixtures / doctest 50 all green.

### Macro-call passthrough hole fixed (the `()` groups in expand_consts + angle_collect)

- **The hole**: the `()` groups in `expand_consts` and `angle_collect_at`
  recursed unconditionally — arguments of `ident!(...)` macro calls (user
  Rust) got DSL constant substitution / `<` wrongly paired; previously only
  `[]` groups had the `bracket_is_passthrough` guard (`ident![...]` /
  `#[...]`), the `()` groups were missed;
- Fix: `()` groups uniformly go through `bracket_is_passthrough` (kept
  verbatim when the preceding token is `!`/`#`) — macro calls `foo!(...)`
  pass through; `#name(...)` directive arguments (preceded by an Ident) and
  DSL tuples `(A, B)` still enter;
- `render_angles` updated in sync: index-based iteration + passthrough check
  (macro-call groups are not rebuilt, spans preserved as-is);
  `angle_collect_at`'s depth-error `map_or_else` simplified;
- Probes: `echo!(@u*)` passes the macro argument `@u*` through verbatim
  (stringify = "@u*"); angle tests add `m!(a < b)` (a Paren macro call
  containing `<`: no error + roundtrip preserved);
- Verified: fmt/clippy -D warnings clean, lib 10 / dsl 51 / regression 26 /
  ui 33 fixtures / doctest 50 all green.

### Constant system cleanup (consts split + stricter behavior)

- Split: consts.rs 520 lines → `consts.rs` (272: module doc + builtin
  constant table + expand_consts entry + collect_user_consts) + new
  `consts_expand.rs` (258: try_expand_at + check_value_refs) — dependencies
  one-way (consts → consts_expand), all ≤350;
- `render_list` / `render_list_strings` merged into a generic
  `render_list<S: ToString>` (supports both &str and String, one duplicate
  less);
- The two `tokens.first().map(...).unwrap_or_else(call_site)` spots in
  try_expand_at converge to `tokens[0].span()` (tokens[0] is always `@`);
- **Stricter behavior**: the known check in `check_value_refs` gains an
  `is_range` condition — a bare range-endpoint reference `@a=@u8` (no `..`)
  now errors **at the definition site** (previously allowed through, only
  blowing up at the use site); locked in by the new ui fixture
  `const_bare_endpoint`;
- Verified: fmt/clippy -D warnings clean, lib 10 / dsl 51 / regression 26 /
  ui 33 fixtures / doctest 50 all green.

### Construction chain rework: `From<TyKind>` + `to_ty()` + `with_span` replace `TyKind::X(TyX(...))` nesting

- User's call: `impl_from_for_ty!`'s `From<$struct> for Ty` was a leftover
  misalignment from the span rework — the macro was meant to be
  "subtype → variant" (`Ty` before the span rework = today's `TyKind`);
- The macro's final four-piece set: `From<TyKind>` (pure structural
  conversion; `TyArray(x).into()` replaces `TyKind::Array(TyArray(x))`),
  `From<Ty>` (call_site flavor, the basis of `to_ty`), `to_ty()` (chain
  entry point; the explicit return type resolves the E0282 of
  `.into().with_span(span)` — method resolution cannot infer the `.into()`
  target), and `From<Box<Ty>>` (for the Expand iterator); the two uncalled
  `From` impls for Option<Ty>/Option<Box<Ty>> are deleted;
- New `Ty::with_span(span)` (changes only the node-level span); the two
  construction forms `Ty::new(span, x.into())` and
  `x.to_ty().with_span(span)` coexist (each serving its own purpose);
- ~50 call sites across the crate replaced: `TyKind::X(TyX(...))` →
  `TyX(...).into()` (TyKind target) or `TyX(...).to_ty().with_span(span)`
  (inside Ty::new); 3 pattern positions (match arms / if let
  destructuring) kept;
- net -74 lines (+171/-245); `to_ty` consuming self is deliberate (clippy
  allow);
- Verified: fmt/clippy -D warnings clean, lib 10 / dsl 51 / regression 26 /
  ui 32 fixtures / doctest 50 all green.

### Cursor positioning convergence (Option A: parse-only read-only cursor)

- Positioning: `Cursor` = a **parse-layer-only** read-only cursor
  (entry/parse_atom/driver/generic/fuzz); the **preprocessing layer
  (preprocess/*) uniformly iterates with Vec + index** (rewrite semantics,
  read-modify-write);
- Changes: `expand_tokens` / `expand_directive` (preprocess/mod.rs) switch to
  `tokens: &[TokenTree] + i` — `expand_directive` returns `(output, consumed)`;
  `where_process`'s signature changes to `tokens: &[TokenTree]` (the entry no
  longer wraps a Cursor); `Cursor::peek_at` / `prev_bracket_passthrough`
  deleted (no callers);
- Verified: fmt/clippy -D warnings clean, lib 10 / dsl 51 / regression 26 /
  ui 34 fixtures / doctest 50 all green; the Cursor usage surface converges
  to the parse layer.

### where-section tidy-up (6-stage pipeline audit + 3 small fixes)

- The pipeline: where_process (bare-where rewrite) → parse_primitive (peels
  off the trailing TyWithWhere) → apply (composition) →
  extract_impl_parts (where_clauses extraction) → trait_bounds (trait-where
  merging) → resolve_where_at (@N resolution);
- A `where` at the end missing a body now errors early (the `i + 1 < len`
  short-circuit removed — `where` is a Rust keyword, so an Ident `where` can
  only be the DSL form); `tokens[i+1]` out-of-bounds → `get`;
  scan_body_boundary's `Vec<&TokenTree>` + cloned → collected directly;
- Verified: all green (same as above).

### parse-layer cleanup

- parse/mod.rs 354 → 339: the ×3 `cursor.peek().map(...).unwrap_or_else(call_site)`
  spots converge to `cursor_span`; the WithAttr/WithPrefix half-apply branches
  (rest empty vs apply) converge to `attach_wrapper` (TyKind + rest +
  trait_name);
- parse_atom.rs: parse_range's `TyKind::Range(TyRange {...})` →
  `TyRange {...}.into()` (unified into style);
- Verified: all green; the parse layer is all ≤350 (339/199/128).

### ast-layer split (types.rs 470 → 4 files, all ≤350)

- `types.rs` 470 → **261**: subtype definitions + Ty/TyKind + Op + MAX_EXPAND +
  count_leaves + the fresh family;
- New `types_visit.rs` (159): the Expand enum + expand_wrapped/expand_rebuild +
  `Ty::map_children` + `Ty::expand` (traversal unified in one place);
- New `types_from.rs` (77): the `impl_from_for_ty!` macro (subtypes → TyKind/Ty/
  Box<Ty> + to_ty) + the 19-variant call list (incl. TyError);
- types_render.rs (169) kept; ast/mod.rs aggregates the re-exports;
- Verified: all green.

### codegen-layer cleanup

- generate_impl: the `let parts = parts;` shadow binding deleted (NLL already
  covers it; a historical leftover); `Ty::new(call_site,
  TyPrimitive(...).into())` → `TyPrimitive(...).to_ty()`; HashSet imported
  explicitly; the stale resolve_where_at comment corrected (blanket-wrapper
  where `@N` now goes through here too — doc sync after the @0 unification);
- impl_parts.rs: extract_impl_parts's 4 dual constructions (the
  WithCode/WithWhere/WithAttr None branches + the WithPrefix target wrapping)
  unified into the to_ty chain; 1 test spot updated in sync;
- ast/types_visit.rs: map_children's `redundant_closure` allow attribute was
  lost in the ast split — added back (`&mut FnMut` cannot be moved into
  `.map(f)`);
- Verified: all green; codegen layer 311/145 lines.

### entry-layer cleanup

- expand_attr_macro: the path-prefix branch converges — `if !include_trait {
  match } else { duplicated None branch }` becomes
  `(!include_trait).then(|| try_parse_path_prefix(...)).flatten()` + a single
  match (the None branch written only once);
- New `Cursor::span()` (the span at the current position, call_site at end) —
  the 3 `peek().map(...).unwrap_or_else(...)` spots in entry converge;
  trait_path's first() spot's `map_or_else` synced; the `path_prefix::` module
  path changed to a use;
- Verified: all green; entry layer 286/67/68.

### util-layer cleanup

- `scan_with` / `scan_stop` dual-name merge: scan_with has no external authors
  (everything goes through the scan_stop forwarder) — scan_stop's
  implementation inlined, the forwarding layer deleted;
- scan_stop's `->` arrow guard uses `is_joint_punct_at` (unified punct
  helpers);
- `Cursor::is_punct` delegates to `is_punct_at` (the duplicate matches!
  removed);
- Verified: all green; util layer 161/41/11.

## 0.6.4 (2026-08-05)

### `@trait` expands early (constant stage / segment level); `@N` becomes the only codegen marker

- User's call: `@trait` should not wait for codegen (only `@N` needs the impl
  generic list). Structural reason: `where{...}` is a Brace group that
  `expand_consts` never entered (bodies use `@` as pattern syntax) — both
  `@trait` and `@N` in where predicates leaked to `resolve_where_at`;
- Three fixes:
  - `expand_consts` recognizes `where` Ident + Brace group (a DSL structure,
    not a body) and enters it to expand `@trait` (batch_impl knows the trait
    path); `@N` (`@` + Literal) returns `None` in `try_expand_at` and stays
    untouched for codegen (no more false "must be followed by a name");
  - `replace_segment_trait` (batch_trait! segment-level) recurses into
    groups — `@trait` inside where predicates is replaced per segment too;
  - `resolve_where_at` drops its `@trait` branch (trait_name param removed) —
    only `@N` remains;
- Verified: batch_impl `where{T: @trait<T>}` and batch_trait! segment-level
  where `@trait` both expand early (probes); pure-fresh `where{@0: Clone}`
  regression green.

### `Apply` trait restored: `apply` as default right-dispatch (span-compatible)

- The span rework had reduced `trait Apply` to only `apply_help` (right
  dispatch lived on `TyKind::apply`), leaving the trait name inconsistent with
  its method; the pre-span design is restored:
  - `trait Apply: Clone + Into<TyKind>` — default `apply(self, o, span)`
    (right-operand structural dispatch, moved from `TyKind::apply`) plus the
    abstract `apply_help` hook;
  - `impl Apply for TyKind` overrides `is_type_param()` and forwards
    `apply_help` to subtypes; subtypes became plain impls
    (`pub(crate) fn apply_help`) — they cannot implement `Apply` because the
    default `apply` builds `Ty::new(span, self)` which needs
    `Self: Into<TyKind>` (compile-time verified);
  - `is_type_param()` default method (overridden by TyKind) replaces
    `matches!(self, ...)` — a generic `Self` cannot match a `TyKind` variant
    (E0308 caught it);
- Span threading unchanged: `Ty::apply` → `kind.apply(o, span)` (trait
  default), every constructed node uses the left operand's span, `o.span`
  only on the fallthrough;
- All tests green (separated-declaration order, array/range/generic hoisting
  all regressed clean).

### `@N` semantic fix (author design review)

- The author's original intent: `@N` should be a direct mapping to `_Param_N_BatchGen_` (a macro-meta-layer constant) — but the fresh number is a global counter and is unrelated to the position in the final impl generics (misaligned when multiple fresh sources / author generics are interleaved), so a direct mapping is unreliable;
- Decision: `@N` = the **N-th fresh generic** inside a where predicate (of the `_Param_{N}_BatchGen_` form). `resolve_where_at` filters the impl generics list down to fresh forms and picks by position — author generics are written by name directly; the blanket-wrapping predicate `@0` (= the only fresh T) unifies naturally with the new rule and is no longer a special case;
- Breaking change: the B1 test `where{@0: @trait<T>}` → `where{T: @trait<T>}`; the tutorial's AtWhere example likewise; the out-of-bounds error message updated;
- Tests: `()^2 where{@0: Clone, @1: Copy}` and `()^3 where{@2: Clone}` (pure fresh) unchanged, all green.

### Generic parameter families + separated-declaration order fix

- New `@all_type_params` / `@all_const_params` / `@all_lifetimes`: `GenericFilter` enum + `resolve_generic_marker` + `get_trait_generic_decl` (in helpers.rs), expanding to a **flat** `<...>` declaration (angle_collect pairs them uniformly); type parameters by name only (bounds go through same-name inheritance), consts complete (a bare name is E0747), lifetimes verbatim; try_expand_at dispatches after the @all branch (batch_impl-only; batch_trait! errors); errors when no parameter of that kind exists;
- **Real bug fixed along the way**: `TyKind::apply`'s WithType hoist branch (`T^<A>X` → `<A>(T^X)`) wrongly hoisted the inner parameters to the outer level for "declaration applied to declaration" (`<'a> <T> X` consecutive declarations) → generated `<T, 'a>` (lifetime must be prior). Fix: when self is `TyKind::TypeParam`, go through `apply_help` to keep the declaration order (`<'a, T>` lifetimes first). Hand-written `<'a> <T>` also blew up before — the test `generic_param_families` locks in the combined shape;
- Tests: dsl 51 (type/lifetime/const three families + combination + bound inheritance); ui `generic_family_batch_trait` (batch_trait! errors).

### Constant name-family rename (author's call)

- Proposal: `@i*`/`@u*`/`@f*` replace `@uint`/`@int`/`@float` (family symbols unified — the original `uint`'s `u` was inconsistent with the range family `u8`'s `u`); the `@u8..64` width-abbreviation proposal was rejected (little benefit, and it introduced a hidden "family inherits from the left endpoint" rule);
- Implementation: `"u*"`/`"i*"`/`"f*"` wildcards in `builtin_named` (try_expand_at detects `tokens[2]` being `*`, lookup = `name*`, consumed 3); `check_value_refs` recognizes the wildcards in sync (an `@uints=@u*` reference inside a value was falsely reported as "unknown @u" — after the fix the lazy-expansion chain is complete). The builtins list in error messages and the missing-name-after-`@` example updated; the ui `const_unknown` snapshot regenerated;
- Tests: dsl `@uints=@u*` (wildcard reference inside a batch_trait value) and `[Box, Rc]^@u*` (wildcard inside a macro-variable None group) all updated and passing; a direct `@u*` probe verifies that usize is included.

## 0.6.3 (2026-08-05)

### Doc fix

- User caught a wrong `// →` annotation in the README header example:
  `#[batch_impl(()^4)]` claimed to expand to 4 tuple impls of different lengths
  (`(A,)` through `(A, B, C, D)`) — `()^N` is a **single** N-tuple
  (`()^4` → `(A, B, C, D)`); multiple lengths come from the `()^1..=4` range
  syntax (tutorial §11 table was always correct; `tuple_pow_basic` locks the
  semantics). Probe-verified before fixing; both EN and zh-CN README annotations
  corrected. Annotation-only, no behavior change.
- Cargo.toml bumped 0.6.2 → 0.6.3.

## 0.6.2 (2026-08-05)

### Span-based diagnostics (L3)

- **Structural rework**: `enum Ty` → `struct Ty { span: Span, kind: TyKind }` (variant-level spans were rejected — "the span goes on the Ty layer, not on TyNum"); `TyKind` carries the right-operand dispatch in ordinary methods (`TyKind::apply` / `TyKind::apply_help`), and `trait Apply` keeps only `apply_help(self, o, span)` (bound as `Clone + Into<Ty>` — TyKind cannot satisfy `Into<Ty>`, hence ordinary methods instead of a trait); `Ty::apply` takes the span and then delegates — the single entry point through which spans flow;
- **Recursion fix**: during the migration, `TyGroup::apply_help` was changed to "wrap back into a Group and apply", causing infinite recursion when `o` is an ordinary type (fuzz `parse_no_panic` / `full_pipeline_no_panic` stack overflow); changed back to `self.0.apply(o)` (the transparency of groups). The fuzzer caught it — the value of the no-panic promise;
- **Diagnostic layer**: `compile_error_str(msg, span)`; the ident-span approach — `Ident::new("compile_error", span)` + `quote!` (parentheses/string/semicolon stay at the call site), because `quote_spanned!(span => compile_error!(...))` makes rustc treat the error as user code in item position ("macros that expand to items must be delimited with braces..."); new `compile_err_at!(span, ...)` macro;
- **Wiring**: parse (cursor/op spans — a missing operand for `^` now points at the `^`), consts (`@` reference spans), blanket wrapping, where_process, entry, lib, codegen; apply errors use `err_ty_at` (the span parameter already flows through `apply_help`);
- **Platform limitation (recorded)**: attribute-macro input spans — top-level tokens exact, tokens inside groups degrade to the call site, and errors returned via `Err` display on the macro-invocation line. Exact spans only appear on the `Ty::Error` path of Ok output (parse/apply). This is rustc behavior and cannot be fixed on the macro side;
- ui snapshots regenerated via TRYBUILD=overwrite (the span changes moved error locations).

### `@all` filtering by receiver kind (L1)

- `ReceiverFilter` enum (Ref / Value / Static) + the `AllMarkerSpec` type alias live in `helpers.rs`; the `resolve_all_marker` table gained `all_ref_methods` / `all_value_methods` / `all_static_methods`, and `get_trait_item_names` gained a receiver-filter dimension;
- syn 3 receiver API: `f.sig.receiver()` returns `Option<&Receiver>`, whose `kind: ReceiverKind` is `Value` / `Reference(..)` / `Typed(..)` (the syn-2-style `receiver.reference` field no longer exists — caught by E0609, switched to matching `ReceiverKind`);
- Motivation: blanket's by-value delegation semantics are ambiguous (Deref/move capability cannot be determined at expansion time); `#blanket(@all_ref_methods)` lets authors delegate only `&self`/`&mut self` methods, with by-value methods keeping the trait's default implementations;
- Tests: `receiver_kind_filters` (ref/mut/val/static each correctly marked and selected) + `blanket_receiver_filter` (a Box blanket delegates `by_ref`; `by_val` falls back to the default — note the default implementation needs `where Self: Sized`, because the `self` receiver in a default method requires it, E0277);
- Docs (zh-CN): the tutorial constant table + architecture's `@all` description and directive table updated; the English mirror is updated at publish time.

### `#blanket` static-method delegation (F1, refactor)

- Reviewer report: `#blanket(@all_static_methods)` generated `(**self).make()` — E0424 (associated functions have no `self`). A pre-existing blanket hole (the delegated body always references self), exposed by the L1 static filter;
- First-version fix: a guard + an error pointing at `#fill(@all_static_methods)` (reviewer's option A);
- After design review, refactored: delegation is strictly better — static methods have no deref-able receiver, but the blanket impl carries `t: Trait`, and `t::make(...)` is fully isomorphic to the `<t as Trait>::Item` projection. `expand_blanket` now selects the delegated body by receiver: `(#self_ty).#name(...)` (has a receiver) vs `#t::#name(...)` (no receiver). The dsl test `blanket_static_delegation` locks in three shapes: direct, chained (`Box<Box<u8>>`), and argument forwarding; the temporary ui error fixture was deleted. Consistent with blanket philosophy: instance methods forward through deref, static methods forward through bounds — both are forwarding, no special-casing.

### Full English-only transition (comments, error messages, docs)

- **Scope**: all Chinese comments in `src/` (`//`, `///`, `//!`, 29 files ~356 spots) and `tests/` (28 .rs + 31 .stderr) translated to English; all 59 `compile_err!` / `compile_error_str!` messages translated; DSL tokens inside the messages kept verbatim;
- **Process**: 5 parallel sub-agents grouped by module (preprocess / parse+apply / ast+codegen / entry+util+analyze+testing+lib / tests), each with the hard rule "never change code logic"; ui `.stderr` snapshots regenerated via `TRYBUILD=overwrite` (56 files) — the authoritative message text is the actual output; snapshots were rewritten from real output;
- **Post-translation cleanup**: nested lists introduced by the sub-agents triggered clippy `doc list item without indentation` warnings; doc comments were flattened into prose to fix them;
- **Docs**: Chinese docs moved into `docs/zh-CN/` (frozen archive), English versions written in place (README / CHANGELOG — all 19 version entries translated / tutorial — 816 lines, 40 rust blocks kept verbatim / architecture / dev-changelog); a second scan translated the Chinese comments **inside** doc code blocks (only the `//` comments of rust blocks; code tokens untouched);
- **Broken-fence fix**: the tutorial's segment-level `@trait` example had a broken fence (`` `ust `` — backtick + CR + `ust`), fixed to ```rust and then compiled as a doctest; the block content matches the passing segment-level test in `tests/dsl.rs`, safe;
- **Verification**: fmt clean, clippy zero warnings, `cargo test --all-targets` all green (lib 10 / dsl 46 / regression 26 / all ui fixtures), doctests 46 (was 45, +1 fixed block), zero Chinese residue in `src/`, `tests/`, and all English docs.

## 0.6.1 (2026-08-05)

### Recursion depth guard restored (regression fix for the 0.1 promise)

- A retrospective review found that 0.1.0's "recursion depth limit (128 levels)" was lost in the 0.3.0 rewrite: empirically, 30000 levels of `[[[...]]]` and `Vec<Vec<...>>` nesting caused `STATUS_STACK_OVERFLOW` (an abort, not a panic — fuzz at depth 3 couldn't detect it);
- Restored: `angle_collect` was split into `angle_collect_at(tokens, depth)`, with depth+1 at the 4 recursion points (None-group flattening / Paren / Bracket / `<>` contents); exceeding `MAX_NEST_DEPTH = 128` reports "nesting depth exceeds 128 levels" — once intercepted at the entry, downstream group depths in consts/expand_tokens/parse/codegen are all ≤ 128;
- Bonus: `parse_primitive`'s chained body/where attachment (`T{a}{b}`) changed from recursion to **iteration** (attaches collected on a stack, then applied inside-out) — a linear chain should never recurse; this recursion source was eliminated;
- Boundary clarification: >128 levels are intercepted inside the macro; **crashes with tens of thousands of `[` nesting levels happen in rustc's tokenize stage** (an external boundary before the macro is even invoked, which no proc-macro crate can intercept) — 128 is far below rustc's threshold, so valid input can never trigger it;
- Tests: ui fixture `deep_nesting.rs` (200 levels of `[`) + angle unit test `angle_nesting_limit` (129-level groups).

### Docs fix: `batch_trait!` directive gap honestly declared (no code change)

- Empirically confirmed that `expand_tokens` is only called by `expand_attr_macro` — `batch_trait!` never does directive expansion, and `#fill` etc. directly report `found '#'`; yet lib.rs:111 / tutorial.md originally claimed "same syntax as `#[batch_impl]`" (a false promise);
- Decision: **no code change** — `batch_trait!` keeps pure spec semantics as a function-like macro (adding a trait definition is the job of `#[batch_impl]`/`#[batch_impl_only]`); the `start_trait`/`trait_bounds` parameters of run_pipeline are already reserved, so a future syntax extension can plug in directly;
- Fixed lib.rs's `batch_trait!` doc + the corresponding tutorial.md section: the right side of `:` is the type DSL + `@` constants; `#` directives require the attribute-macro entry; the CHANGELOG 0.6.1 entry was synced.
- Same origin as the 0.5.6 (`A<>` passthrough) / 0.5.7 (bounds not inherited) limitations: the directive domain depends on the trait definition, so only the attribute-macro entry works.

### Module reorganization: folder mod + files (eliminating the "flat" structure)

- The 10 flat files under the crate root were gathered into layered directories, each directory's `mod.rs` aggregating re-exports (reference sites uniformly write the directory-level `crate::xxx::X`, not submodule paths):
  - `entry/`: entry points and driving (original `expand.rs` → `mod.rs`, `batch_trait_entry.rs` → `driver.rs`, `path_prefix.rs` folded in);
  - `preprocess/`: token rewriters (`consts.rs`, `empty_generics.rs` moved in from the crate root; `preprocess_helpers.rs` renamed to `helpers.rs`);
  - `analyze/`: trait-definition semantic analysis (`trait_bounds.rs` moved in);
  - `util/`: shared utilities (`scan.rs` / `diagnostic.rs` moved in, aggregated by mod.rs);
  - `testing/`: test infrastructure (`fuzz.rs` moved in, `cfg(test)`).
- The `parse/` `apply/` `ast/` `codegen/` layers are untouched; lib.rs now only has the macro declarations + the module tree.
- Dependency direction is one-way: util → ast → parse/apply → preprocess/analyze → codegen → entry → lib.

### Logic consolidation (phase D: deduplicate rather than delete)

- `trait_bounds::generic_param_names`: the generic-parameter-name collection loops in blanket.rs / empty_generics.rs converged into a shared function;
- `parse::parse_binary_chain`: the `-` (left-assoc) and `^` (right-assoc) branches have isomorphic skeletons and were converged into a parameterized function (the error message keeps the `(e.g. T-U)` example suffix; ui snapshots unchanged);
- `types_render::render_param` / `render_optional`: codegen's impl-generic rendering reuses the single-declaration renderer; the four-arm dual-state rendering of WithPrefix/WithAttr/WithCode/WithWhere was converged;
- The two `apply_tuple` macros: the "pass through to the inner layer then re-wrap" logic of the four wrapper kinds WithTrait/WithType/WithCode/WithWhere is macro-ized into apply_help (lesson: passing `self.1` as a macro argument resolves to the module-level `self` due to call-site hygiene — E0424; field access must be written inside the macro body);
- `fuzz::full_pipeline_no_panic` now goes through the real `expand_attr_macro` entry (the previously hand-written pipeline missed constant expansion and `A<>` verbatim copying, so the fuzzed path diverged from production); `expand_attr_macro` now takes proc_macro2 types so unit tests can call it, with the lib.rs entry converting;
- Three candidates were dropped (with reasons): unifying path collection (path_prefix's strict state machine vs. the loose segment-loop collection — unifying would degrade diagnostics), merging expand_wrapped/expand_rebuild (would require introducing `expect`, violating "never panic"), and switching consts to scan_stop (no duplication worth switching).

### Review fixes (reviewer B1-B4 + supplementary tests)

- **B1 (real bug, one line)**: the @trait branch in codegen/mod.rs wrote `id == "Trait"` (capitalized) — the @trait of ordinary where predicates (`where{@0: @trait<T>}`) was wrongly rejected and the error message contradicted itself; the other 4 places in the crate are all lowercase. **Lesson**: the dev-changelog's earlier claim that "resolve_where_at syncs to lowercase" was actually never applied — PowerShell's Select-String is case-insensitive, which backfired (the residual check falsely reported success). Test: dsl `review_fixes_locked` (B1 scenario + the self-referential bound needs an added `impl WhereAtTrait<u32> for u32`).
- **B2 (regression risk)**: under the new order (`@` before `<>` pairing), real None groups at expand_consts runtime (macro-variable `$(...)*`/`$x:ty` expansion output) were not yet flattened by angle_collect — `@` inside a group was no longer expanded (the 0.6.0 order worked); the old comment "real None groups are already flattened by angle_collect at the entry, so this case never occurs" no longer holds under the new order. Fix: `expand_consts` gained a `delimiter![none]` branch — under the new order `<>` groups don't exist yet, so a None group is necessarily a real transparent group, and the old ambiguity is gone (the `delimiter![none]` accidentally flattening angle groups, hit in 0.6.0, cannot recur). Test: dsl `review_fixes_locked` (macro-variable + `@uint` probe inside a group verified empirically; in the 2024 edition `gen` is a reserved word, so the macro name had to change).
- **B3 (docs)**: `@all_default_types` depends on trait associated-type defaults (`type T = u8;`) — nightly (`associated_type_defaults`; stable reports E0658) — tutorial now notes this marker is only usable in nightly scenarios (`@all_required_types`'s `type T;` declaration works on stable).
- **B4 (defense)**: defining an `@trait=[...]` constant in `batch_trait!` would be intercepted by the special marker and silently shadowed by the segment-level substitution — `collect_user_consts` now rejects `trait` as a constant name ("reserved marker" error).
- Reviewer's supplementary test, dsl section 35 `macro_meta_review_extras` (full positive-path coverage: @all_required all kinds / @all_default_constants / marker subtraction / @trait<T> top-level spec / `[a,b]` in #delegate / blanket where with only @0 / `()^3 where{@2: Clone}` multi-parameter positional references) — all pass.

### Macro-meta layer completed (0.6.1 main line: `@` as the sole macro-meta marker + blanket bound merging)

- Background: the author pointed out "`#all` looks wrong and violates `#`'s two formats" — `#` should only be directive names; scope selection (which items to pick) is a macro-meta-layer operation, unified under `@`;
- The `@all` family: `try_expand_at` gained a branch (`resolve_all_marker` extracted as a shared table — used by both the directive domain and the macro-meta layer), expanding to a Bracket group (`render_list_strings`); exclusive to `batch_impl` (needs trait_def), `batch_trait!` errors; the entire `#all` family was deleted (parse_marker removed, the `#` branches of parse_name_tokens/parse_minus_target removed);
- Directive arguments now support `[a,b]` (group contents parsed recursively; empty groups error; `-` exclusion supports `-[a,b]`) — the `@all` expansion output takes exactly this shape, and hand-written equivalents are allowed;
- Trait-aware constants (ConstCtx::Attribute carries trait_def): `@trait` expands the local trait name; `@Cow` is built in (`Cow<'_>` + an inherent bound predicate — quote doesn't pair angle brackets, so the ty must use `Group::new(delimiter![<>])` manually; different in kind from the removed bare-type-name constants: only with a bound does it have reuse value);
- Blanket wrapper bound predicates: a trailing `where{...}` (after `:N`) is merged into the impl where; `resolve_target_predicates` handles `@0` (→ fresh T) and `@trait`; **lesson**: `quote!(where { #(#wrapper_preds),* })` joins each TokenTree as a list element with commas — the predicate stream must be inserted as a whole;
- `<>` keeps only names: the blanket generic declaration's TypeParam takes only the ident, const/lifetime as-is (a bare name `N` triggers E0747); `T: Trait` goes into the where base predicates (merged alongside the wrapper predicates); trait-parameter inline bounds are handled by codegen's inheritance logic (a previous move caused `X: Clone` duplication — inheritance fills bounds by position, see `gen_where_probe` for the empirical check);
- `@0` generalized: codegen substitutes `@N` (→ the Nth impl generic's name) and `@trait` (→ the trait name) when rendering where predicates — tuples `()^2 where{@0: Clone}` and ordinary specs `where{@0: Default}` now work (previously only the blanket wrapper where was special-cased: `@0` always meant the target generic fresh T, pre-substituted by resolve_target_predicates; the two don't conflict); out-of-bounds/malformed cases are collected into errs for reporting (generate_impl doesn't return Result); test dsl `where_position_refs`.

- `@Trait` → `@trait` rename + path-ification: the built-in name family unified to all-lowercase (`@uint`/`@scalar`/…); the content changed from "local trait name" to "full trait path" — `batch_impl` = local name, `batch_impl_only` = external path (`#ext::Trait:` prefix) — so blanket wrapper wheres can write `@0::Owned: @trait` without hand-writing the path; implementation: path-prefix resolution moved **earlier**, before `@` expansion (`@trait` needs trait_full_path; ConstCtx::Attribute gained a trait_full_path field and a `trait_full_path()` accessor); blanket's resolve_target_predicates switched to trait_full_path (trait_def.ident only gives the local name, wrong in external scenarios); codegen's resolve_where_at synced to lowercase; **lesson**: PowerShell Select-String is case-insensitive, so the residual check falsely reported success (it had actually been replaced).

- `batch_trait!` segment-level `@trait`: with multiple segments each having a different trait name, the `@trait` inside constant values (e.g. `@type_t=<T>@trait<T>`) is replaced per segment by the entry's segment loop with that segment's trait path (`replace_segment_trait`) — "generic declaration + trait name" is packaged for reuse across segments; implementation points: try_expand_at now returns `Option` — the Trait ctx's `@trait` returns `None` (kept as-is, no lazy-expansion recursion triggered — expanding to itself → encountering it again → a stack-overflow infinite loop, empirically STATUS_STACK_OVERFLOW); check_value_refs skips `@trait` (special marker, not a constant reference); test dsl `trait_const_segment` (lesson: the trait definition must carry generics matching the spec's `<T> Trait<T>`; `Box^[T,(T,)]` generic overlap E0119 was a author-writing issue, so the test uses `[T, Vec<T>]`).

- Tests: dsl `macro_meta_complete` (@trait/@Cow/blanket where/[a,b]/where specs), `trait_const_value_with_angles` kept; full regression green.

### Preprocessing order fix: `@ <> # where`

- Background: the author proposed that the macro-meta layer (`@`) should be the outermost pass. The bug in the then-current order (`<> @ #`) was verified empirically: `batch_trait!( @inner = Vec<u8>; @outer = Vec<@inner>; ... )` — the `@inner` of `Vec<@inner>` gets paired into the angle group by angle_collect, while expand_consts deliberately does not enter `<>` groups (`delimiter![<>]` and real None groups expand to the same value and can't share an arm; recorded in comments) — the leftover `@` reports `found '@'`; the direct value `@map = HashMap<u32, String>` happened not to break only because the definition-site pairing saved it, and the nested/reference scenario exposed it;
- Fix: both entry points moved `collect_user_consts` + `expand_consts` before `angle_collect` — the `@` expansion output (which may contain flat `<...>`) is uniformly paired by the subsequent angle_collect; the `#` directive and bare-where rewrite keep their positions;
- Capability matrix: batch_impl/only = built-in `@` + `<>` + `#` + where; batch_trait! = custom `@` + `<>` + where;
- Tests: dsl `trait_const_value_with_angles` (`@map` direct value + `@outer` nested value; E0252 lesson — dsl.rs already uses HashMap; E0119 lesson — batch_trait! generates the impl itself, don't hand-write duplicates).

### New scope markers: `@all_required*` / `@all_default*`

- Background: the `@all` family never distinguished the default-implementation status of trait items (`#fill(@all)` also overrode items with default implementations, and excluding them one by one with `@all + -name` was tedious); the author proposed filtering by status;
- Implementation: `get_trait_item_names` gained a `default: Option<bool>` filter parameter (`Some(true)` = only defaulted, `Some(false)` = only required, `None` = all), with syn fields used to judge: `TraitItem::Fn(f).default` / `Const(c).default` / `Type(t).default` (fn = default body, const = default value, type = default type);
- `parse_marker` switched to table dispatch (kinds, default) — 12 markers inlined, the four thin wrappers `get_all_trait_methods/items/constants/types` deleted;
- Semantic points: `@all_required*` used alone is complete (fills only the required items, defaults kept); `@all_default*` used alone misses required items → E0046, so it must be combined with the required side / hand-written items; required ∪ default = all;
- Tests: dsl `all_default_required_markers` (three scenarios: fill combination / fill with only required / blanket with only required; E0034 lesson: the three traits must each occupy a distinct integer type);
- The three directives (fill/delegate/blanket) share `parse_names_from_tokens`, so one change benefits all.

### Spot-checking old test cases (git history) — found and fixed `T^<A,B>` argument loss

- Comparing v0.5.0's deleted examples/{tests,ds_tests,my_tests,debug_tests}.rs (~4800 lines) against the current dsl/regression test matrix, 4 candidate blind spots were verified empirically:
  - `[&, self]^[u32, i64]` (mixed-prefix list cross product), `()-[usize, isize]-[u32, i32]` (empty-tuple double-list subtraction chain) — behavior correct, already covered;
  - `HashMap^<u32, String>` (caret followed by a generic-parameter list) — **real bug**;
  - `[usize #fill(@all){..}, isize #fill(@all){..}]` (list elements with independent directives) — overlaps dsl `directive_fill`, not separately added.
- **Bug root cause**: an ordering defect in parse_primary — a lone `Group(<>)` input is pre-empted by the `[TokenTree::Group] → parse_group` branch, parse_group doesn't recognize `<>` groups and falls into `_ => empty()`, so `parse_type_params` (which should handle the standalone `<A,B>` operand) is never reached; with a body, the empty result gets wrapped by `TyWithCode` and escapes the `is_empty_operand` check → `<u32, String>` is silently dropped and the output is a bare `HashMap`, with no diagnostic at all (without a body it reports "missing operand after `^`" — split behavior);
- **Fix**: the `[Group] → parse_group` branch excludes `delimiter![<>]`, so angle groups fall through to parse_type_params — per the established semantics in the apply/mod.rs comment, `T^<A,B> => T<A,B>` (`HashMap^<u32, String>` → `HashMap<u32, String>`);
- Test: regression `caret_angle_param_list` (`contains_key` asserts the impl lands on the full generic type, preventing regression to a bare `HashMap`).

## 0.6.0 (2026-08-04)

### New feature: the `@` constant system (src/consts.rs)

- Built-in name family (`@uint`/`@int`/`@float`/`@num`/`@scalar`) + range family (`@u8..u128` etc., with endpoint/width/family/ordering validation), expanding to Bracket lists equivalent to hand-written ones, through the original pipeline (the macro-meta layer only does lexical substitution, taking no part in in-domain parsing)
- `batch_trait!`'s leading `@name=value;` definition segment (`collect_user_consts`): **lazy expansion** — the value's arbitrary tokens are stored as-is, and at the reference site they're concatenated and recursively expanded (`expand_consts`'s reference branch recurses first, then extends); `check_value_refs` validates reference visibility at the definition site (circular/forward references intercepted — under lazy expansion `@a=@a` would recurse forever)
- Reference substitution (`expand_consts`) recurses into `Paren`/`Bracket`, passes through Brace and `ident![...]`/`#[...]` (reusing `bracket_is_passthrough`)
- Pipeline position: after `angle_collect`, before directive preprocessing (inserted once at each of the two entries; `batch_trait!` before `where_process`)
- Lesson ×2: the first version of `expand_consts` mistakenly added a `delimiter![none]` branch that flattened angle groups (same value) as real None groups — removed; value-shape validation dropped after lazy expansion (B1/B2's reject-at-definition semantics were replaced by DSL errors at the reference site; accepted in review)

### New feature: `#blanket` covering delegation

- `expand_directive`'s return type changed from `TokenTree` to `Vec<TokenTree>` (a directive can now produce multiple tokens; the five existing directives wrap themselves in `vec!` at the dispatch site, zero internal changes)
- `expand_blanket`: **generalized wrapper elements** (any type expression + optional trailing `:N` depth annotation; `parse_blanket_wrappers` returns `BlanketWrapper { ty, depth }`; `is_single_colon` distinguishes `::` paths), fresh generics, per-wrapper generation of `<T: Trait> wrapper^T { delegation body }` multi-segment specs
- The delegation body's `*` count = depth + 1 (parsed as `"*".repeat(depth + 1) + "self"`); the target type = wrapper `^T` (`Box^Arc:2` → `Box<Arc<T>>`, `Cow<'_>` → `Cow<'_, T>`)
- **Generic traits**: trait type parameters are copied to the impl generics (parameters first, fresh `T` last; the reverse order `T: Trait<X>` → E0401) + trait arguments filled with the parameter names + where predicates passed through; the spec's trait-name part is emitted only when generic (omitted when not — a `Trait &^T` prefixed target after the trait name can't be parsed; a regression once broke `{&,Box,Rc}`)
- **Assoc type/const delegation**: the narrow `TraitItem::Fn` matching was opened up; Type/Const go through `build_from_item`'s existing output shape, with bodies projecting via `<T as Trait<X>>::name`
- Key fixes ×2: blanket runs after `angle_collect` — the generic declaration manually builds angle groups (`Group::new(delimiter![<>], ...)`); the body is a Brace group (angle_collect doesn't enter it), so flat `<...>` inside it such as `Cow<'_>` gets one extra `angle_collect` pairing pass
- Pitfall: `quote!(#tp.ident)` field-access interpolation (`.ident` treated as a literal) — take a reference first, then interpolate
- Boundaries: `*const`/`*mut` / `self` / empty elements / invalid `:N` error out with guidance to hand-write `#delegate`; default depth 1 (the macro doesn't guess the Deref layer count); by-value receivers allowed (Deref/move semantics are information-asymmetric, rustc covers it)

### Tests and docs

- dsl sections 35/36 (const system, blanket double-attribute stacking); new ui fixtures (const_unknown / const_range_bad / blanket_ptr / blanket_bad_depth; blanket_generic removed along with generic-trait support; const_cycle / const_forward see the review-fixes section)
- architecture.md: consts.rs added to the module graph, pipeline updated (const expansion, multi-token directives), the macro-meta layer landed in the domain-isolation table, new "attachment semantics" section
- tutorial.md: section 7's `#blanket` subsection, section 11's `@` constants subsection

### Review fixes (pre-release)

- **F1**: `cargo +nightly fmt` fixed the formatting differences in consts.rs / preprocess/mod.rs
- **F2**: dsl.rs's `BlanketInc` dead_code (blocked by clippy -D warnings) — `b.inc()` goes through Deref to u16's own impl, so the blanket `&mut` impl was never called; the test switched to UFCS to directly exercise the blanket delegation path (`&mut u16` matches two impls at once, requiring disambiguation)
- **F3**: when an `@name=value;` definition segment comes after the trait segments, the `try_expand_at` definition-segment branch distinguishes the diagnostic by context — `batch_trait!` reports "constant definitions must precede all trait segments", while `batch_impl`/`batch_impl_only` keep "custom constants are not supported"
- **F4**: the flat `<A, B>` arguments of the blanket generic bound `T: Trait<X>` were wrongly cut by `split_at_depth0` at the comma (`T: Two<A` / `B>`); the first version only worked by render-idempotency luck (a fragile point); fixed by **grouping the arguments** (`t_bound` uses the same `Group::new(delimiter![<>], ...)` as `trait_part`) — parsing is correct from the start, with no reliance on idempotency; dsl 38's `Two<A, B>` case is locked by regression; parse/generic.rs comments changed to the general warning "macro-generated angle brackets inside groups must be pre-paired"

- **B1**: `collect_user_consts`'s `@`-reference value check `consumed == value.len()` — `@a=@num garbage` reports "extra tokens after the reference" instead of silently dropping trailing content (**superseded by lazy expansion**: value shapes are opened up to arbitrary tokens, see this version's new-feature section)
- **B2**: a `@` embedded in a constant **list** value (`[@uint, u16]`) was rejected at the definition site — accepting it without expansion would defer the error to the use site (diagnostic far from the source); lists are atomic values, use the `@name` form (**superseded by lazy expansion**: embedded references in list values now expand normally, see dsl 38)
- **B3**: `#blanket`'s delegation bound switched to `trait_full_path` — in the `#[batch_impl_only (#ext::Trait: ...)]` path-prefix scenario, a bare dummy name fails to resolve (E0412/E0277); the `expand_tokens`/`expand_directive`/`expand_blanket` signature chain gained a `trait_full_path` parameter (fuzz synced)
- **B4**: the unknown-`@`-constant diagnostic appends "user constants must be defined before the reference" in batch_trait! scenarios (after lazy expansion, taken over by `check_value_refs`'s definition-site visibility check, see the new-feature section)
- **B6**: `contains_at` recurses into all groups (the `@uint` of `[Foo<@uint>]` gets paired into a None group by angle_collect, so a flat check would miss it) — **superseded by `check_value_refs`** (after lazy expansion, reference visibility is uniformly validated at the definition site, recursing into all groups)
- Tests: regression gained path-prefix + blanket pass cases (`cmp_path_prefix_blanket`; the method ambiguity between `&u8` and u8's own impl is disambiguated with UFCS); ui gained the two fixtures const_cycle / const_forward (circular/forward references error at the definition site)

### Documentation-system restructuring (merged in from the former 0.5.8)

- README rewritten as a sales version (669 → 117 lines): why use it / mental model / quick start / feature overview table / links
- Tutorial split out into `docs/tutorial.md` (the original syntax reference + combos reorganized into 13 progressive chapters; lib.rs added `#![doc = include_str!(docs/tutorial.md)]`, so the docs.rs front page = sales pitch + tutorial; all tutorial code blocks run in doctests)
- Developer docs split out into `docs/architecture.md` (architecture diagram, key design decisions, error mechanism, test matrix, release process)
- CHANGELOG split into a author version (CHANGELOG.md) and a developer version (this file); all historical entries from 0.1.0 to the latest migrated by category
- Note: rustdoc compiles code blocks without a language annotation as Rust by default (the `<impl-generics>...` skeleton needs a `text` annotation)

## 0.5.7 (2026-08-03)

### The `delimiter!` delimiter-spelling macro

- Defined at the top of `preprocess/mod.rs` (imported into the crate root via `#[macro_use]`), it unifies the scattered `Delimiter::*` literals using source delimiter spellings, with calls uniformly delimited by `[]`
- `Delimiter::None`'s two semantics are distinguished by two spellings: `delimiter![<>]` (the angle-group carrier) vs. `delimiter![none]` (real transparent groups); 43 occurrences converged across the crate
- Fixed the dangling `ANGLE_BRACKET` reference in angle.rs's module docs
- proc-macro crates forbid `#[macro_export]`, so a macro can't be defined in `angle.rs` and be crate-wide visible; it is therefore placed at the top of the parent module (textual scoping requires the declaration before all authors)

### Bracket guard alignment

- The Bracket recursion guards in `expand_tokens` and `where_process` gained `#` (previously only `ident![...]` was excluded, so a `#name{body}` inside a `#[...]` attribute would be mistaken for a directive and error; now aligned with `angle_collect`'s attribute guard)

### lib.rs split (632 → 202 lines)

- `expand.rs`: entry implementation + the shared pipeline `run_pipeline` (parse → generate → angle-group restore; `angle_collect` and the bare-where rewrite don't enter the pipeline — pairing is destructive, and where must precede `A<>` expansion)
- `trait_bounds.rs`: TraitBounds + syn AST reference collection
- `empty_generics.rs`: the `A<>` verbatim-copy expansion
- `angle_tests` moved into `angle.rs`; the `crate::TraitBounds` path kept compatible via `pub(crate) use`
- Error-mechanism division explained: the entry layer propagates `Result` vs. the DSL layer passing through `Ty::Error`; `batch_trait!` segment-level errors uniformly `return Err`

### syn AST reference collection (where predicates)

- Added syn's `visit` feature: single-segment paths and generic arguments are parameter-reference positions; path segments after `::` (the `B` of `A::B`), associated-type binding names (the `Item` of `dyn Trait<Item = T>`), and HRTB binders (the `'a` of `for<'a>`) are naturally excluded — replacing `bound_refs`'s token scan (incidentally fixing HRTB false positives in inline bounds)
- Added `visit_expr` to collect const-generic arguments / array lengths (the `N` of `[T; N]`; empirically, missing them silently generated code referencing undeclared names); impl generic names like `const N` normalized to `N`
- `TraitBounds.extra_predicates`: unmerged predicates (tokens + referenced parameter names), appended to the impl where after codegen's reference check

### Misc

- CI: MSRV job gained doctests (`--doc` can't mix with other selection options, split into two steps)
- Tests: angle unit tests (attribute/macro-body guards, nested-group rebuild in rendering, span-preservation noted as untestable — in fallback mode `Span::mixed_site()` is call_site); regression added `batch_trait!`'s `A<>` passthrough; dsl section 34 coverage matrix; ui added `rename_where.rs` / `where_const_ref.rs`; codegen unit test locks the `WhereArr<>` expansion (guarding against cache-style false positives such as "tests passed but the IDE expansion contains compile_error")

## 0.5.6 (2026-08-03)

### src organized into per-layer directories

- Pipeline layers: `parse/` (parser + atom layer + generics), `preprocess/` (directives + helpers + bare where + angle groups), `ast/` (Ty definitions + rendering), `apply/` (the Apply trait + tuple containers), `codegen/`; same-named files merged into `mod.rs` (eliminating `module_has_same_name`), submodules re-exported via `pub(crate) use`, external paths unchanged

### Angle-group preprocessing (angle.rs)

- proc-macro2 only groups `()`/`[]`/`{}`; `<>` is flat Punct — new `angle_collect` does a single scan at the pipeline entry: real `None` groups flattened + flat `<...>` paired into `None` groups (the `->` arrow doesn't participate); recurses into `Paren`/`Bracket`, doesn't enter `Brace` (body passthrough), doesn't enter `ident![...]` macro bodies / `#[...]` attributes
- `render_angles` mirrors this on the output side (`None` groups → flat `<...>`), preserving original spans when rebuilding `Paren`/`Bracket` (fixing the clippy diagnostic-mapping problem where doc-attribute spans became call_site)
- Wrap-up: orphaned `<`/`>` error out (unlocking downstream depth-logic deletion); `<>` depth branches removed from `scan_with` / `scan_body_boundary` / path scanning
- fuzz's full pipeline gained `angle_collect`

## 0.5.5 (2026-08-03)

### `A<>` verbatim-copy implementation

- `TraitBounds` rewritten as a positional structure (`TraitParam`: name / bound / refs)
- `bound_refs` does conservative token-level reference detection (prefer a false-positive rejection of auto-inheritance over ever generating wrong code)
- `expand_empty_trait_generics` preprocessing scan (`Ident<>` at depth 0, guarded by the `->` arrow)
- Replaces the initial version's "lifetimes matched by name + degrade to no inheritance": renames now go from silent degradation to an explicit error

## 0.5.4 (2026-08-03)

### `-name` subtraction implementation

- `parse_name_tokens` rewritten as keep/exclude dual lists + `#` marker expansion (with `parse_marker` / `parse_minus_target` helpers); the `#except` branch removed

### Bound-inheritance implementation

- `extract_trait_bounds` extracts the name→bound mapping from trait generics (Punctuated rendered via ToTokens as `A + B`), passed through `parse_batch_trait_entry` into `generate_impl`, which fills in bounds for `(name, None)` parameters
- Fixed the `quote!(#tp.bounds)` pitfall: quote interpolation doesn't support field access (treats `.bounds` as a literal); switched to taking a reference first

### Misc

- Release-artifact smoke verification (first time the real published artifact was verified usable)
- README quick-start version fixed (0.5.1 → 0.5.4; crates.io versions are immutable, so it was republished)

## 0.5.3 (2026-08-02)

### Refactoring and internal implementation

- **Preprocess return-type convergence**: directive expansion output converges to exactly one `{...}` group token
- **Directive-argument parsing refactored**: `parse_names_from_tokens`'s awkward encoding (commas encoded as `Err(None)`) changed to ordinary iterative collection
- **fuzz extended to the full pipeline**: `full_pipeline_no_panic` runs random token streams through the complete pipeline
- **`Apply` trait refactored**: the right operand's "structural context" early dispatch moved down into default methods (Array dispatch / Group transparent / WithCode, WithWhere passthrough / WithType hoisted out / Range expansion / Error passthrough); removed `TyArray`'s unreachable Cartesian-integral branch and `TyFn`'s unreachable Group branch; `trait Apply: Clone + Into<Ty>` (dispatch needs to reuse the left operand)
- **`Ty::expand`'s return value changed to an explicit enum**: `enum Expand { Leaf, Many }` (the original `Result<Vec<Ty>, Ty>` used `Err` to mean leaf — a counter-intuitive design)
- **Combinatorial expansion cap**: `MAX_EXPAND = 1024`, checked in `tuple_pow` / `pow_cartesian` (products per round) / `map_range` / `TyArray`'s Cartesian-integral branch, with `apply::check_expand_limit` as the unified entry
- **Array-chain expansion product cap**: `count_leaves` leaf-count validation
- **Tuple Cartesian-product bound fix**: `instantiate_combo` mistook parameter names for bounds (`(A: Clone, T)^N` generated `_Param: A`); changed to keep the real bounds
- **Logic-slimming refactor** (zero behavior change): `Ty::expand`'s wrapping boilerplate extracted into `expand_wrapped` / `expand_rebuild`; the directive-expansion skeleton merged into `expand_many`
- **Doc-drift fixes**: removed the u8 range for tuple generation from the README, updated test-matrix counts, added unsafe fn / `#except` / operand-strictness notes

### Fixes (internal)

- `#delegate` argument forwarding hardened: `collect_call_args` returns an error for non-identifier patterns
- Empty-range diagnostic: `map_range` errors on empty ranges
- Trailing-operator silent-segment-swallowing fixed: the Dash/Caret branches error on empty operands
- Empty-operand strictness: left-empty check + leading/consecutive-comma detection at the 3 entry points
- Directive-argument comma strictness

## 0.5.2 (2026-08-01)

### Testing and engineering

- **Parser fuzz verification**: `src/fuzz.rs` (proptest) feeds random tokens to `where_process` / `parse_item`, asserting no panics
- **Release hygiene**: `#![forbid(unsafe_code)]`, `#![deny(missing_docs)]`, fixed the Windows MSVC `linker_messages` warning
- **CI**: GitHub Actions (fmt / clippy -D warnings / test / doc, stable + MSRV 1.93 dual toolchains)

### Array/slice builder (`TyPrimitiveArray`)

- `TySlice` and `TyFixedArray` merged into `TyPrimitiveArray(Option<Box<Ty>>, Option<TokenStream>)`
- `()^N` fresh-generic tuples auto-hoisted out (`T^<A>X` => `<A>(T^X)`, nested `WithType` params merged into the impl generics)
- `TyNum` / `TyRange` changed from `u8` to `usize`

## 0.5.1 (2026-07-31)

### where-support implementation

- `where{...}` suffix: `TyWithWhere` / `TyWhere` nodes, merged by codegen into the impl's where clause
- Bare-where rewrite: new `where_process.rs` (after directive preprocessing, before DSL parsing), with boundary detection excluding `ident!{...}` macro-call bodies and code blocks inside angle brackets

## 0.5.0 (2026-07-28)

### Engineering

- `try_parse_path_prefix` state machine (requires at least one `::`, avoiding `#Display: ...` ambiguity)
- Precise `Spacing::Joint` checks (`::`, `->`, `..` prevent misreading adjacent non-joined punctuation)
- Range handling centralized (the `Apply for Ty` outer match uniformly expands a right-side Range)
- Module-level docs (`//!`) fully filled in
- Module split: `scan.rs` / `parse_atom.rs` / `generic.rs` / `types_render.rs` / `apply_tuple.rs` / `batch_trait_entry.rs` / `path_prefix.rs` / `preprocess_helpers.rs`

## 0.4.2 (2026-07-27)

### Engineering refactor

- `apply::trait Type` renamed to `trait Apply`
- `rustfmt.toml` (edition=2024, max_width=75, etc.); PRs require `cargo +nightly fmt --check`
- `src/diagnostic.rs`: the single `compile_error_str(msg)` constructor (two duplicate implementations removed, preventing diagnostic-construction drift)
- `ScanMode { Lossy, Strict }` + a single `scan_with` (eliminating two similar-but-differently-behaving `<>` depth loops)
- `extract_impl_parts`'s `WithType` branch append → prepend (`<A>[<B>T1, <C>T2]` now emits `impl<A, B>` and `impl<A, C>`)
- Error hardening: `expand_tokens`'s two `peek().unwrap()` calls replaced with `let Some else`; `tuple_pow`'s single-element `.unwrap()` changed to `expect` with a message
- Entry convergence: `extract_trait_path` / `extract_last_ident` inlined into `batch_trait!`
- Test system: `tests/dsl.rs` (20) + `tests/ui.rs` (8 fail + 1 pass, trybuild)
- Tests and examples reorganized: the 4 example test files deleted (~4800 lines), added `examples/quickstart.rs` + `tests/regression.rs`

### Misc

- `expand_delegate`'s `todo!("error")` replaced with `compile_error!`
- preprocess.rs comments and `get_trait_item`'s error message updated

## 0.4.1 (2026-07-25)

- Fixed the issue where custom macros didn't carry trait_def

## 0.4.0 (2026-07-25)

### Directive-system implementation

- New `preprocess.rs`: the directive-preprocessing module, recursively expanding only `[...]` (Bracket) groups
- `expand_tokens` / `expand_directive` return `Result`; errors emit `compile_error!` instead of panicking
- Zero `panic!` / `unreachable!` across the crate: the AST layer's `Ty::Error` variant is emitted via ToTokens; the preprocessing layer's `parse_method_names_from_tokens` / `get_trait_method_sig` return `Result`
- Directive extensibility: unknown `#name` delegates to the Rust attribute-macro system (changed to function-like macro calls in 0.5.3)
- `examples/my_tests.rs`: 36 directive tests

## 0.3.0 (2026-07-24)

### Complete rewrite

- Rewritten from scratch; the public API and DSL syntax are consistent with v0.2.x, with no code relationship to the old version internally
- Architecture: `lib.rs` (entry + shared driver) / `preprocess.rs` / `parse.rs` / `types.rs` / `apply.rs` / `codegen.rs`
- Parsing model: `Cursor<'a>` borrowed-slice cursor + precedence climbing (`Semi` < `Comma` < `Dash` < `Caret`), with `scan_stop` uniformly handling `<>` depth and the `->` guard; arbitrary Rust types pass through as Primitive nodes
- AST design: the `Ty` enum with 20 variants (three kinds: leaf / wrapper / container)
- Operator semantics: the `Type` trait's `apply(self, o)` (`^` right-assoc, `-` left-assoc, array dispatch, tuple generation)
- Tests: 95+ tests items / 56+ ds_tests items all passing, clippy zero warnings

## 0.2.2 (2026-07-20)

### Fixes and code review

- `fn^i32` auto-generates parentheses
- Unified `->` handling (`has_top_level_char` / `parse_balanced` / `find_top_level_colon` / `split_at_punct` exclude the `>` of `->`)
- P0: `split_raw` detects extra `>`; `parse_balanced` gives a detailed error ("unclosed `<` (N levels remaining)")
- P1: `expand_nested_bracket` comment (`unwrap_count - 1` semantics); `generate_tuples` returns Result (Cartesian-product over limit); `batch_trait!` empty-path check

## 0.2.1 (2026-07-20)

### Fixes (BUG-1/2/3 and precedence)

- BUG-1: `expand_caret` splits the right side at the first top-level `-` (`^` binds tighter than `-`)
- BUG-2: `parse_target_items` dropped content after `<>` (`parse_balanced`'s pos was discarded)
- BUG-3: `expand_single` didn't filter Attribute/Unsafe prefixes (`unsafe^#[attr]^T`)
- fn-type precedence: in `fn^(u32,i32)-usize` the `-` is the return type
- Nested caret preserves the `Fn` prefix

### Code Quality

- `ImplSpec::new()` constructor; `expand_caret` split out `expand_bracket_with_comma` / `expand_nested_bracket`; `dash_append` split out fn handling; `#![allow(linker_messages)]`

## 0.2.0 (2026-07-19)

### Implementation details

- `ImplSpec` gained `assoc_bindings` / `attributes` fields
- `PrefixItem` gained `ConstPtr` / `MutPtr` / `Fn` / `Attribute` variants
- `parse_segment` separates associated-type bindings when parsing `TraitName<Item=T>`
- Tests: macro-test 113 / ds-test 15 / consistency / nesting / parallelism

## 0.1.1 (2026-07-19)

### Implementation details

- `PrefixItem::Container` gained a `prefill` field; `parse_single_prefix` recognizes `Ident<...>`; `apply_caret` appends prefilled generics; `append_to_generic_container`
- README precedence notes; the Planned section removed

## 0.1.0 (2026-07-19)

### Initial release

- Safety: recursion depth limit (128 levels), `byte_range()` stable position suffixes, Cartesian-product combination cap (1024)
- Error handling: Chinese-language messages, original spans preserved, `compile_error!` instead of panic
- Tests: macro-test 99 / ds-test 15

## Project evolution history

> One-line mainline per generation (official releases):
> **0.1 release** · **0.2 attributes and prefixes** (fn/pointers/`#[attr]`/assoc) · **0.3 rewrite** (unified model rebuilt by hand) · **0.4 directive system** (`#fill`/`#delegate`/open extension) · **0.5 where system** (`where{...}` + bound inheritance + `A<>` verbatim copy) · **0.6 constant system** (`@` name family/range family/custom).
> The two prototype generations before 0.1.0 (crate originally named `auto_impl`) and the motivation for the 0.2 rewrite are below.

### Early-structure comparison (from the crate's original name auto_impl, up to before the 0.2 rewrite)

### 0.-1 (2026-07 prototype, single file, 684 lines)

- **Static type lists**: the spec was a sequential structure of "generics + trait generics + target + body", with no `^`/`-` operators, no tuple generation, no prefix system — the target type was a static type passed through as tokens
- But **80% of the design was already finalized**: the `[]` ambiguity (comma = list / none = slice), `()` grouping vs. tuple, generic inheritance (children append the parent's), body inheritance (list-level shared / child-level override), the dangling trait-generics diagnostic ("`MyTrait<T>` parsed as trait generic parameters, but a target type is missing"), `compile_error_at` span location, Chinese-language error messages
- **Automatic trait-generic completion**: when the trait has generics, they were auto-completed from `trait_generics` (`#trait_name<#(#params),*>`) — cut in 0.0 when `^` was introduced, brought back by 0.5.5's `A<>` verbatim copy

### 0.0 (2026-07 prototype, single file, 1961 lines)

- **The leap: type composition as operators** — `^` (right-assoc: `A^B=A<B>`, `&^T=&T`, `[A]^[B]` Cartesian product), `-` (left-assoc tuple construction), `()^N`/`^M..N` tuple generation, fresh generics (`A_7f3a_` span-position hash suffixes), the prefix system (`&`/`&mut`/`self`/`unsafe`), the recursion guard (`RecursionGuard` at 128 levels, present from day one) — every core concept of the DSL was finalized here; 0.1→0.6 added no new concepts, only refinement and peripheral systems
- Defects already planted (fixed only in 0.2.1/0.2.2): `split_raw` without a `->` guard; `expand_caret`'s right side without dash splitting (`HashMap^K-V` parsed as nested instead of parallel)

### 0.1.x (2026-07 first release series)

- **Module split done**: 0.0's single file cut directly by section into 9 files under `core/` (types/recursion/utils/codegen/tuple/caret/dash/parser + lib.rs entry) — that's 0.2's 9-file structure;
- **prefill pre-filled generics** (`HashMap<K>^V → HashMap<K, V>`): `PrefixItem::Container` gained a `prefill` field, wired into both the caret and dash paths;
- Recursion guard kept verbatim (`RecursionGuard` word-for-word identical to 0.0);
- 0.1.1 didn't yet have: fn/pointer/attribute prefixes (PrefixItem only had 6 variants), assoc bindings (ImplSpec 5 fields), the global `->` guard (only dash-local) — added in 0.2.0/0.2.2.

### 0.2 (2026-07-19, 9 files, 3197 lines)

- Continues on the 0.1.1 structure: +`fn`/`*const`/`*mut`/`#[attr]` prefix variants, +assoc_bindings/attributes fields, +the global `->` guard (unified in 0.2.2);
- BUG-1/2/3 erupted all at once (`^` right-side dash split, `parse_balanced` pos discard, prefix-chain filtering) — the "organized by operator + scattered depth" model hit its limit; 0.3.0 rewrote it.

> **Rewrite motivation (author's note)**: before 0.2, the approach was "explain the design + AI incremental implementation" — ideas popped out one by one, the architecture grew patch by patch, and no one fully held the whole model; in the 0.2.x era, fixing even a common-sense bug (like the `->` guard) took ages to locate — depth logic was scattered across five places, `^`/`-` had dual implementations, and changing one place required confirming the behavior of all the others. So 0.3.0 was **manually rewritten** by the author: first rebuild the unified model (precedence chain + Apply trait + Ty enum); the safety facility (recursion guard) was not rebuilt along with the model, until its 0.6.1 regression (see the 0.6.1 section).
> The real reason the architecture was stable after 0.3 is not the rewrite itself, but that **the author has fully held the model ever since** — every line has a known why, and fixing bugs no longer requires cross-checking across scattered locations.

### Three "cut and later brought back" threads

- **Automatic trait-generic completion**: present in 0.-1 → cut in 0.0 (`<...>` after the trait name became ambiguous once `^` was introduced) → brought back by 0.5.5's `A<>` verbatim copy;
- **Recursion guard**: present in 0.0 → lost in the 0.3 rewrite's fresh start (not rebuilt) → restored in 0.6.1 (`MAX_NEST_DEPTH`, see the 0.6.1 section);
- **Body-merge semantics**: 0.-1/0.0/0.1.1 children override the list level → 0.2 changed to concatenation (standalone bodies merge with shared bodies; same-named methods are reported by the compiler).

### Line-count evolution

`684 (0.-1) → 1961 (0.0) → ≈2153 (0.1.1) → 3197 (0.2) → 1628 (0.3.0 initial version)`
`→ ≈1586 (0.3.0 final version, five files) → 4400 (0.6)`