-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgaps.json
More file actions
1422 lines (1422 loc) · 327 KB
/
Copy pathgaps.json
File metadata and controls
1422 lines (1422 loc) · 327 KB
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
{
"schema_version": 1,
"description": "FX language specification gaps tracker. Each gap is an unresolved design question or missing specification.",
"gaps": [
{
"id": 1,
"name": "comptime_grammar",
"description": "comptime keyword is in design §17 but grammar has no productions for comptime fn, comptime begin...end, comptime if for platform branching. comptime if for platform selection shown in §17.1 (comptime if platform.simd_width >= 512). No grammar production beyond regular if_expr with comptime prefix",
"prior_art": "Zig's comptime is the gold standard: a single keyword that prefixes expressions, variables, function parameters, and blocks, using identical syntax to runtime code (no separate meta-language). Types are first-class comptime values. C++ took 5 standard revisions (constexpr in C++11, relaxed in C++14, if-constexpr in C++17, consteval/constinit in C++20, expanded in C++23) to approximate this, with a fundamentally different model requiring explicit annotation on function signatures. D has CTFE (compile-time function evaluation) with static-if and mixins, using enum for comptime variables and a separate template parameter list for comptime args. Nim uses a static keyword with minimal annotation burden, allowing arbitrary computation at compile time. Zig and Nim share the 'same language for meta' property; C++ templates are a separate sub-language.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §6.7): comptime_expr production covers single-expr comptime, comptime begin...end blocks, and comptime if/else if/else chains (matching Zig's uniform treatment). comptime_fn_decl is COMPTIME prefix on fn_decl — same syntax as runtime, compile-time evaluated. comptime_let_stmt adds to §6.1 stmt production. Follows Zig's 'same language for meta' principle; no separate metalanguage.",
"docs_adjusted": true
},
{
"id": 2,
"name": "staging_syntax",
"description": "code(T) type, quote, splice (~) described in §17.2 but grammar has no code(T) production, no quote syntax, ~ is prefix but not formalized for splice. Design §17.2 shows quote and splice. Backtick quotation was considered. How does quoting work? Just code(T) + ~ splice? What creates a code value?",
"prior_art": "BER MetaOCaml is the typed staging reference: brackets .<e>. delay computation (producing 'a code), escape .~e splices code inside brackets, and run .!e compiles and executes delayed code. The type checker tracks stage levels, incrementing/decrementing on bracket/escape. Scala 3 uses '{e} for quotes and ${e} for splices, regulated by a level-counting Phase Consistency Principle, with typed Expr[T] values; splices are permitted outside quotes making them true duals. Typed Template Haskell uses [|| e ||] for typed quotation and $$(e) for typed splices, restricted to two levels with a module-boundary stage restriction. All three systems produce typed code values; MetaOCaml supports arbitrary multi-stage, while Scala 3 and TH are essentially two-level (compile-time + runtime).",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §6.8): code_type production is CODE '(' type ')' — ordinary type application. quote_expr uses backtick-paren: '`' '(' expr ')' '`' (lexer disambiguates from backtick-keyword-escape via the following LPAREN token). splice_expr reuses the existing '~' prefix from prefix_expr (disambiguated from bitwise NOT by operand type at elaboration time — NOT requires a bits(n) operand, splice requires a code(T) operand). run() is a stdlib function, not a grammar form. Follows MetaOCaml's .<e>./.~e model with FX's syntactic conventions (backtick-paren for visual distinction from bitwise NOT and backtick-escape).",
"docs_adjusted": true
},
{
"id": 3,
"name": "decorator_grammar",
"description": "decorator keyword in §17.3 and keyword list but grammar has no decorator_decl production",
"prior_art": "Python decorators (@decorator before def) are runtime higher-order functions that wrap/replace the decorated function, applied at definition time. TC39/TypeScript decorators use the same @decorator syntax on classes and class members, operating at class definition time via a descriptor protocol; the TC39 proposal went through extensive revision over 7+ years. Java annotations (@Annotation) are pure metadata with no behavior -- they require separate reflection-based or compile-time processors (APT) to act on them, which decouples usage from implementation. Rust proc macros (#[attr] and #[derive(...)]) operate at compile time on token streams, providing powerful AST-level code generation but with a fundamentally different mechanism from runtime decoration. FX's decorator keyword appears closest to Python/TS semantics but applied to a statically-typed dependently-typed language.",
"resolution": "Resolved by Proposal 8: decorators are ordinary fn declarations with the @[decorator] attribute applied via the existing attribute_decl + fn_decl grammar (fx_grammar.md §5.1 + §5.4). No new production needed. Per §17.3 of the design spec: 'A decorator is a function annotated with @[decorator]; there is no separate decorator...end decorator block.' Usage at application site is also standard attribute syntax: @[cached] fn fibonacci(...) — attributes + fn_decl cover it. Compose via comma-separated attributes: @[cached, retry(3)] applies right-to-left.",
"docs_adjusted": true
},
{
"id": 4,
"name": "hardware_grammar",
"description": "Entire §18 (register_file, pipeline, stage, hardware fn, format for bit layouts) has zero grammar productions. Contextual keywords listed in §17 but no syntax rules",
"prior_art": "All mainstream HDL-in-GPL approaches are embedded DSLs, not dedicated syntax: Chisel (Scala) and SpinalHDL (Scala) use operator overloading and class hierarchies to express hardware, forced to invent keywords like 'when/otherwise' to avoid clashing with host syntax -- they cannot override if/else. Clash compiles a subset of Haskell directly to VHDL/Verilog/SystemVerilog, leveraging the natural mapping between pure functional code and hardware, but is not an embedded DSL -- it is a compiler. Amaranth (Python) and MyHDL (Python) use Python generators and signal objects for concurrency, converting via AST parsing to Verilog. Bluespec SystemVerilog uses Guarded Atomic Actions (rule-based) with a Haskell-derived type system, compiled to Verilog. No existing general-purpose language has dedicated first-class hardware grammar productions (register_file, pipeline, stage) -- FX's approach of dedicated syntax for hardware constructs within a GP language is genuinely novel.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §5.16, ~150 lines of new EBNF). Productions added: hardware_fn_decl (HARDWARE FN ... combinational), hardware_module_decl (HARDWARE MODULE ... sequential with reg/wire/on-clock), pipeline_decl (PIPELINE ... stages with emit/stall/flush/forward/redirect), stage_decl (STAGE ... with stage_member), register_file_decl (REGISTER_FILE ... at base, reg_with_fields, field_decl with access_mode, virtual_reg, driven_machine_decl). Contextual keywords catalogued: hardware, pipeline, stage, register_file, wire, reg, rising, falling, emit, stall, flush, forward, redirect, onehot, field, virtual, driven_by, when, write_order, at, RW/RO/WO/W1C/W1S/RC/RS/RSVD. Typed closers added: end hardware fn, end hardware module, end pipeline, end stage, end register_file, end reg, end on. layout_decl stays as-is (already covered in Proposal 4).",
"docs_adjusted": true
},
{
"id": 5,
"name": "session_type_grammar",
"description": "Session type declaration syntax described in §11 but grammar has no session_decl production. Old FxParse.mly had basic send/receive but new grammar doesn't",
"prior_art": "Session types as dedicated grammar is genuinely novel -- every existing language encodes sessions via nested generics or library types, not dedicated syntax. Scribble is a protocol description language using label(Types) from Role to Role syntax for multiparty session types, but it is a standalone specification language, not embedded in a GP language. GV (Gay-Vasconcelos) uses ?T.S for input, !T.S for output, and end!/end? for session closure as type-level syntax in a formal calculus. FreeST extends this with context-free session types using !T/? T for send/receive and internal/external choice operators, implemented as a standalone functional language. SILL implements the Caires-Pfenning propositions-as-sessions paradigm. Links integrates asynchronous GV-style sessions into a web programming language. All use type-level encodings rather than dedicated grammar productions for session declaration.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §5.15). Productions: session_decl (SESSION lower_ident ... END SESSION), session_body (sequence of steps or REC upper_ident + steps for recursive sessions), session_step (SEND/RECEIVE with payloads, BRANCH/SELECT with arms, recursive continuation reference, END terminator), session_payload (named field : type), branch_arm (lower_ident => steps END). Multiparty global session types via global_session_decl (From -> To : label(payload)). Channel operations send(ch, ...) / receive(ch) / select(ch, label) / cancel(ch) / receive_branch(ch) are ordinary function calls; no new expression grammar needed. Closes gap #5.",
"docs_adjusted": true
},
{
"id": 6,
"name": "codata_grammar",
"description": "codata declarations (§3.5) and unfold expressions (§17.6) have no grammar productions",
"prior_art": "Agda copatterns are the state of the art: coinductive types use the 'coinductive' keyword on records, and values are defined by copattern matching (branching on destructors/observations rather than constructors), with productivity checking replacing termination checking. A GHC fork by Sullivan (GHC 8.4) added a {-# LANGUAGE Copatterns #-} extension with codata types and nested copattern matching using GADT-like declaration syntax. The 'Codata in Action' paper (Downen, Sullivan, Ariola, Peyton Jones, ESOP 2019) demonstrates inter-compilation between data and codata. Uroboro provides symmetric data/codata with balanced pattern and copattern matching, where codata types define destructor signatures. Cedille encodes coinductive types via lambda encodings (Mendler-style) with constant-time constructors. Idris explored copattern syntax for codata via projection-based definitions. No mainstream language has dedicated codata grammar productions.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §5.14 and §6.9). codata_decl: CODATA lower_ident type_params? codata_member+ END CODATA — each member is a destructor signature FN name(SELF) : type. unfold_expr: UNFOLD optional <size_expr> unfold_arm+ END UNFOLD — each arm is destructor_name => expr ';'. Plus §6.10 sized_clause added to spec_clauses so codata-constructing functions carry explicit size parameters per §3.5 productivity rules. Productivity / guardedness is kernel-level (Coind-ν axiom, Appendix H.5 of design spec) — not a grammar concern.",
"docs_adjusted": true
},
{
"id": 7,
"name": "select_expr",
"description": "Multi-channel select wait (§11.9). Grammar has select_expr and select_arm — this IS covered",
"prior_art": "Go's select statement is the canonical multi-channel wait: switch-like syntax with case arms for channel send/receive operations, random selection among ready cases, and an optional default for non-blocking. Rust's tokio::select! macro uses pattern = async_expr => handler syntax with optional if preconditions, cancelling remaining branches when one completes. Erlang's receive does selective pattern matching on a single process mailbox (not multiple channels) with optional after timeout. OCaml's Event module (Concurrent ML-style) uses first-class composable events: Event.choose takes a list of events, Event.sync blocks until one succeeds, with Event.wrap for post-processing -- the most compositional design but library-level rather than language syntax. FX's dedicated select/end select block syntax with select arms is closest to Go but as an expression returning a value.",
"resolution": "Already in grammar",
"docs_adjusted": true
},
{
"id": 8,
"name": "dimension_taint_class_decl",
"description": "dimension declarations (§17.5) and taint_class declarations (§12.3) have no grammar productions",
"prior_art": "Perl's taint mode (-T flag, since 1989/1994) is the oldest: a dynamic runtime flag on scalar values, propagated through expressions, with fatal errors on tainted values in sensitive operations (system calls, file ops). Ruby had a similar $SAFE level and taint flag (deprecated in 3.x). Java's Checker Framework provides @Tainted/@Untainted type qualifiers as a pluggable type system, with SFlow providing context-sensitive type-based taint analysis using subtyping (@Untainted <: @Tainted). CheckLT extends Checker Framework with a dynamically configurable security lattice for information flow. All existing approaches use either runtime flags (Perl/Ruby), annotation-based type qualifiers on existing types (Java), or external static analysis tools (Semgrep taint mode) -- none provide dedicated grammar productions for declaring taint classes or security dimensions as first-class language constructs. FX's dimension/taint_class declarations are novel.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §5.18). Both are simple declaration-only constructs: dimension_decl: DIMENSION upper_ident ';' and taint_class_decl: TAINT_CLASS upper_ident ';'. No parameters, no body — they introduce names for compiler-tracked dimensions. 'taint_class' added as global keyword (was missing from the 87-keyword list).",
"docs_adjusted": true
},
{
"id": 9,
"name": "module_types_functors",
"description": "Module types and functors described in §5.5 (module type Ordered, module functor MakeSet). Old FxParse.mly had full support. New grammar has nothing. Design §5.5 shows module type / module functor / struct. Grammar has nothing",
"prior_art": "OCaml's module system is the gold standard: module types (signatures) specify interfaces with abstract types, functors are functions from modules to modules supporting higher-order functors, and OCaml uniquely offers both applicative functors (memoized, type-preserving) and generative functors (fresh abstract types each call). SML has a similar system but only generative functors. 1ML (Rossberg 2018) unifies core and module languages so functions/functors/type-constructors are one construct and structures/records/tuples are one construct, eliminating the stratification duplication, encoded in System F-omega. MixML (Dreyer & Rossberg 2013) adds mixin-style recursive linking to ML modules, unifying structures and signatures, solving the 'double vision' problem for recursive modules. Rust lacks ML modules but simulates some patterns via traits with associated types. Haskell's Backpack provides separately-typecheckable packages inspired by MixML.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §5.17). module_type_decl: MODULE TYPE upper_ident module_type_member* END MODULE TYPE — members include abstract type decls, type aliases, val signatures, and fn signatures. module_functor_decl: MODULE FUNCTOR upper_ident (upper_ident : upper_ident) module_body END MODULE FUNCTOR — single-argument functor with named argument and signature. Functor application via module_binding: MODULE upper_ident = module_expr; where module_expr is a named module, a functor application, or an anonymous STRUCT module_body END STRUCT literal. 'struct' is a contextual keyword (recognized only in functor argument position). Follows OCaml's system pragmatically; generative vs applicative functor distinction is a kernel-level semantic decision, not grammar.",
"docs_adjusted": true
},
{
"id": 10,
"name": "extern_block_form",
"description": "Design §5.5 shows extern \"C\" fn...end extern block form. Grammar has single extern_decl but not the block form",
"prior_art": "Rust's extern \"C\" { ... } block groups multiple foreign function declarations under a single calling convention and #[link] attribute, supporting both importing (extern block with fn signatures) and exporting (#[no_mangle] pub extern \"C\" fn). Zig's @cImport(@cInclude(\"header.h\")) directly translates C headers to Zig declarations at compile time, bundling a C compiler toolchain -- the most seamless C interop of any language. Nim uses per-declaration {.importc.} pragmas with optional {.dynlib.} for dynamic linking, plus a c2nim tool for bulk header conversion. Haskell's FFI uses foreign import ccall per-declaration with no block grouping syntax. Swift uses Clang module maps and bridging headers, importing entire C/ObjC/C++ modules. FX's extern block form is closest to Rust's but with explicit begin/end delimiters.",
"resolution": "Resolved by Proposal 10 (fx_grammar.md §5.10 extended). extern_decl now has two forms: single-fn 'EXTERN string_lit? FN lower_ident ... ';'' (existing) and block 'EXTERN string_lit extern_member+ END EXTERN ';''. extern_member is either a fn signature or a 'VAL lower_ident : type ';'' for extern global values (C globals like errno). Block form groups multiple foreign declarations under a single ABI string (Rust-style). Typed closer 'end extern' was already in §14.",
"docs_adjusted": true
},
{
"id": 11,
"name": "refinement_on_complex_types",
"description": "Grammar has app_type \"{\" expr \"}\" for unnamed refinements but what about refinements on complex types like (list(i64)) { length(x) > 0 }? Parenthesized base types before { } may be incomplete",
"prior_art": "Liquid Haskell uses {v: T | pred} where T can be any type including parameterized ones, with measures lifting algebraic type structure into refinements and abstract refinement types parameterizing predicates over type constructors. F* uses x:t{phi} where t can be any type including applications like vec a n. Flux for Rust indexes generic types with refinement variables (impl RVec<T, @n>) and supports T{v: pred} on any Rust type. Stainless for Scala uses require/ensuring contracts and @invariant annotations on parameterized case classes, with System FR supporting refinement types on arbitrary type expressions.",
"resolution": "Resolved by Proposal 10 (fx_grammar.md §7, fx_design.md §10.1). Grammar refinement rule lifted from 'postfix_type \"{\" expr \"}\"' to 'app_type \"{\" expr \"}\"' and 'lower_ident \":\" app_type \"{\" expr \"}\"' so any applied, parameterized, or nested type expression carries a predicate uniformly: 'list(i64) { length(x) > 0 }', 'map(k, v) { size(x) <= 256 }', 'tensor(f32, [b, s, d]) { b > 0 }'. §10.1 documents the 'refinement on any type expression' rule. Matches Liquid Haskell and F* in expressive power; FX's implicit binder remains 'x' (value being refined) with named form 'r: T { pred(r) }' for explicit naming or shadowing.",
"docs_adjusted": true
},
{
"id": 12,
"name": "dependent_function_types",
"description": "(x: i64) -> i64 { result > x } — result type depends on parameter. Grammar has typed_param arrow but doesn't show how result type references parameter names. Implicit in dependent types but should be stated",
"prior_art": "All dependently-typed languages use essentially identical Pi-type surface syntax: Agda and Lean 4 write (x : A) -> B x, Idris writes (x : A) -> B x, Coq writes forall (x : A), B x. The named parameter in parentheses scopes over the return type. F* uses x:t -> t' where x is bound in t', and extends this with refinements: val incr : x:int -> y:int{y > x}. Lean 4 and Agda additionally support implicit parameters with {x : A} -> B x. FX's (x: i64) -> i64{result > x} follows the same pattern but uses a distinguished 'result' variable for the return value refinement.",
"resolution": "Resolved by Proposal 10 (fx_design.md §10.1 expanded). FX's inline dependent function type uses the grammar-native named-refinement form: '(x: T) -> r: U { pred(r, x) }'. The parameter name 'x' is in scope for the return type; the refinement's local binder 'r' names the returned value. No magic 'result' identifier — the binder is explicit per rigor-first. Example: 'fn guess_bigger : (x: nat) -> r: nat { r > x };'. Kernel translates to 'Π (x :_r T) → Σ (r :_1 U) × (pred)' per §31.2. For named 'fn' declarations, 'post r =>' remains the primary form; the inline Pi form is for function-typed values and callback signatures where a 'post' clause has no attachment point. No grammar change needed — already supported by existing app_type named-refinement rule.",
"docs_adjusted": true
},
{
"id": 13,
"name": "universe_hierarchy",
"description": "Type appears everywhere but Type_0 : Type_1 : ... never defined. No universe levels, no universe polymorphism specified. Foundational gap — needed to prevent Girard's paradox. Type_0 : Type_1 : ... never defined. Universe polymorphism not specified. Foundational for soundness",
"prior_art": "Lean 4 uses Sort u as the fundamental universe with Type u = Sort (u+1) and Prop = Sort 0; universe variables are declared with 'universe' keyword and mostly inferred invisibly; universes are non-cumulative. Agda has Set_i : Set_(i+1) with explicit Level type, lzero/lsuc/max operations, and Setomega above the hierarchy. Coq uses Type_i with universe polymorphism and a separate impredicative Prop sort. F* uses Type u#i syntax with universe levels as natural numbers, max, and offsets (l + k); inference handles most cases. The consensus best practice is Lean 4's approach: invisible by default, explicit only when needed.",
"resolution": "Resolved by Proposal 7 (§31.4 Universe Levels, Appendix H.1): predicative cumulative hierarchy with type<u> : type<level.succ(u)> and type<u> <: type<v> when u <= v. No impredicative Prop — single hierarchy, erasure handled by existing Ghost grade (usage=0). Level expressions: level.zero, level.succ(u), level.max(a, b), plus variables bound by <k: level>. Surface syntax: bare 'type' is sugar for type<0> when no level var in scope; otherwise type<k> reusing existing kind bracket pattern. Universe polymorphism via <k: level, a: type<k>> — explicit at binder (rigor-first), inferred at use site. Folded into dim 1 (Type); no new dimension. Five kernel axioms: U-wf, U-hier, U-cumul, U-level, U-poly. Consistency theorem stated in §27.4 over the kernel.",
"docs_adjusted": true
},
{
"id": 14,
"name": "higher_kinded_types_usage",
"description": "Grammar has kind -> kind but no examples beyond Functor<f: Type -> Type>. How do you write a value of higher-kinded type? Apply it? Needs clarification",
"prior_art": "Haskell has native HKT support where type constructors like Maybe have kind * -> * and are used directly in typeclass definitions (class Functor f where fmap :: (a -> b) -> f a -> f b); kind annotations use Type/* syntax. Scala uses F[_] syntax for type constructor parameters in traits/classes (trait Functor[F[_]]). Idris makes kinds explicit as ordinary dependent function types (F : Type -> Type). OCaml lacks native HKT but encodes them via module functors or the 'Lightweight Higher-Kinded Polymorphism' defunctionalization technique. TypeScript cannot express HKT natively; libraries like fp-ts use interface-merging tricks to simulate them.",
"resolution": "Resolved by Proposal 11 (§3.13 expanded). HKT usage uses the same parenthesized application syntax as value-level function application: 'f(a)' where 'f' has kind 'Type -> Type' and 'a' has kind 'Type'. FX deliberately EXCLUDES three HKT features that bring disproportionate complexity: (1) type-level lambdas — introduce a named type alias instead; (2) associated types — carry the type information via explicit class type parameters (Collection<c, elem>); (3) kind polymorphism beyond the basic '<k: kind>' binder — split into two declarations if needed. Rigor-first: kinds are explicit at declaration, never inferred (T044 on omission).",
"docs_adjusted": true
},
{
"id": 15,
"name": "quotient_type_grammar",
"description": "Design §3.7 shows quotient type rat = ... No grammar production. No quotient keyword in grammar",
"prior_art": "Lean 4 has quotient types as a kernel primitive: Quotient.mk constructs elements, Quotient.lift defines functions (requiring proof that the function respects the equivalence relation), and Quotient.sound asserts equality of related elements. Cubical Agda supports quotient types as higher inductive types (HITs) with computational content via path constructors on the interval type. In HoTT, quotient types are derivable from the univalence axiom. The setoid approach (used in older Coq) bundles a type with an equivalence relation but does not enforce the abstraction barrier. The pragmatic consensus is Lean 4's approach: built-in Quot type with mk/lift/sound/ind as primitives.",
"resolution": "Resolved by Proposal 11 (fx_grammar.md §5.9 adds quotient_decl, §3.7 expanded, keyword added). Grammar: 'QUOTIENT TYPE lower_ident type_params? \"=\" type BY expr \";\"'. Keyword 'quotient' added to global list (92 total). Kernel already has Quot axioms (Appendix H.7: Quot-form, Quot-mk, Quot-lift). Functions on a quotient are defined via stdlib 'Quot.lift<T, R, U>(f: T -> U, q: Quot(T, R)) : U pre forall x y. R(x, y) ==> f(x) == f(y);' — the 'pre' clause is the respecting obligation, discharged at compile time by SMT; no runtime proof argument. Quot.lift is the SOLE eliminator (no pattern-match escape to the representative — that would break the abstraction barrier). Set-quotient semantics (not HIT) — this is Lean 4's practical choice; cubical path constructors deferred as v2 research.",
"docs_adjusted": true
},
{
"id": 16,
"name": "any_never_parsing",
"description": "Any and never discussed in §3.9. Neither is a keyword. Are they prelude types? How does narrowing from Any work syntactically? Relationship to dyn unclear",
"prior_art": "TypeScript has two top types: 'any' (disables checking) and 'unknown' (type-safe, requires narrowing via typeof/instanceof guards); 'never' is the bottom type for functions that never return. Scala has Any as the root of all types and Nothing as the bottom (subtype of everything, used for Nil : List[Nothing] covariance trick). Kotlin mirrors Scala with Any/Any? at top and Nothing at bottom; Nothing? is the type of null. Haskell lacks subtyping so has no top type; Data.Void is the uninhabited bottom type. In all subtyping languages, top/bottom are prelude types (not keywords), and narrowing from top uses pattern matching or type-test expressions.",
"resolution": "Resolved by Proposal 12 (§3.9 rewritten as 'The Never Type' — Any removed entirely). FX has no Any top type and no dynamic narrowing. Three native patterns cover every use case that Any/dyn Trait would serve in Rust or TypeScript: (1) closed enums for tagged data (JSON/YAML/config — enumerate variants, compiler checks exhaustiveness, no runtime type descriptor); (2) contract decode at boundaries (§14 produces typed values with validators); (3) explicit existentials for opaque types with behavior (§16.5 'exists T. { ... }' records where the record's shape IS the vtable). 'never' remains as the kernel empty inductive type, auto-imported from Std.Prelude. Rationale for removing Any: runtime type descriptor contradicts §1.5 compile-time erasure; soundness puzzles with linear types (where does a linear value go when narrow<T> mismatches?); three native patterns cover the use cases without runtime type info. Earlier Proposal 11 attempt at restricting Any to @[copy] was rolled back — clean removal is preferable. No grammar change; keyword count unchanged.",
"docs_adjusted": true
},
{
"id": 17,
"name": "tuple_indexing",
"description": "pair.0, pair.1 — old FxParse.mly had DOT_PROJ INT_LIT. New grammar has postfix_expr . lower_ident but integer indices aren't lower_ident. Needs postfix_expr . int_lit. pair.0, pair.1 need postfix_expr . int_lit in grammar",
"prior_art": "Rust and Swift both use dot-numeric syntax (t.0, t.1) where the index is a compile-time decimal literal with no leading zeros or suffix; Rust's tuple index is a special lexical form, not a general integer literal. Python uses standard bracket indexing (t[0], t[1]) consistent with its sequence protocol. C++ requires the verbose std::get<0>(t) template function (compile-time index); C++17 added structured bindings (auto [a, b] = t). Kotlin uses destructuring declarations with component1()/component2() convention rather than positional indexing. The Rust/Swift .N syntax is the cleanest and requires adding postfix_expr . int_lit to the grammar.",
"resolution": "Resolved by mechanical grammar fix (fx_grammar.md §6.2 postfix_expr): added 'postfix_expr . int_lit' for tuple indexing. int_lit here is the bare decimal literal (no suffix, no leading zeros — prevents ambiguity with decimal literals like 3.14). Rust/Swift-style. Enables pair.0, triple.2, etc.",
"docs_adjusted": true
},
{
"id": 18,
"name": "comprehension_syntax",
"description": "List comprehensions in §4.8: [x * x for x in 0..10]. No grammar production. Grammar has list literals but no comprehension syntax. Needs [expr FOR pattern IN expr (IF expr)?]. [x * x for x in 0..10 if x > 5] — same as gap 19. Grammar needs [ expr FOR pattern IN expr (IF expr)? ] including multi-source",
"prior_art": "Python's [expr for x in iter if cond] syntax (with set {expr for...} and dict {k:v for...} variants) is the most widely copied design, supporting multiple for clauses and guards. Haskell originated the idea with [expr | gen, guard] mathematical set-builder notation. Scala uses for { x <- iter if cond } yield expr which desugars to map/flatMap/withFilter chains and works on any monad. C# LINQ provides from x in iter where cond select expr with additional join/group/orderby clauses. Erlang uses [expr || gen, filter]. F# uses [for x in iter do if cond then yield expr] as computation expressions. Python's for...in...if form is the universal standard.",
"resolution": "Resolved by mechanical grammar fix (fx_grammar.md §6.4): list_comprehension production '[' expr comprehension_clause+ ']'. comprehension_clause is FOR pattern IN expr or IF expr. Multiple FOR clauses produce nested iteration; IF clauses filter. Follows Python's for...in...if universal standard. Desugars to pipe chain of map/flat_map/filter per §4.8. Single-source [x*x for x in 0..10] works; multi-source [(x, y) for x in xs for y in ys if x != y] works.",
"docs_adjusted": true
},
{
"id": 19,
"name": "stepped_ranges",
"description": "Design §4.5 shows 0..100 by 5. Grammar has .. and ..= as infix operators but no by step syntax. 0..100 by 5 — grammar has .. and ..= as infix but no by step. Needs expr .. expr BY expr or library function",
"prior_art": "Python uses range(start, stop, step) as a built-in function. Rust uses (0..10).step_by(2) as a method on the Range iterator adapter, keeping range syntax clean. Kotlin has the most syntactic support: 0..10 step 2 using 'step' as an infix keyword, with downTo for descending ranges. Swift uses stride(from:to:by:) / stride(from:through:by:) as library functions. C++20 uses views::iota | views::stride via ranges. The design choice is syntax (Kotlin's 'step', FX's proposed 'by') vs library method (Rust's .step_by()). FX's 0..100 by 5 follows Kotlin's approach of a syntactic keyword, which is more readable but requires a grammar production.",
"resolution": "Resolved by mechanical grammar fill (fx_grammar.md §6.2 range_expr): 'add_expr range_op add_expr (BY add_expr)?'. The 'by' keyword was already in the 87-keyword list; just needed the production. §4.5 design spec clarified: stride may be negative for reverse iteration; 'by' binds tighter than comparison and looser than additive. Kotlin model.",
"docs_adjusted": true
},
{
"id": 20,
"name": "nested_fstrings",
"description": "Nested f-strings: f\"outer {f\"inner {x}\"}\". Lexer brace depth tracking would need to handle recursive f-string scanning. Not specified",
"prior_art": "Python 3.12 (PEP 701) lifted all f-string nesting restrictions: f-strings can now contain same-quote f-strings to arbitrary depth by switching from a custom parser to the PEG parser with brace-depth tracking. JavaScript template literals support unlimited nesting since backtick delimiters differ from the $\\{\\} interpolation syntax, making recursive scanning straightforward. C# interpolated strings support arbitrary nesting including raw string literals in C# 11. Rust's format! macro does NOT support nesting or arbitrary expressions inside interpolation holes; only identifiers and simple field access are allowed. The key implementation requirement is a lexer with recursive brace-depth tracking (as Python 3.12 demonstrated).",
"resolution": "Resolved by design decision (fx_design.md §2.4): nested f-strings are FORBIDDEN. Inside an f-string's '{...}' interpolation, another f-string literal 'f\"...\"' is compile error T059 ('nested f-string; bind to a variable first'). Plain double-quoted strings and other expressions are fine inside '{...}'. When compound interpolation is needed, compute the inner string separately: 'let inner = f\"inner {x}\"; let s = f\"outer {inner}\";'. Rationale: Python 3.12's PEP 701 is an entire grammar change for fragile benefit; one-level lifting via 'let' is always enough and produces readable code. Matches Rust's format! and pre-3.12 Python restrictions.",
"docs_adjusted": true
},
{
"id": 21,
"name": "record_update_self",
"description": "Grammar has { expr WITH field_init }. Design shows { self with headers: ... }. self is a keyword — is it valid as base expression in record update? Should work since self is an expression",
"prior_art": "Haskell uses `x { field = val }`, OCaml uses `{ x with field = val }`, Rust uses `Struct { field: val, ..x }` (RFC 2528 extended this to type-changing updates), and Elm uses `{ x | field = val }` with row-polymorphic records. PureScript offers the most principled approach via row polymorphism: `point { x = val }` with nested update support and wildcard update functions `_ { f = v }`. All languages create a new immutable record; the key design axis is whether type-changing updates are allowed (Haskell/Rust yes, Elm no) and whether row polymorphism enables generic update functions (PureScript/Elm yes, others no).",
"resolution": "Resolved by Proposal 4 (§2.7 rule 16, §3.4). Record update uses spread syntax '{ ...base, field: value }' instead of 'with'. Since 'self' is an ordinary expression inside impl/instance methods, '{ ...self, headers: [(k, v), ...self.headers] }' works trivially. No special rule for 'self' needed — the spread form takes any expression as its base. The old 'with' keyword is reserved exclusively for effect annotations (§2.7 rule 17).",
"docs_adjusted": true
},
{
"id": 22,
"name": "static_vs_instance_methods",
"description": "Grammar impl_decl has method declarations but no syntax distinction between static methods (no self) and instance methods. Resolution rules in §16.1-15.3 not reflected in grammar",
"prior_art": "Rust distinguishes via presence/absence of `self` parameter: associated functions without `self` are static (called `Type::new()`), methods with `&self`/`&mut self`/`self` are instance-bound, with three ownership variants. Python uses decorators `@staticmethod` (no params) and `@classmethod` (`cls`) vs regular methods (`self`). Kotlin replaces static entirely with `companion object` blocks that are real singleton objects capable of implementing interfaces. Swift uses `static func` (non-overridable) and `class func` (overridable in subclasses) keywords vs plain `func` for instance methods. FX's approach of inferring static vs instance from self-parameter presence mirrors Rust's design.",
"resolution": "Resolved by Proposal 12 part 2 (fx_grammar.md §5.13 adds self_param production + impl_fn_params rule, fx_design.md §16.1 expanded). Grammar: self_param = SELF | REF SELF | REF MUT SELF | AFFINE SELF (no type ascription). impl_fn_params starts with self_param for instance methods or no self for static methods. Applies to impl_member, instance_member, and class_member. Semantics: self's type is inferred from the enclosing impl T / instance Trait for T / class Trait<T> block; writing 'fn is_open(ref self: Connection)' inside 'impl Connection' is compile error T064 ('self type is implicit in impl/instance/class block'). Rust-style omission preserves rigor-first for semantic values (effects, modes, lifetimes) while treating self's type as syntactic context rather than semantic inference. Static methods have no self parameter and are called via Type.method(args) rather than value.method(args).",
"docs_adjusted": true
},
{
"id": 23,
"name": "class_default_method_bodies",
"description": "Design §16.4 shows default methods with = expr in class declarations. Grammar class_member has FN lower_ident fn_params : type ; — signature only, no body option. Grammar class_member needs optional fn_body for default implementations",
"prior_art": "Haskell type classes allow default method bodies directly in the `class` declaration; instances inherit the default unless they override it. Rust traits do the same: default method bodies are defined inline in the `trait` block and any `impl` can accept or override them. Swift takes a two-step approach where protocols declare signatures only and default bodies are provided in a separate `extension` block on the protocol, which is more verbose but allows adding defaults retroactively. Scala traits support concrete method bodies directly. The consensus across all four languages is that default implementations are essential for evolving interfaces without breaking existing implementors.",
"resolution": "Resolved by Proposal 12 part 2 (fx_grammar.md §5.13 class_member extended). Grammar: class_member FN form now ends with class_fn_body = ';' (abstract — instances must supply body) | fn_body (default — instances may override). Effects, spec clauses, and default bodies all permitted. Semantics: default body is type-checked against the declared signature at class-declaration site (not re-checked per instance). An instance omitting methods that have default bodies inherits the default; abstract methods must be implemented by every instance. Standard Haskell/Rust/Swift type-class default-method pattern.",
"docs_adjusted": true
},
{
"id": 24,
"name": "instance_where_constraints",
"description": "Can an instance have type class constraints? instance Ord for Tree<a: type> where Ord(a). Grammar instance_decl doesn't include where_clause. instance Ord for Tree<a: type> where Ord(a) — grammar needs where_clause on instance_decl",
"prior_art": "Haskell uses instance context syntax `instance (Eq a) => Ord (Tree a) where ...` with the `=>` arrow separating constraints from the instance head; this is fundamental to the type class system and required for almost all parameterized instances. Rust uses `where` clauses on `impl` blocks: `impl<T> Ord for Tree<T> where T: Ord { ... }`. Scala 3 uses `given` with context bounds: `given [T: Ord]: Ord[Tree[T]]` or the older `given Ord[Tree[T]](using Ord[T])`. The coherence property (at most one instance per type) is enforced in Haskell and Rust but not Scala. All three systems consider constrained instances essential; without them, instances for parameterized types are impossible.",
"resolution": "Resolved by Proposal 12 part 2 (fx_grammar.md §5.13 instance_decl extended, fx_design.md §16.4 expanded). Grammar: instance_decl accepts optional 'WHERE constraint (, constraint)* ;' before instance_member+. Semantics: the where clause is part of the instance's resolution signature — the compiler selects this instance only at call sites where the surrounding context establishes the listed constraints. Enables 'instance Ord for Tree<a> where Ord(a)'. Coherence maintained: one instance per (Trait, Type) pair globally; no specialization. Standard Haskell / Rust / Scala 3 parameterized instance pattern.",
"docs_adjusted": true
},
{
"id": 25,
"name": "mutual_type_recursion",
"description": "Functions have fn rec ... and ... for mutual recursion. Types have no equivalent. How do mutually recursive types (type expr and type stmt) work?. type expr and type stmt referencing each other. No type ... and ... syntax exists",
"prior_art": "OCaml uses \"type t1 = ... and t2 = ...\" with the \"and\" keyword to define mutually recursive types in a single declaration group; any cycle must pass through at least one variant or record type. Haskell requires no special syntax because all top-level definitions are mutually recursive by default. Rust allows types within the same module to reference each other freely without forward declarations, but recursive types need indirection (Box, Rc) for known size. C/C++ use forward declarations (\"struct B;\") to allow mutual references through pointers. F* uses \"and\" for mutual recursion of both functions and types, matching OCaml.",
"resolution": "Resolved (fx_grammar.md §5.9 type_decl restructured). type_decl: TYPE type_rec_binding (AND type_rec_binding)* ';' — one grammar production covers both single and mutually recursive cases. Each type_rec_binding is an alias, record, or variant form. Single trailing ';' after the last binding. Any cycle must pass through at least one variant or record type (standard OCaml invariant, enforced by kernel's strict positivity check on Ind). Non-recursive declarations are one-binding groups; looks identical to before.",
"docs_adjusted": true
},
{
"id": 26,
"name": "gadt_examples",
"description": "Grammar has upper_ident : type ; for GADT constructors in variant_ctor. Design §5.4 doesn't show GADT examples. Needs at least one example",
"prior_art": "Haskell uses the GADTs pragma with \"data T a where\" blocks where each constructor declares its full return type signature (e.g., \"Lit :: Int -> Term Int\"), enabling type refinement on pattern match. OCaml supports GADTs natively since 4.00 using variant syntax with per-constructor type constraints and existential quantification. Scala 3 unifies GADTs with its enum syntax: \"case IntBox(n: Int) extends Box[Int]\" -- the extends clause specializes the type parameter, so ADTs and GADTs share identical surface syntax. Idris and Agda use indexed families as their core mechanism (\"data Vect : Nat -> Type -> Type where\"), which subsumes GADTs since constructors can be indexed by values, not just types. Type inference with GADTs is undecidable in general; Haskell and OCaml both require user-supplied type annotations at GADT match sites.",
"resolution": "Resolved (fx_grammar.md §5.9 adds GADT example in prose). The grammar form 'upper_ident ':' type ';' was already present in variant_ctor; the gap was a missing example in the design spec. Added example showing 'type term<a> Lit : int -> term<int>; Bool : bool -> term<bool>; If : term<bool> -> term<a> -> term<a> -> term<a>; end type' — constructor-per-line syntax with return type refining the indexed family per standard GADT semantics.",
"docs_adjusted": true
},
{
"id": 27,
"name": "tactic_by_blocks",
"description": "Design §10.7 lists built-in tactics (ring, field, linarith, omega, decide, simp, norm_num). No grammar for by { tactic1; tactic2; } blocks. calc_expr exists but no by_block or tactic_expr",
"prior_art": "Lean 4 uses \"by\" blocks where tactics are ordinary Lean programs: \"theorem p : P := by tactic1; tactic2\" -- no separate metalanguage. Coq/Rocq has Ltac (dynamic, interpreted) and Ltac2 (typed, compiled) as separate tactic languages, plus the option of writing tactics in OCaml; Mtac2 adds typed backward reasoning with static guarantees. Isabelle uses Isar structured proofs with \"have\"/\"hence\"/\"thus\" for declarative forward reasoning plus \"apply\"-style tactic scripts. F* uses \"assert P by tau\" which desugars to assert_by_tactic, where tau is an F* function of type unit -> Tac unit; tactics are first-class F* code, not a separate language. F* also has \"calc\" blocks for equational/relational reasoning chains. The trend is toward unifying the tactic language with the host language (Lean 4, F*) rather than maintaining a separate metalanguage (Coq Ltac).",
"resolution": "Resolved by Proposal 14 (fx_grammar.md §6.4 assert_expr gets BY by_clause; §6.5 calc_step unified to same by_clause form). Dafny-style hint blocks, no DSL. Two forms: (a) single named tactic 'assert P by ring;' or 'assert P by smt(solver: z3, theories: [QF_NIA], timeout: 10s);' where tactics are ordinary stdlib ghost functions — ring/linarith/omega/decide/simp/norm_num/smt; (b) hint block 'assert P by begin stmt* end;' where stmts are ordinary FX statements (lemma calls, asserts, reveal, calc, let bindings for intermediate expressions), restricted at elaboration to ghost grade + Tot effect. Mutation/IO/runtime control flow inside a hint block are compile error P007. Calc steps use the same by_clause form: 'a == b by ring;' or 'a == b by begin comm_mul(a,c); end;'. No separate tactic DSL — FX's own statement grammar is the hint language. Covers 'FX should be expressive on math' via ordinary lemmas + calc chains + hint blocks composing naturally.",
"docs_adjusted": true
},
{
"id": 28,
"name": "assert_using_multiple_lemmas",
"description": "Design shows assert P using lemma1(x), lemma2(y); with multiple lemmas. Grammar has ASSERT expr USING expr ; — unclear if expr_list is supported",
"prior_art": "Lean 4 uses \"have h : P := proof_term\" or \"have h : P := by tactic\" to introduce intermediate facts, composing multiple lemma applications in sequence. Coq uses \"assert (H : P) by tactic\" to create a named hypothesis mid-proof, with the tactic block scoped to proving P. Dafny uses \"assert P by { hint_statements; }\" where the hint block is scoped -- facts established inside the by-block are not available downstream, providing proof compartmentalization. F* uses \"assert P by (fun () -> apply_lemma l1; apply_lemma l2)\" where multiple lemma applications are sequenced inside the tactic. Isabelle uses \"have P using facts by method\" with an explicit \"using\" clause to supply lemmas. FX's \"assert P using lemma1(x), lemma2(y)\" is closest to a desugaring of sequential lemma application, but the comma-separated list syntax is unique.",
"resolution": "Resolved by Proposal 14 (fx_grammar.md §6.4 assert production). ASSERT expr USING expr (',' expr)* ';' — comma-separated lemma list at grammar level matches the §10.4 design text. Mechanical grammar fix.",
"docs_adjusted": true
},
{
"id": 29,
"name": "verify_exports_semantics",
"description": "Grammar has verify...exports block but semantics of what exports means for proof context not specified. Can exports be arbitrary expressions? Only propositions?",
"prior_art": "Dafny's \"assert P by { S }\" is the closest prior art: the by-block establishes P using hint statements S, and crucially, facts from inside the by-block do NOT leak into the surrounding scope -- only P itself becomes available downstream. Dafny also has module \"export\" sets with \"provides\" (opaque) and \"reveals\" (transparent) clauses controlling what verification facts clients can see. SPARK Ada has \"pragma Assert_And_Cut(P)\" which acts as a verification cut point: everything proved before the pragma is forgotten and only P is carried forward, effectively scoping proof context. Frama-C/ACSL uses function contracts (requires/ensures) and statement-level assertions with ghost code for verification-only state. JML similarly uses assert/assume/loop_invariant annotations with method-scoped contracts. The \"verify...exports\" block pattern in FX combines Dafny's scoped proof blocks with its module export visibility control into a single construct.",
"resolution": "Resolved (fx_design.md §10.3 expanded). Each item in the exports list must be a boolean proposition (type bool or prop); non-propositions are compile error R004. Facts established inside the verify block but not listed in exports are scoped to the block — they do NOT leak into surrounding context. Follows Dafny's 'assert P by { S }' scoping. Verify blocks are proof-isolation mechanisms; they are not general scoping for values (use begin...end for that).",
"docs_adjusted": true
},
{
"id": 30,
"name": "fail_effect_builtin",
"description": "Fail is used extensively in examples but never declared as an effect. Is it built-in? Where is fail(e) defined? Is it a keyword or library function?. Fail used everywhere but never declared as effect. Where does fail(e) come from? Built-in effect or library?",
"prior_art": "Koka models exceptions as the \"exn\" effect tracked statically in function types -- a function typed \"fun() : exn int\" visibly can throw, and the type system composes effects naturally (exn + io = io,exn). Algebraic effects theory views exceptions as the degenerate case where the handler never resumes the continuation. OCaml 5 has algebraic effect handlers that subsume exceptions, but they are dynamically typed (no effect annotations in function signatures) with one-shot continuations. The Eff language was designed with exceptions as a special case of its handle...with construct, deliberately mirroring OCaml's try...with syntax. Java's checked exceptions (the only mainstream static exception tracking) are universally considered a design failure due to viral signature pollution. The clean design is Koka's: Fail/exn as a built-in effect in the effect row, with raise as an effect operation and try/catch as a handler, all tracked in the type system without Java's verbosity problems.",
"resolution": "Resolved (fx_design.md §4.9 expanded). Fail is a built-in effect provided by the kernel — effectively 'effect Fail<E> { fn fail<A>(e: E) : A; }'. 'fail' is NOT a keyword; it is an operation name exported by the kernel-provided Fail effect (in prelude). Typing rule: fn fail<A: type, E: type>(e: E) : A with Fail(E) — return type universally quantified per operational never-returns semantics. Kernel translation: fail(e) desugars to perform Fail.fail(e); try expr desugars to perform Fail.propagate(expr); try...catch desugars to handle{body} with Fail{...}. The Fail effect and the fail operation live together; effect handlers (§9.6) are the general mechanism; fail/try/catch are the surface sugar. Follows Koka precedent. Propagation remains explicit (try prefix required) per the control-flow-effect discipline established in §4.9 rigor-first revision.",
"docs_adjusted": true
},
{
"id": 31,
"name": "effect_lift_syntax",
"description": "Design §5.12 mentions lift. Grammar has LIFT in keyword list. No grammar production for declaring effect lifts between user-defined effects",
"prior_art": "Koka uses implicit effect row subtyping -- a function with fewer effects can be called where more effects are expected, with no explicit lift syntax; the compiler inserts evidence-passing coercions automatically via constant-time handler lookup. Frank generalizes lift/inject into 'adaptors' -- arbitrary finite maps from ambient to local abilities that can mask, reorder, and duplicate effect instances, preventing 'effect pollution' where a handler accidentally captures an unrelated same-named effect. Multicore OCaml has no effect type system -- effects are dynamically dispatched to the nearest handler, so lifting is implicit but untyped (unhandled effects raise runtime exceptions). Eff (Biernacki et al. 2018) uses 'lift' for extending effect rows outward in Hindley-Milner inference. No language has explicit lift declarations between named effects as FX proposes; all use implicit subtyping or row extension.",
"resolution": "Resolved by Proposal 13: the 'lift' keyword is removed from the global list (92 → 91). It had no grammar production and no documented semantic. Effect subtyping is handled by the §9.3 lattice plus user-declared 'subsumes' edges inside 'effect ... end effect' blocks (§9.5). There is no runtime lift operation and no explicit coercion syntax — subsumption is implicit at every call site via lattice rules, and the programmer extends the lattice by declaring subsumes edges where needed.",
"docs_adjusted": true
},
{
"id": 32,
"name": "effect_row_extension",
"description": "Design §9.2 shows with eff, IO meaning 'whatever eff is, plus IO'. Grammar effect_row is just effect_term % ','. Row extension vs listing effects syntactically unclear",
"prior_art": "Koka (Leijen 2014) is the canonical implementation: effect rows are <label1,label2|e> where e is a polymorphic row tail variable, and extension is written by adding labels before the tail. Row polymorphism with duplicate labels enables open effect rows -- functions are automatically polymorphic over unmentioned effects. Links (Hillerström & Lindley) implements the first effect handlers using actual Rémy-style row polymorphism, where effect rows use the same row variable mechanism as record rows. PureScript has native row types for records with row polymorphism but uses them primarily for records rather than effects. Haskell extensible-effects libraries (freer-simple, polysemy, effectful) approximate row polymorphism through type-class encodings using open union types, which is fragile compared to native row types. The key design question for FX is whether the with eff, IO syntax is sugar for row extension (Koka-style <IO|eff>) or a separate mechanism.",
"resolution": "Resolved by Proposal 13 (§9.3 effect-row-as-union paragraph). The 'with e1, e2, eff' annotation denotes the set {e1, e2} ∪ eff. Order and duplication are immaterial; the list is syntactically flat for readability while semantically the effect row is a row-extension operator producing union. Effect variables in the list contribute their contents to the union at elaboration time. Gap #32 conflated list and row-extension semantics; they converge because the effect lattice (§9.3) uses set-union join. No new grammar production needed — the existing 'effect_row = effect_term % \",\"' rule IS the row-extension, semantically.",
"docs_adjusted": true
},
{
"id": 33,
"name": "machine_composition_operators",
"description": "Design §13.4 shows * (product), *sync (synchronized), >> (sequence), match (choice), *{while} (loop). None have grammar productions",
"prior_art": "ASTD (Algebraic State Transition Diagrams, Frappier et al.) is the closest prior art: it combines statecharts with CSP-like process algebra operators including sequence, closure (iteration), parallel synchronization (parameterized on a sync set), quantified choice, and quantified interleaving -- with graphical + JSON textual syntax compiled to C++ via the cASTD compiler, with proof obligations for invariant preservation. Statecharts (Harel 1987) have parallel (AND-state) and hierarchical composition but only graphically -- XState encodes parallel states as type:parallel in JavaScript objects. TLA+ composes specs via conjunction of next-state relations, not algebraic operators. The Gamma Statechart Composition Framework supports heterogeneous statechart composition with formal verification via UPPAAL mapping. No mainstream programming language has algebraic state machine composition operators as first-class syntax; FX proposed *, *sync, >>, match, *{while} operators would be genuinely novel as language-level constructs.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §10 extended). machine_decl now has two forms: traditional MACHINE upper_ident ... END MACHINE with members, OR MACHINE upper_ident = machine_compose_expr ';' for algebraic composition. machine_compose_expr: product '*', sync product '*sync(events)', sequence '>>', loop '*{while cond}', choice via match_expr, grouped subexpressions. machine_transformer_chain: '|>' applied pipeline of transformer_calls (intercept, guard, monitor, etc.) — e.g. OrderFlow |> guard(perm) |> intercept(logger). Ties in with §13.12 machine transformers. Covers the full composition surface from §13.4.",
"docs_adjusted": true
},
{
"id": 34,
"name": "machine_refinement_syntax",
"description": "Design §13.6 shows refinement RequestImpl refines RequestSpec via ... No grammar production",
"prior_art": "Event-B (Abrial 2010) is the gold standard for refinement: machines declare refines MachineAbstract with gluing invariants relating concrete to abstract variables, and the Rodin platform generates proof obligations (guard strengthening, simulation, variant decrease). TLA+ treats refinement as implication -- a refined spec implies the abstract spec under a refinement mapping of state variables, with stuttering invariance enabling stepwise refinement. CSP refinement is checked via FDR with trace, failures, and failures-divergences notions. ASM refinement uses simulation relations. UML-B adds state machine refinement on top of Event-B, allowing nested state machines to refine abstract states. The seL4 verification (Isabelle/HOL -> Haskell -> C) is the most famous spec-to-code refinement chain. FX refines keyword on machines aligns closest with Event-B but integrated into a programming language rather than a standalone specification tool.",
"resolution": "Resolved by Proposal 8 (fx_grammar.md §5.19). refinement_decl: REFINEMENT lower_ident REFINES upper_ident VIA expr ';' followed by zero or more property_clause ('property' lower_ident : expr ';') and END REFINEMENT ';'. Bisimulation (§13.19) via bisimulation_decl with relates/initial/step clauses. Covers both §13.6 machine refinement and §18.12 ISA refinement usages — same grammar, different contexts. 'refinement', 'refines', 'via', 'bisimulation' added to keyword list (global or contextual as appropriate).",
"docs_adjusted": true
},
{
"id": 35,
"name": "machine_event_sourcing",
"description": "Design §13.23 shows @[event_sourced] attribute on machines. Semantics of what the compiler generates not specified beyond prose",
"prior_art": "All existing event sourcing implementations are manual framework-level patterns, not compiler-generated. Akka Persistence requires manually defining Command/Event types, command handlers that emit events, and event handlers that update state. Marten (.NET) provides convention-based auto-projection where defining Apply(EventType) methods on aggregates lets Marten wire persistence, but projection logic is hand-written. The Wolverine/Marten Critter Stack reduces boilerplate by expressing CQRS handlers as pure functions returning events, but definitions remain manual. EventStoreDB projections are written in JavaScript server-side. Axon Framework uses @EventSourcingHandler annotations but requires manual handler implementation. Compiler-generated event sourcing from state machine definitions -- where the compiler derives event types, command handlers, and projections from machine transitions -- is genuinely novel with no existing precedent.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 36,
"name": "contract_format_binding_detail",
"description": "Design §14.4 shows format declarations with field mappings (id : 'id' as number). Grammar format_field (lower_ident : expr ;) is too simple for this",
"prior_art": "Protocol Buffers uses an IDL (.proto) with message/field definitions compiled by protoc into language-specific code; fields identified by numbers support schema evolution. FlatBuffers uses a similar IDL with zero-copy deserialization. Cap-n Proto uses a C-like IDL with zero-copy serialization and promise pipelining for RPC. ASN.1 is the oldest IDL standard (ITU-T), supporting multiple encoding rules (BER, DER, PER, XER, JSON) from a single schema with constraint checking in generated code. CDDL (RFC 8610) describes CBOR/JSON structures with Rust tooling that derives types from schemas. Apache Avro uses JSON schemas with reader/writer schema resolution for evolution. All are external IDL-to-code-generator pipelines; FX format declaration as a first-class language construct with field mappings integrating schema definition directly into the type system is novel.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 37,
"name": "task_group_syntax",
"description": "Design §11.7 shows task_group fn(group) => ... No grammar production. No task_group keyword",
"prior_art": "Kotlin coroutineScope is the most mature structured concurrency API: child coroutines must complete before scope exits, failures cancel siblings, and the hierarchy is enforced by the type system. Swift TaskGroup follows the same philosophy with automatic child cancellation. Java StructuredTaskScope (Loom, JDK 21+) brings structured concurrency with ShutdownOnFailure/ShutdownOnSuccess policies as opt-in. Python Trio nurseries pioneered the concept; asyncio.TaskGroup (3.11+) adopted similar semantics. Go rejected a language-level proposal (#29011), relying on errgroup + context + WaitGroup. FX task_group fn(group) => ... is closest to Kotlin coroutineScope but with explicit group parameter for spawning, combining the Trio/Swift model with effect-typed concurrency.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 38,
"name": "parallel_map_par",
"description": "Design §11.8 shows items.par() |> map(...). .par() is a method call, no special grammar needed. But semantics of what .par() returns and compiler handling not specified",
"prior_art": "Rust Rayon is the best-in-class parallel iterator library: .par_iter() converts sequential to parallel with compile-time safety via Send + Sync + Fn bounds -- ownership prevents data races without requiring purity. Haskell parMap/parList achieves parallel safety by construction since purity is inherent. Scala .par was deprecated because it had no mechanism to enforce side-effect-freedom, leading to non-deterministic data races. Java parallelStream() relies on documentation-only conventions that operations should be stateless -- the compiler provides no enforcement. Intel TBB provides parallel_for/reduce with manual safety responsibility. FX .par() requiring Tot effect is the theoretically cleanest solution: the type system enforces purity for parallel operations, catching at compile time what Scala/Java catch only at runtime. This combines Haskell safety guarantees with Rayon ergonomic API.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 39,
"name": "memory_ordering_annotations",
"description": "Design §11.10 shows with MemoryOrder(data: Release, flag: Release). Named args inside effect annotations need grammar support. effect_term may handle it but unclear",
"prior_art": "C++ std::memory_order provides six orderings (relaxed, consume, acquire, release, acq_rel, seq_cst) as enum arguments to atomic operations; default is seq_cst. Rust Ordering enum mirrors C++20 with five values (omitting consume); atomic fences can apply orderings to multiple variables. Java uses volatile for seq_cst on fields and VarHandle (Java 9+) for fine-grained access modes (getAcquire, setRelease, getOpaque) plus explicit fences. LLVM IR uses ordering annotations on atomic instructions. All existing approaches pass ordering as an argument to individual operations or as a field modifier -- none use named-parameter effect annotations like FX with MemoryOrder(data: Release, flag: Release) that associate orderings with specific variables in scope. Making memory ordering part of the effect system is novel.",
"resolution": "Resolved by Proposal 6: the MemoryOrder effect annotation is abandoned in favor of per-operation ordering arguments, matching C++ / Rust / Java VarHandle idioms. Each atomic method takes an @Ord argument: cell.load(@Acquire), cell.store(@Release), cell.fetch_add(@AcqRel, delta), cell.cas(@SeqCst, old, new). Default when omitted is @SeqCst. Valid orderings are constrained per operation (T053 on invalid pair). This is more local (ordering lives at the access, not the function signature), more composable (works inside pipelines and closures), and aligned with the operational semantics (orderings are per-access, not per-function). The MemoryOrder effect form is gone entirely.",
"docs_adjusted": true
},
{
"id": 40,
"name": "atomic_blocks",
"description": "Design §13.20 shows atomic ... end atomic; for multi-machine atomic chains. No grammar production",
"prior_art": "Haskell STM (GHC, since 6.4) is the gold standard for language-integrated atomic blocks: atomically :: STM a -> IO a executes transactions with automatic retry on conflict, composable via retry and orElse. The STM monad enforces that I/O cannot occur inside transactions. Implementation uses TL2-style global version clocks. Clojure STM uses dosync blocks with refs/alter/commute; MVCC provides snapshot isolation, and commute enables commutative operations avoiding write conflicts (10x faster than alter under contention). Both restrict transactions to memory operations only. Database ACID transactions are the conceptual ancestor. Research includes SwissTM and LSTM (eager conflict detection). FX atomic...end atomic for multi-machine atomic chains goes beyond single-memory STM to coordinate atomic transitions across multiple state machines, which has no direct precedent in any programming language.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 41,
"name": "inline_assembly",
"description": "Design §19.6 shows asm x86_64 ... end asm; with in/out/clobber registers. No grammar production",
"prior_art": "Rust's asm! macro (RFC 2873, stabilized 2022) uses format-string templates with typed operand constraints (in/out/inout/sym/const), register classes, and options (pure, nomem, nostack, att_syntax). GCC's extended asm uses colon-separated output:input:clobber sections with single-letter constraint codes. D offers two styles: DMD's Intel-syntax DSL with implicit operand binding, and GDC's GCC-style extended asm; LDC supports both. Zig uses GCC-style asm with named operand placeholders (%[name]) and has proposals for architecture-dialect-specific blocks (issue #10761) that would parse assembly as structured syntax rather than string templates. LLVM inline asm underlies both Rust and Zig, using the same constraint string format. FX's asm...end asm with explicit in/out/clobber sections is closest to Rust's asm! but as a block statement rather than a macro invocation.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 42,
"name": "lock_ordering_declaration",
"description": "Design §19.4 shows lock_order SpinIRQ < DeviceLock < SubsystemLock < GlobalLock. No grammar production",
"prior_art": "Linux lockdep is the most mature runtime lock-ordering validator: it tracks lock-class dependency graphs across ~8191 classes, detects cycles (potential deadlocks), and validates nesting rules (CONFIG_PROVE_RAW_LOCK_NESTING). ThreadSanitizer (Clang/GCC) builds a directed lock-acquisition graph at runtime and reports cycles as potential deadlocks (TSAN_OPTIONS=detect_deadlocks=1), though it has known false positives with asymmetric unlocking. Java's @GuardedBy annotation (javax.annotation.concurrent, Checker Framework, Google Error Prone) declares which lock protects a field, enabling static checking; however, research from UW showed the original JCIP specification is ambiguous and permits data races under some interpretations. Facebook's RacerD (Infer) infers locking disciplines without requiring annotations. No mainstream language provides a first-class lock_order declaration in the grammar; all existing approaches are either runtime validators (lockdep, TSan) or annotation-based static analysis (@GuardedBy). FX's lock_order as a grammar-level declaration with compiler-enforced ordering is novel.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 43,
"name": "address_space_types",
"description": "Design §19.7 shows phys_addr, virt_addr, bus_addr as distinct types. These are type aliases but the restriction against mixing needs type system support. Not grammar — type checker concern",
"prior_art": "OpenCL defines disjoint address space qualifiers (__global, __local, __constant, __private) as type qualifiers on pointers, enforced at compile time; implicit conversions between address spaces are restricted, and the generic address space was added in OpenCL 2.0. CUDA uses memory space specifiers (__device__, __shared__, __constant__, __managed__) that determine GPU memory allocation but are less integrated into the type system than OpenCL. CHERI capabilities encode bounds, permissions, and provenance directly into 128-bit pointers at the hardware level, making address-space safety an ISA property rather than a language feature; C/C++ compilers target CHERI by mapping language pointers to capabilities. C's restrict qualifier provides aliasing hints but not address-space separation. Rust's raw pointer types (*const T, *mut T) and NonNull<T> encode nullability but not address-space identity. FX's phys_addr/virt_addr/bus_addr as distinct types with type-system-enforced non-mixing combines CHERI-style provenance tracking with OpenCL-style address-space separation in a dependently-typed setting.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 44,
"name": "specialized_test_grammar",
"description": "Design §23 shows test_theory/test_metatheory with expect_compile_error/expect_accepted/matches, test_machine for exhaustive state exploration (§23.3), test_contract for boundary round-trip (§23.4), property-based tests (§23.2). Grammar has only generic test_decl — needs specialized productions for all test variants. Includes property keyword/production for property-based tests",
"prior_art": "Zig's test keyword creates first-class test blocks colocated with source code that are compiled only when explicitly requested via `zig test`; they support named doctests, std.testing assertions, and built-in memory leak detection via std.testing.allocator. Pyret provides check: blocks (standalone test suites with error isolation between blocks) and where: blocks (inline function-level tests that execute with the function declaration), using operators like is, raises, and satisfies. Rust uses the #[test] attribute on functions with a separate test harness; Go uses _test.go file convention with TestXxx functions; D has unittest blocks as a language construct similar to Zig. FX's test as a full declaration keyword with typed variants (test_theory, test_metatheory, test_machine, test_contract) and structured expectations (expect_compile_error, expect_accepted, matches) goes beyond all prior art by integrating proof-level testing, exhaustive state exploration, and contract boundary testing into the language grammar.",
"resolution": "Resolved by Proposal 14 (fx_design.md §23.2 rewritten). Attribute-based dispatch on generic test_decl — @[theory]/@[metatheory]/@[machine]/@[contract] select variants without new keywords or grammar productions. Property-based tests use @[property] fn ordinary boolean-returning functions — no 'property' keyword, no 'property_decl' production; the test runner dispatches on the attribute and generates inputs. Collapses test/property/test_theory/test_metatheory/test_machine/test_contract down to the two existing grammar forms (test_decl and fn_decl with attributes), matching the no-keyword-proliferation pattern from Proposals 4 / 12.",
"docs_adjusted": true
},
{
"id": 45,
"name": "benchmark_comparison",
"description": "Design §23.5 shows @[compare] and case 'name' = expr; inside benchmarks. Grammar bench_decl is BENCH lower_ident stmt* END BENCH ; — no case support",
"prior_art": "Rust's criterion.rs (and its fork gauge) provides a Haskell-inspired DSL with bench/bgroup/nf/whnf combinators, statistical analysis, and HTML/CSV/JSON output, but is a library not a language construct. Go's testing.B (with the new B.Loop in Go 1.24) is built into the standard library with benchstat for statistical comparison between runs. Haskell's criterion/gauge libraries use defaultMain/bench/bgroup with nf/whnf evaluation control and automatic statistical reporting. JMH (Java Microbenchmark Harness) uses @Benchmark annotations with @BenchmarkMode, @Warmup, and @Measurement parameters. Catch2 (C++) uses BENCHMARK macros inline with test cases. Google Benchmark (C++) uses a state.KeepRunning() loop pattern with benchmark registration macros. No language provides benchmark comparison (A/B case syntax) as a grammar-level construct; all existing solutions are either libraries (criterion, JMH, Google Benchmark) or lightweight built-ins (Go testing.B, Zig test). FX's bench...end bench with @[compare] and named case syntax is novel.",
"resolution": "Resolved by Proposal 14 (fx_grammar.md §12 bench_decl). bench_member now covers stmt or 'CASE string_lit = expr ;' — contextual 'case' keyword admitted in bench-block body. @[compare] attribute on the enclosing bench triggers side-by-side reporting of the named cases. 'case' was already reserved in the contextual-keyword-for-test-blocks list; the grammar now uses it.",
"docs_adjusted": true
},
{
"id": 46,
"name": "error_code_taxonomy",
"description": "Design §10.10 references structured error codes (T0xx, R0xx, E0xx, M0xx, S0xx, I0xx, P0xx, N0xx, W0xx). Format defined but full catalog not specified",
"prior_art": "Rust maintains a comprehensive EXXXX error index (E0001-E0782+) with --explain support and per-error markdown documentation files in the compiler source. TypeScript uses TSXXXX numeric codes (~2000 diagnostics) stored in a central JSON registry; TS2322 (type assignment) is the most common. C# uses CSXXXX codes with similar structure. GCC and Clang take a fundamentally different approach with named hierarchical warning flags (-Wall -> -Wunused -> -Wunused-variable) rather than numeric codes, allowing granular control via -Werror=<group>. MSVC uses C4XXX numeric codes. Java's javac explicitly rejected numeric error codes and structured output as non-goals. The trend is converging toward richer structured diagnostics regardless of numbering scheme. FX's two-letter prefix taxonomy (T0xx type, R0xx resource, E0xx effect, M0xx modality, S0xx session, I0xx lifetime, P0xx proof, N0xx numeric, W0xx warning) provides finer semantic categorization than any existing system by encoding the error's domain in the prefix.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 47,
"name": "fxc_annotate_output",
"description": "Design §22.8 shows fxc annotate output. Format of suggestions (line numbers, before/after diffs) not formally specified",
"prior_art": "Rust's clippy --fix and cargo fix apply automatic lint fixes and edition migrations; rust-analyzer provides Quick Fix code actions for auto-import and diagnostic resolution via LSP. Python's ecosystem has MonkeyType (Instagram, runtime type collection), pytype (Google, static inference with merge-pyi for applying stubs), pyre infer (Facebook, educated type guesses), autotyping (simple return type inference), and mypy-baseline (filters existing errors to report only new ones). TypeScript migration to strict mode is manual but tooling like ts-migrate (Airbnb) automates adding @ts-expect-error comments. Pyright (Microsoft) infers types for unannotated code in IDE context. The general pattern is: collect types via runtime/static analysis, generate annotations, then incrementally tighten strictness. FX's fxc annotate as a compiler subcommand that produces structured before/after diffs with line numbers is a more integrated approach than the separate-tool model used by all existing ecosystems.",
"resolution": "Resolved by Proposal 14 (fx_design.md §22.8 expanded). 'fxc annotate' emits two formats: text (category-grouped listing, human-readable) and JSON (via '--format=json' or via the 'POST /annotate' agent endpoint). JSON schema: per-suggestion {line, col, kind, before, after, applicability, rationale?}. 'applicability' follows rustfix's graded scale: 'machine_applicable' (safe to auto-apply), 'has_placeholders' (requires review), 'maybe_incorrect' (suggestion only). Agents filter and apply selectively via POST /edit.",
"docs_adjusted": true
},
{
"id": 48,
"name": "proof_state_display_format",
"description": "#show and #plan pragmas produce structured output (§10.7). Output format not formally specified",
"prior_art": "Lean 4's InfoView is the most sophisticated: a React-based panel in VS Code showing tactic state (hypotheses + goal after turnstile), diff highlighting (green/red for added/removed subexpressions between tactic steps), and extensible user widgets via ProofWidgets. Coq has ProofGeneral's goals window (Emacs), ProofTree/Traf for tree visualization, PeaCoq for side-by-side tactic previews, and Alectryon/CoqPSV for rendered documentation of proof states. Agda's Emacs mode uses the *All Goals* buffer showing holes with their types and contexts, updated via explicit commands (C-c C-l, C-c C-,) rather than continuously. Isabelle/jEdit's Output panel provides continuous cursor-position-driven proof state display. Paperproof (Lean 4 VSCode extension) renders tactic proofs as visual trees. FX's #show and #plan pragmas producing structured output need to specify the format; Lean 4's approach of cursor-driven continuous display with diff highlighting is the current gold standard.",
"resolution": "Resolved by Proposal 14 (fx_design.md §10.7 expanded). Single canonical schema: 'type ProofState = { known: list(string); goal: string; suggestion: option(string) };'. #show prints the structure as text; #plan emits a proof skeleton with sorry leaves; GET /proof-state (§24.7) returns the same data as JSON. One schema, three consumers — agents use REST, humans read inline text, tooling parses JSON.",
"docs_adjusted": true
},
{
"id": 49,
"name": "dot_shorthand_grammar",
"description": ".field as bare expression in function arg position — design §4.2 and rule 25. Grammar has NO production for it. Needs atomic_expr alternative. Also .field op expr and multiple dots same element not formalized",
"prior_art": "Scala's _.field creates an anonymous function where each underscore is a separate parameter (_.x + _.y means (a,b) => a.x + b.y), not multiple accesses on the same element. Kotlin's it is an implicit single-parameter name in lambdas (users.map { it.name }), requiring explicit it. prefix. Swift's key path expressions (\\.name since Swift 5.2) can be passed directly to map/filter as accessor functions; the Point-Free team extended this to Case Paths for enums. Elm automatically generates .field accessor functions for record fields that can be used point-free (List.map .name users). Elixir's capture operator & with &1 creates shorthand lambdas (&(&1.name)), with each &N being a positional parameter. F# has a proposed _.Property syntax (inspired by Elm). FX's .field shorthand where multiple dots in the same expression all refer to the same element (e.g., .x + .y means z => z.x + z.y) is genuinely novel; no existing language shares a single implicit parameter across multiple dot accesses in an expression.",
"resolution": "Resolved by mechanical grammar fix (fx_grammar.md §6.4): added '. lower_ident' as an atomic_expr alternative. The postfix rule 'postfix_expr . lower_ident' chains further (so '.nested.field' parses as the dot-shorthand atomic '.nested' followed by postfix '.field'). Operator combinations '.active and .age >= 18' work through the standard expression grammar (each bare dot is an atomic_expr). The semantic restriction (valid only in function-argument position, all bare dots share the same implicit parameter 'it') is enforced at elaboration time, not in the grammar — the parser accepts '.field' anywhere and the elaborator rewrites the enclosing call argument into 'fn(it) => <expr with all bare dots replaced by it.>'.",
"docs_adjusted": true
},
{
"id": 50,
"name": "default_parameter_values",
"description": "fn connect(host: string, port: nat = 443). Grammar fn_param has no = expr default. Evaluation rules (call site vs definition site) not specified",
"prior_art": "Python evaluates defaults once at definition time (the famous mutable default argument trap), making it the outlier. C++, Swift, and Kotlin all evaluate defaults at each call site, but differ in scope: C++ performs name lookup in callee scope but evaluation at caller scope and forbids referencing other parameters; Swift evaluates in caller context restricted to global scope (SE-0411 specifies argument evaluation order) and also forbids referencing earlier parameters; Kotlin evaluates in callee context with access to this and earlier parameters, making it the most expressive. Scala supports named arguments with defaults and allows referencing earlier parameters. Ruby and C# evaluate at call site. The key design decision for FX is call-site vs definition-site evaluation (call-site is the modern consensus) and whether defaults can reference earlier parameters (Kotlin says yes, Swift/C++ say no). FX should follow Kotlin's model: call-site evaluation with access to earlier parameters.",
"resolution": "Resolved (fx_grammar.md §5.6 fn_param + fx_design.md §4.1). Grammar: 'mode? lower_ident : type (= expr)?'. Semantics: Kotlin model — call-site evaluation, later params may reference earlier params (compile error R002 if a default references a later parameter). Named-argument syntax composes naturally; evaluation order is left-to-right by declaration order regardless of argument ordering at call site. The Python definition-site pitfall (mutable default shared across calls) doesn't arise in FX because defaults are re-evaluated per call.",
"docs_adjusted": true
},
{
"id": 51,
"name": "if_let_pattern",
"description": "if let Some(v) = expr; ... end if — not in grammar or design. Would be IF LET pattern = expr ; body ELSE body END IF",
"prior_art": "Swift pioneered if-let for optional unwrapping (Swift 1.0, 2014); Rust adopted and generalized it via RFC 0160 to work with any refutable pattern, not just optionals. Rust later extended with if-let chains (RFC 2497) allowing `if let Some(x) = e && x > 0`. Kotlin takes a different path via smart casts and `when` expressions with experimental when-guards (KEEP-371). C# 7.0+ added pattern matching in `if` via `is` patterns (`if (obj is string s)`). Python 3.10 added structural pattern matching with `match`/`case` but deliberately omitted if-let; proposals exist but are controversial. The key design tension is whether if-let is sugar for a single-arm match (Rust) or a special optional-unwrapping form (Swift).",
"resolution": "Resolved (fx_grammar.md §6.5 if_expr extended with if_head: 'expr | LET pattern = expr' + fx_design.md §4.4). If-let works with any refutable pattern, not just option/result — follows Rust's generalization. else-if-let chains supported. Kernel translation: single-arm match with wildcard fall-through to else; same typing rules as §4.3 match. No new typing rule needed.",
"docs_adjusted": true
},
{
"id": 52,
"name": "let_else",
"description": "let Some(v) = expr else fail(NotFound); — not in grammar. Consistent with Fail effect (not early return). Needs grammar: LET pattern = expr ELSE stmt",
"prior_art": "Swift's `guard let x = expr else { return }` (Swift 2.0, 2015) is the original: it mandates divergence in the else branch and binds into the enclosing scope, not a nested one. Rust adopted a similar construct via RFC 3137 as `let Some(x) = expr else { return; }`, stabilized in Rust 1.65 (2022), explicitly citing Swift's guard-let as prior art. The divergence requirement is enforced at the type level (else block must be `!` / `Never`). Kotlin uses the Elvis operator idiom `val x = expr ?: return` for the same pattern but without language-level enforcement. OCaml and Haskell lack this entirely, relying on pattern matching in let (which is irrefutable) or monadic do-notation. The construct is particularly valuable for reducing rightward drift from nested if-let/match.",
"resolution": "Resolved (fx_grammar.md §6.1 stmt + fx_design.md §4.6). Grammar: 'LET pattern (: type)? = expr ELSE stmt ;'. Semantics: else stmt must have type 'never' (diverge via fail, return, break, continue, or a call returning never). Falling through the else is compile error T055. Binds into the enclosing scope (not a nested one) — matches Rust/Swift. Kernel translation: two-arm match where the wildcard arm is the diverging stmt. Composes with Fail effect: 'let Some(v) = opt else fail(NotFound);' adds Fail to the enclosing function's row per §4.9.",
"docs_adjusted": true
},
{
"id": 53,
"name": "for_loop_pattern_destructuring",
"description": "for (k, v) in map.entries(); — grammar has FOR lower_ident IN expr (single identifier only). Needs FOR pattern IN expr",
"prior_art": "Rust allows full irrefutable pattern destructuring in for loops (`for (k, v) in map.iter()`) as a natural consequence of patterns being allowed wherever bindings occur (RFC 0160, generalized in 2015 edition). Python has supported tuple unpacking in for loops since its earliest versions (`for k, v in dict.items()`), extended to arbitrary iterables. Kotlin supports destructuring via `componentN()` conventions (`for ((k, v) in map)`), requiring the iterated type to declare component functions. Swift uses tuple patterns (`for (k, v) in dict`). JavaScript/TypeScript support it via `for (const [k, v] of map)` using array destructuring. C++17 added structured bindings (`for (auto [k, v] : map)`) after years of proposals. The consensus across modern languages is that for-loop variables should accept the same pattern syntax as let bindings.",
"resolution": "Resolved (fx_grammar.md §6.5 for_expr: widened 'FOR lower_ident IN expr' to 'FOR pattern IN expr' + fx_design.md §4.5). Only irrefutable patterns accepted (tuple destructuring, record destructuring, single-constructor variants). Refutable patterns are a compile error — use match inside the body or upstream .filter. Kernel translation: 'for pattern in e; body end for' desugars to 'for __it in e; let pattern = __it; body end for' and then to recursion per §6.9 kernel translation.",
"docs_adjusted": true
},
{
"id": 54,
"name": "debug_intrinsic",
"description": "debug(value) that works on any type, produces structured output. Library function or compiler built-in? Not specified",
"prior_art": "Rust's `dbg!` macro (RFC 2361, stable 2019) is the gold standard for expression-level debug intrinsics: it prints file:line, the stringified expression, and its value to stderr, then returns the value so it can be inserted into any expression without restructuring code. Inspired by Haskell's `traceShowId`. Python added `breakpoint()` as a built-in (PEP 553, Python 3.7) with configurable backend via `PYTHONBREAKPOINT` env var, addressing the awkward `import pdb; pdb.set_trace()` idiom. JavaScript has the `debugger` statement (a language keyword since ES1) that pauses execution in attached dev tools. Zig provides `@breakpoint()` as a compiler builtin that emits a hardware trap instruction, working even in release builds. The design space splits into two categories: printf-style inspection (Rust dbg!, Haskell trace) vs. debugger-entry (Python breakpoint, JS debugger, Zig @breakpoint). For a dependently-typed language, the key question is whether debug output is an effect (it should be, for purity) and whether the intrinsic can show proof-irrelevant terms.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 55,
"name": "doctest_in_doc_comments",
"description": "Examples in /// doc comments that are compiled and run as tests. Not specified anywhere",
"prior_art": "Rust doctests are the most comprehensive implementation: code blocks in `///` doc comments are extracted and compiled as standalone test programs by `cargo test`, with automatic `fn main()` wrapping and `?`-based error handling. Python's `doctest` module (stdlib since 2.1) matches `>>>` REPL prompts in docstrings against expected output. Elixir's `ExUnit.DocTest` uses `iex>` prompts in `@doc` attributes, tightly integrated with the test runner. Haskell has the `doctest` package matching `>>>` prompts in Haddock comments, with `cabal-docspec` as a newer alternative. Go uses specially-named `Example` functions in `_test.go` files with `// Output:` comments, serving as both tests and documentation rendered by `godoc`. The core design tension is testing granularity: Rust and Go test whole programs/functions, while Python and Elixir test individual expressions. For FX, doctests should be typed and verified (not just value-compared), which is a novel opportunity no existing language exploits.",
"resolution": "Resolved by Proposal 14 (fx_design.md §23.7 new section). Rust-style: ```fx fenced blocks inside /// doc comments are extracted, compiled, and run as tests named '_doctest_<hash>'. Invoked via 'fxc test --doc'. Fence modifiers: ```fx (compile+run), ```fx,ignore (compile only), ```fx,should_fail (expect compile error). Hidden preamble via '// hidden: ' line prefix (included in test, not in rendered docs). Doctests inherit module imports and profile; failing doctests fail release builds.",
"docs_adjusted": true
},
{
"id": 56,
"name": "vmap_tensor_lifting",
"description": "vmap(f) lifts scalar fn to batched. Type-level batch dimension lifting. Discussed but not written into spec",
"prior_art": "JAX's `jax.vmap` (2018+) is the reference implementation: it traces a function operating on individual elements and automatically inserts batch dimensions, composable with `jit`, `grad`, and `pmap`. PyTorch's `torch.func.vmap` (formerly functorch, merged into PyTorch 2.0) uses an internal `BatchedTensor` type that overrides operators to produce batched behavior. Dex (Google Research) takes a type-theoretic approach: array dimensions are part of the type via dependent types, and `for` loops over index sets are the parallelism primitive, making vectorization a type-level operation rather than a runtime transform. Futhark uses size types (`[n]f32`) with second-order array combinators (`map`, `reduce`, `scan`) compiled to GPU kernels. NumPy broadcasting rules (implicit dimension lifting) are the de facto standard but are untyped and error-prone. Einstein summation notation (`einsum`) from NumPy/JAX/PyTorch provides dimension-aware contraction. For FX, the opportunity is to make vmap a type-level operation where batch dimensions are tracked in the type system (like Dex but with graded modalities for parallelism guarantees).",
"resolution": "",
"docs_adjusted": false
},
{
"id": 57,
"name": "query_syntax",
"description": "Multi-source data query syntax (from u in users join p in posts on ... where ... select ...). Or just use comprehensions. Design decision needed",
"prior_art": "C# LINQ (2007) is the canonical language-integrated query: `from x in xs where p(x) select f(x)` desugars to `SelectMany`/`Where`/`Select` method chains, with the compiler providing syntax and type checking. F# uses computation expressions (`query { for x in xs do where (p x); select (f x) }`) which generalize LINQ to arbitrary monads. Scala uses for-comprehensions (`for { x <- xs; if p(x) } yield f(x)`) which desugar to `flatMap`/`filter`/`map`, applicable to any type with those methods (not just databases). Haskell's `esqueleto` and `persistent` libraries embed SQL in do-notation using a `SqlQuery` monad, providing type-safe queries without special syntax. Kotlin's Exposed uses a DSL approach (`Users.select { Users.age greater 18 }`) without language-level syntax. The key insight from LINQ's success is that query syntax is just monadic comprehension with sugar, and languages with powerful enough comprehension syntax (Haskell, Scala) don't need dedicated query keywords. For FX, comprehension syntax over indexed monads would subsume LINQ-style queries.",
"resolution": "Resolved by design decision (fx_design.md §4.8): FX has no dedicated database-query syntax. Multi-source queries are expressed as pipe chains over the Query(T) source type (§11.12) — map, filter, group_by, sort_by, join as ordinary stdlib functions. The Query(T) source is lazy (builds a plan; execute() runs it); the optimizer may rewrite the plan before execution (predicate pushdown, projection pushdown, join reordering per §11.12). Comprehensions (§4.8) cover the single-source case. Follows Scala/Haskell precedent (powerful enough comprehensions + pipe chains subsume LINQ without language-level ceremony). No new grammar required.",
"docs_adjusted": true
},
{
"id": 58,
"name": "existential_vs_dyn",
"description": "Design has both dyn Closeable and exists T. { ... }. dyn not a keyword. Are existentials the only dynamic dispatch? Does dyn need to be a keyword? Relationship between Any and dyn unclear",
"prior_art": "Rust's `dyn Trait` is an existential type implemented via fat pointers (data + vtable), with dyn-compatibility restrictions (no `Self` in return position, no generics in methods). Haskell supports existential types via `ExistentialQuantification` extension (`data AnyShow = forall a. Show a => MkAnyShow a`) or GADTs, carrying typeclass dictionaries as proof witnesses. OCaml uses first-class modules as existential types: packing a module into a signature erases the concrete types, and the module's functions serve as the interface. Scala 3 has both opaque types (for zero-cost abstraction) and match types/path-dependent types from DOT calculus for existential reasoning. The fundamental connection is that `dyn Trait` = `exists T. (T, Trait<T>::vtable)`, making existential types and dynamic dispatch two views of the same concept. For FX with graded dependent types, existentials can carry mode/grade witnesses alongside the vtable, enabling dispatch that is aware of ownership, linearity, and effects.",
"resolution": "Resolved by Proposal 12 (§16.3 Level 3 removed, §16.5 rewritten). FX has exactly one surface syntax for runtime polymorphism: explicit existential records built on the kernel Sigma type (Appendix H.3). 'dyn Trait' is removed from the language entirely — no keyword, no grammar production, no Level 3 vtable lookup rule. Rationale: 'dyn Trait' hides the vtable layout behind a trait declaration and requires dyn-compatibility rules (no Self in return position, no generic methods). The explicit existential 'exists (T: type), { val: T; method: fn(ref T, ...) -> ...; }' makes the vtable visible at the type level — the record's fields ARE the vtable. Zero compiler-generated metadata; works uniformly with Self-returning methods, generics, and linear types. Named existentials at stdlib level (e.g., 'type Closeable = exists (T: type), { val: own T; close: fn(own T) -> unit with IO };') preserve ergonomics for common cases. The relationship to Any is now trivial because Any is also gone (gap #16): three mechanisms collapsed to one. Method resolution lattice simplifies to Levels 0-2 (impl > instance > class default) — fully static, decidable.",
"docs_adjusted": true
},
{
"id": 59,
"name": "type_inference_algorithm",
"description": "Not specified anywhere. Bidirectional? Hindley-Milner extended? Interaction with dependent types, effects, modes? When are annotations required vs inferred?",
"prior_art": "Hindley-Milner (Damas-Milner 1982) provides complete inference for rank-1 polymorphism with let-generalization, used by ML, OCaml, and Haskell 98. GHC's OutsideIn(X) (Vytiniotis et al., 2011) extends HM with type class constraints, GADTs, and type families using a constraint-based approach that gathers constraints outside-in and solves them. Bidirectional type checking (Pierce & Turner 2000, surveyed by Dunfield & Krishnaswami 2021) combines synthesis and checking modes, scaling to dependent types where full inference is undecidable. Idris uses bidirectional elaboration from a high-level surface language to a fully explicit core (TT), with tactic-based unification for implicit arguments. Agda uses the same bidirectional approach. Scala 3 uses local type inference (Odersky et al.) extended for DOT calculus path-dependent types. F* uses bidirectional checking with SMT-backed constraint solving for refinement types. For FX with 19 dimensions (modes, grades, effects, ownership), the only viable approach is bidirectional checking with elaboration to a fully explicit core, plus SMT-assisted constraint solving for refinement predicates and grade arithmetic.",
"resolution": "Partially resolved by Proposal 7 (§31.2-3): the 'fully explicit core' is the kernel calculus of §31. Surface FX elaborates into kernel terms via bidirectional elaboration (Pierce-Turner 2000, extended to graded modalities per Wood-Atkey 2022). Checking mode: the expected type is known and the term is checked against it. Synthesis mode: the term's type is derived from its structure (§4.6 let-binding rules). SMT oracle discharges refinement obligations with UNSAT cores audited in audit.smtq. Full operational details of the elaborator still pending — the kernel is specified, the elaboration algorithm from surface to kernel is referenced but not fully enumerated. Remaining work: specify the exact elaboration algorithm with its unification strategy and SMT hand-off points.",
"docs_adjusted": true
},
{
"id": 60,
"name": "subtyping_rules",
"description": "Refinement subtyping (nat { x > 0 } <: nat), effect subtyping (Tot <: IO), lifetime subtyping. Complete rules never given",
"prior_art": "System F-sub (Cardelli & Wegner 1985, Pierce 1991) introduced bounded quantification (`forall X <: T. ...`) with undecidable subtyping in the full system (F-sub-omega). The DOT calculus (Amin et al., 2016) formalizes Scala's path-dependent types with type members having bounds, where subtyping is proved sound via logical relations (proven mechanically in 2025 extensions). TypeScript uses structural subtyping with union/intersection types, deliberately sacrificing soundness for usability. Java/Kotlin use nominal subtyping with use-site variance (wildcards). Liquid Haskell and F* implement refinement subtyping: `{v:Int | v > 0} <: {v:Int | v >= 0}` via SMT validity checking of the logical implication. Effect subtyping in Koka uses row polymorphism (avoiding subtyping undecidability), while Frank and the Explicit Effect Subtyping calculus (Saleh et al.) use coercion-based effect subtyping with coherence proofs. The 2025 paper 'Recursive Subtyping for All' resolves the long-standing challenge of combining bounded quantification with recursive types via nominal unfolding rules. For FX, subtyping operates on multiple dimensions simultaneously: refinement predicates (SMT), effect rows (lattice), lifetimes (outlives), ownership modes (linear <: unrestricted), and grades (0 <: n <: omega).",
"resolution": "Partially resolved by Proposal 7 (§31.3, Appendix H.9): the kernel states subtyping as one judgment 'Γ ⊢ T <: T'' unified across all dimensions, discharged by the T-sub axiom. Composes: (a) universe cumulativity (U-cumul, type<u> <: type<v> when u<=v), (b) grade subsumption (Grade-subsumption, for every Tier S dimension with a preorder), (c) lattice join/meet for Tier L dimensions (Grade-lattice, validity check rules out incompatible joins), (d) refinement weakening via SMT oracle implication, (e) effect row inclusion via lattice structure. Each dimension's preorder is part of its semiring/lattice instance (§31.5). Full decidability of the combined subtyping judgment depends on SMT oracle for refinement predicates; the kernel itself is sound regardless.",
"docs_adjusted": true
},
{
"id": 61,
"name": "implicit_argument_resolution",
"description": "Grammar has #expr for implicits. But HOW does compiler find implicit arguments? Unification? Type class search? Both? Not specified",
"prior_art": "Scala 3 replaced the overloaded 'implicit' keyword with given/using, separating declaration from resolution and adding context functions as first-class citizens. Haskell type classes enforce global coherence (one instance per type) with a dedicated instance search, while Agda instance arguments use double-braces {{}} with standard records — no separate class concept — and allow local instances. Coq offers two mechanisms: type classes (backtracking proof search, can loop) and canonical structures (unification-triggered, more predictable), often used together in Mathlib-scale projects. Idris auto implicits rely on unification heuristics that are less predictable than Agda/Coq but integrate naturally with quantitative type theory in Idris 2.",
"resolution": "Resolved by Proposal 11 (§4.1 expanded). FX implicit argument resolution is unification-only. The compiler fills each '#param' from the unification of explicit arguments' types against the parameter types. No type-class search, no canonical structures, no implicit conversions. When unification cannot uniquely determine an implicit it is compile error T047 ('ambiguous implicit type argument') with a concrete suggestion to pass the argument explicitly. Trait instance selection uses the §16.3 specificity lattice (inherent > instance > class-default), which is decidable and never backtracks. Implicits exist for eliding redundant type arguments only, never for value-level lookup. Matches Rust's approach of explicit type inference without Haskell's typeclass-search or Scala's implicit-value machinery.",
"docs_adjusted": true
},
{
"id": 62,
"name": "as_cast_expression",
"description": "Design §3.9 shows n as Any. Grammar has AS in as_pattern but not as expression-level cast. Needs expr AS type production",
"prior_art": "Rust separates unsafe primitive casting ('as' for numeric truncation/extension) from safe trait-based conversion (From/Into for infallible, TryFrom/TryInto for fallible), making cost and fallibility explicit in the type. Swift provides three operators with escalating safety: 'as' (guaranteed upcast), 'as?' (returns Optional on failure), 'as!' (forced downcast, crashes on failure). Kotlin uses 'as' (throws ClassCastException) and 'as?' (returns null), complemented by smart casts that auto-narrow types after 'is' checks. C++ offers static_cast/dynamic_cast/reinterpret_cast/const_cast, each with distinct safety guarantees. TypeScript's 'as' is purely a compile-time type assertion with no runtime effect.",
"resolution": "Resolved by Proposal 4 (§2.7 rule 28): FX has no expression-level 'as T' cast and no type-ascription-in-expression '(expr : T)'. Every value conversion is a named function or constructor so cost is visible at the call site: widen<T>(x) / narrow<T>(x) for numeric; direct existential record construction (§16.5) for runtime polymorphism; R(bits) for layout view; etc. 'expr as T' in expression position is compile error T052 with suggestion depending on source and target types. 'as' keyword retains two meanings: pattern-binding 'pat as name' in match arms, and import aliasing 'open Lib as L'. No grammar extension needed — closed by deliberate design choice.",
"docs_adjusted": true
},
{
"id": 63,
"name": "named_tuple_elements",
"description": "Can tuples have named fields? (left: list(a), right: list(a)) vs anonymous (list(a), list(a)). Design doesn't clarify",
"prior_art": "C# ValueTuples (C# 7+) provide named fields as compile-time sugar over positional Item1/Item2 access — names are erased at runtime and ignored for equality comparison. Python's NamedTuple creates actual nominal types with runtime introspection, immutability by default, and both positional and named access. Swift labeled tuples are structural (not nominal), support mixed named/unnamed elements, but the core team considers them a design regret due to complexity around casting and protocol conformance. TypeScript 4.0 added labeled tuple elements ([x: number, y: number]) that improve IDE tooling and rest-parameter names but have no effect on type compatibility. Rust uses the newtype pattern (single-field structs) instead of named tuples, trading convenience for nominal type safety.",
"resolution": "Resolved by design decision: FX tuples are positional-only. Named fields require a record type (§3.4). Rationale: tuples are for anonymous positional pairs with O(1) indexing (pair.0, pair.1); records are for named structured data with nominal identity. Swift's labeled tuples are considered a design regret by their own core team. C#'s erased names are a footgun (equality ignores them). Keeping a clean boundary — tuples positional, records nominal with names — follows Rust's resolution without named tuples' complexity. The one-line upgrade from a 2-element tuple to a named record is straightforward (add 'type pair { left: T1; right: T2 };').",
"docs_adjusted": true
},
{
"id": 64,
"name": "derive_list",
"description": "@[derive(...)] — what's derivable? What does each derivation generate? Not specified",
"prior_art": "Haskell has the most mature deriving system with multiple strategies: stock (compiler-generated for Eq/Ord/Show/etc.), newtype (coerce from wrapped type), anyclass (empty instances), via (derive through a representationally equal type), plus GHC Generics for shape-based derivation. Rust's #[derive] invokes procedural macros that receive TokenStreams and produce code via the syn/quote crates, requiring derive macros to live in separate crates; the ecosystem is massive (serde, clap, etc.). Scala 3 provides a derives clause backed by compiler-generated Mirror instances that expose type-level structure, intentionally low-level so libraries like Shapeless 3 and Kittens build ergonomic derivation on top. Kotlin data classes auto-generate equals/hashCode/toString/copy/componentN but are not extensible to user-defined derivations. Swift's Codable protocol uses compiler synthesis for Encodable/Decodable conformance, limited to specific protocols.",
"resolution": "Resolved by Proposal 12 part 2 (fx_design.md §16.10 Derivable Traits new section). Closed stdlib-fixed catalog of five traits: Eq (structural equality), Ord (total order with derived compare / eq / neq / lt / le / gt / ge), Show (to_string), Hash (for hash maps and sets), Default (default value from each field's Default). Each derive generates the canonical unambiguous implementation from the type's shape; each requires the same trait on every field recursively. Parameterized types require explicit 'where' bounds per rigor-first (T065 on omission) — FX does not auto-add bounds. Catalog is closed: user packages cannot add new derivable traits (T066 on @[derive(UnknownTrait)]); custom code generation uses comptime explicitly at call site rather than hidden behind a derive attribute. Excluded from catalog because dedicated mechanisms already exist: Copy (@[copy] attribute per §5.4), Send/Sync (not concepts in FX — ownership + Alloc do the work), Arbitrary (@[arbitrary] per §23.2), Serialize/Deserialize (contracts per §14).",
"docs_adjusted": true
},
{
"id": 65,
"name": "algebraic_structure_grammar",
"description": "Design §16.6 shows structure CommMonoid(T) ... law ... end structure;. Grammar has law as contextual keyword in class but no structure block production",
"prior_art": "Lean 4's class/structure system (used extensively in Mathlib's 600+ classes, 8000+ instances) handles algebraic hierarchies via multi-parameter classes with proof-carrying fields, using the 'old structure command' pattern to solve diamond inheritance. Isabelle provides both locales (multi-parameter, with automatic notation via sublocale renaming) and type classes (single-parameter, integrated with the simplifier), where locales handle the hierarchies that type classes cannot express. Haskell's algebraic-structures ecosystem builds on type classes but lacks proof fields and struggles with the diamond problem (no dependent types). Coq's packed classes methodology (used in MathComp for the Odd Order Theorem proof) combines canonical structures with coercions for predictable inference in deep hierarchies, though maintaining large hierarchies is challenging.",
"resolution": "Resolved by Proposal 12 part 2 (fx_design.md §16.6 rewritten). Algebraic structures no longer use 'structure CommMonoid(T) ... end structure' form; they are ordinary '@[structure] class CommMonoid<T: type> ... end class' declarations reusing §16.4 type-class grammar in full. The @[structure] attribute signals to the compiler that 'law' clauses in the class body are optimization-relevant (SMT-verified against each instance, exploited for parallel reduction / factoring / short-circuit / deduplication). No separate 'structure' keyword needed — §2.3 already committed to @[structure] class; §16.6 now matches. P006 error code for instance that fails a declared law.",
"docs_adjusted": true
},
{
"id": 66,
"name": "evaluation_semantics",
"description": "Left-to-right stated but no operational semantics. Reduction strategy? Strict everywhere? comptime? Ghost? No formal small-step or big-step semantics",
"prior_art": "Haskell is lazy (call-by-need) by default, using thunks and graph reduction — non-strict semantics allows processing infinite data but risks space leaks from unevaluated thunks; the compiler uses strictness analysis to optimize. OCaml is strict (call-by-value) with explicit laziness via 'lazy'/'Lazy.force', giving predictable performance and memory behavior. Idris 2 is strict by default with an explicit Lazy type, combined with totality checking — in a total language, lazy and strict evaluation are semantically equivalent since all computations terminate. Scala offers call-by-name parameters (lazy evaluation per call site) alongside strict evaluation. The CBPV (Call-by-Push-Value) framework by Levy provides a unified formal semantics that subsumes both CBV and CBN as embeddings.",
"resolution": "Partially resolved by Proposal 7 (§31.2 kernel terms with explicit β/ι/ν/η reductions; §27.4 strong normalization theorem). Kernel reductions: (β) for Pi-elim on lambda; (ι) for Ind-elim on constructor (Appendix H.4); (ν) for Coind-elim on unfold (Appendix H.5, guardedness-enforced); (η) for function extensionality. Evaluation order at the surface is left-to-right per §1.2 ('argument evaluation is left-to-right, always') — realized at kernel level as the standard call-by-value reduction strategy on Pi-elim. Comptime evaluation is the same kernel normalization applied at elaboration time (§17.1). Ghost terms at grade 0 are erased, not evaluated (Grade-zero axiom). Strong normalization theorem stated in §27.4; mechanized proof in Lean 4 pending. Full small-step operational semantics enumeration deferred — the reductions are named, but the call-by-value strategy is not yet fully specified at the surface level for operations like effect handler dispatch and session select.",
"docs_adjusted": true
},
{
"id": 67,
"name": "memory_model",
"description": "When two threads access shared memory, what does FX guarantee? TSO? ARM model? Own abstract model? §11.10 mentions auto sync inference but the MODEL isn't defined",
"prior_art": "C/C++11 adopted 'DRF-SC or Catch Fire' — data races are undefined behavior, with sequentially consistent atomics as the default synchronization primitive; the thin-air problem (unsound out-of-thin-air values for relaxed atomics) remains an open research problem after 40+ years of relaxed memory hardware. Java's JMM was the first mainstream language memory model but was later shown unsound with respect to standard compiler optimizations. Rust and Swift both inherited C/C++'s memory model via LLVM, with DRF-SC semantics — Rust's ownership system prevents most data races statically, making the memory model rarely user-visible. Go's memory model guarantees DRF-SC with happens-before edges from channel operations and sync primitives, deliberately simpler than C++. LLVM's model differs subtly from C++ by giving defined behavior (undef) to racy non-atomic reads rather than full UB.",
"resolution": "Resolved by Proposal 6 (§11.10 rewritten, new §20.5, new Appendix G, §21.2 refreshed): FX's memory model is defined bottom-up by per-architecture emit tables rather than top-down by an abstract specification. The approach exploits FX's full control of source-to-bytes translation (no LLVM) — the instruction sequence emitted for every (operation, ordering, width, arch) tuple is fixed and published, and the source semantics refine against the ISA's formal memory model (x86-TSO, ARM Flat, RVWMO, MIPS RC — cited in Appendix G). Source types: atomic<T> (single-word), atomic_wide<T> (16-byte with hardware support), atomic_counter<T> (relaxed-by-design), seqlock<T> (portable fallback). Default ordering is @SeqCst everywhere — cheap on every supported target after §21.2 auto-sync downgrade runs. DRF-reject theorem: linearity + exclusive borrows mean races on non-atomic memory are compile errors, not UB. Thin-air freedom inherited for free because the compiler doesn't speculate across atomics. Targets: x86-64 (TSO), arm64 (ARMv8.1+ LSE baseline), rv64 (GC+Zaamo baseline, Zacas/Zabha optional), mips64 (legacy profile). New error codes: T053 (invalid ordering/element type), T054 (atomic_wide on unsupporting target), P003 (arch intrinsic mismatch).",
"docs_adjusted": true
},
{
"id": 68,
"name": "operator_dispatch",
"description": "a + b where both are i64 — is + a type class method? Compiler intrinsic? §16.6 has algebraic structures but doesn't say whether basic + goes through them",
"prior_art": "Haskell dispatches operators through type classes — (+) has type Num a => a -> a -> a, resolved at compile time via dictionary passing (or specialization), with the class hierarchy (Num < Fractional < Floating) determining which operations are available. Rust resolves operators via trait implementations in std::ops (Add, Sub, Mul, etc.) using monomorphization at compile time or vtable dispatch for trait objects; only trait-backed operators can be overloaded, no custom operators. Kotlin uses the 'operator fun' modifier on specifically-named member/extension functions (plus, minus, times, etc.), resolved by standard overload resolution rules restricted to operator-marked candidates. Swift allows both overloading existing operators and defining entirely new custom operators with custom precedence groups, all resolved statically. Lean 4 uses type class instances (e.g., instance : Add Nat) following the Haskell model but with dependent-type-aware unification.",
"resolution": "Resolved by Proposal 12 part 2 (fx_design.md §2.6 addendum + §16.11 new section). Arithmetic and bitwise operators (+, -, *, /, %, &, |, ^, ~, <<, >>) are compiler intrinsics defined only for built-in numeric / bool / bits(n) / signed_bits(n) / trits(n) types. User types cannot extend them; they use named methods (Matrix.add(a, b), a.add(b)). Comparison operators (==, !=, <, <=, >, >=) dispatch through stdlib Eq and Ord type classes; user types participate via @[derive(Eq)] / @[derive(Ord)] or manual instance. Boolean not/and/or and constructor-test 'is' are intrinsics. Attempting to implement a user trait named Add does not make + overloadable — the operator always dispatches to the intrinsic. Rationale: under FX's LLM-first primary-user constraint, 'a + b' must have locally-knowable effects (Tot for built-in numerics); allowing it to dispatch to arbitrary user code with arbitrary effects regresses readability. Comparison operators are the exception because they are structural and their effect is always Tot.",
"docs_adjusted": true
},
{
"id": 69,
"name": "old_in_postconditions",
"description": "old(x) used in postconditions to refer to pre-call value. old is not a keyword. Built-in function? Only valid inside post clauses? What happens outside?",
"prior_art": "Eiffel originated the 'old' keyword in postconditions as part of Design by Contract — 'old expr' denotes the pre-call value of expr, evaluated eagerly at method entry and compared at exit. Dafny uses old(expr) in ensures clauses and other two-state contexts, with the verifier treating it as a logical snapshot rather than a runtime copy. SPARK/Ada 2012 provides X'Old as an attribute that creates a physical copy at subprogram entry (plus Loop_Entry for loop invariants); the copy semantics have real runtime cost. JML uses \\old(expr) in ensures clauses for Java, while ACSL provides both \\old(expr) and the more general \\at(expr, label) for referring to values at arbitrary program points. All these constructs address the same fundamental need: postconditions are two-state assertions relating pre-state and post-state values.",
"resolution": "Resolved (fx_design.md §10.2 expanded). 'old' is a spec-language built-in in the prelude — not a keyword. Valid only in two-state spec contexts: post clauses, invariant clauses, verify blocks over two-state machines. Outside these (in pre clauses, runtime code) it is compile error R003. Typing rule: old(expr) : T where T is expr's type in the body state, but evaluated against the function's entry state. Ghost-graded (grade 0), erased at compile time, zero runtime cost. Kernel translation: old(expr) desugars to load_snapshot(expr, __entry_state) where __entry_state is an implicit ghost parameter added to functions with two-state obligations. Follows Dafny/JML model (logical snapshot, not physical copy); SPARK's X'Old is similar but forces runtime copy which FX avoids via ghost erasure.",
"docs_adjusted": true
},
{
"id": 70,
"name": "fail_propagation_mechanism",
"description": "Design says Fail propagation is automatic. HOW? Delimited continuations? Implicit try/catch wrapping? The concrete mechanism is unspecified",
"prior_art": "Rust's ? operator is the gold standard — a postfix operator on Result<T,E>/Option<T> that early-returns on Err/None with automatic From trait conversion between error types, replacing verbose match blocks with concise propagation. Haskell's do-notation for the Either monad provides monadic error propagation where >>= silently short-circuits on Left values, but requires wrapping in the monadic context throughout. Swift uses try/do/catch with throwing functions marked 'throws', requiring explicit try at each call site — heavier syntax but mirrors monadic patterns without wrapper types. Zig uses error unions (!T) with 'try' (shorthand for 'catch |err| return err') and named error sets, plus errdefer for error-path-only cleanup — the compiler enforces exhaustive error handling. Go's explicit 'if err != nil { return err }' pattern is widely criticized for verbosity but makes control flow maximally visible.",
"resolution": "Resolved by §4.9 rewrite (Proposal 4 rigor-first). Kernel translation specified: fail(e) desugars to 'perform Fail.fail(e)' at the kernel level — a standard algebraic-effect operation (Appendix H.5 coinductive effect family). 'try expr' desugars to 'perform Fail.propagate(expr)' which the kernel handles as a monadic bind over the Fail row. 'try ... catch ... end try' desugars to 'handle { body } with Fail { fail(e, k) => catch_body(e); return(v) => v }'. Effect handlers (§9.6) are the general mechanism; fail/try/catch are the surface syntax. Propagation is explicit — every call to a function whose effect row contains Fail(E) or Exn(E) requires either a 'try' prefix (to propagate) or a surrounding 'try ... catch ... end try' block (to handle). Missing marker is compile error E042.",
"docs_adjusted": true
},
{
"id": 71,
"name": "numeric_literal_resolution",
"description": "42 could be i64, u8, i32. 3.14 could be decimal, dec64, f64. Type-directed literal resolution rules partially specified in §3.1 but incomplete for all cases",
"prior_art": "Haskell's Num class is the most principled approach: integer literal 42 has type (Num a) => a via desugaring to fromInteger (42 :: Integer), with type defaulting rules (default to Integer, then Double for Fractional) resolving ambiguity when no context constrains the type variable; GHC extends defaulting to non-numeric classes via NamedDefaults. Rust uses bidirectional type inference with a hard-coded i32 default for unconstrained integer literals and f64 for floats, with optional suffixes (42i64, 3.14f32) for explicit annotation; ambiguity is a compile error rather than defaulting to a widened type. Swift uses the ExpressibleByIntegerLiteral protocol: any conforming type can be initialized from an integer literal, with Int as the default type alias; the compiler validates range at compile time (e.g., UInt8 = 600 is a static error). Scala combines compiler-hardcoded numeric widening (Int to Long, Float to Double) with literal narrowing (integer literal 42 can narrow to Byte/Short if in range), though Scala 3 moved away from implicit numeric widening. Kotlin uses smart casts and explicit conversion functions (toInt(), toLong()) with no implicit widening, requiring explicit calls for cross-type conversion.",
"resolution": "Resolved by Proposal 11 (§3.1 expanded). Complete rules: (1) unsuffixed integer literal defaults to 'int' (arbitrary-precision, N001 warning if used without Alloc); in context expecting fixed-width iN/uN, resolves at compile time with literal-fit check (T060 if literal exceeds target width). (2) unsuffixed decimal literal (e.g. '3.14') defaults to 'decimal' (arbitrary precision); with suffix dN or fN resolves to the suffixed type; in context expecting fixed-width dec, resolves with precision-fit check (T060). (3) decimal literal does NOT auto-resolve to float (lossy boundary) — compile error T061 with suggestion to use to_float() or explicit fN suffix. (4) Refinement-narrowed types further constrain literal fit: 'u8 { x < 100 } = 150' is compile error R005 ('literal violates type refinement'). This is rigor-first literal resolution — explicit suffix or explicit binding type, no silent widening.",
"docs_adjusted": true
},
{
"id": 72,
"name": "machine_state_of_keyword",
"description": "Machine states use state Connecting of { host: string }. Variants dropped of. Inconsistency: do machine states use () like variants or keep of? Flagged but not resolved",
"prior_art": "XState (the dominant JavaScript statechart library) models state-associated data via a top-level 'context' object on the machine definition, not per-state data: context is a flat record shared across all states, mutated only through assign() actions triggered by transitions, with TypeScript typing via a schema property. UML statecharts (derived from Harel's original formalism) use 'extended state variables' as attributes of the owning classifier (the context), available across all states; guards on transitions test these variables, and entry/exit actions modify them, but the variables are not syntactically scoped to individual states. Ragel (a state machine compiler for C/C++/Java) attaches actions to transitions and states via inline code blocks but has no dedicated data-per-state syntax. Rust's typestate pattern encodes state data via generic type parameters (Connection<Connecting> vs Connection<Connected>) where each state struct carries its own fields, achieving per-state data through the type system rather than runtime state machines. No mainstream statechart formalism provides dedicated syntax for per-state data declarations like FX's proposed 'state Connecting of { host: string }'.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 73,
"name": "name_resolution_conflicts",
"description": "Two open modules define the same name — error? Last wins? Require qualification? No rule stated",
"prior_art": "Rust's glob import conflict rule is lazy: two use foo::* bringing the same name are permitted silently until the ambiguous name is actually used, at which point it becomes a compile error; explicit (non-glob) imports always shadow glob imports, and the compiler suggests qualified paths or explicit imports for disambiguation. OCaml uses a 'last open wins' rule where the most recently opened module's bindings shadow earlier ones; warning 44 flags shadowing by open (suppressed with open!), and type-directed disambiguation applies specifically to record labels and variant constructors. Haskell requires explicit disambiguation for ambiguous unqualified names from multiple imports, typically via qualified imports (import qualified M as Q), hiding clauses (import M hiding (f)), or selective imports (import M (f, g)); two modules can share a qualifier if their exported names don't overlap. Python's from module import * silently overwrites any existing name with the last-imported definition, with no warning or error; PEP 8 strongly discourages wildcard imports, and __all__ controls which names are exported. Scala resolves import conflicts by letting later imports shadow earlier ones within the same scope, with explicit imports taking priority over wildcard imports.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 74,
"name": "namespace_separation",
"description": "Can type foo and fn foo coexist in the same module? Same namespace or separate?",
"prior_art": "Rust has three namespaces (type, value, macro) allowing struct Foo, fn foo, and macro foo! to coexist; syntax position disambiguates (type positions vs value positions vs macro invocations), and use imports a name in all namespaces where it exists. Haskell has two namespaces (type and value) where a type constructor and data constructor can share the same name (e.g., data Foo = Foo), which is idiomatic; class methods share the value namespace with ordinary functions, and record field accessors historically polluted the value namespace (mitigated by DuplicateRecordFields and OverloadedRecordDot in GHC 9.2+). OCaml has separate namespaces for modules, types, and values, plus a sub-namespace for record labels and variant constructors that supports type-directed disambiguation; a module M can coexist with a value m and a type m. Java has a single nominal namespace where a class name serves as both type and constructor (via new), though packages, types, and members occupy distinct scopes within the JLS. C# similarly uses a single namespace for types but separates members, while F# follows OCaml's model with module/type/value separation.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 75,
"name": "lookup_order",
"description": "Name lookup order: local scope -> module scope -> opened modules -> prelude? Never stated",
"prior_art": "Rust's method lookup follows a strict algorithm: build an auto-deref chain (T, *T, **T, ...), at each level try the type by-value then &T then &mut T, and at each step inherent methods take priority over trait methods; the compiler stops at the first deref level where any match is found, producing an ambiguity error if multiple candidates exist at the same level. Python uses C3 linearization for method resolution order (MRO) in multiple inheritance, producing a deterministic linear ordering that preserves local precedence (children before parents) and monotonicity; the MRO is computed at class definition time and fails with TypeError if no consistent linearization exists. C++ uses a two-phase lookup: ordinary unqualified lookup searches outward from the call site through local/class/namespace scopes (stopping at the first scope containing any match), then ADL (argument-dependent lookup / Koenig lookup) adds the namespaces of argument types to the candidate set; overload resolution then picks the best match from the merged set. Scala's implicit resolution uses a two-tier system: local scope (local declarations > explicit imports > wildcard imports > outer scope) has priority over the implicit scope (companion objects of the type and its supertypes); within a tier, more specific candidates win. Swift uses four dispatch mechanisms (inline, static, vtable-dynamic, message-dispatch), with protocol witness tables for protocol requirements and static dispatch for protocol extension methods not declared as requirements.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 76,
"name": "scoped_open_grammar",
"description": "Design shows begin open M; ... end;. Grammar has no production for scoped open inside expressions",
"prior_art": "OCaml has the most mature scoped open syntax with two forms: 'let open M in e' for block-scoped opens and 'M.(e)' as lightweight sugar, both limiting the import to the enclosed expression; OCaml 4.08+ extended this to generalized opens accepting arbitrary module expressions including 'open struct ... end' for ad-hoc local modules. Rust supports block-scoped use declarations inside any {} block (functions, impl blocks, match arms), with the import visible only within that block; this is idiomatic and widely used. Scala permits import statements anywhere in the program (top-level, inside classes, inside methods, inside arbitrary blocks), with lexical scoping ensuring the import is visible only from the import statement to the end of the enclosing block; this was a notable innovation over Java's file-level-only imports. Haskell's qualified import system (import qualified M as Q) is file-scoped rather than expression-scoped, with no local import syntax; the closest equivalent is local module aliasing in GHC. Python allows import statements inside functions (through importlib or direct import), but not inside arbitrary blocks, and prohibits 'from M import *' inside functions; Kotlin restricts imports to file-level only with no block-scoped alternative.",
"resolution": "Resolved by Proposal 10 (fx_grammar.md §6.1 stmt extended). 'import_decl' is now a valid stmt form, so 'open M;' and 'include M;' work inside any block. Scoping is automatic because declarations inside a block are block-local — no new 'let open ... in' construct needed. Design example 'begin open Std.Math.Lemmas; lemma_div_mod(x, y); end;' parses directly as a 'begin stmt* expr ';' end begin' with the first stmt being an import_decl. Matches Rust's block-scoped use and Scala's nested imports. OCaml's 'M.(e)' sugar is deliberately omitted — one-import-one-form keeps the surface minimal.",
"docs_adjusted": true
},
{
"id": 77,
"name": "string_concatenation",
"description": "HOW do you combine two strings? ++ was removed (F* heritage). No operator exists. concat(a, b)? s1.concat(s2)? The + operator? Fundamental and unspecified",
"prior_art": "Rust deliberately avoids a simple + operator on String; format!() macro and push_str() are idiomatic, with + consuming the left operand due to ownership semantics. Python uses + for concat and f-strings for interpolation; Go uses + but recommends strings.Builder (Go 1.10+) for loops since + is O(n^2) on immutable strings. Kotlin provides $variable/${expr} string templates as its primary mechanism, with + available but discouraged. Swift uses \\(expr) interpolation built into string literals as the primary approach, with + also supported. Haskell uses ++ (list append) and the more general <> Semigroup/Monoid operator; naive ++ is O(n) due to linked-list strings, so difference lists or Text/Builder are used in practice. Java's + operator is compiled to invokedynamic StringConcatFactory since JEP 280 (Java 9), eliminating the old StringBuilder transformation and enabling runtime strategy selection. The design space splits into operator-based (+ or ++), method-based (push_str/append/concat), macro/interpolation-based (format!, f-strings, templates), and typeclass-based (<> Semigroup); FX with its graded types could make <> the Semigroup concat and provide f-string interpolation as sugar.",
"resolution": "Resolved by Proposal 4 (§3.10 and §26.2): FX forbids operator overloading (§16.8), so string concatenation is method-based: s.concat(t) or s.append(t). Interpolation is the canonical pattern via f-strings: f\"{a}{b}\" for any composition. Joining a list uses list.join(sep). The method catalog (concat/split/replace/case/join/substring) ships in stdlib §26.2 Text. Language spec §3.10 adds one paragraph noting the discipline; full method inventory is a stdlib concern. No + operator, no <> Semigroup operator — only methods and f-strings.",
"docs_adjusted": true
},
{
"id": 78,
"name": "string_indexing_semantics",
"description": "s[0] returns first grapheme? First codepoint? First byte? §3.10 says grapheme-clustered but indexing rules unspecified",
"prior_art": "Rust forbids direct s[i] indexing entirely because O(1) access is impossible on UTF-8 and it is ambiguous whether the unit is bytes, code points, or grapheme clusters; users must choose explicitly via .bytes(), .chars(), or .char_indices() (grapheme clusters require the external unicode-segmentation/icu_segmenter crate). Python indexes by code point (s[0] returns the first Unicode scalar), which is O(1) internally via a flexible representation (Latin-1/UCS-2/UCS-4) but conflates code points with user-perceived characters, breaking on combining sequences and emoji. Swift indexes by extended grapheme cluster using an opaque String.Index type (not integers), making s[s.startIndex] return the first user-perceived character; this is O(n) to advance n positions but is Unicode-correct by default, with .unicodeScalars and .utf8/.utf16 views available for lower-level access. Go indexes by byte (s[0] returns the first byte); rune iteration requires range loops or []rune conversion. Java's charAt() returns a UTF-16 code unit (not a code point), silently splitting surrogate pairs for characters above U+FFFF; codePointAt() and codePoints() stream are needed for correct Unicode handling. Swift's grapheme-clustered approach is closest to FX's stated design in section 3.10; FX should adopt grapheme-cluster indexing as default with explicit .code_points and .bytes views for lower-level access.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 79,
"name": "string_slicing_semantics",
"description": "s[0..5] — by what unit? Graphemes? Codepoints? Bytes? Same question as gap 91",
"prior_art": "Rust slices &str by byte offset and panics at runtime if a range boundary falls inside a multi-byte UTF-8 sequence (e.g., splitting a 2-byte Cyrillic character at byte 1); the safe alternative is .get(range) returning Option<&str>. Python slices by code point index with s[0:5] returning the first 5 code points; out-of-bounds gracefully returns shorter strings rather than panicking, but code-point slicing can split grapheme clusters (e.g., base + combining accent). Go slices by byte like Rust but does NOT panic on invalid boundaries, silently producing byte sequences that may not be valid UTF-8 characters; correct slicing requires []rune conversion. Swift uses opaque String.Index values for slicing (s[i..<j]) that always land on extended grapheme cluster boundaries; creating a Substring is constant-time since it shares storage with the parent String, but advancing an index is O(n). Java's substring() operates on UTF-16 code units and can split surrogate pairs if boundaries fall mid-pair. Swift's approach of type-safe indices that cannot land on invalid boundaries is the safest; FX should follow Swift by making grapheme-cluster-indexed slicing the default (consistent with gap 78) and providing .code_points[range] and .bytes[range] views for lower-level slicing, with the byte-level view returning a Result or Option rather than panicking.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 80,
"name": "in_as_membership_test",
"description": "for x in xs uses in for iteration. x in set as boolean test — valid? in is keyword but only specified in for context",
"prior_art": "Python uses 'in' as both an iteration keyword (for x in xs) and a boolean membership operator (x in collection), with the membership form dispatching to __contains__(), then __iter__(), then __getitem__(); it also works on strings as a substring test ('ab' in 'abc' is True). Kotlin copies Python's dual use: 'in' for iteration in for-loops AND as a boolean operator that compiles to collection.contains(x); it also provides '!in' as a negated form and supports operator overloading via the 'contains' convention. Swift does NOT use 'in' for membership testing; it uses .contains() method calls on collections and ranges, with 'in' reserved exclusively for for-in loops and closure parameter syntax. Rust has no 'in' membership operator; .contains() is a method on iterators, slices, strings, and ranges. Haskell uses the elem function (typically infix: x `elem` xs) for list membership (O(n)) and Data.Set.member for set membership (O(log n)); using elem on a Set via Foldable is a common performance trap since it degrades to O(n). The Python/Kotlin approach of dual-use 'in' is the most ergonomic; FX should adopt 'x in collection' as sugar for collection.contains(x), requiring a Contains trait implementation, with 'not in' or '!in' as the negated form.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 81,
"name": "not_is_precedence_bug",
"description": "not x is None — not is prefix above all infix, is is level 8. Parses as (not x) is None which is WRONG. Precedence bug needs resolution",
"prior_art": "Python solves this by making 'is not' a single compound comparison operator (higher precedence than 'not'), so 'not x is None' parses as 'not (x is None)' due to 'is' binding tighter than 'not'; PEP 8 mandates the 'x is not None' form for clarity. Kotlin uses '!is' (bang before keyword) as a dedicated negated type-check operator with smart-cast in the else branch. C# 9.0 introduced combinatorial patterns with explicit precedence (not > and > or), enabling 'x is not null' and 'x is not Type'. Swift has no negation pattern combinator; negation requires guard/else control flow. FX should either make 'is not' a compound operator (Python/C# approach) or lower 'not' below comparison precedence so 'not x is None' parses as 'not (x is None)' naturally.",
"resolution": "Resolved by Proposal 4 (§2.6 and fx_grammar.md §3): 'not' is lowered in the precedence table to sit just above 'and' and below every comparison and 'is' test. 'not x is None' now parses as 'not (x is None)' naturally; 'not x > 5' parses as 'not (x > 5)'. This matches Python's model. No compound 'is not' token is introduced — the precedence change alone fixes the bug uniformly for every comparison. 'not x and y' still parses as '(not x) and y' because 'and' is below 'not'.",
"docs_adjusted": true
},
{
"id": 82,
"name": "chained_comparisons",
"description": "0 < x < 10 — grammar has comparisons as left-associative binary. Parses as (0 < x) < 10 comparing bool to 10. Should be rejected or handled specially",
"prior_art": "Python desugars 'a < b < c' to '(a < b) and (b < c)' with short-circuit evaluation and single evaluation of middle operand; this is built into the grammar as a special comparison-chain production. Julia does the same using '&&' for scalars and '&' for elementwise, with middle operands evaluated once; evaluation order of sub-expressions is officially undefined. Raku supports chaining natively with lazy short-circuit semantics. C/C++/Java/JS parse 'a < b < c' as '(a < b) < c' which compares a bool to an integer — clang-tidy flags this as bugprone-chained-comparison. Rust rejected a chaining proposal (internals discussion) citing complexity and backward compatibility. C# has an open discussion (#6899) but no adoption. FX should either support Python/Julia-style desugaring or emit a type error when bool appears as a comparison operand.",
"resolution": "Resolved by Proposal 4 (§2.6 and fx_grammar.md §3): chained comparison is non-chaining in the grammar. '0 < x < 10' is compile error T050 with suggestion '0 < x and x < 10'. Python-style desugaring would work under rigor-first (Appendix E classifies it as mechanical desugaring) but the one-way-to-do-it rule for LLM generation chooses explicit rejection. Every pairwise comparison written explicitly is shorter for parsing and unambiguous.",
"docs_adjusted": true
},
{
"id": 83,
"name": "partial_application_named_args",
"description": "fn add(a: i64, b: i64). Does add(a: 1) return a fn(b: i64) -> i64? Or error for missing argument? Not specified",
"prior_art": "OCaml's labeled arguments allow partial application by supplying any labeled arg in any order; a trailing positional argument triggers default-value substitution, creating ambiguity between partial and full application resolved by positional-arg presence. Scala uses explicit underscore placeholder ('add(1, _: Int)') and multiple parameter lists for currying, but does not auto-curry from named args. Kotlin has no built-in partial application; currying requires manual wrapper functions. Haskell auto-curries all functions but lacks named arguments entirely. A 2025 ESOP paper (Sun and Oliveira) formalizes named arguments as intersection types with soundness proofs -- only OCaml and this work have type-theoretic foundations. FX should decide: explicit placeholder syntax (Scala '_'), auto-curry from named args (OCaml-style), or require all arguments at the call site.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 84,
"name": "dot_shorthand_on_methods",
"description": "xs |> .sort() — does .sort() work as dot-shorthand? Design §4.2 only specifies .field, not .method()",
"prior_art": "Swift key paths (backslash-dot syntax like \\User.name) reference properties but cannot reference methods -- this is an ongoing Swift Evolution discussion. Kotlin's member references (::method) create callable references to methods but require the type prefix. Scala's underscore placeholder ('_.method()') creates anonymous functions from method calls and composes well with higher-order functions. Elixir/Gleam pipe operators ('|>') insert the left operand as the first argument to the right function, achieving method-chain-like composition without dot syntax. Rust requires explicit closures ('|x| x.method()'). FX's dot-shorthand '.field' for pipe is similar to Scala's placeholder; extending it to '.method()' is natural and well-precedented by Scala, but must specify whether it captures as a closure or is syntactic sugar for pipe insertion.",
"resolution": "Resolved by Proposal 12 part 2 (fx_design.md §4.2 expanded). Bare '.something' in function-argument position desugars to 'fn(it) => it.something' for both field access ('.field') and method call ('.method(args)'). Multiple dots in the same expression share the same implicit parameter 'it'. Examples: 'items |> map(.to_string())', 'users |> filter(.email.ends_with(\"@acme\"))', 'list |> sort_by(.length())'. The grammar already parses this via atomic_expr '.' lower_ident + postfix_expr call_args; the elaborator recognizes bare-dot method calls and wraps the enclosing call argument into the synthesized lambda.",
"docs_adjusted": true
},
{
"id": 85,
"name": "literal_type_context_resolution",
"description": "let x = 42; — what type? int? If context expects u32, resolves? §3.1 partially specifies. Empty list [] type inference? Must annotate?",
"prior_art": "Rust uses special 'integral' and 'float' inference variables for unsuffixed numeric literals: context determines the type, and if unconstrained, defaults to i32/f64. Known edge cases exist where method resolution interacts poorly with defaulting. Haskell uses type-class defaulting declarations ('default (Integer, Double)') which are user-configurable; the monomorphism restriction forces defaulting when polymorphism would otherwise apply. TypeScript infers literal types for const bindings ('const x = 42' has type 42, not number) and widens for let bindings; 'as const' forces literal types throughout objects/arrays. Scala performs numeric widening (Int to Long) at call sites expecting wider types. FX should specify: (1) the default type for unconstrained integer literals (likely int), (2) whether context-directed narrowing (int literal -> u32) is allowed, and (3) whether literal types exist as synth, default, or error in let bindings.",
"resolution": "Resolved jointly with gap #71 (Proposal 11, §3.1 expanded) and gap #86 (rigor-first §4.6). Composite rule: (1) synthesis-mode RHS with explicit suffix ('42u8', '3.14f64') gets that type directly. (2) unsuffixed numeric literal in synth position defaults to 'int' for integers or 'decimal' for decimals (arbitrary-precision, N001 warning). (3) in checking mode with expected type iN/uN/dN/fN, literal resolves at compile time with fit check (T060/R005). (4) empty collections, unsuffixed literals in checking-only positions, and other 'synth-impossible' RHS require binding ascription per §4.6; otherwise compile error T045. No singleton literal types (TypeScript-style) — FX's literal resolution is purely for integer/decimal/bool/string literals hitting their expected primitive type, not for creating new types.",
"docs_adjusted": true
},
{
"id": 86,
"name": "empty_collection_type",
"description": "let xs = []; — type is list(?). How is element type inferred? Must there be annotation? Defer to first use?",
"prior_art": "Rust requires type annotation when element type of Vec::new() cannot be inferred from subsequent usage; the turbofish syntax ('Vec::<i32>::new()') or type annotation ('let v: Vec<i32>') resolves it. If later usage constrains the type (e.g., v.push(42)), inference flows backward to determine Vec<i32>. Haskell's empty list '[]' is polymorphic ('forall a. [a]') and resolves at each use site via Hindley-Milner unification; the monomorphism restriction may force defaulting in top-level bindings. Kotlin requires explicit type parameter ('emptyList<Int>()') since generic return types need help. Swift infers from context ('let xs: [Int] = []') or requires annotation. FX should specify whether empty collections are polymorphic (Haskell-style) or require annotation when unconstrained, and whether forward usage (like Rust's push) can resolve the type retroactively.",
"resolution": "Resolved by §4.6 rigor-first let-binding rule (gap #132 Proposal 4 era). Empty collections '[]', '{}' are checking-mode RHS — they cannot self-synthesize a type. Binding ascription is REQUIRED: 'let xs : list(i64) = [];' ok; 'let xs = [];' is compile error T045 ('let-binding type cannot be inferred'). FX does not do backward inference from later usage (rigor-first — the type information must be explicit at the binding site). The diagnostic for T045 concretely suggests the ascription form based on what checking-mode source the RHS is (empty collection, unsuffixed literal, polymorphic call without type args, etc.). Matches Swift/Kotlin, stricter than Rust (which backward-infers from usage) and Haskell (which accepts polymorphic ambiguity).",
"docs_adjusted": true
},
{
"id": 87,
"name": "defer_and_fail_interaction",
"description": "When fail(e) aborts to handler, do deferred cleanup actions run? Critical for resource safety. If not, linear resources leak. Mechanism unspecified",
"prior_art": "Go's defer always runs when the enclosing function returns, including during panic unwinding; recover() in a deferred function can catch panics. Swift's defer runs on all scope exits including throw paths; the block cannot itself throw or transfer control. Zig uniquely provides both 'defer' (always runs) and 'errdefer' (runs only on error return), giving the most precise control -- errdefer is the key innovation for resource cleanup on failure paths without cleanup on success. Rust's Drop trait runs during unwinding, but a double panic (panic in Drop during unwind) aborts the process; std::thread::panicking() allows checking. All three languages (Go/Swift/Zig) agree defers run on error paths in LIFO order. FX should guarantee defers run when fail(e) unwinds to a handler, and consider Zig-style errdefer for error-only cleanup.",
"resolution": "Resolved by §7.11 (Proposal 3). The scope-exit semantics table explicitly lists Fail(_) propagation as: defer runs yes (LIFO, before propagate), errdefer runs yes (interleaved LIFO with defer). Defers run on every scope exit including Fail/Exn propagation; errdefers run only on Fail/Exn paths, not on normal return. The no-Fail-in-defer rule (G-Defer) prevents double-fault during unwinding. Proposal 13 added a §7.11 return-expression ordering addendum clarifying that on normal return, the expression evaluates first, then defers run LIFO, then control transfers — matching Go/Swift/Zig semantics (gap #88).",
"docs_adjusted": true
},
{
"id": 88,
"name": "defer_and_return_ordering",
"description": "defer runs at scope exit. Does defer run BEFORE or AFTER return expr evaluates? Ordering unspecified",
"prior_art": "Go specifies that deferred functions execute after the return statement's result parameters are set but before the function returns to its caller; deferred functions can read and modify named return values (e.g., 'defer func() { i++ }()' increments the return). Go's defer arguments are evaluated eagerly at the defer statement, not at execution time -- a common gotcha. Swift's defer executes at scope exit (block-scoped, not function-scoped unlike Go); it cannot contain return/break/throw, so it cannot modify the return value. Zig's defer is also block-scoped and executes in LIFO order at scope exit; defer arguments are inline expressions. All three: return expression evaluates first, then defers run before control transfers to caller. FX should specify: return expr evaluates first, then defers execute in LIFO order, then control transfers -- matching the universal consensus.",
"resolution": "Resolved by Proposal 13 (§7.11 return-expression ordering addendum). On normal return, the return expression evaluates in the exiting scope first (so 'return compute(x)' runs compute(x) to a value), then defer cleanups run in LIFO order, then control transfers to the caller with the already-computed return value. A deferred cleanup cannot observe or modify the returned value — the value is captured at return-expression evaluation, before defers fire. This matches Go, Swift, and Zig semantics and avoids both the 'defer runs before return computes' surprise and the 'defer can mutate return value' footgun.",
"docs_adjusted": true
},
{
"id": 89,
"name": "multiple_fail_effects",
"description": "fn a() : i64 with Fail(E1), Fail(E2) — valid? Two separate Fail with different error types? Or must combine into union?",
"prior_art": "Rust enforces a single Result<T, E> per function; multiple error types must be unified into a single enum (manual or via thiserror), erased via Box<dyn Error>, or handled with anyhow::Error for application code. Swift SE-0413 (typed throws, Swift 6.0) deliberately restricts functions to one thrown error type; the community wraps multiple errors in enums. Koka's algebraic effect system allows multiple distinct effect instances in a function type (e.g., Fail(E1) and Fail(E2) as separate effects), resolved via named handlers or effect-instance injection with level numbers; however, Koka's practical guidance is to create domain-specific error effects rather than stacking generic ones. Java's multi-catch ('catch (A | B e)') handles multiple exception types but the throws clause lists them separately. FX should require a single Fail(E) per function with E as a union type, matching the Rust/Swift consensus -- Koka-style independent fail effects add complexity without proportional benefit.",
"resolution": "Resolved by Proposal 13 (§4.9 multi-Fail rejection paragraph, §9.3 lattice clarification). Two explicit Fail terms in a source-level effect annotation — 'with Fail(E1), Fail(E2)' — is compile error E045 ('redundant Fail effects; combine into a single Fail with a union error type'). The effect lattice (§9.3) still normalizes Fail(E1) \\/ Fail(E2) to Fail(E1 | E2) internally when composing effects across call sites, but the surface annotation must name a single Fail(T) with T a named closed-union type. Matches §4.9's 'error types are closed unions' rule and Swift SE-0413 'one thrown error type per function' decision. Catch arms see one failure dimension with one error type to dispatch on.",
"docs_adjusted": true
},
{
"id": 90,
"name": "nested_handlers",
"description": "handle (handle body H1) H2 — nested effect handling. Continuation type composition not specified",
"prior_art": "Koka, Eff, and Multicore OCaml all use innermost-handler-wins semantics: when an effect operation is performed, the runtime searches up the handler stack and the first handler with a matching clause handles it; unhandled operations propagate outward. Koka uses lexically-scoped handler lookup with row-polymorphic effect types and deep handlers (handler reinstalls itself around the resumed continuation). Multicore OCaml searches linearly up the fiber stack (one-shot continuations only) with unchecked effect types -- unhandled effects are runtime errors. Eff supports multi-shot continuations with checked effect types and effect instances for disambiguating multiple handlers of the same effect type. The Effekt language demonstrated that handler nesting order has observable semantic consequences when mutable state interacts with control effects (different nesting orders produce different results). FX should specify innermost-handler-wins with deep handler semantics, and require the effect type system to track which effects are handled at each nesting level.",
"resolution": "Resolved by Proposal 13 (§9.6 nested-handler paragraph). When a body is enclosed in multiple handlers, an effect operation performed in the body is handled by the innermost lexically-enclosing handler that provides a clause for that operation's effect. Operations of effects not covered by the innermost handler propagate outward to the next enclosing handler. If both handlers clause the same operation, the inner runs and the outer never sees the op unless the inner's continuation re-performs it. This matches Koka, Multicore OCaml, Effekt, and Frank — universal consensus in algebraic-effect implementations. A worked example showing inner Reader + outer Log handlers is included in §9.6.",
"docs_adjusted": true
},
{
"id": 91,
"name": "effect_polymorphism_in_handlers",
"description": "Can handler be polymorphic over remaining effects? fn run<eff: effect>(body: unit -> a with State(s), eff) : (a, s) with eff",
"prior_art": "Koka uses row-polymorphic effect types where handlers are polymorphic over a tail effect row variable, allowing unhandled effects to pass through transparently via Hindley-Milner-style inference. Frank makes every operator (handler) implicitly polymorphic over unhandled commands: a unary function is just a handler whose handled command set is empty, and Frank's effect polymorphism ensures alternative computations inherit the same effect permissions. Eff is statically typed with parametric polymorphism but its types do not express effect information; handler behavior is determined dynamically. Helium (lambda_HEL) adds existential effects and local effects to an ML-style module system for modular effect abstraction. Effekt takes a different approach with contextual effect polymorphism where effects express required capabilities from lexical scope rather than parametric effect variables, avoiding the complexity that leaks in Koka/Frank when higher-order functions interact with effect polymorphism.",
"resolution": "Resolved by §9.6 existing content plus Proposal 13 confirmation. The run_state example in §9.6 already uses effect-polymorphic signature 'fn run_state<a: type, s: type, eff: effect>(init: s, body: unit -> a with State(s), eff) : (a, s) with eff'. The handler's typing rule (also in §9.6) treats the body's effect row as '<E | eff>' where E is the effect being handled and eff is a tail that may be concrete or polymorphic. The handler removes E from the row, leaving eff; the continuation k carries eff. Koka-style row-polymorphism over handlers is first-class and requires no new mechanism — the effect variable is just a type parameter of kind 'effect'.",
"docs_adjusted": true
},
{
"id": 92,
"name": "comptime_code_purity",
"description": "What can comptime code do? Only Tot? Can it read files for codegen? Allocate? Boundary between compile-time and runtime unspecified",
"prior_art": "Zig comptime is pure by design: no IO, no syscalls, no inline assembly, no access to host architecture details; this makes compilation hermetic, reproducible, safe, and cacheable. C++ constexpr is gradually relaxing restrictions (constexpr allocation allowed in C++20 but must be freed before evaluation ends; consteval forces compile-time-only execution) but still cannot declare constexpr std::vector locally even in C++26. D's CTFE allows most language features except mutable static variables, non-portable casts, reinterpret casts, and device/IO access; any function without side effects works at compile time without annotation. Nim requires compile-time functions to be side-effect-free: no ptr/ref/var types in constants, no cast, and no FFI calls at compile time. The key design split is annotation-site (C++ requires constexpr/consteval markup) vs call-site (Zig/D decide at the call site whether to evaluate at compile time).",
"resolution": "Resolved by Proposal 14 (fx_design.md §17.1 expanded). Comptime is strictly Tot + compiler-internal allocation. Permits: arithmetic, string/data-structure computation, calls to other comptime fns, reading constants and declared types in the current compilation unit, reading target-property values via std/platform. Forbids: any IO (file, network, stdin/stdout, env, clock, randomness), calls to fns with non-Tot effect rows, unbounded recursion without 'decreases'. Matches Zig's pure-by-design model. Asset embedding goes through workspace manifest (§25.3) — declare assets with content hash, comptime reads pre-resolved bytes via stdlib 'asset(name) : bytes' primitive. This discipline is what §25.5 supply-chain defenses rely on: a package cannot execute arbitrary code at install or build time because comptime cannot touch the filesystem or network. Rules out xz-utils attack surface by construction.",
"docs_adjusted": true
},
{
"id": 93,
"name": "comptime_dependent_types",
"description": "If type depends on comptime value, when is type fully resolved? Before or during type checking?",
"prior_art": "Zig treats types as first-class comptime values: functions can take 'comptime T: type' parameters and return types, and later comptime parameters can depend on earlier ones, giving a flavor of dependent types but strictly at compile time (types are fully erased before codegen). C++ non-type template parameters allow values in types (e.g., std::array<int, N>) but are resolved during template instantiation, not during general type checking; generic code is not type-checked until used. In Idris and Lean 4, types genuinely depend on runtime values with no phase distinction; the elaborator resolves implicit arguments, coercions, and type-level computation during type checking itself, using the Decidable type class in Lean 4 to bridge compile-time decidability and runtime computation. The fundamental tradeoff is Zig's approach (phase-separated, all type computation erased) vs full dependent types (Idris/Lean, no phase barrier, types can depend on runtime values but decidability of type checking then depends on the theory's equality semantics).",
"resolution": "Resolved by Proposal 14 (fx_design.md §17.1 expanded). Types depending on comptime values are normalized at elaboration via §31.2 kernel reduction — same mechanism as ordinary type-level computation. When the value is a runtime value, the type involves a §6.7 dependent grade and SMT discharges obligations over it. No new mechanism required; cross-reference only.",
"docs_adjusted": true
},
{
"id": 94,
"name": "break_continue_scoping",
"description": "break/continue in keyword list, used in for/while. Can you break from match arm inside loop? Scoping rules for break/continue across nested constructs unspecified",
"prior_art": "Rust uses 'label: syntax on loops (e.g., 'outer: loop { break 'outer; }) with labels following hygiene and shadowing rules; RFC 2046 extends this to labeled block expressions allowing break from non-loop blocks with a value. Java has supported labeled break/continue since 1.0 with label: syntax on any statement (break label; / continue label;), though modern Java style prefers method extraction over labeled breaks. Kotlin uses label@ syntax on loops with break@label / continue@label, where IDEs colorize labels for visibility. Swift uses label: syntax on loops (outerLoop: for ...) with break outerLoop / continue outerLoop. All four languages scope break/continue to the nearest enclosing loop by default and require explicit labels for outer loops; none allow break from inside a match/when arm to target an enclosing loop without a label.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 95,
"name": "nested_if_expression_values",
"description": "let x = if a; if b; 1 else 2 end if else 3 end if; — inner if as statement inside outer if branch. Does last expr ; rule work here?",
"prior_art": "Rust, Kotlin, Scala 3, and OCaml all treat if as an expression that returns a value, with the last expression in each branch determining the branch's value. All four require branches to have compatible types when the if is used as an expression. In Rust, if without else returns () (unit); in Kotlin, else is mandatory when if is used as an expression; in OCaml, omitting else is essentially an error for non-unit types. Nested if expressions work uniformly in all four: the inner if is just another expression in the branch body, and its value becomes the branch's value via the last-expression rule. Scala 3's new quiet syntax (if cond then expr1 else expr2, no parens) with optional end if markers is closest to FX's delimited if syntax.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 96,
"name": "for_while_as_expressions",
"description": "Grammar puts for/while in atomic_expr but they produce no value (no return, body is stmt*). Implicitly unit? Is let x = for ... valid?",
"prior_art": "Rust's loop { } is the only loop form that can return a value via 'break expr', with for/while always returning () (unit); RFC 1624 explored extending break-with-value to for/while but it was deferred. Scala's for/yield comprehension is syntactic sugar for map/flatMap/withFilter chains and returns a new collection; a for without yield returns Unit. Kotlin's run { } scope function allows executing a block where an expression is needed (returning the last expression), and stdlib functions like generateSequence serve as expression-oriented loop alternatives. Python's walrus operator (:=) allows assignment within while/if conditions but loops themselves are statements returning None. Most languages treat for/while as unit-typed statements; only Rust's loop and Scala's for-yield provide value-producing loop forms.",
"resolution": "",
"docs_adjusted": false
},
{
"id": 97,
"name": "record_spread",
"description": "{ ...base, field: value } vs { base with field: value }. with syntax exists but ... spread in records doesn't. Is with the only way?",
"prior_art": "JavaScript/TypeScript use { ...base, field: value } spread syntax for shallow immutable updates, which is the most widely recognized syntax but only creates shallow copies. Rust uses struct update syntax { field: value, ..base } (note: dots at end, not beginning) which moves non-overridden fields from base; RFC 2528 extends this to allow type-changing updates on generic structs. Elm uses { record | field = value } with pipe syntax, enforced immutable by the compiler; Haskell uses record { field = newValue } syntax. Scala case classes provide a .copy(field = value) method generated by the compiler. OCaml uses { expr with field = value }. The two main syntactic families are spread-style (JS ...base, Rust ..base) and with-style (OCaml/Elm/Haskell with/|); all share the nested update problem where deeply nested fields require verbose chaining or lens libraries.",
"resolution": "Resolved by Proposal 4 (§2.7 rule 16, §3.4, fx_grammar.md §6.4). Record update uses spread '{ ...base, field: value }' — one syntax for record updates, format overrides in contracts, and any named-field update. The spread element must come first; subsequent fields override in left-to-right order. '{ ...cfg }' alone is a copy. The 'with' keyword was removed from record update (was one of the three polysemous 'with' positions in earlier drafts) and now means only 'effect annotation after return type' per rule 17.",
"docs_adjusted": true
},
{
"id": 98,
"name": "self_outside_impl",
"description": "self is a keyword. Can you use it as parameter name in regular fn? Or only inside impl/instance blocks?",
"prior_art": "In Rust, lowercase self is a keyword restricted to the first parameter of methods inside impl/trait blocks (as self, &self, &mut self, or self: Box<Self>); uppercase Self is a contextual type alias for the implementing type, usable in impl blocks and (via RFC 2300) in type definitions. Swift makes self an implicit parameter in methods; the mutating keyword controls whether self is inout; a proposal for explicit self parameter declarations was discussed but not adopted. Python's self is a convention, not a keyword -- any name works for the first parameter of instance methods, though self is universal; Python 3.11+ added typing.Self for return-type annotations. Kotlin uses this as the implicit receiver in class methods and extension functions, with this@label for disambiguation in nested scopes. The key design question is whether self/Self should be a hard keyword everywhere or contextually available only inside type implementations.",
"resolution": "Resolved by Proposal 12 part 2 (fx_design.md §16.1 expanded, fx_grammar.md §5.13 notes). 'self' is a keyword usable only as the leading parameter of an instance method inside 'impl T', 'instance Trait for T', or 'class Trait<T>' blocks (and inside 'codata ... fn name(self) ...' destructor signatures per §5.14). Using 'self' as a parameter name in a regular function declaration, as a let-binding name, or as a record field name is compile error T064. When an ordinary identifier spelled 'self' is genuinely needed, backtick-escape it ('`self`') to make it an ordinary identifier per §2.2. Hard-reserve eliminates the keyword-vs-identifier polysemy FX removed from 'with', 'ref', 'format' in earlier proposals.",
"docs_adjusted": true
},
{
"id": 99,
"name": "wildcard_in_let",
"description": "let _ = expensive_computation(); — discard result. _ is in patterns. Is it valid on left of let? Should be (it's a pattern) but worth confirming",
"prior_art": "Rust allows let _ = expr; where _ is the wildcard pattern that matches any value without binding or moving it (distinct from let _x = expr which does bind); Rust also supports const _ = expr for compile-time evaluation with discarded result. Go requires the blank identifier _ for discarding values (e.g., _, err := f()); unused variables without _ are a compile error. Haskell uses _ as a wildcard pattern in any pattern position including let bindings. OCaml allows let _ = expr but idiomatically prefers let () = expr for unit-typed expressions since _ silently accepts any type, potentially hiding bugs. The convention is universal across ML-family and systems languages: _ in let position is a pattern that discards the value, and every language that supports pattern matching in let bindings supports it.",
"resolution": "Resolved (fx_design.md §4.6). 'let _ = expr;' is valid — the wildcard pattern matches any value, binds nothing. No new grammar needed (the existing stmt: LET pattern (: type)? = expr ';' already covers it because '_' is an atomic_pattern). For linear values the discard is a drop (§7.1); for classified + linear it includes automatic secure_zero (§12.7). Documentation added to §4.6 making the idiom explicit.",
"docs_adjusted": true
},
{
"id": 100,
"name": "method_chaining_on_borrows",
"description": "x.sort().filter(.active).take(count: 10) — each method consumes and returns new value. But what if method returns ref? Can you chain on a borrow?",
"prior_art": "Rust builders use two patterns: by-value (methods take self and return Self, enabling clean chaining but preventing reuse) and by-mutable-borrow (methods take &mut self and return &mut Self, allowing reuse but requiring a named binding first since temporaries are dropped). RFC 3519 (arbitrary self types) generalizes method receivers beyond &self/&mut self/Box<Self> to any type implementing Receiver<Target=Self>. Swift's mutating methods on structs cannot chain because each call produces an immutable temporary; the workaround is to use classes (reference types) or non-mutating methods returning new copies. Kotlin's apply/also scope functions return the receiver object enabling side-effect chaining without explicit return-self boilerplate, while let/run return the lambda result for transformation chains. The fundamental tension is that borrowing-based chaining (&mut self -> &mut Self) conflicts with temporary lifetime rules, which is why owned-value chaining or scope-function patterns are preferred.",
"resolution": "Resolved by Proposal 12 part 2 (fx_design.md §16.1 and §16.9). Builder pattern uses 'own self' methods returning 'Self' (consuming-and-reproducing). Methods with 'ref mut self' must not return 'ref mut Self' — compile error M013 ('ref-mut-self method must not return ref mut Self; rewrite as own-self builder or split into two calls'). Rust's ref-mut-self-returning-ref-mut-Self pattern creates lifetime errors that are notoriously confusing for agent-generated code and human reviewers; own-self builders compose uniformly under FX's linearity model. The compiler generates in-place update where possible because the value flows through the chain as unique-owner, so the runtime cost matches imperative mutation while the observable semantics are 'one new value per method'. This is the one place FX systematically hides mutation behind immutable semantics — sound because linearity guarantees a single owner (§16.9).",
"docs_adjusted": true
},
{
"id": 101,
"name": "collision_linear_fail",
"description": "Linear resource in function with Fail: what happens to resource on fail path? defer mandatory? Compiler auto-insert drop? Dimension collision rule needed",
"prior_art": "Haskell LinearTypes (GHC proposal #0111, POPL 2018) explicitly leaves this unresolved: linear functions guarantee 'consumed exactly once IF the result is consumed exactly once,' so exceptions that abort bypass the guarantee -- linear values may leak. The Tweag blog (2020) shows the correct model uses linear logic's Top type (Either Top a), making exceptions uncatchable in linear contexts. ATS's linear type system similarly cannot prevent linear resource leaks through exceptions -- the ATS mailing list documents this as a known limitation. Rust sidesteps the problem with affine types: Drop trait auto-inserts destructors during panic unwinding, but double-panic aborts the process, and a 2021 lang-team proposal (#97) discusses banning unwinding from Drop entirely. Munch-Maccagnoni (arXiv:2510.23517, 2025) presents the first calculus reconciling true linearity with exceptions via Curry-Howard destructors: weakening performs a side-effect (calling the destructor), giving resource safety even under exceptions, with LIFO release order when exchange is not used.",
"resolution": "Resolved by Proposal 3 (§7.11 rewritten, see also §4.9, §13.3): FX adopts a Zig-style two-form cleanup model — 'defer expr;' runs at every scope exit, 'errdefer expr;' runs only when the scope exits via a Fail or Exn propagation. Both are scoped to the nearest enclosing lexical block (begin/try/catch arm/if-branch/match-arm/loop body) and fire in LIFO order. Rule G-Defer and G-ErrDefer restrict defer body effects to the enclosing subset minus Fail/Exn — preventing double-fault during unwinding. Rule G-Linear-Cleanup-On-Fail demands that for every Fail/Exn abort site F in a function body, every linear binding live at F has a matching defer or errdefer cleanup registered in an enclosing scope, otherwise compile error M011. The rule keys on liveness (drop(x) ends x's liveness; defer drop(x) keeps x live with registered cleanup), not on which mechanism 'satisfies' it. Unwinding order is specified: defer+errdefer interleave by declaration order, LIFO-reversed for execution, then Fail/Exn propagates. Async cancellation modeled as a typed Fail(Cancelled) raised at await; defers run via the same mechanism (§11.7). Div bodies mean the scope never exits, so defers never run — honest about divergence. Interaction with §13.3 inverse transitions specified: local defers/errdefers fire first within the transition body, then the machine-level inverse compensation chain walks back.",
"docs_adjusted": true
},
{
"id": 102,
"name": "collision_classified_fail",
"description": "Error value carries classified data. Handler logs error -> secret leaks. Error types must inherit security grade of contents",
"prior_art": "Jif (Cornell, Myers et al.) is the primary prior art: a security-typed Java extension where every value carries an information-flow label, the program-counter label (pc) tracks implicit flows through control structure, and exceptions are a known leak channel -- whether an exception is thrown can depend on secret data, creating a covert channel. Jif requires explicit declassification at designated points to release information. FlowCaml (Simonet, 2003) brings the same idea to OCaml with full label inference over a lattice of security levels, but is only termination-insensitive -- timing channels through exception paths and termination are explicitly out of scope. Askarov and Sabelfeld (ESORICS 2005) formalize how exception handling in security-typed languages leaks information and propose repairs. The fundamental lesson is that error types must inherit the security grade of the data that caused the error, and exception handlers at a lower security level must not observe whether the exception was thrown.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule I002, Appendix E.1): when a function body's Fail(E) sites produce error values containing classified (secret-graded) data, the declared effect row must contain Fail(secret E). Declaring Fail(E) with a classified payload is compile error I002 — no auto-classification. The secret E marker then propagates through the handler per noninterference: catch arms observing the payload inherit classified grade. Rigor-first rejects the Jif-style pc-label inference that could auto-classify — the programmer names 'secret' explicitly.",
"docs_adjusted": true
},
{
"id": 103,
"name": "collision_session_fail",
"description": "Session channel abandoned mid-protocol on fail. Session type must include abort branch or fail is forbidden in session code",
"prior_art": "Fowler, Lindley, Morris, and Decova (POPL 2019, 'Exceptional Asynchronous Session Types: Session Types Without Tiers') introduce Exceptional GV (EGV), the first integration of exception handling with asynchronous session types in a functional language, proving preservation, global progress, confluence, and termination. EGV adds session exceptions -- a restricted class of exception handler that propagates failure along linear session endpoints by cancelling the peer's channel, maintaining session progress even under exceptions. The Links web programming language implements EGV via elaboration to effect handlers: raise introduces a SessionFail effect, and try-otherwise elaborates to a handler that cancels session endpoints in the failed continuation. Kokke ('Rusty Variation') implements EGV-style deadlock-free sessions with failure in Rust, departing from exceptions to Result-based monadic errors. The key insight is that session types must include an explicit abort/failure branch in the protocol, or failure must be forbidden in session-typed code -- silent channel abandonment violates the session contract.",
"resolution": "Resolved by Proposal 5 (§6.8 composition note, §11.3 cancel primitive) as a reduction to existing infrastructure rather than a new rule. Session channels are linear (§11.3), so G-Linear-Cleanup-On-Fail (§7.11, M011) already demands cleanup on every Fail/Exn abort path. The missing piece was the cleanup primitive: §11.3 adds stdlib 'cancel(ch) : unit with Fail(Cancelled)' per EGV semantics (Fowler-Lindley-Morris-Decova POPL 2019), which propagates a typed Cancelled message to the peer and linearly consumes the channel. Canonical usage 'errdefer cancel(ch);' registers cleanup near channel acquisition. No new error code — M011 covers it once cancel is available.",
"docs_adjusted": true
},
{
"id": 104,
"name": "collision_borrow_async",
"description": "Borrow across await point. Caller may mutate/move data during suspension. Borrows cannot cross await, or region must outlive async computation",
"prior_art": "Rust is the canonical prior art: the borrow checker forbids holding references across .await points because the future may be moved between threads (the future state machine captures the borrow, but the referent may be mutated or moved during suspension). This is tracked across multiple rustc issues (#78938, #61211, #106688, #58884) and remains an ergonomic pain point -- workarounds include scoping borrows before await, async move blocks, and Arc cloning. Swift 6 addresses the analogous problem with Sendable checking and actor isolation: non-Sendable values cannot cross actor/isolation boundaries, and the compiler enforces that mutable state is snapshot before suspension points; SE-0414 (region-based isolation, 2024) reduces false positives by proving safe usage without Sendable conformance. Kotlin coroutines capture local variables by reference in the Continuation object, and after suspension a coroutine may resume on a different thread -- ThreadLocal mutations are explicitly documented as lost across suspension points, with synchronization left to the programmer. Go has no ownership system: goroutine closures capture variables by reference (not by value, unlike Java lambdas), creating pervasive data races that are only caught at runtime via the -race detector.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule L002, Appendix E.1): a borrow binding (shared or exclusive) live at an await(...) site is compile error L002. 'Live at await' uses the same liveness analysis as §6 grade checking. Scoping the borrow to before await, or cloning via @[copy], satisfies the rule. FX makes this a cross-dimension rule because the underlying risk (caller mutates or moves referent during suspension) is a consequence of async scheduling, not ownership alone — matches Rust's borrow-checker-across-await semantics without the workaround-heavy ergonomics.",
"docs_adjusted": true
},
{
"id": 105,
"name": "collision_ct_async",
"description": "CT and Async are contradictory — async has variable timing. Must be incompatible (compile error)",
"prior_art": "No programming language combines constant-time execution guarantees with async/await scheduling. CT-wasm (Watt et al., POPL 2019), FaCT (Cauligi et al., 2017/2019), and Jasmin all operate on synchronous, straight-line code where the compiler controls instruction selection and can verify timing independence. CT-wasm explicitly forbids secret-dependent control flow (br_if, if, call_indirect on secret values). Async scheduling introduces fundamentally non-deterministic timing: task preemption, executor thread migration, and queue depth all create observable timing variation that depends on system load rather than secret data, but an attacker cannot distinguish the two. The Constant-Time Wasmtime project (arXiv:2311.14246, 2023) achieves end-to-end verified CT compilation but only for synchronous wasm functions, relying on ARM DIT hardware for microarchitectural guarantees. Every real-world crypto library (libsodium, BoringSSL, HACL*, Libjade) runs cryptographic operations synchronously and avoids async entirely. The combination should be a hard compile-time error.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule E044, Appendix E.1): declaring 'with CT, Async' on the same function is compile error E044. No refinement permits the combination — the two dimensions are contradictory. Crypto functions that must appear to callers as async wrap a synchronous CT core in an await at the boundary, keeping the CT region sync-only. Matches the universal practice of real-world crypto libraries (libsodium, BoringSSL, HACL*, Libjade).",
"docs_adjusted": true
},
{
"id": 106,
"name": "collision_ct_fail",
"description": "fail on secret-dependent condition in CT context is timing channel. fail forbidden when condition depends on classified values in CT",
"prior_art": "CT-wasm (Watt et al., POPL 2019) enforces that secret values cannot be used as conditions in control flow instructions (br_if, if, br_table, call_indirect) -- only public values may branch. This directly implies that fail/raise on a secret-dependent condition is forbidden, since exception dispatch is control flow. FaCT (Cauligi et al., UCSD) transforms secret-dependent branches into constant-time select (cmov) sequences, eliminating branch-based timing channels, but has no exception mechanism -- errors must be handled by returning sentinel values in constant time. Jasmin (used by Libjade) operates at near-assembly level with systematic protections against timing and Spectre-v1, including zeroization of secrets, but again has no exception mechanism. The fundamental issue is that exception/trap propagation introduces non-deterministic timing (CT-wasm's formal model explicitly lists trap propagation as a source of non-determinism). The only sound approach is to forbid fail/raise when the condition depends on classified (secret) values in a CT context.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule I003, Appendix E.1): inside a 'with CT' function, fail(e) whose condition (surrounding if or match scrutinee) is classified is compile error I003. This specializes §12.5's general CT-branch rule to the control-flow effect Fail: dispatching an exception exposes the branch taken, leaking the secret. Remedy: compute a secret-independent result first (via ct_select or masked operation) and raise fail outside the secret region, or drop CT from the function's effect row. Follows CT-wasm's formal model of trap propagation as a timing source.",
"docs_adjusted": true
},
{
"id": 107,
"name": "collision_linear_async",
"description": "Linear value alive across await. Task cancellation must run deferred cleanups. Or linears consumed before await",
"prior_art": "Rust's async system is the primary prior art: ownership (affine types) interacts with async via Send/Sync bounds -- a future must be Send if it will be spawned on a multi-threaded executor, meaning all values captured across .await points must be Send. Non-Send types (Rc, RefCell borrows) cannot live across await points. Task cancellation (dropping a future) runs Drop on all captured values, providing cleanup but not guaranteeing linear consumption -- a cancelled task's linear resources are destroyed, not consumed. The async_session_types crate and Ferrite (CMU, Balzer et al.) implement session-typed channels over async Rust, leveraging move semantics to enforce protocol steps consume the channel linearly. Munch-Maccagnoni's 2025 calculus (arXiv:2510.23517) addresses the theoretical foundation: destructors-as-weakening ensures linear values are properly released even when control effects (including async cancellation) bypass normal flow. The practical tension is that task cancellation is an implicit Drop (affine), not an explicit consume (linear) -- true linear values must either be consumed before each await or protected by a defer/destructor mechanism.",
"resolution": "Resolved by Proposal 3 (§7.11 G-Linear-Cleanup-On-Fail + async cancellation modeled as typed Fail(Cancelled) per §7.11 scope-exit table). Async task cancellation raises a typed Fail(Cancelled) at the await site, so the same defer/errdefer infrastructure that handles sync Fail paths handles async cancellation uniformly. Rule M011 (gap #101) demands defer/errdefer cleanup for every linear binding live at every Fail abort site; async cancellation is simply another Fail site per this classification. No new error code — the Proposal 3 mechanism was sufficient.",
"docs_adjusted": true
},
{
"id": 108,
"name": "collision_monotonic_concurrent",
"description": "Two threads increment monotonic counter. Read-modify-write race. Monotonic mutation on atomic types requires atomic operations",
"prior_art": "LVars (Kuper and Newton, FHPC 2013, POPL 2014) are the foundational prior art: lattice-based data structures that allow only monotonically increasing writes and threshold reads (blocking until a lower bound), guaranteeing deterministic parallelism. The LVish library (PLDI 2014) extends this to practical Haskell with handlers and quiescence detection. CRDTs (Shapiro et al., 2011) solve the distributed version: state-based CvRDTs require values to form a join-semilattice with a monotonic merge function, guaranteeing eventual consistency without coordination. Bloom^L (Conway et al., SoCC 2012) combines monotonic logic programming with lattice types for coordination-free distributed computing -- monotonic code needs no locking, barriers, or consensus. Kuper's dissertation shows LVars and CvRDTs share the same mathematical framework (semilattice + monotonic updates), but CRDTs lack a 'threshold read' equivalent, which LVars provide for strongly consistent queries. For FX's monotonic counters and state, the key insight is that monotonic mutation on concurrent/atomic types requires either CAS-based atomic operations (for counters: fetch_and_add) or lattice merge (for richer state), and reads must be threshold-style or accept observing any past-consistent value.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule M012, Appendix E.1): a binding whose mutation dimension (§6.3 dim 18) is 'monotonic' or 'append_only' in a machine or module whose 'concurrency' (§13.10) is not 'single_thread' and whose store is not 'atomic(T)' is compile error M012. The safety condition (LVars, CvRDTs): concurrent monotonic updates are race-free when the underlying write is atomic (for scalars) or the merge is commutative/associative/idempotent (for lattice state). FX checks the atomic wrapper or lock-free concurrency declaration; user-defined lattice merge remains available via §6.5 PCMs.",
"docs_adjusted": true
},
{
"id": 109,
"name": "collision_ghost_runtime",
"description": "Ghost proof (grade 0) justifies unchecked runtime operation. sorry ghost proof used for unchecked access is trust violation",
"prior_art": "F* uses a Ghost effect (GTot) with a hard compiler-enforced barrier: ghost computations cannot appear in Tot contexts except when the return type is non-informative, and the erased type requires reveal (which incurs GTot) to unwrap, preventing accidental runtime use. Idris 2's QTT-based multiplicity 0 marks erased arguments at the type level -- pattern matching on 0-multiplicity values is a type error unless the value is uniquely inferrable from other arguments, giving a clean compile-time guarantee. Lean 4 uses the noncomputable keyword (refined in v4.29.0 for simpler semantics) to block code generation for definitions relying on axioms like Classical.propDecidable, with Prop values erased during compilation; real bugs have occurred where erasure interacted badly with the optimizer. Coq erases Prop during extraction (replacing proof terms with a canonical box), but this can break functor application (issue #5540); MetaCoq (POPL 2024) provides verified extraction with a certified erasure phase. Agda (since v2.6.1) supports @0/@erased annotations under the --erasure flag, tracking a run-time/compile-time mode distinction in the type checker and replacing erased higher-order arguments with placeholders; this is grounded in McBride's 'I Got Plenty o' Nuttin'' and Atkey's QTT semantics.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule P002, Appendix E.1): a ghost-graded value (grade 0, erased) appearing as the scrutinee of a runtime if/while/match, pattern guard, or array index is compile error P002. The Tier F erasure rule (§6.3) blocks this at code generation; P002 is the user-facing diagnostic emitted at the source site. Ghost values may appear only in erased positions: pre, post, decreases, assert, and inside verify blocks. Matches F* GTot, Idris 2 multiplicity 0, Agda @0 boundary.",
"docs_adjusted": true
},
{
"id": 110,
"name": "collision_multiple_fail",
"description": "Multiple Fail(E1), Fail(E2) in same function. Rule: single Fail(E) per function, combine with union type",
"prior_art": "Koka's row-polymorphic effect system tracks each exception-like behavior as a separate named effect in the function's type (e.g. <exn1,exn2,io>), with handlers composed by layering -- the type system ensures no unhandled effect escapes, though generic exn handlers can accidentally swallow named exceptions. Rust uses compound error enums with From trait implementations for automatic conversion via the ? operator (e.g. enum MyError { Io(io::Error), Json(json::Error) }), making error composition explicit but requiring boilerplate; crates like thiserror and anyhow reduce this. Swift SE-0413 (typed throws, accepted Dec 2023) adds throws(E) syntax allowing a single typed error per function, with the recommendation to use enums to combine multiple error cases -- this is essentially FX's 'single Fail(E), combine with union type' approach. Effect.ts for TypeScript automatically unions error types when composing Effect<Success, Error, Requirements> values, giving type-safe error channels where the compiler tracks all possible errors across composed operations. Java's multi-catch syntax (catch (IOException | SQLException e)) provides syntactic sugar but no type-level composition of error effects across function boundaries.",
"resolution": "Resolved by the §9.3 effect lattice — no new rule needed. Fail(E1) \\/ Fail(E2) = Fail(E1 | E2) follows from the lattice join definitionally. Function signatures name the full union; callers that handle one variant and propagate the other use standard row subtyping. The §6.8 composition section explicitly documents this reduction as 'multiple Fail effects follow from the §9.3 effect lattice — no collision, just algebra.' No error code required; no proposal change needed.",
"docs_adjusted": true
},
{
"id": 111,
"name": "collision_classified_linear_fail",
"description": "Three-way: secret key (classified + linear) in Fail function. Unzeroed secret in memory on fail path. Must defer secure_zero",
"prior_art": "Rust's zeroize crate (RustCrypto) uses core::ptr::write_volatile with atomic fences to prevent compiler optimization of zeroing, and the Zeroizing<T> wrapper implements Drop to auto-zero on scope exit -- but this is NOT airtight: Drop may not run on panic='abort', stack moves can leave copies (Pin is needed), and mem::forget bypasses Drop entirely. The fundamental tension is that Rust lacks true linear types (only affine -- values can be dropped without consumption), so the 'error path leaves secret unzeroed' scenario requires discipline rather than type enforcement. Verdagon's 'Higher RAII and Seven Arcane Uses of Linear Types' argues that true linear types (where values MUST be consumed, not just dropped) would solve this by making it a compile-time error to abandon a secret on any path. The secrecy crate layers on top of zeroize by wrapping secrets in a Secret<T> type that prevents cloning and Debug-printing, but still relies on Drop for cleanup. C++ faces the same issue: RAII destructors run during stack unwinding, but optimizers may elide memset calls on dead objects; SecureZeroMemory (Windows) and explicit_bzero (POSIX) are the OS-level countermeasures. No existing language enforces the three-way 'classified + linear + error path guarantees zeroing' at the type level -- FX's combination of classification labels, linear grades, and defer-based cleanup would be novel.",
"resolution": "Resolved as composition of Proposal 3 (§7.11 M011 defer/errdefer for linear) and §12.7 secure zeroing on drop. The compiler emits 'secure_zero(v); drop(v)' automatically when a classified-plus-linear binding leaves scope, including via Fail unwinding. No new error code. Example: an errdefer registering cleanup of a 'secret linear aes_key' runs through §12.7 on every abort path. FX's classified-by-default labels + linear grades + defer gives the full three-way guarantee at the type level — novel, documented in §6.8 as 'composition example rather than new rule'.",
"docs_adjusted": true
},
{
"id": 112,
"name": "collision_session_async_classified",
"description": "Three-way: encrypted session over async channel. Classified session state across await. Timing may leak protocol state info",
"prior_art": "Capecchi, Castellani, Dezani-Ciancaglini & Rezk (2010, 2014) combine session types with security levels in a pi-calculus setting, proving that well-typed processes guarantee both session safety and secure information flow (including controlled declassification) -- but this is purely theoretical with no async runtime or implementation. Honda, Vasconcelos & Yoshida (2000) assign security levels to channels in the pi-calculus with subtyping-based information flow control, embedding Volpano-Smith's secure imperative calculus. Jif (Cornell) provides label-based information flow in Java with compile-time and runtime enforcement, but has no session types or async. Padovani & Zavattaro (ECOOP 2025) study fair termination of asynchronous session types grounded in linear logic, addressing orphan-message freedom, but without security labels. No existing language or implementation combines all three dimensions (session types + async/await + information flow security labels) in a single type system -- the closest are pairwise combinations in the academic literature. FX's three-way collision is genuinely novel territory; the timing-leak concern (async scheduling revealing session protocol state) appears unaddressed even in the theoretical work.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule I004, Appendix E.1): sending classified data over a session channel from a 'with Async' context requires either 'with CT' or an explicit declassify at the send point. Otherwise compile error I004. The three-way collision is honest about a genuine limitation: even well-typed session code can leak protocol state via send latency when the data is secret. Valid responses: (a) synchronous CT region at the send (consistent with E044 — CT region must not itself be async), (b) explicit declassification per §12.4. Closes the gap the academic literature (Capecchi-Castellani-Dezani-Rezk 2014) left open — conservatively, at the cost of extra ceremony.",
"docs_adjusted": true
},
{
"id": 113,
"name": "collision_decimal_overflow",
"description": "dec64 has 16 significant digits. Computation exceeds. IEEE 754-2008 rounding? Trap? Promote? Precision dimension tracks error",
"prior_art": "Scheme/Racket's numeric tower provides the cleanest model: exact integers are arbitrary-precision, and inexactness is contagious -- mixing exact and inexact numbers yields inexact results, with explicit exact->inexact coercion required for lossy conversions. R7RS allows implementations to silently coerce exact results to inexact on overflow, but flags this as non-conformant behavior. Haskell's fromIntegral silently wraps when narrowing Integer to Int (a well-known pitfall; Galois's Cryptol project filed issue #637 to avoid unsafe fromInteger/fromIntegral), while Integer itself is unbounded. Python's built-in int is arbitrary-precision but OverflowError occurs at the int->float boundary when the integer exceeds float64 range. Rust's num-bigint crate uses TryFrom for narrowing BigInt to fixed-width types, returning Err on overflow (to_f64 returns infinity for huge values rather than failing). For FX's decimal collision specifically, IEEE 754-2008 decimal64 defines trap-or-round behavior via status flags (inexact, overflow, underflow), and the standard permits either signaling or default (round) handling per operation -- FX's precision dimension could track whether a computation has gone through a lossy path, similar to Scheme's exactness contagion but at the type level.",
"resolution": "Resolved by Proposal 5 (§6.8 new rule N002, Appendix E.1): a function with exact-decimal return type or parameter (decimal, dec32, dec64, dec96, dec128, dec256, dec512, dec1024) declaring 'with overflow(wrap)' is compile error N002. Wrap has no meaning for IEEE 754-2008 decimal arithmetic (which defines trap/round/saturate). Arbitrary-precision 'decimal' cannot overflow — its overflow annotation is 'exact'. Fixed-width decimals require 'with overflow(trap)' or 'with overflow(saturate)'. Mixing with 'exact' requires compiler-discharged proof the result fits — uses the §6.3 overflow preorder exact <= trap.",
"docs_adjusted": true
},
{
"id": 114,
"name": "collision_borrow_spawn",
"description": "Mutable borrow captured by spawned task. Exclusive access must be verified at spawn point",
"prior_art": "Rust's tokio::spawn requires T: Future + Send + 'static, meaning spawned tasks cannot borrow local data at all -- values must be moved in or wrapped in Arc<Mutex<T>> for shared access, and the 'static bound ensures the task owns all its data (common confusion: 'static means the TYPE outlives 'static, not the VALUE). Swift's Sendable protocol (Swift 5.5+, enforced strictly in Swift 6) prevents non-Sendable types from crossing concurrency boundaries; value types (structs, enums) are implicitly Sendable, while classes must guarantee thread-safe internal state; actors provide a Sendable-by-design mechanism for shared mutable state. Kotlin's structured concurrency ties coroutine lifetimes to CoroutineScope, ensuring child tasks cannot outlive their parent scope -- this sidesteps the borrow problem by scope-based lifetime management rather than ownership transfer, with automatic cancellation propagation. Go's goroutines capture variables by reference with no compiler enforcement (the only safety mechanism is the runtime race detector with -race flag), making loop-variable capture bugs and concurrent closure mutations a pervasive source of data races (Uber's 2022 study catalogued common patterns). Java's virtual threads (JEP 444) exposed ThreadLocal memory leaks at scale (50K threads = 50K copies of pooled-assumption caches), addressed by Scoped Values (JEP 487, finalized Java 25); structured concurrency remains in preview. FX's approach of verifying exclusive access at spawn point via ownership grades is closest to Rust's model but could be more ergonomic by allowing scoped borrows into structured-concurrency child tasks (which Rust's current spawn cannot express).",
"resolution": "Resolved by Proposal 5 (§6.8 new rule L003, Appendix E.1): 'spawn_in(group, closure)' inside a task_group (§11.7) permits the closure to capture borrows whose lifetimes outlive the surrounding group — structured-concurrency invariant ensures all spawned tasks complete before the task_group scope exits, so borrows live long enough by construction. This matches Rust's thread::scope (stable 2022). A bare unscoped 'spawn(closure)' requires every captured value to be 'own' or '@[copy]'; capturing any borrow from unscoped spawn is compile error L003. Diagnostic suggests the scoped alternative concretely. More ergonomic than Rust tokio::spawn's 'static bound because FX's task_group is first-class.",
"docs_adjusted": true
},
{
"id": 115,
"name": "lifetime_region_syntax_contradiction",
"description": "§2.2 states single quote ' does not appear anywhere in FX, and fx_lexer.md §6.3 lists ' explicitly as a non-token (lexer error outside strings and comments). But §8.2 uses the apostrophe-prefixed form <'r> for region parameters, §28.3 uses <'a> for session lifetimes, §7.10 uses ref('a) in closure signatures. Meanwhile §8.1 uses the kind form r: region without any sigil. Two incompatible surface syntaxes for the same concept, and the lexer explicitly forbids the character one of them uses.",
"prior_art": "Rust's 'a lifetime syntax uses apostrophe prefix; Cyclone pioneered region syntax with backtick; OCaml's 'a is a type variable not a lifetime; F* uses named kinds (region, heap, nat, type) for lifetime-like parameters without sigils; Agda/Idris/Lean do the same.",
"resolution": "Resolved by Proposal 4 (§2.7 rule 27, §3.13, §8.1, §7.10, §28.3): FX uses kind form uniformly. Region parameters are declared <r: region> at definition sites (parallel to <a: type>, <n: nat>, <eff: effect>) and used as ref(r) T at use sites. The 'static' identifier is the top-of-lattice region constant. Higher-ranked region bounds use forall(r: region). fn(...). FX has no apostrophe token anywhere — the single quote remains forbidden per §2.2. All spec examples in §8.2, §28.3, §7.10, §11.1, and Appendix E updated to kind form; lexer loses an exception it never implemented.",
"docs_adjusted": true
},
{
"id": 116,
"name": "bit_literal_nb_prefix",
"description": "§2.4 states that the prefix Nb denotes an N-bit literal, with example 8b11110000 being an explicit 8-bit literal. Under maximal munch, 8b parses as integer 8 followed by identifier b — ambiguous lexer rule. No grammar or lexer production exists for Nb.",
"prior_art": "Verilog N'b uses apostrophe separator (unavailable in FX per §2.2 / gap #115). Julia uses digit-count width inference: 0b11110000 has type UInt8 (8 digits), 0b1111111100000000 has type UInt16 (16 digits). Rust uses suffix: 0b11110000u8. Zig uses type ascription: @as(u8, 0b11110000).",
"resolution": "Resolved by Proposal 4 (§2.4, §3.2, fx_grammar.md §2.3): bit literal width equals digit count (leading zeros significant, underscores excluded). 0b1010=bits(4), 0b00001010=bits(8), 0b0000_1010=bits(8). This is Julia's rule applied to bit vectors. Type ascription (let x : bits(16) = 0b1010) or u8/i8-style suffix (0b1010u8) overrides and zero-extends. A literal whose digit count exceeds the ascribed width is compile error T051. No Nb prefix — width lives in the digits (for exact intent) or in the type (for explicit width with zero-extension). Lexer loses no exceptions; uses the existing 0b prefix only.",
"docs_adjusted": true
},
{
"id": 117,
"name": "ref_keyword_polysemy",
"description": "ref has two unrelated meanings distinguished only by parse context (disambiguation rule 26). ref x: T before a binding = borrow mode. ref(value) as expression = create mutable heap cell. Two opposite semantics under one keyword, LLM-hostile.",
"prior_art": "Rust separates borrow (&/&mut) from cells (RefCell::new). OCaml uses ref only for cells (borrowing is not a language concept). Haskell uses IORef/STRef. Swift uses inout for parameter passing and separate types for cells.",
"resolution": "Resolved by Proposal 4 (§2.3, §2.7 rule 26, §4.6): added 'cell' keyword for heap-allocated mutable cells. 'cell(initial)' constructs, x.get()/x.set(v) methods. 'ref' keyword is now exclusively borrow-mode (shared ref, ref mut exclusive, ref(r) T region-attached). The two keywords have no overlapping syntactic position. Grep is now reliable: 'ref' finds borrows, 'cell' finds mutable cells. Updated examples in §4.6, §7.10, §9.6 (run_state), §9.8 (fibonacci), §17.3 (decorator cache), §27.2 (ML value restriction). Keyword count 86 → 88.",
"docs_adjusted": true
},
{
"id": 118,
"name": "with_keyword_polysemy",
"description": "with has three unrelated meanings by position (disambiguation rule 17). Position 1: after return type = effect annotation. Position 2: record update '{ cfg with port: 9090 }'. Position 3: format override 'format json = json_defaults with { ... }'. Three meanings for one keyword — LLM-hostile.",
"prior_art": "Haskell uses with only for record update. OCaml uses it for record update and try/with. Python uses it for context managers. Scala uses it for trait composition. Koka uses it for effect handlers. Two meanings tolerated; three is unusual.",
"resolution": "Resolved by Proposal 4 (§2.7 rule 17, §3.4, §14.4): 'with' now has exactly one meaning — effect annotation after a return type. Record update and format override both use spread syntax '{...base, field: value}' (rule 16). Contract inheritance uses 'extends' (§14.9). Handler composition uses 'handle' (§9.6). Updated examples in §3.4 (records) and §14.4 (format override). One keyword, one meaning.",
"docs_adjusted": true
},
{
"id": 119,
"name": "format_keyword_polysemy",
"description": "format has two unrelated meanings by enclosing block (disambiguation rule 15). Standalone = bit-level hardware layout (§18.1). Inside contract = wire-format serialization binding (§14.4). Different field syntax, different semantics, same keyword.",
"prior_art": "No mainstream language overloads one keyword for bit-layout and serialization. SystemVerilog uses packed struct, Protobuf uses 'message', Rust uses #[repr] attributes for layout and serde derives for serialization. Clean separation is universal.",
"resolution": "Resolved by Proposal 4 (§2.3, §2.7 rule 15, §18.1, §18.2, §18.14, §30.1, §30.3, §30.5): hardware bit layouts renamed from 'format' to 'layout'. 'format' keeps its meaning inside contract blocks (serialization binding, §14.4). Rationale: most programmers associate 'format' with serialization (JSON/protobuf), not bit layouts; bit layout is the more specialized hardware concept deserving the specialized keyword. Fewer touch points (§18.1-18.14 and archetype examples) than renaming contract format. Appendix A closer list updated: 'end layout' replaces the standalone-format closer; 'end format' is gone because contract-nested format statements don't need a matching closer (they're a declaration inside the contract block). Keyword count 86 → 88 (cell + layout).",
"docs_adjusted": true
},
{
"id": 120,
"name": "end_keyword_proliferation",
"description": "end appears in 25+ typed-closer variants. While typed closers aid LLM parsing (unambiguous block termination), the proliferation creates cognitive load.",
"prior_art": "Ada uses typed end closers with ~10 variants. Ruby uses bare 'end' everywhere (simpler but loses self-documentation). Lua/Pascal/Elixir use generic end. Python has indentation-based blocks.",
"resolution": "Resolved by Proposal 4 (§2.3, Appendix A): merged clear synonyms rather than aggressive restructure (typed closers are self-documenting and worth keeping distinct for semantically distinct constructs). Merged: 'end test' now covers test, test_theory, test_metatheory (variant selected by @[theory]/@[metatheory] attribute, §23.6). 'end module' covers module, module type, module functor (§5.5). 'end fn' replaces 'end decorator' (decorators are @[decorator] fn, §17.3). 'end class' replaces 'end structure' (structures are @[structure] class, §16.6). 'end module' replaces 'end hardware' (hardware modules are @[hardware] module, §18.8). 'end register_file' subsumes 'end register' (no separate register closer). Net: ~29 variants → ~24 distinct closers. Attribute-based variant selection removes syntactic multiplicity while preserving semantic information in the source.",
"docs_adjusted": true
},
{
"id": 121,
"name": "codata_productivity_checking",
"description": "§3.5 literally notes 'productivity checking is not explicitly addressed in §3.5; this is a gap.' Codata types (streams, copatterns) require productivity checking — the coinductive analog of termination checking — to guarantee that every observation (head, tail) produces a value in finite time. Without productivity checking, a codata definition can silently diverge: unfold head => loop_forever(); tail => ...; would typecheck. The design specifies decreases for inductive termination but has no equivalent guardedness/productivity mechanism for coinductive definitions. This is a soundness concern: unproductive codata breaks the Tot guarantee.",
"prior_art": "Agda uses a syntactic guardedness checker for coinductive types: corecursive calls must be directly under a constructor (observation), with no intervening computation that could diverge. Agda also supports sized types (--sized-types) as an alternative where coinductive types carry a size annotation and productivity is checked structurally via size decrease on observations. Coq uses a syntactic guardedness condition similar to Agda's, checking that corecursive calls appear under cofixpoint constructors. Idris 2 uses a totality checker that handles both termination and productivity, with the 'covering' keyword for partial productivity proofs. The Coalgebraic approach (Basold & Geuvers 2016) provides a type-theoretic foundation for productivity via copattern matching with clock variables. Abel & Pientka's well-founded recursion approach unifies termination and productivity checking.",
"resolution": "Resolved by Proposal 1 (see §3.5, §6.3, §17.6): Dimension 20 (Size) added as a Tier S semiring grade domain (omega + 1) tracking observation depth for codata values. Codata construction via unfold<s> requires the Productive effect capability (granted by 'with Productive'), an explicit sized clause in the function signature, and syntactic guardedness (recursive references appear strictly inside destructor bodies, extending Coppo-Dezani guardedness and Abel-Pientka POPL 2013 copatterns to FX's multi-destructor codata). Size consumes one unit per destructor call; stream<s>(a) permits observations up to depth s. Inductive consumers (take, drop) use a decreases measure plus a 'pre n <= s' bound relating consumer depth to source size. The 'with Div' effect escapes productivity for genuinely non-productive definitions at the cost of trust level Sorry. Hardware signals under 'on rising(clk)' (§18.10) are sized streams at size omega, and temporal logic operators (§13.18) desugar to sized-stream quantification. docs_adjusted updated.",
"docs_adjusted": true
},
{
"id": 122,
"name": "axiom_consistency_checking",
"description": "Two contradictory axiom declarations (axiom a: x == 1; axiom b: x == 2;) can prove False, breaking the entire type system. §10.6 tracks axioms in the trust dimension (trust level Assumed) and says they compile in all modes, but specifies no mechanism to detect or prevent contradictory axiom sets. The trust dimension tracks usage (functions calling axiom-tainted code inherit trust Assumed) but not logical consistency of the combined axiom set. A single axiom False : prop would allow proving anything. This is a foundational soundness concern for any project using axiom declarations.",
"prior_art": "Lean 4 does not check axiom consistency — axioms are trusted by convention, and the user is responsible for ensuring they form a consistent theory. The Lean community maintains an informal list of axioms known to be consistent (propext, funext, choice, quot.sound). Coq similarly trusts axioms but provides the Print Assumptions command to list all axioms a theorem depends on, enabling manual auditing. Isabelle/HOL takes the most principled approach: new axioms are strongly discouraged in favor of definitional packages (typedef, function, inductive) that are consistency-preserving by construction; the 'axiomatization' command exists but is flagged. Metamath requires explicit axiom sets and provides tools to verify relative consistency. No mainstream proof assistant automatically checks axiom consistency (which is undecidable in general), but all provide auditing tools.",
"resolution": "Resolved by Proposal 14 (fx_design.md §10.6 expanded). Consistency is undecidable; FX relies on three composing mechanisms. (1) Provenance — every axiom should carry @[provenance(\"source\")] citing the paper/theorem/foundational category; missing provenance is warning W002. Workspace policy (§25.9) can escalate via 'require_axiom_provenance = true' in policy.fxpolicy, turning W002 into a build-breaking error. (2) Trust propagation — §10.6 Sorry/Assumed/Verified lattice propagates as min through the call graph; release builds require Verified and reject sorry, so axioms that gate real invariants surface as trust drops. (3) Supply-chain audit — §25.11 contract diff warns on new axioms at publish time, and §25.9 workspace signing ties each axiom introduction to an authorized signer. Provenance strings are audit-aid (not machine-verified — a malicious actor can write any string); real safety comes from trust propagation + supply-chain signing. 'fxc --show-axioms symbol' enumerates the transitive axiom set for any definition.",
"docs_adjusted": true
},
{
"id": 123,
"name": "smt_theory_selection",
"description": "The spec mentions SMT verification throughout (§10 refinements, §18.6 bit-vector decidability, §6.7 dependent grades) but never specifies which SMT-LIB theories/logics the compiler uses. Which logic for integer refinements — QF_LIA (linear), QF_NIA (nonlinear), AUFLIA (arrays+uninterpreted functions+linear)? Which for bit vectors — QF_BV? How does the compiler partition obligations across theories? Does it use a combined theory (ALL) or switch per-obligation? §18.6 mentions QF_BV for hardware but the general case is unspecified. Theory choice directly determines what's automatically provable vs what needs manual lemmas.",
"prior_art": "F* uses a fixed encoding into FOL with theory extensions: integers use QF_LIA/QF_NIA with fuel-controlled unfolding, bit vectors use QF_BV, and the overall logic is a combination theory. F* encodes refinement types as SMT assertions and relies heavily on Z3's E-matching for quantifier instantiation with pattern-based triggers. Dafny uses Boogie as an intermediate verifier which targets Z3 with AUFLIA + triggers, with a well-documented encoding of heap and arrays. Liquid Haskell uses a decidable fragment (QF_UFLIA — quantifier-free uninterpreted functions + linear integer arithmetic) to guarantee decidable checking at the cost of expressiveness. Why3 abstracts over multiple provers and lets the user choose which backend to target per-obligation. The key design choice is decidable-but-limited (Liquid Haskell) vs expressive-but-unpredictable (F*/Dafny).",
"resolution": "Resolved by Proposal 14 (fx_design.md §10.16 new section). Fixed default theory set: QF_UFLIA (uninterpreted functions + linear integer arithmetic), QF_BV (fixed-width bit vectors, §18), QF_NRA (nonlinear real arithmetic, §3.1 fractions), QF_FP (IEEE 754 floating point, §3.11 precision), plus bounded quantifiers with E-matching triggers. User-defined spec functions axiomatized by default (signature + post as SMT axioms); body-level reflection opt-in via @[reflect]. Per-obligation escape hatch: 'by smt(solver: z3, theories: [QF_NIA], timeout: 30s)' as a tactic call uses existing call_args grammar. Z3 default solver; CVC5 available where shipped. Solver version is part of §10.13 proof cache key.",
"docs_adjusted": true
},
{
"id": 124,
"name": "fuel_unfolding_control",
"description": "F* uses fuel/ifuel to control how many times recursive function definitions are unfolded in SMT encoding. FX §10.12 mentions proof budgets and timeouts but specifies no equivalent mechanism. Without fuel control: (1) recursive functions may be unfolded indefinitely, causing SMT timeout, (2) no way to tune per-definition unfolding depth, (3) proofs about recursive functions are unpredictable — sometimes Z3 finds the proof with N unfoldings, sometimes it needs N+1 and times out. §10.7 lists tactics (ring, omega, simp) but these are proof strategies, not unfolding controls. The design's opaque-by-default body visibility (§10.15) partially mitigates this but doesn't address recursive definitions that ARE visible.",
"prior_art": "F* provides --fuel N (controls unfolding depth of recursive data type matching, default 2) and --ifuel N (controls unfolding of recursive function definitions, default 1), tunable globally and per-definition via #push-options/#pop-options. These are the primary knobs for proof predictability. Dafny has an opaque attribute and reveal statements for manual unfolding control, plus {:fuel N} attributes per function. Lean 4 uses a different approach: simp lemmas and rfl/unfold tactics give explicit control over when definitions are unfolded, with no automatic unfolding in the kernel. Coq similarly requires explicit unfold/simpl/cbn tactics. The automated-prover approach (F*/Dafny) needs fuel knobs; the tactic-prover approach (Lean/Coq) gets explicit control for free but requires more manual proof.",
"resolution": "Resolved by Proposal 14 (fx_design.md §10.15 clarification). FX ships no fuel mechanism. 'reveal f;' inside a verify block unfolds ONE level of the named definition; recursive calls in f's body remain opaque. For deeper reveals, write multiple 'reveal' statements. Matches Lean 4 'simp only [f]' and Dafny 'reveal f()'. The opaque-by-default model (§10.15) plus explicit single-level reveals replaces the F* fuel dial with explicit, auditable decisions visible in the source — an agent reading the verify block sees precisely which definitions are in scope.",
"docs_adjusted": true
},
{
"id": 125,
"name": "char_literal_coercion",
"description": "§2.4 says 'No single-quoted character literals — use \"c\" and let the type system resolve to char when the context expects it.' But the coercion rule is never specified. Questions: (1) Is \"c\" (one-char string) implicitly coerced to char when expected type is char? (2) What about \"ab\" in char context — compile error? What error message? (3) What about \"\" (empty string) in char context? (4) Is this a subtyping relationship (string <: char when length 1) or a special literal form? (5) How does this interact with f-strings — is f\"{x}\" ever char? (6) Does the refinement system prove the length, or is it a syntactic check on the literal?",