print, the REPL inspector and the break buffer's locals all walk a concrete
type through render.ml, and a union fell through its Named arm to <Shape>.
It now recovers the case from the tag by a chain of comparisons -- the same
shape the enum arm already had, and for the same reason: the name is erased
before any backend sees it -- and reads the fields of that case only. Reading
the others would be reading a payload that is not there.
It prints (Shape.Dot {.x 1.5 .y -2.5}), which is what the source would write.
The union table has to reach the walk, so Render.ctx grew a field and its
three construction sites in session.ml and one in check.ml pass it. That is
the whole of the session.ml change.
test/programs/unions.flan is the program: a case with no fields, a case wider
than another, a case holding a string, a union in a struct, a union through a
call in both directions, ZII, reassignment, and printing. Its layout was
checked against clang's for the same declaration -- 32 bytes aligned 8 with
the payload at offset 8, and 40/8 for the struct holding it.
79 lines
3.0 KiB
Plaintext
79 lines
3.0 KiB
Plaintext
;;;; Union values: declaring one, making one, matching one, printing one.
|
|
;;;;
|
|
;;;; The layout claim is the load-bearing one, so it is asserted rather than
|
|
;;;; described: a union is a tag and room for the largest case, aligned to the
|
|
;;;; widest member of any case, which is C's struct { int tag; union {...}; }.
|
|
;;;; That is what the macro expander will need to agree with byte for byte, so
|
|
;;;; `Shape` here is deliberately the shape a Form has: a case with no fields,
|
|
;;;; a case whose members are wider than another's, and a case holding a
|
|
;;;; string -- the three things a payload blob has to hold without disturbing
|
|
;;;; the alignment of any of them.
|
|
|
|
(defunion Shape
|
|
[Empty
|
|
(Dot [x f64 y f64])
|
|
(Rect [w i32 h i32])
|
|
(Tag [name string n u8])])
|
|
|
|
;; A union crosses a call boundary in both directions, as a parameter and as a
|
|
;; return type -- a value that cannot do that is not a value.
|
|
(defn area [s Shape] f64
|
|
(match s
|
|
(Rect w h) (* (f64 w) (f64 h))
|
|
(Dot _x _y) 0.0
|
|
_ -1.0))
|
|
|
|
(defn widen [n i32] Shape (Shape.Rect {.w n .h (* n 2)}))
|
|
|
|
;; A union as a struct field, which is the path that makes its size and
|
|
;; alignment visible to something other than a slot.
|
|
(defstruct Cell [id i32 s Shape])
|
|
|
|
(defn describe [s Shape] string
|
|
(match s
|
|
Empty "empty"
|
|
(Dot x y) (if (= x y) "dot on the diagonal" "dot")
|
|
(Rect w h) (if (= w h) "square" "rect")
|
|
(Tag name n) name))
|
|
|
|
(defn main [] i32
|
|
;; A case with no fields is a whole value and is written as a name.
|
|
(println (describe Shape.Empty))
|
|
(println (describe (Shape.Dot {.x 2.0 .y 2.0})))
|
|
(println (describe (Shape.Dot {.x 1.0 .y 2.0})))
|
|
(println (describe (Shape.Rect {.w 3 .h 3})))
|
|
(println (describe (Shape.Tag {.name "tagged" .n 7})))
|
|
|
|
;; ZII: omitted fields are zeroed, exactly as in a struct literal.
|
|
(println (describe (Shape.Rect {.w 0})))
|
|
|
|
;; Returned from a call, then matched.
|
|
(print (i64 (area (widen 4)))) (println "")
|
|
(print (i64 (area (Shape.Dot {.x 9.0 .y 9.0})))) (println "")
|
|
(print (i64 (area Shape.Empty))) (println "")
|
|
|
|
;; Through a struct field, and copied: assigning a Cell copies the union's
|
|
;; bytes, so the copy's payload must be the original's.
|
|
(let [c (Cell {.id 1 .s (Shape.Tag {.name "in a cell" .n 3})})
|
|
d c]
|
|
(println (describe (.s d)))
|
|
;; A zeroed union is the first declared case -- Empty -- which is what
|
|
;; makes case order part of the contract.
|
|
(let [z (Cell {.id 2})]
|
|
(println (describe (.s z)))))
|
|
|
|
;; A local assigned a second case: the tag moves and the payload is rewritten.
|
|
(let [v Shape.Empty]
|
|
(set v (Shape.Rect {.w 5 .h 6}))
|
|
(print (i64 (area v))) (println "")
|
|
(set v (Shape.Tag {.name "reassigned" .n 1}))
|
|
(println (describe v)))
|
|
|
|
;; The structural printer, which reads only the case in hand: the other
|
|
;; cases' fields are not there to read.
|
|
(print Shape.Empty) (println "")
|
|
(print (Shape.Dot {.x 1.5 .y -2.5})) (println "")
|
|
(print (Shape.Tag {.name "printed" .n 9})) (println "")
|
|
(print (Cell {.id 7 .s (Shape.Rect {.w 1 .h 2})})) (println "")
|
|
0)
|