The case inside a struct literal is the same unwritable value as a bare one
const_defconst_init matched MakeCase at the top level only, so a case nested in a struct literal got the general computed message, whose advice nobody could follow. Emit.const found it by recursing; this finds it the same way, left to right and first offender wins, which is the order the emitter spelled fields in. FIX.org's claim that the messages did not change is rewritten to say what did change about the general one.
This commit is contained in:
parent
ce93ac7622
commit
49ee8774d3
23
FIX.org
23
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.
|
||||
|
||||
32
lib/check.ml
32
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
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user