flan/test/programs/generic-map-reject.flan
Joseph Ferano 762bc988fa The map operations are deferred, and the clause is what pays for it
hashable? gated the type and not the operations: a generic could take and
return a (Map $t V) and could not get or put into one. The hash and the
equality are emitted as concrete symbols chosen from the key type, and
while $t is a variable there is no symbol to name.

The five arms that reach the pair - put, get, has-key?, reserve, clone -
now check their arguments and return a placeholder of the operation's own
type when the key is a type variable: Unit for put and reserve, None for
get so the (Option V) around it still checks, false for has-key?, a zeroed
map for clone. The node is thrown away with the rest of the abstract pass
and the real one is built in the copy, exactly as println's is.

What makes that different from print's free ride is the clause. A map
operation can fail at a concrete type; it is deferred anyway because
{:where (hashable? $t)} is in the signature, so the refusal lands at the
call that asked for the type, against a requirement the author wrote down.
A generic that declares nothing gets no deferral - deferred_key checks
first, and map_type has usually refused the signature already. So the rule
for the allow-list is not a headcount: either the operation cannot fail
after substituting, or a declared predicate gives its failure somewhere to
land. The comment at the print arm says that now instead of "stays two
long".

The instantiation-time refusal names the call site, the type it asked for,
the predicate and the clause, rather than repeating the generic's name
twice.
2026-09-13 15:23:37 +07:00

27 lines
1.1 KiB
Plaintext

;;;; The map operations over a type-variable key, refused at the call site.
;;;;
;;;; A generic body is checked once with its type variables abstract, and the
;;;; map operations are one of the few forms that cannot be answered there:
;;;; the hash and the equality are emitted as concrete symbols chosen from the
;;;; concrete key type, and there is none until a copy exists. So they are
;;;; deferred to the instantiation, the way print and println are.
;;;;
;;;; What makes deferring them safe — and different from an unconstrained
;;;; (+ a b), which stays refused at the definition — is the clause. The
;;;; signature says {:where (hashable? $t)}, so a call site that asks for a
;;;; key type with no usable equality is refused against a requirement the
;;;; author wrote down, at the call that asked for it. A float is that type:
;;;; NaN is not equal to itself, and 0.0 and -0.0 are equal while differing
;;;; bytewise.
(defn seen? [k $t] bool
{:where (hashable? $t)}
(let [m (map-new t i32)]
(put m k 1)
(let [answer (has-key? m k)]
(free m)
answer)))
(defn main [] ()
(println (seen? 3))
(println (seen? 1.5)))