feat(compiler): richer field defaults + cross-file consts

log-watcher diagnostics 55 -> 48 (all json-rooted now). corpus 71/0, woc 565/0.

- emit.ml: a field default may now be `nil`, `{}` (a fresh empty container of
  the field's declared type) or `Rec {}` (a record built from its own field
  defaults) — the workload's `st: TailState = TailState {}` and
  `scheduled: map<Text, CronWatch> = {}` shapes
- parser.ml/main.ml: a top-level `const` is visible to every file of its
  module, not just its own file — files in a directory are one module by the
  discovery contract, and the workload reads logtail.wo's `const CHUNK` from
  mcp.wo. A file's own const still wins on a name collision

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
shoney.arickathil 2026-08-14 16:56:55 +02:00
parent fb9d60fc77
commit f59340a1e3
3 changed files with 78 additions and 12 deletions

View file

@ -198,14 +198,31 @@ let finish (collector : Woc_lib.Diag.Collector.t) (lookup : Woc_lib.Diag.source_
check-pass calls see, so a class declared in one file resolves for a
field/constructor/etc. in another regardless of discovery order. *)
(* Parses every discovered file, then makes one more constant-substitution
pass so a `const` declared in one file reaches its siblings: files in a
directory are one module and unconditionally visible to each other
(haxe-parity Task 1), and the workload relies on it (logtail.wo's
`const CHUNK` is read from mcp.wo). Parser.parse already substituted each
file's own constants; this second pass only fills names that were still
unresolved, since a file's own const wins over a sibling's. *)
let parse_all (collector : Woc_lib.Diag.Collector.t) (sources : (string * string) list) :
(string * Woc_lib.Ast.program) list =
List.map
(fun (f, src) ->
let toks = Woc_lib.Lexer.tokenize collector ~file:f src in
let prog = Woc_lib.Parser.parse collector ~file:f toks in
(f, prog))
sources
let parsed =
List.map
(fun (f, src) ->
let toks = Woc_lib.Lexer.tokenize collector ~file:f src in
let prog = Woc_lib.Parser.parse collector ~file:f toks in
(f, prog))
sources
in
let module SM = Woc_lib.Parser.StringMap in
let all_consts =
List.fold_left
(fun acc (_, prog) -> SM.fold SM.add (Woc_lib.Parser.top_level_consts prog) acc)
SM.empty parsed
in
if SM.is_empty all_consts then parsed
else List.map (fun (f, prog) -> (f, Woc_lib.Parser.subst_consts ~extra:all_consts prog)) parsed
let merge_symbols (syms_list : Woc_lib.Types.symbols list) : Woc_lib.Types.symbols =
let module SM = Woc_lib.Types.StringMap in

View file

@ -2076,14 +2076,54 @@ and emit_default_value (p : pctx) (f : fstate) ~(dst : int) ~(fty : Ast.field_ty
let bad () =
err p ~code:cannot_lower_code ~file:f.f_file ~pos
~message:
"cannot lower this field default — only Int/Text/Bool literals, `now()`, and `[]` are \
supported";
"cannot lower this field default — only Int/Text/Bool literals, `nil`, `now()`, `[]`, \
`{}` and `Rec {}` are supported";
put f (ins_abx op_loadk dst (const_int p 0))
in
match d with
| Ast.DefaultNow -> put f (ins_abc op_builtin dst dst b_now)
| Ast.DefaultOpaque toks -> (
match List.map (fun (t : Token.t) -> t.Token.kind) toks with
(* `= nil` — the zero word, whatever `?T` the field is *)
| [ Token.KwNil ] -> put f (ins_abx op_loadk dst (const_int p 0))
(* `= {}` — a fresh empty container of the field's own declared type,
the same rule `[]` above follows *)
| [ Token.LBrace; Token.RBrace ] -> (
match container_imm p (Some fty) (match unwrap fty with Map _ -> true | _ -> false) with
| Some imm ->
put f
(ins_abc op_builtin dst imm (match unwrap fty with Map _ -> b_map_new | _ -> b_multi_new))
| None -> bad ())
(* `= Rec {}` — a record built from ITS own field defaults, which is how
the workload's `st: TailState = TailState {}` self-initializes. Only
the empty literal: a default with real field values is a general
expression, and defaults are an opaque token span by design. *)
| [ Token.Ident cn; Token.LBrace; Token.RBrace ] -> (
match class_of_name p cn with
| None -> bad ()
| Some cid ->
put f (ins_abx op_new dst (check_bx p f pos "class" cid));
let outer = f.f_temp in
if f.f_temp <= dst then f.f_temp <- dst + 1;
(match Types.StringMap.find_opt cn p.p_syms.Types.classes with
| None -> ()
| Some (ci : Types.class_info) ->
List.iter
(fun (fname, _, fdefault, _) ->
match fdefault with
| Some d2 -> (
match field_of p cid fname with
| None -> ()
| Some (idx, fty2) ->
let save = f.f_temp in
let t = alloc_temp p f pos in
emit_default_value p f ~dst:t ~fty:fty2 ~pos d2;
f.f_cur_line <- pos.line;
put f (ins_abc op_setf dst (check_field_idx p f pos idx) t);
f.f_temp <- save)
| None -> ())
ci.Types.fields);
f.f_temp <- outer)
| [ Token.Int n ] -> put f (ins_abx op_loadk dst (check_bx p f pos "constant" (const_int p n)))
| [ Token.Dash; Token.Int n ] ->
put f (ins_abx op_loadk dst (check_bx p f pos "constant" (const_int p (-n))))

View file

@ -1843,10 +1843,19 @@ let params_bound (base : StringSet.t) (params : Ast.param list) : StringSet.t =
let const_map (consts : Ast.const_decl list) : Ast.expr StringMap.t =
List.fold_left (fun acc (c : Ast.const_decl) -> StringMap.add c.Ast.name c.Ast.value acc) StringMap.empty consts
let subst_consts (prog : Ast.program) : Ast.program =
let top_consts =
const_map (List.filter_map (function Ast.Const c -> Some c | _ -> None) prog.Ast.decls)
in
(* Every top-level `const` a program declares, for a caller that needs to
substitute one file's constants into ANOTHER file of the same module —
every file in a directory is unconditionally visible to every other
(haxe-parity Task 1's discovery contract), so a `const CHUNK = 65536` in
one file is in scope in its neighbours. *)
let top_level_consts (prog : Ast.program) : Ast.expr StringMap.t =
const_map (List.filter_map (function Ast.Const c -> Some c | _ -> None) prog.Ast.decls)
(* [extra] holds constants declared elsewhere (a sibling file's); this
program's own top-level ones win on a name collision, exactly as a
class-level const outranks a top-level one below. *)
let subst_consts ?(extra = StringMap.empty) (prog : Ast.program) : Ast.program =
let top_consts = StringMap.fold StringMap.add (top_level_consts prog) extra in
let decls' =
List.map
(function