Found by a read-only audit of emit.ml's failwith sites, each of which is a claim
that the checker guarantees something. Three of those claims were false, and
every one failed in the shape NEXT.md calls the worst available: type checks,
then dies with no source location.
An enum comparison is lowered now rather than refused. Types.is_comparable
already admits an enum, so the checker was stating an intent the backend never
honoured - (= k :a) is the first thing anyone writes with an enum, and it raised
Failure("comparison on K"). An enum is an i32 at run time, so all six
operators are an icmp. Signed, because (defenum K [a -1]) is accepted and an
unsigned compare would call -1 the largest member.
A union in a type position is refused instead. Constructing a union value and
reading a field of one were already refused, so nothing could ever be done with
such a value - only the declaration got through, and it reached clang as a
reference to an undefined %"U", which is a link error naming an emitted symbol
with the source location long gone.
A function type annotation is refused too. The function *value* was refused
where it is written; the annotation was refused nowhere, so (defn f [g (Fn []
i32)]) died with "no layout for". It now sits beside the Map line directly
above it, which is the same shape of not-yet.
The audit also found the sentence that covered the last two: NEXT.md and
check.ml's header both claim unions and function values are rejected by name.
That is true of values and false of types, which is exactly the gap the two
findings lived in.
28 lines
1.0 KiB
Plaintext
28 lines
1.0 KiB
Plaintext
;;;; Comparing enums, which type checked and then died in the backend.
|
|
;;;;
|
|
;;;; Types.is_comparable admits an enum, so the checker was stating an intent
|
|
;;;; emit never honoured: (= k :a) passed check and then raised
|
|
;;;; Failure("comparison on K") with no source location. An enum is an i32 at
|
|
;;;; run time, so all six operators lower to icmp.
|
|
(defenum K [lo -1 mid 0 hi 1])
|
|
|
|
(defstruct S [k K])
|
|
|
|
(defn eq? [k K] bool (= k :mid))
|
|
(defn below? [k K] bool (< k :mid))
|
|
|
|
(defn main [] i32
|
|
;; Equality, both ways round.
|
|
(print-line (if (eq? :mid) "eq yes" "eq no"))
|
|
(print-line (if (eq? :hi) "eq yes" "eq no"))
|
|
|
|
;; Ordering, and signed: lo is -1, so an unsigned compare would call it the
|
|
;; largest member and answer the other way.
|
|
(print-line (if (below? :lo) "lo below mid" "lo not below mid"))
|
|
(print-line (if (below? :hi) "hi below mid" "hi not below mid"))
|
|
|
|
;; And through a struct field, which is a different path to the same compare.
|
|
(let [s (S {:k :hi})]
|
|
(print-line (if (= (.k s) :hi) "field eq yes" "field eq no")))
|
|
0)
|