flan/test/programs/shim-nul.flan
Joseph Ferano 9ce51ba94e One slice over everything with elements, and the warning at the push
as-slice was a warning, not an operation. The input type already decides
which of the two things happens — a Vec can only be borrowed, an array or a
string can only be viewed, and no call site picks between them — so the second
name expressed no choice a reader could make. And it warned at the moment the
view is taken, which is the one moment nothing is wrong; the danger arrives
later, at the push. slice now takes a Vec at all three arities and as-slice
is gone.

(slice v lo) was free, and is the arity the Vec never had: the runtime already
reads a hi of -1 as "to the end", so the tail form passes the caller's lo and
the same -1 — no slot, no length read, no second evaluation. The merge is
entirely in the checker; the Vec path builds the flan_vec_as_slice call it
always built and neither backend has a line about any of it.

A Vec a call returned is refused at every arity, and not for the array's
reason. (slice (mk)) over an array dangles. (slice (make-vec)) does not — the
storage outlives the expression — but the header is a temporary, so nothing
can ever free the block. The refusal says that and names the let.

The name's own refusal sits in ordinary_call after every table, so a program
that defines an as-slice still reaches its own. It reads for somebody who has
never heard of the old name and writes the call back out, spelling each
argument that is a name or a number.

The warning moved to where it bites: BUILT.md gains a section beside the Vec
table and the push row points at it, spec-memory.md's Borrowing says the same.
Investigated and deliberately not built — a diagnostic for a live view at the
push. (reserve v 100) then a slice, a push and a read is correct code under
the contract the spec chose, so any flag on it is a false positive by the
language's own semantics rather than by an approximation. FIX.org has the
finding and the syntactic sketch that does not work.
2026-09-21 09:51:35 +07:00

29 lines
1.3 KiB
Plaintext

;;;; A string with a NUL in it, handed to C.
;;;;
;;;; A Flan string is ptr+len and a C string ends at its first NUL, so the two
;;;; disagree about what the value *is* the moment one of those bytes is in the
;;;; middle. The generated shim copies and terminates — string-of-bytes.flan
;;;; pins that — and a copy of these bytes is a C string of length 1 where the
;;;; program passed 5. The function would then act on a value nobody wrote.
;;;;
;;;; flan_path_cstr has always refused this for a path, on the grounds that the
;;;; file opened would not be the file named. Nothing about a path is special:
;;;; the same reasoning covers a window title, a shader name and a query, so
;;;; the shim refuses it too, naming the declare-c that was called. It is a
;;;; trap rather than a condition because a foreign call has no allocation site
;;;; for the compiler to guard and no transfer channel of its own.
(declare-c c-puts [s string] i32 "puts")
(defn main [] i32
(let [v (vec-new u8)]
(push v 104) ; h
(push v 105) ; i
(push v 0)
(push v 104) ; h
(push v 105) ; i
(println "before")
(c-puts (string (slice v)))
(println "unreachable")
(free v))
0)