An Option has no fields to take the address of, and two rows now say so

FIX.org carried Addr(Pfield ...) on an Option as a hole in both backends. It is
not reachable from the language: a field access goes through struct_target,
which admits a struct or a pointer to one and refuses everything else by name
with a location, so (addr (.x o)) is refused at the field and never reaches a
place. The node that failed was one the compiler built for itself.

The refusal is pinned on the bare field and on the address of one, and FIX.org
now records the finding, including the asymmetry that stays: the x86 backend
lays out an Option's tag and value as fields and the LLVM backend does not.
Neither path is reachable, so matching them would be untestable code written to
balance a road nobody drives on.
This commit is contained in:
Joseph Ferano 2026-09-17 22:59:03 +07:00
parent 81b807f544
commit 9f85139f1f
2 changed files with 29 additions and 3 deletions

16
FIX.org
View File

@ -156,9 +156,19 @@ against 985ms on LLVM.
arena does not make safe — and that a Map has no operation answering *where*
a value lives, which is what reading an arena-parsed EDN document back would
need. Both were relayed to the arena agent.
- [Tast.Addr (Tast.Pfield ...)] on an Option fails in both backends. The
working route is [Prim (AddrOf, [Field ...])]. Found by the drop lane, not
fixed.
- [Tast.Addr (Tast.Pfield ...)] on an Option: closed, and closed as
unreachable rather than fixed. Nothing in the source language builds it.
[.field] goes through [struct_target], which admits a struct and a pointer
to one and refuses everything else by name with a location — "(Option Point)
is not a struct, so it has no fields" — so [(addr (.x o))] never reaches a
place for [addr] to take. The node the drop lane hit was one the compiler
built for itself. Two rows in test_flan.ml pin the refusal, on the bare field
and on the address of one.
What is still asymmetric, and is a note rather than a bug: [x86.ml]'s
[field_loc] does lay out an Option's tag and value, and [emit.ml]'s [place]
admits only a named struct. Neither is reachable, so neither is tested, and
growing the LLVM side to match would be untestable code written to balance a
path nothing takes.
- Re-run still does not work under --two-process: a finished child is
genuinely gone. It now works under --x86 because --x86 runs merged.
- sand.flan still holds an uncommitted experiment line that is refused with a

View File

@ -912,6 +912,22 @@ let () =
(defn f [s [u8]] i32 (let [c (Cursor {.src s})] (g (addr c))))");
rejects_check "addr of a non-place"
"(defn f [] () (addr (+ 1 2)))" ~needle:"addr takes the address of a place";
(* An Option's two fields exist in both backends' layouts — the tag and the
value and the structural printer reads the tag through them. What has no
spelling in the source language is reaching one: [match] and [some] are
how an Option is opened, and a (.field o) that read the value of a None
would be reading storage the tag says is not there. FIX.org recorded
[Addr (Pfield ...)] on an Option as a hole in both backends; this is the
pair of rows that says the hole has no door the refusal is the field
access itself, so (addr ...) never gets a place to take the address of. *)
rejects_check "a field of an Option"
"(defstruct Point [x i32 y i32])\n\
(defn f [o (Option Point)] i32 (.x o))"
~needle:"(Option Point) is not a struct, so it has no fields";
rejects_check "the address of a field of an Option"
"(defstruct Point [x i32 y i32])\n\
(defn f [o (Option Point)] (Ptr i32) (addr (.x o)))"
~needle:"(Option Point) is not a struct, so it has no fields";
(* ── Option, some, match ───────────────────────────────────────── *)
accepts "some unwraps in an Option-returning function"