diff --git a/FIX.org b/FIX.org index fc13d3f..202e31b 100644 --- a/FIX.org +++ b/FIX.org @@ -1252,11 +1252,24 @@ what the refusal sees is an [Int]. That pass is integers only, which is why a second folder and was not done; a test pins that there is not one. ** What came out -- [Emit.const]'s two refusals are gone. The general one and the data type case - one are the checker's now, word for word, so the messages a program sees did - not change. What is left in their place is a [failwith] in the file's own - idiom: no program reaches it, and it fires only if the checker's accepted set - and [Tast.const_init] ever stop agreeing. +- [Emit.const]'s two refusals are gone. Both are the checker's now. The data + type case one is word for word what it was. The general one gained what the + checker knows and the emitter did not: it names the constant — "the constant + c is computed" rather than "this one is computed" — and it spells the way + through, =(defvar c ...)= or a literal, with the integer arithmetic the + folding pass accepts named beside it. Both are located at the declaration + now rather than at the expression inside it, which is where every other + refusal about a global points and what [next-error] jumps to. What is left + in the emitter is a [failwith] in the file's own idiom: no program reaches + it, and it fires only if the checker's accepted set and [Tast.const_init] + ever stop agreeing. +- The case is searched for through the aggregates, which is what [Emit.const] + did by recursing: =(defconst g S (S {.u (U.B {.x 1})}))= is a case the image + cannot hold just as much as a bare one, and "this is computed" would be + advice nobody could act on. Left to right and first offender wins, so a + computed field written before a case field still gets the general message — + the emitter's own order, since it spelled the fields in order and failed at + the first one it could not spell. - [emit_global]'s =gconst || const_init ginit= lost its left half. The form no longer decides anything there; the initialiser does, and a zeroinitializer is now always a defvar waiting for the startup function. diff --git a/lib/check.ml b/lib/check.ml index 06889e4..380c062 100644 --- a/lib/check.ml +++ b/lib/check.ml @@ -7501,10 +7501,33 @@ let no_union_const env loc n (v : Tast.expr) = A data type case says it with its own message, which is the one thing a reader could not work out from "this is computed": the case would have to be serialised into the payload blob, and that is an encoder rather than an - order of operations. *) + order of operations. It is searched for the way [Emit.const] used to find it + — down through the aggregates, because a case inside a struct literal is the + same unwritable value as a case on its own, and [(defconst g S (S {.u (U.B + {.x 1})}))] told about "computed" would be advice nobody could follow. + + Left to right and stopping at the first value the image cannot hold, which + is [Emit.const]'s own order: it spelled the fields in order and failed at + the one it could not spell. So a computed field written before a case field + is still the general message, because that is the field a reader meets + first. *) +(* The first subexpression a constant image has no value for, descending + through the aggregates whose parts are themselves constants. [None] is a + value the linker can write, which is [Tast.const_init] arrived at from the + other side — the two walk the same nodes, and this one keeps the offender + rather than the verdict. *) +let rec unwritable (e : Tast.expr) = + match e.Tast.e with + | Tast.Int _ | Tast.Float _ | Tast.Bool _ | Tast.Str _ | Tast.Unit + | Tast.Zero _ | Tast.Uninit _ | Tast.None_ -> None + | Tast.Make (_, es) | Tast.Arr es -> List.find_map unwritable es + | Tast.Some_ v -> unwritable v + | _ -> Some e + let const_defconst_init env loc n (v : Tast.expr) = - match v.Tast.e with - | Tast.MakeCase (dname, case, _) -> + match unwritable v with + | None -> () + | Some { Tast.e = Tast.MakeCase (dname, case, _); _ } -> fail loc "a constant cannot be %s.%s — a data type's payload is a blob, and \ writing a case into one at link time needs a byte-level encoder that \ @@ -7515,7 +7538,7 @@ let const_defconst_init env loc n (v : Tast.expr) = (match Hashtbl.find_opt env.datas dname with | Some { Tast.cases = c :: _; _ } -> c.Tast.vname | _ -> "its first case") - | _ when not (Tast.const_init v) -> + | Some _ -> fail loc "a constant's value must be a compile-time constant — the constant %s \ is computed. A defvar may have a computed initialiser, because it runs \ @@ -7524,7 +7547,6 @@ let const_defconst_init env loc n (v : Tast.expr) = the constant a literal — integer constants may also be written as \ arithmetic over literals and other constants, which is folded here" n n - | _ -> () (* A global's initialiser runs at startup: from [main], after the runtime is up, before a line of the program's own code. Nothing has established a diff --git a/test/test_flan.ml b/test/test_flan.ml index 75e5a1e..3c3192c 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -2090,6 +2090,14 @@ let () = rejects_check "a computed defconst names the way through" "(defn seed [] i64 7) (defconst c i64 (seed))" ~needle:"Write (defvar c ...)"; + (* The search for a case goes down through the aggregates, because a case + inside a struct literal is the same value the image cannot hold and the + general message's advice — make it a defvar, or write a literal — is not + followable for one. [Emit.const] recursed for the same reason. *) + rejects_check "a data type case nested in a constant struct" + "(defdata U [A (B [x i32])]) (defstruct S [u U]) \ + (defconst g S (S {.u (U.B {.x 1})}))" + ~needle:"needs a byte-level encoder that does not exist"; accepts "a constant written as a literal" "(defconst x u64 0xcbf29ce484222325)"; accepts "a constant written as arithmetic over other constants"