feat(compiler): remove @gc from the language — GC-ness is fully inferred (7b)
`@gc` is no longer part of the language: a developer never writes or mentions
it. GC-ness is decided entirely by inference (structural cycles + demand
promotion), which the earlier 7b commits made complete and precise.
- parser: `@gc` on a class is now WO-E104 ("GC-ness is inferred; run
`woc --dump-gc`. Remove it."). `is_gc` stays false; the class classifies by
inference. No `.wo` in the repo carries `@gc` anymore.
- types.ml: retired the WO-W201 machinery (suggest_gc_annotation,
has_recursive_structure(_type), has_unique_field, gc_suggestion_code) — it
suggested `@gc`, now obsolete since inference traces exactly those classes.
- runner.ml: deleted the 8 WO-W201 gc-suggestion test blocks; the @gc-exemption
test's `Cache` is made self-referential so inference classifies it gc without
an annotation.
- fixtures: dropped `@gc` from rc.wo (Cache demand-promotes via its escape),
elision.wo (Cache given a self-ref to stay structurally gc for the rc-elision
dump), pricing-demo.wo (PriceCache doesn't escape -> now owned), and the
abandoned-cycle/budget-steps corpus (Node is structurally gc). rc.wo keeps a
placeholder comment line so its line-indexed rc assertions hold. Goldens
re-blessed.
- docs: error catalog gains WO-E104 and marks WO-W201 retired; gc-cycle README
records the keyword removal.
Verified: woc-test 553/0 (was 566 minus the 13 retired WO-W201 checks),
test_diag 14/0, oop-e2e 79/0, employee 8/0, log-watcher 7/0. `git grep '@gc'`
finds only comments — success criterion 1 met.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1cedbda0bb
commit
2379a95016
14 changed files with 50 additions and 169 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -101,3 +101,4 @@ prototypes/wo-db/
|
|||
__pycache__/
|
||||
*.pyc
|
||||
dist/
|
||||
docs/examples/*/target/
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ let fail (st : state) (site : Ast.pos) (code : string) (message : string) : 'a =
|
|||
|
||||
let syntax_code = Diag.parsing_prefix ^ "01" (* WO-E101: generic syntax error *)
|
||||
let table_code = Diag.parsing_prefix ^ "02" (* WO-E102: invalid @table(...) configuration *)
|
||||
let gc_removed_code = Diag.parsing_prefix ^ "04" (* WO-E104: `@gc` — GC-ness is inferred *)
|
||||
|
||||
(* haxe-parity Task 2: the haxe keyword verdict table's `inline` row —
|
||||
"adopt (values): const compile-time values; inline *functions*
|
||||
|
|
@ -332,10 +333,19 @@ let parse_type_annotations (st : state) : type_annotations =
|
|||
let table = ref None in
|
||||
while peek st = Token.At do
|
||||
ignore (advance st);
|
||||
let at_pos = peek_pos st in
|
||||
let name = expect_ident st "annotation name" in
|
||||
(match name with
|
||||
| "gc" ->
|
||||
is_gc := true;
|
||||
(* iteration 7b: `@gc` is not part of the language — GC-ness is inferred
|
||||
(structural cycles + demand promotion; see `woc --dump-gc`). Reject it
|
||||
rather than accept a meaningless annotation. `is_gc` stays false. *)
|
||||
Diag.Collector.add st.collector
|
||||
(Diag.error ~code:gc_removed_code ~file:st.file ~line:at_pos.Ast.line
|
||||
~col:at_pos.Ast.col
|
||||
~message:
|
||||
"`@gc` is not a valid annotation: GC-ness is inferred by the \
|
||||
compiler (run `woc --dump-gc`). Remove it." ());
|
||||
skip_paren_args st
|
||||
| "table" -> table := Some (parse_table_cfg st)
|
||||
| _ -> skip_paren_args st);
|
||||
|
|
|
|||
|
|
@ -302,34 +302,6 @@ let with_builtin_records (syms : symbols) : symbols =
|
|||
{ acc with classes = StringMap.add name info acc.classes })
|
||||
syms predeclared_records
|
||||
|
||||
let rec has_recursive_structure (cls : class_info) : bool =
|
||||
List.exists (fun (_, ty, _, _) ->
|
||||
match ty with
|
||||
| Ast.Scalar name -> name = cls.name (* direct self-reference *)
|
||||
| Ast.Ref name -> name = cls.name
|
||||
| Ast.Multi name -> name = cls.name (* multi Self *)
|
||||
| Ast.Map (k, v) -> k = cls.name || v = cls.name (* map<_, Self> / map<Self, _> *)
|
||||
| Ast.Backlink _ -> false
|
||||
| Ast.Nullable inner -> has_recursive_structure_type inner cls.name
|
||||
) cls.fields
|
||||
|
||||
and has_recursive_structure_type (ty : Ast.field_ty) (cls_name : string) : bool =
|
||||
match ty with
|
||||
| Ast.Scalar name -> name = cls_name
|
||||
| Ast.Ref name -> name = cls_name
|
||||
| Ast.Multi name -> name = cls_name
|
||||
| Ast.Map (k, v) -> k = cls_name || v = cls_name
|
||||
| Ast.Backlink _ -> false (* a computed inverse holds no owned structure *)
|
||||
| Ast.Nullable inner -> has_recursive_structure_type inner cls_name
|
||||
|
||||
(* @unique field -> persistent identity (plan's "When NOT to emit": a
|
||||
class with a @unique field should not get the @gc suggestion even if
|
||||
it also has recursive/shared structure). Annotation *names* only, per
|
||||
Ast.field's own doc comment -- "unique" is what parse_field stores for
|
||||
a bare `@unique`. *)
|
||||
let has_unique_field (cls : class_info) : bool =
|
||||
List.exists (fun (_, _, _, anns) -> List.mem "unique" anns) cls.fields
|
||||
|
||||
(* iteration 7b: GC-ness is inference-first. A class is traced if the inference
|
||||
pass put it in `syms.traced` (structural cycle, or a Phase-2 demand
|
||||
promotion), OR — as a temporary bridge until demand promotion lands — it
|
||||
|
|
@ -338,15 +310,6 @@ let is_gc_class (syms : symbols) name =
|
|||
StringSet.mem name syms.traced
|
||||
|| (try (StringMap.find name syms.classes).is_gc with Not_found -> false)
|
||||
|
||||
let gc_suggestion_code = Diag.warning_prefix ^ "201" (* WO-W201 *)
|
||||
|
||||
let suggest_gc_annotation ~file (cls : class_info) (collector : Diag.Collector.t) : unit =
|
||||
if not cls.is_gc && Option.is_none cls.table && not (has_unique_field cls)
|
||||
&& has_recursive_structure cls then
|
||||
Diag.Collector.add collector
|
||||
(Diag.warning ~code:gc_suggestion_code ~file ~line:cls.pos.line ~col:cls.pos.col
|
||||
~message:(Printf.sprintf "%s has recursive/shared structure that borrow checker cannot prove. Consider adding @gc if this is an ephemeral in-memory cache. If this maps to a database table, keep owned (default)." cls.name) ())
|
||||
|
||||
(* Ast.field_ty -> the internal resolved typ. Hoisted out of
|
||||
typecheck_program (where it was a local closure) so the .wob emitter
|
||||
can reach the same mapping instead of keeping a second copy of it;
|
||||
|
|
@ -515,9 +478,7 @@ let collect_declarations ~file (prog : program) (collector : Diag.Collector.t) :
|
|||
| Some (existing : class_info) ->
|
||||
report_duplicate_decl collector ~file ~kind:"class" ~name:c.name ~pos:c.pos
|
||||
~first_pos:existing.pos
|
||||
| None ->
|
||||
classes := StringMap.add c.name info !classes;
|
||||
suggest_gc_annotation ~file info collector)
|
||||
| None -> classes := StringMap.add c.name info !classes)
|
||||
| Ast.Interface i ->
|
||||
let methods = List.map (fun (m : Ast.method_sig) ->
|
||||
{ name = m.name;
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@
|
|||
22:5 ASSIGN self.prices = amount
|
||||
25:3 METHOD adopt(take other: Product) -> Product
|
||||
26:5 RETURN other
|
||||
31:1 CLASS PriceCache @gc
|
||||
32:3 FIELD entries: map<Text, Int>
|
||||
35:1 TYPE Note
|
||||
36:3 FIELD id: Id
|
||||
37:3 FIELD body: Text
|
||||
38:3 FIELD created: Timestamp = now()
|
||||
41:1 METHOD discount(mut amount: Int, take pct: Int) -> Int
|
||||
42:3 RETURN amount
|
||||
30:1 CLASS PriceCache
|
||||
31:3 FIELD entries: map<Text, Int>
|
||||
34:1 TYPE Note
|
||||
35:3 FIELD id: Id
|
||||
36:3 FIELD body: Text
|
||||
37:3 FIELD created: Timestamp = now()
|
||||
40:1 METHOD discount(mut amount: Int, take pct: Int) -> Int
|
||||
41:3 RETURN amount
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class Product {
|
|||
}
|
||||
}
|
||||
|
||||
@gc
|
||||
class PriceCache {
|
||||
entries: map<Text, Int>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
k0 TEXT "Cache"
|
||||
k1 TEXT "Holder"
|
||||
k2 TEXT "hits"
|
||||
k3 TEXT "cache"
|
||||
k4 TEXT "read"
|
||||
k5 TEXT "proven"
|
||||
k6 TEXT "main"
|
||||
k7 INT 41
|
||||
k8 INT 1
|
||||
k3 TEXT "peer"
|
||||
k4 TEXT "cache"
|
||||
k5 TEXT "read"
|
||||
k6 TEXT "proven"
|
||||
k7 TEXT "main"
|
||||
k8 INT 41
|
||||
k9 INT 1
|
||||
== CLASSES ==
|
||||
c0 Cache flags=gc fields=[hits:SCALAR]
|
||||
c0 Cache flags=gc fields=[hits:SCALAR, peer:GCREF]
|
||||
c1 Holder flags=- fields=[cache:GCREF]
|
||||
== INTERFACES ==
|
||||
== VTABLES ==
|
||||
|
|
@ -33,7 +34,7 @@ m2 main args=0 regs=6 [free fn] [ENTRY]
|
|||
drops: pc 14 owned={} gc={r0}
|
||||
drops: pc 15 owned={} gc={}
|
||||
0000 NEW r0, c0
|
||||
0001 LOADK r1, k7
|
||||
0001 LOADK r1, k8
|
||||
0002 SETF r0, f0, r1
|
||||
0003 NEW r1, c1
|
||||
0004 MOVE r2, r0
|
||||
|
|
@ -42,7 +43,7 @@ m2 main args=0 regs=6 [free fn] [ENTRY]
|
|||
0007 MOVE r4, r1
|
||||
0008 CALL r4, m1
|
||||
0009 MOVE r3, r4
|
||||
0010 LOADK r5, k8
|
||||
0010 LOADK r5, k9
|
||||
0011 ADD r2, r3, r5
|
||||
0012 BUILTIN r2, r2, print_int
|
||||
0013 DROP r1
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
-- the emitted body must contain NO borrow op and NO rc op at all — the
|
||||
-- disassembly below is the evidence. `main` is the contrast: an escape
|
||||
-- into a field is a KEPT acquire, so RC_INC does appear there.
|
||||
@gc
|
||||
class Cache {
|
||||
hits: Int
|
||||
peer: ?Cache
|
||||
}
|
||||
|
||||
class Holder {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ class Product {
|
|||
}
|
||||
}
|
||||
|
||||
@gc
|
||||
class PriceCache {
|
||||
entries: map<Text, Int>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@gc
|
||||
-- Cache is inferred `gc`: it escapes (returned out of `escaping`), so demand promotion traces it.
|
||||
class Cache {
|
||||
n: Int
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1231,98 +1231,6 @@ let () =
|
|||
check "Float is not a builtin scalar" (not (Types.is_builtin_scalar "Float"));
|
||||
check "Timestamp is a builtin scalar" (Types.is_builtin_scalar "Timestamp")
|
||||
|
||||
let () =
|
||||
(* class Node { next: Node } -- direct self-reference, no @gc, no
|
||||
@table, no @unique field: WO-W201 must fire, at the class's own
|
||||
(real) file/line/col, and a warning-only run must still exit 0
|
||||
(Diag.Collector's severity-keyed exit-code contract). *)
|
||||
let path = "node.wo" in
|
||||
let _, collector = typecheck_str ~file:path "class Node {\n next: Node\n}\n" in
|
||||
let diags = Diag.Collector.diagnostics collector in
|
||||
check_eq "gc-suggestion: exactly one diagnostic (WO-W201)" ~expected:1
|
||||
~actual:(List.length diags) string_of_int;
|
||||
(match diags with
|
||||
| [ d ] ->
|
||||
check "gc-suggestion: code is WO-W201" (d.Diag.code = "WO-W201");
|
||||
check "gc-suggestion: severity is Warning" (d.Diag.severity = Diag.Warning);
|
||||
check "gc-suggestion: real file/line/col (node.wo:1:1, the `class` token)"
|
||||
(d.Diag.site.Diag.file = path && d.Diag.site.Diag.line = 1 && d.Diag.site.Diag.col = 1)
|
||||
| _ -> check "gc-suggestion: exactly one diagnostic" false);
|
||||
check_eq "gc-suggestion: a warning-only run exits 0, not 1" ~expected:0
|
||||
~actual:(Diag.Collector.exit_code collector) string_of_int
|
||||
|
||||
let () =
|
||||
(* @gc class Cache { next: Cache } -- same recursive shape as above,
|
||||
but already @gc: WO-W201 must NOT fire. *)
|
||||
let _, collector = typecheck_str ~file:"cache.wo" "@gc\nclass Cache {\n next: Cache\n}\n" in
|
||||
check_eq "gc-suggestion: @gc class reports nothing" ~expected:0
|
||||
~actual:(List.length (Diag.Collector.diagnostics collector)) string_of_int
|
||||
|
||||
let () =
|
||||
(* @table(...) class Node2 { next: Node2 } -- recursive, but DB-backed
|
||||
via @table: WO-W201 must NOT fire (plan: "@table -> must be owned"). *)
|
||||
let _, collector =
|
||||
typecheck_str ~file:"node2.wo"
|
||||
"@table(name: \"nodes\")\nclass Node2 {\n next: Node2\n}\n"
|
||||
in
|
||||
check_eq "gc-suggestion: @table class reports nothing" ~expected:0
|
||||
~actual:(List.length (Diag.Collector.diagnostics collector)) string_of_int
|
||||
|
||||
let () =
|
||||
(* class Node3 { id: Id @unique; next: Node3 } -- recursive, but has a
|
||||
@unique field (persistent identity): WO-W201 must NOT fire (plan's
|
||||
"When NOT to emit" list, second bullet). *)
|
||||
let _, collector =
|
||||
typecheck_str ~file:"node3.wo"
|
||||
"class Node3 {\n id: Id @unique\n next: Node3\n}\n"
|
||||
in
|
||||
check_eq "gc-suggestion: class with a @unique field reports nothing" ~expected:0
|
||||
~actual:(List.length (Diag.Collector.diagnostics collector)) string_of_int
|
||||
|
||||
let () =
|
||||
(* class Point { x: Int; y: Int } -- a plain data struct, no
|
||||
recursive/shared fields: WO-W201 must NOT fire either. *)
|
||||
let _, collector =
|
||||
typecheck_str ~file:"point.wo" "class Point {\n x: Int\n y: Int\n}\n"
|
||||
in
|
||||
check_eq "gc-suggestion: simple data struct reports nothing" ~expected:0
|
||||
~actual:(List.length (Diag.Collector.diagnostics collector)) string_of_int
|
||||
|
||||
let () =
|
||||
(* class Calc { items: multi Item } (golden/ast/body-statements.wo's own
|
||||
shape) -- a `multi` field of an UNRELATED type, not `multi Self`.
|
||||
has_recursive_structure must key off self-reference, not "any multi
|
||||
field": a bare `Ast.Multi _ -> true` would spuriously fire WO-W201
|
||||
on every plain data class that merely holds a collection. *)
|
||||
let _, collector =
|
||||
typecheck_str ~file:"calc.wo" "class Calc {\n items: multi Item\n}\n"
|
||||
in
|
||||
check_eq "gc-suggestion: unrelated `multi Item` field reports nothing" ~expected:0
|
||||
~actual:(List.length (Diag.Collector.diagnostics collector)) string_of_int
|
||||
|
||||
let () =
|
||||
(* class Bucket { entries: map<Text, Item> } -- same over-trigger risk
|
||||
for `map`, neither side self-referential. *)
|
||||
let _, collector =
|
||||
typecheck_str ~file:"bucket.wo" "class Bucket {\n entries: map<Text, Item>\n}\n"
|
||||
in
|
||||
check_eq "gc-suggestion: unrelated `map<Text, Item>` field reports nothing" ~expected:0
|
||||
~actual:(List.length (Diag.Collector.diagnostics collector)) string_of_int
|
||||
|
||||
let () =
|
||||
(* class Tree { children: multi Tree } -- `multi Self` must still fire
|
||||
(the plan's own literal example of the heuristic). *)
|
||||
let path = "tree.wo" in
|
||||
let _, collector =
|
||||
typecheck_str ~file:path "class Tree {\n children: multi Tree\n}\n"
|
||||
in
|
||||
let diags = Diag.Collector.diagnostics collector in
|
||||
check_eq "gc-suggestion: `multi Self` still fires WO-W201" ~expected:1
|
||||
~actual:(List.length diags) string_of_int;
|
||||
match diags with
|
||||
| [ d ] -> check "gc-suggestion: `multi Self` diagnostic is WO-W201" (d.Diag.code = "WO-W201")
|
||||
| _ -> check "gc-suggestion: `multi Self` exactly one diagnostic" false
|
||||
|
||||
let () =
|
||||
(* class BadExample { code: INVALID_TYPE } -- INVALID_TYPE is not a
|
||||
builtin, class, or interface: WO-E225 must fire, at
|
||||
|
|
@ -1955,13 +1863,15 @@ let () =
|
|||
(Option.is_some (find_substring ~needle:"previous loop iteration" d.Diag.message)))
|
||||
|
||||
let () =
|
||||
(* @gc is exempt from all of it (spec rule 5): the exact shape that is
|
||||
WO-E301 above is silent here, and produces no move-table entries
|
||||
either — a @gc transfer is an rc site, not a move. *)
|
||||
(* A traced (gc) class is exempt from all of it (spec rule 5): the exact
|
||||
shape that is WO-E301 above is silent here, and produces no move-table
|
||||
entries either — a gc transfer is an rc site, not a move. `Cache` is
|
||||
self-referential (`peer: ?Cache`), so inference classifies it gc without
|
||||
any annotation (iteration 7b). *)
|
||||
let src =
|
||||
"@gc\n\
|
||||
class Cache {\n\
|
||||
"class Cache {\n\
|
||||
\ n: Int\n\
|
||||
\ peer: ?Cache\n\
|
||||
}\n\
|
||||
\n\
|
||||
fn keep(take c: Cache) -> Int {\n\
|
||||
|
|
|
|||
|
|
@ -209,13 +209,14 @@ opcode-27/28 retirement, and the sweep list.
|
|||
|
||||
**Phase 2b (landed).** Demand promotion: the ownership pass, run in collect
|
||||
mode, promotes any class whose value *must escape* (returned, stored where it
|
||||
outlives its scope) — the acyclic-but-aliased case (`PriceCache`) inference by
|
||||
structure cannot see. So GC-ness is now fully inferred (cycles by structure +
|
||||
aliasing by demand), and `@gc` is redundant everywhere. What remains is
|
||||
*removing the `@gc` keyword itself* — the parser rejecting it, and the RC/`@gc`
|
||||
golden + `test_diag` assertions being rewritten — which is coupled to Phase 3
|
||||
(the RC machinery those tests cover is deleted there), so the keyword removal
|
||||
lands with Phase 3.
|
||||
outlives its scope) — the acyclic-but-aliased case inference by structure cannot
|
||||
see, targeting the escaping projection's type precisely. So GC-ness is fully
|
||||
inferred: cycles by structure + aliasing by demand.
|
||||
|
||||
**`@gc` removed from the language (landed).** The keyword is now a hard error
|
||||
(`WO-E104`): a developer never writes or mentions it; `woc --dump-gc` shows what
|
||||
inference decided. WO-W201 (which suggested `@gc`) is retired. No `.wo` in the
|
||||
repo carries `@gc`. This is the front-end half of iteration 7b, complete.
|
||||
|
||||
When 7b lands, the acceptance is:
|
||||
- `woc --dump-gc docs/examples/gc-cycle` classifies `Node gc (cycle …)` /
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ half of the story ("moved here" / "borrowed here" / etc.).
|
|||
| WO-E101 | generic syntax error: an unexpected token where the grammar expected something else, including running off the end of the file inside an unclosed block/type/interface body. Declaration-level recovery syncs to the next top-level keyword so one bad declaration yields one diagnostic, not a cascade. | `expected ')' or ',', got NEWLINE` |
|
||||
| WO-E102 | an invalid `@table(...)` configuration: `name` given twice, an `index` with no columns, or an argument key other than `name`/`index`. | `@table(name: ...) given twice` |
|
||||
| WO-E103 | haxe-parity Task 2. `inline fn ...` — the haxe keyword verdict table's own reject half of the `inline` row (`const` values are the adopted half). The whole declaration is discarded by the usual top-level recovery, same as any other bad declaration. | `` `inline fn` is rejected — optimization is the compiler's job `` |
|
||||
| WO-E104 | iteration 7b. `@gc` on a class — the annotation is gone: GC-ness is inferred (structural cycles + demand promotion; `woc --dump-gc`). The annotation is skipped for recovery and the class classifies by inference. | `` `@gc` is not a valid annotation: GC-ness is inferred by the compiler (run `woc --dump-gc`). Remove it. `` |
|
||||
|
||||
## WO-E2xx / WO-W2xx — types (Task 6, `compiler/src/types.ml`; WO-E214 Task 8, `compiler/bin/main.ml`; WO-E215 plan 3 Task 2, `compiler/src/types.ml`; WO-E210/E216–E218/W202 haxe-parity Task 1, `compiler/src/types.ml`; WO-E201 haxe-parity Task 2, `compiler/src/types.ml`; WO-E208 haxe-parity Task 3, `compiler/src/types.ml`; WO-E203 haxe-parity Task 4, `compiler/src/types.ml`)
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ half of the story ("moved here" / "borrowed here" / etc.).
|
|||
| --- | --- | --- |
|
||||
| WO-E201 | haxe-parity Task 2. An `and`/`or` operand whose type is confidently known (the same narrow, "stay silent when underivable" deriver WO-E209 uses — `confident_typ`) and is not `Bool` — this language has no truthiness. Reserved since Task 6, its first real emission site. Haxe-parity Task 3 gave it two more sites: a `switch` expression's arms disagree on their yielded type (the switch's own type is fixed by the first arm — types.ml's "first wins" convention — every later arm is checked against it, via the regular `.typ` inference, not `confident_typ`; since haxe-parity Task 4 the comparison is *structural* — two typedef records with the same shape are the same type, `typ_equal`); and (review fix, Critical 2) a `case` value whose representation (`WO_K_TEXT` vs. `WO_K_SCALAR`) doesn't match the switch subject's — a real VM segfault if unchecked (a `Text` subject picks EQS, and EQS's `str_check` dereferences whatever sits in a mismatched `Int` case value's register), checked via `confident_typ`, silent when either side is unresolved. Haxe-parity Task 4 added the union-subject site: a `case` value over a confidently union-typed subject that does not name one of that union's variants (a misspelled variant, a variant of some other union, or a plain literal — union arms match variants, never values). Task 4's fix round 1 added three inverse/porosity sites, each a reviewer-reproduced silent-wrong-behavior hole: a variant-named `case` over a confidently **`?Union`** subject (it can never match — `switch` does not narrow `?T`; the message points at handling nil first, since forced handling is Task 6's), a variant-named `case` over a confidently **non-union** subject (`switch n { case Lo: }` over `n: Int` silently ordinal-matched), and a **cross-union `==`/`!=`** (`X == P` from two different bare unions was silently true whenever the ordinals matched; same-union comparison stays legal). Lexical scope wins at every one of these sites — a local sharing a variant's name is never misread as one. | `` `Wat` is not a variant of union `Kind` `` |
|
||||
| WO-E203 | haxe-parity Task 4 (`typedef` records + enum payload variants). A payload variant's argument count doesn't match its declaration, at either of the two places payload fields are positional: a construction (`Failed("a", "b")` against `Failed(reason: Text)`) or a `switch` pattern (`case Failed(a, b):`). The pattern site also rejects a non-name argument (`case Failed("x"):` — payload fields are bound positionally, never matched by value) and a payload-binding pattern sharing its arm with other values (`case Failed(r), Pending:` — the binding would be meaningless on the other match). Reserved since plan 2 Task 6; these are its first real emission sites, scoped to variant payloads only — user `fn`/method call arity is still the emitter's WO-E403, unchanged (see "Reserved, not yet emitted" below for the history of that gap). | `` variant `Failed` of `Status` takes 1 payload argument(s), given 2 `` |
|
||||
| WO-W201 *(warning)* | a class has recursive/shared structure (a field, directly or through `ref`/`multi`/`map`/`?`, refers back to its own class) that the ownership pass cannot prove disjoint, has no `@table`, and has no `@unique` field — suggests `@gc`. | `Node has recursive/shared structure that borrow checker cannot prove. Consider adding @gc if this is an ephemeral in-memory cache. If this maps to a database table, keep owned (default).` |
|
||||
| WO-W201 *(retired, iteration 7b)* | Suggested `@gc` for a recursive/shared class. Retired: `@gc` no longer exists (WO-E104) and inference classifies exactly these classes as traced automatically, so the suggestion is obsolete. | *(no longer emitted)* |
|
||||
| WO-E202 | a `.field` access names a field that the base's class (a *declared* class — an unresolved/placeholder expression type never triggers this) doesn't have. | `unknown field \`price\` on \`Product\`` |
|
||||
| WO-E206 | a constructor literal (`ClassName { ... }`) omits a field the class declares that is neither defaulted nor nullable. Haxe-parity Task 4 narrowed it from "omits any field with no default was already the rule, but defaults were unenforceable" to the real omittability rule: a field with a declared default is filled by the emitter (`TailState {}` — the sample's defaults-fill-in pattern), and a `?`-typed field omitted is nil (the zero word `NEW` already leaves), for classes and typedef records alike. | `missing field \`sku\` in constructor of \`Product\`` |
|
||||
| WO-E207 | a constructor literal names a class that isn't declared anywhere in the (possibly multi-file) program. `typedef` records (haxe-parity Task 4) are classes to this check — a record name resolves here like any declared class. | `unknown type \`Widget\` in constructor` |
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
-- same trick runtime/test/test_cycle.c uses (BIG=130) so ASan can prove
|
||||
-- a Node is actually freed, not just recycled inside the arena's own
|
||||
-- freelist where a sanitizer can never see it.
|
||||
@gc
|
||||
class Node {
|
||||
peers: multi Node
|
||||
p1: Int
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
-- same trick runtime/test/test_cycle.c uses (BIG=130) so ASan can prove
|
||||
-- a Node is actually freed, not just recycled inside the arena's own
|
||||
-- freelist where a sanitizer can never see it.
|
||||
@gc
|
||||
class Node {
|
||||
peers: multi Node
|
||||
p1: Int
|
||||
|
|
|
|||
Loading…
Reference in a new issue