TraceLogLevel carried `warning` beside `log-warning` so that sand.flan's one call kept working while the rename landed. The review found it was not inert: render.ml folds members last-wins, so a 4 read back as :warning in the break loop, the did-you-mean offered it, and every TraceLogLevel error listed it. A spelling nobody should write was the one the compiler taught. So the call is respelled and the alias is gone. Every member of every mapped enum carries its enum's prefix now, with no exception to explain.
557 lines
32 KiB
Org Mode
557 lines
32 KiB
Org Mode
* Additional things
|
|
|
|
** flan dev blocks 4-5s before first eval when the program doesn't call agent/start
|
|
"ready on sock" prints in [merged_setup] before [accept_loop] ever runs.
|
|
[merged_serve] (lib/dev.ml:4417) waits up to 10s (await ~ms:10000, line 4427)
|
|
for agent.sock to exist before starting accept_loop. The listen socket is
|
|
already up, so Emacs connects fine, but nothing answers until that wait
|
|
finishes/times out. So a program with no (agent/start ...) looks "ready" but
|
|
actually stalls the first request for up to 10s.
|
|
|
|
Maybe accept_loop should start immediately and the agent-socket wait should
|
|
happen concurrently / only gate agent-specific requests?
|
|
|
|
Update: happening a lot with sand.flan, not just once. sand.flan calls
|
|
agent/start (sand.flan:125) but only after rl/init-window (line 122)
|
|
returns, so if window setup is slow the 10s wait in merged_serve lapses
|
|
before agent/start ever runs — and since accept_loop is gated behind that
|
|
wait, this isn't just a noisy log line, it's a real Emacs stall every time.
|
|
Reinforces: fix should be starting accept_loop unconditionally rather than
|
|
tuning the timeout.
|
|
|
|
Update: likely explains C-M-i (completion-at-point) and eldoc both breaking
|
|
at once. Both read flan--defs (emacs/flan.el:1511), refreshed only on
|
|
connect and after an accepted eval, always through `ignore-errors`
|
|
(flan.el:592, 1743, 1841, 2333). If a refresh lands during the accept_loop
|
|
stall it just silently fails, leaving flan--defs empty/stale with nothing
|
|
retrying it — killing both features with no visible error. Same underlying
|
|
fix (start accept_loop unconditionally) should resolve this too.
|
|
|
|
Update: also probably explains "redefined game-draw via C-c C-c but raylib
|
|
never updates" — deliver (lib/dev.ml:159, called at line 764) pushes the new
|
|
module over the same agent connection. A stale/half-set-up connection from
|
|
the accept_loop race could report "ok" on install without the game thread's
|
|
agent/poll ever actually picking it up at the next frame boundary. Quick
|
|
workaround: M-x flan-restart-program for a fresh connection, then redefine
|
|
again. Real fix is still the accept_loop one.
|
|
|
|
** add // for forced truncating division?
|
|
Lexically free (comments are `;`, not `/`). But nothing is actually missing:
|
|
`/` on two ints already truncates toward zero (sdiv/udiv, lib/emit.ml:2529),
|
|
and casting a float division truncates toward zero too ((i32 (/ 7.0 2.0)) ->
|
|
fptosi -> 3, lib/emit.ml:2909). So `//` as sugar for that is trivial.
|
|
|
|
Catch: Python's `//` is not toward-zero truncation, it's *floor* division —
|
|
rounds toward -infinity. (-7 // 2) is -4 in Python, but sdiv/fptosi give -3.
|
|
So "Python-like //" and "forced truncation" are two different asks.
|
|
|
|
Resolved: Joe wants the toward-zero-truncation one, for when a float
|
|
division needs an int result. (int (/ a b)) already does this — no new
|
|
operator needed. Not adding //.
|
|
|
|
** "generic code over type variable X" is a bad error for a plain typo
|
|
Hit with `int` before defining the defalias for it. resolve_name
|
|
(lib/check.ml:776-778) treats any unrecognized lowercase name as a type
|
|
variable by default; near_miss (lib/check.ml:670-698, single-edit-distance
|
|
only) is what rescues real typos into "unknown type X — did you mean Y?"
|
|
(line 770-772). `int` -> `i32` is 2 substitutions, outside that net, so it
|
|
falls into "generic code over the type variable int is not implemented yet —
|
|
milestone 5" instead of a plain "unknown type int".
|
|
|
|
Common near-misses (int, float, double, str, ...) all get this confusing
|
|
message rather than "unknown type". Maybe near_miss should also check a
|
|
short hardcoded list of common non-flan spellings (int/float/double/bool/str
|
|
etc. from other languages) regardless of edit distance.
|
|
|
|
** eval result ghost text position/minibuffer, both deliberate
|
|
Both things I want changed are documented decisions, not bugs:
|
|
- Position is "end of the form sent" (flan.el:1822-1825), not end of line —
|
|
deliberately different from flan-watch's end-of-line ghost text so the two
|
|
don't get confused (flan.el:1381-1389).
|
|
- Overlay and minibuffer echo are mutually exclusive on purpose
|
|
(flan-inline-result docstring, flan.el:91-101): "saying the same number
|
|
twice is how a reader learns to stop reading both."
|
|
|
|
I want it at end of line + always also in the minibuffer. Revisit —
|
|
would mean changing flan--report's `at` computation and the shown/echo
|
|
either-or in flan.el:1847-1848.
|
|
|
|
** "unknown name u8" from (defconst grid [rows [cols u8]]) — not a real i8/u8 bug
|
|
i8/u8 etc. all exist fine (Types.ikind_of_name, lib/types.ml:72). Real issue:
|
|
defconst's 2-arg form (defconst name value) has no type slot — the second
|
|
form is always parsed as a VALUE via expr, never as a type (lib/parse.ml:
|
|
1341-1345). So [rows [cols u8]] became an array *literal*, and `u8` inside
|
|
it read as a bare variable reference, which is genuinely unbound as a value
|
|
-> "unknown name u8".
|
|
|
|
defconst has no "type only, no value" form; that's defvar's job. Fix: use
|
|
defvar instead — (defvar grid [rows [cols u8]]) is defvar's 2-arg
|
|
(name Type) form (lib/parse.ml:1333-1335), zero-initialized, and parses the
|
|
bracket correctly as a type via texpr. Fits anyway since grid is mutable.
|
|
|
|
** (when test) with no body should work like (when test (do))
|
|
lib/parse.ml:305-309: `when` is a hardcoded special form (not a macro yet —
|
|
"until macros land at milestone 5") and requires `body <> []`, else fails
|
|
"when is (when test body ...)". No real reason for the restriction — empty
|
|
body could just become Ast.Do [], same as (do) already means. One-line fix
|
|
(drop the `body <> []` guard) whenever this file gets touched.
|
|
|
|
** bare struct literal {.field v} can't infer its type from context (e.g. defn return type)
|
|
`(defn get-mouse-cell [] Cell ... {.row r .col c})` fails to parse — "a bare
|
|
map is not an expression; write (Type {.field v})". Root cause: this refusal
|
|
is in `expr` at PARSE time (lib/parse.ml:274-276), before type-checking ever
|
|
runs, so it can't see that the enclosing defn's return type is Cell. check.ml
|
|
does support expected-type-driven checking elsewhere (`check ~want:ty`), but
|
|
a bare struct literal never reaches it — parse rejects it first.
|
|
|
|
To make this work, the refusal would need to move from parse into check, so
|
|
it can consult a `want` type (return type, let annotation, etc.) and only
|
|
fail when there truly isn't one available. Real feature, not a small change.
|
|
|
|
** struct destructuring shorthand: {.row .col} binding same-named locals
|
|
Only two struct-pattern forms exist: {:keys [x y]} (lib/parse.ml:729-753) and
|
|
{name .field} pairs (line 759-762) — both spell the field name twice ({:keys
|
|
[x y]}) or need name+dot per field. :keys makes sense for dyn maps (keys
|
|
aren't statically known field names), but a struct's fields are typed and
|
|
known, so a bare {.row .col} that just binds `row`/`col` locals directly
|
|
would read better and is the common case.
|
|
|
|
Would be a new match arm in `dmap` (lib/parse.ml:724-778) for a lone .field
|
|
symbol with no name before it. No obvious grammar collision — nothing
|
|
currently matches a bare .field symbol on its own. Not implemented.
|
|
|
|
** C-c C-i should auto-inspect at point, C-u C-c C-i for the minibuffer
|
|
Feature request. Today flan-inspect (emacs/flan-inspect.el:681-696) always
|
|
prompts via read-string, pre-filled with the sexp before point — no
|
|
current-prefix-arg handling anywhere in the file, and emacs/MANUAL.md:369
|
|
documents no C-u variant either. Not sure if I'm misremembering discussing
|
|
this for flan-inspect specifically or some other command — check that too
|
|
if it comes up again. Wanted: plain C-c C-i inspects the expression at point
|
|
with no prompt; C-u C-c C-i opens the minibuffer to type a different one.
|
|
|
|
** inc/dec (pure) and ++/-- (mutating) — doable as user macros, no compiler change
|
|
No inc/dec/1+/++/-- exist anywhere (prelude or special forms) — (+ x 1) is
|
|
the only spelling today.
|
|
|
|
Unlike most items above, this needs no compiler change: defmacro already
|
|
exists at user level (vendor/raylib's with-drawing etc.), and `place` (what
|
|
`set` parses — lib/parse.ml:961-983: vars, .field, at, deref) is already
|
|
general enough. Can write today:
|
|
|
|
(defmacro inc [args] `(+ ~(at args 0) 1))
|
|
(defmacro dec [args] `(- ~(at args 0) 1))
|
|
(defmacro ++ [args] `(set ~(at args 0) (+ ~(at args 0) 1)))
|
|
(defmacro -- [args] `(set ~(at args 0) (- ~(at args 0) 1)))
|
|
|
|
inc/dec are generic for free since +/- already are. ++/-- reuse the place
|
|
twice (read then write) — fine for a plain var, double-evaluates a place
|
|
like (at arr (compute-index)) if the index has side effects. Same
|
|
non-hygienic-macro tradeoff with-drawing/with-mode-2d already accept.
|
|
Maybe worth putting these in the prelude itself rather than per-project.
|
|
|
|
** (Cell 1 2) as a positional struct constructor
|
|
Designated initializers (C/Odin-style) already exist: {.field v ...} + ZII
|
|
zero-fill for omitted fields (zii_fill, lib/check.ml:3814), any order, {}
|
|
fully zeroed. Not missing — just spelled with braces/dots.
|
|
|
|
Positional (Cell 1 2) genuinely doesn't exist, and can't be added as a
|
|
parser special case the way {.field ...} was: struct-literal recognition
|
|
happens at PARSE time purely by syntax shape (Sym name applied to a
|
|
{.field...}-shaped map, lib/parse.ml:593-600) — the parser has no symbol
|
|
table, so (Cell 1 2) is syntactically identical to any function call
|
|
(parse.ml:602-603 Ast.Call). Would need check.ml to notice an Ast.Call's
|
|
head resolves to a struct type rather than a function, and reinterpret the
|
|
positional args in field-declaration order there instead. Same parse/check
|
|
boundary issue as the bare-struct-literal-return-type item above — recurring
|
|
pattern worth keeping in mind for future struct/type ergonomics asks.
|
|
|
|
** a DEADBEEF-style sentinel-fill builtin, instead of zero
|
|
Idea: a debug memset that writes a real byte pattern instead of the zero
|
|
llvm.memset already uses (lib/emit.ml:934), for catching reads of
|
|
uninitialized memory. Distinct from Tast.Uninit, which compiles to LLVM
|
|
`poison` (lib/emit.ml:1613) — a semantic "don't care" marker, not an actual
|
|
inspectable byte pattern.
|
|
|
|
Technical snag: llvm.memset only takes a single repeated i8, so a real 4-byte
|
|
0xDEADBEEF pattern needs a fill loop, not memset — more expensive. This is
|
|
why MSVC/glibc-style debug allocators use a single repeated byte instead
|
|
(0xCC/0xCD/0xDD/0xFD). Need to decide: cheap single-byte sentinel, or a real
|
|
4-byte pattern via a loop. Name candidates:
|
|
- single-byte (memset-cheap): 0xCC (MSVC uninit stack), 0xCD (MSVC "Clean"
|
|
unwritten heap), 0xDD (MSVC "Dead" freed heap), 0xFD (MSVC fence/guard)
|
|
- 4-byte (loop, reads great in a hex dump): 0xDEADBEEF, 0xBAADF00D (Windows
|
|
LocalAlloc uninit), 0xFEEEFEEE (Windows HeapFree'd), 0xDEADC0DE, 0xC0FFEE,
|
|
0x8BADF00D (Apple watchdog-timeout crash code)
|
|
|
|
Scope: should take a struct or an array (by place/pointer) and fill it with
|
|
the sentinel — general over both, not just raw byte buffers. Ties into the
|
|
memcpy discussion (2026-09-20): a struct/array is a first-class aggregate
|
|
value in flan today (copies happen via load/store, no memcpy builtin
|
|
exists), so this would be the first builtin that reaches into one of those
|
|
and overwrites it as raw bytes rather than treating it as a typed value —
|
|
same category of operation llvm.memset already does for zero, just exposed
|
|
as a real user-facing builtin instead of only an internal codegen detail.
|
|
|
|
Not designed or implemented, just an idea.
|
|
|
|
Built, 2026-09-20, as two builtins rather than one — the author's answer to
|
|
the cheap-byte-or-real-pattern question was "why not both? we need some sort
|
|
of memset -1 right? and dead-beef can loop, that's fine". They are
|
|
(filled BYTE) and (dead-beef), spelled the way [zeroed] is: the value of
|
|
whatever type is expected of them, so (set grid (filled 0xFF)) is how a place
|
|
is filled and there is no place-taking form to learn beside [set].
|
|
|
|
(dead-beef) writes the default DEADBEEF and (dead-beef 0xBAADF00D) writes the
|
|
pattern given, under one byte-order rule: a pattern's ascending bytes are its
|
|
big-endian bytes, which is how the hex literal reads left to right. So every
|
|
candidate listed above is spellable without the compiler naming any of them,
|
|
and the bare form is checked into the spelled-out default rather than being a
|
|
case a backend knows about. The pattern may be computed, not only written —
|
|
same rule as the byte arm, and both backends byte-reverse at run time when it
|
|
is.
|
|
|
|
The snag above is why there are two and not one with a wider operand. The
|
|
byte fill is one llvm.memset / one rep stosb; the 4-byte pattern is a loop on
|
|
both sides, a counted dword loop in emit.ml and rep stosd in x86.ml, because
|
|
the intrinsic really does only take a repeated i8.
|
|
|
|
What may be filled is numbers, and structs and fixed arrays built out of
|
|
numbers — nothing that carries a tag, a length, an owning pointer or a
|
|
collector descriptor. That boundary, and why each refusal is the runtime's
|
|
rather than a matter of taste, is written up in FIX.org, "The two byte
|
|
fills".
|
|
|
|
** struct field type change: got a plain type error, not the documented refusal
|
|
Hit "expected i32, found u8" after changing Cell's row/col from u8 to i32.
|
|
Turned out to be a leftover (u8 (/ my cell-size)) cast at the construction
|
|
site that I hadn't updated to i32 yet — an ordinary, correct type error, not
|
|
a hot-reload problem. Already fixed in the file.
|
|
|
|
Worth remembering: a struct field type change is one of exactly three
|
|
redefinitions the session actually refuses outright rather than silently
|
|
mis-loading (docs/BUILT.md:1213-1219: "a struct's fields | the values the
|
|
process is holding have the old layout"). That refusal has its own distinct
|
|
message — if I hit it for real it won't look like an ordinary type mismatch.
|
|
|
|
** compiler messages need a persistent, copyable buffer — not just *Messages*
|
|
No dedicated log/output buffer for compiler errors exists anywhere in
|
|
emacs/flan.el. Today it's only: a transient error overlay
|
|
(flan--error-overlays, cleared on the next command) and `message` to
|
|
*Messages* (append-only, awkward to copy out of, no structure). Want both
|
|
the ghost text AND a persistent buffer that accumulates these, so a message
|
|
can actually be copy-pasted (e.g. to hand to Claude) without hunting through
|
|
*Messages*. Not designed or implemented.
|
|
|
|
** defmacro should support real parameter lists, not just one [Form] arg
|
|
Every defmacro today takes exactly one parameter, the whole call's arg list
|
|
as [Form] (lib/parse.ml comment: (defmacro m [args] body) is (defn m [args
|
|
[Form]] Form body)). Porting a Clojure macro like
|
|
(defmacro do-grid [[r rows c cols] & body] ...) means manually picking apart
|
|
`args` by hand every time — (at args 0), match on Form.Vec to unwrap a
|
|
binding vector, (form-rest args 1) for the body, no arity checking, no
|
|
destructuring in the signature itself. with-drawing/with-mode-2d
|
|
(vendor/raylib/modes.flan) already do this by hand and it works, but it's
|
|
real boilerplate for something Clojure gets for free from defmacro's own
|
|
parameter list. Real design gap, not a small fix — would mean parsing macro
|
|
params with the same destructuring patterns [let] already has (dvec/dmap,
|
|
lib/parse.ml:698-799) plus variadic &body support, applied at the macro
|
|
call site before expansion rather than left to the macro body.
|
|
|
|
** profiling: use Tracy, don't build our own
|
|
No profiling infra or prior discussion exists in FIX.org/plan.org/docs.
|
|
Recommendation: bind Tracy (TracyC.h, a pure C API) via declare-c — same
|
|
mechanism raylib already goes through. Building a bespoke profiler means
|
|
redoing a capture protocol, a viewer, flame graphs and a timeline, all of
|
|
which Tracy already does well, for a domain (real-time/game loops) it was
|
|
built for.
|
|
|
|
Two integration points already fit flan's own design:
|
|
- Tracy's frame mark maps directly onto flan's existing frame-boundary
|
|
notion — where agent/poll is called each loop iteration.
|
|
- flan already has precedent for dev-build-only instrumentation wired in as
|
|
an LLVM constructor (the allocation registry, lib/emit.ml ~4165). Automatic
|
|
per-function Tracy zones in dev builds, using the same dev/cell
|
|
indirection every call already goes through, could follow that pattern
|
|
instead of requiring hand-instrumentation everywhere.
|
|
|
|
Alternatives considered:
|
|
- Optick — similar to Tracy but less active, historically Windows-first. No
|
|
real advantage over Tracy.
|
|
- Superluminal — great UI, but commercial and Windows-only. Ruled out (Linux).
|
|
- perf/Hotspot — free, zero-instrumentation sampling, but not frame-aware,
|
|
not live-viewable, Linux-only. Fine as a second opinion, not primary.
|
|
- Chrome Trace Event Format / Perfetto (or a tiny header-only exporter for
|
|
it) — much smaller lift than Tracy: push timestamped begin/end onto a
|
|
buffer, dump JSON, view in Perfetto/chrome://tracing after the fact. No
|
|
live view, no socket server — but it's the one option with an actual story
|
|
for wasm32 (plan.org's committed third target: write trace to a buffer,
|
|
pull out via JS). Tracy's capture side assumes a real OS socket the viewer
|
|
connects to, which doesn't really work in a wasm sandbox.
|
|
|
|
Conclusion: Tracy for native dev-loop profiling now (best tool, trivial
|
|
declare-c binding, fits the frame-boundary idea already in the design). If
|
|
wasm32 profiling becomes a real need later, a small Chrome-Trace-Format
|
|
exporter is the justified "build our own" — not a Tracy replacement, a
|
|
different target Tracy doesn't reach. Not designed or implemented.
|
|
|
|
** no type-limit constants: u8-max, i32-max, f32-infinity, etc. — nothing exists
|
|
Confirmed missing entirely. No MAX/MIN constants in the prelude for any
|
|
sized int type; min/max are just binary comparison builtins, not type-bound
|
|
limits. No C limits.h/float.h macro import either (cimport only pulls in
|
|
declared functions/structs/typedefs, not #define constants) — no path to
|
|
INT_MAX/FLT_MAX that way.
|
|
|
|
Worse for infinity/NaN: there's no literal syntax at all. float_repr
|
|
(lib/form.ml:87-104) prints inf/nan as words for a runtime value that
|
|
happens to be one, but the reader doesn't parse those words back ("nan.0 is
|
|
no improvement on a literal no reader accepts either way"). Only way to get
|
|
+inf today is at runtime: (/ 1.0 0.0) (float division by zero is IEEE-754
|
|
defined, not a trap the way integer division by zero is) — no way to just
|
|
write it down. Real gap, not implemented.
|
|
|
|
** investigate SBCL's redefinition model — warn + keep old value until callers update
|
|
Right now a global/struct type change is a hard refusal (lib/session.ml:
|
|
304-311, docs/BUILT.md:1213-1219) — "restart to change it." I remembered a
|
|
design where the OLD value/layout keeps being used until the calling code
|
|
is recompiled against the new one, since code using the old shape probably
|
|
won't even compile anymore anyway, so nothing unsafe reads mismatched
|
|
memory. Found it — it's real, but only planned for a narrower case:
|
|
|
|
plan.org's Hot Reload section (~685-696): a signature-changing function
|
|
redefinition creates a new internal function version + trampoline. Newly
|
|
compiled callers use the new one; existing callers and stored function
|
|
values keep the old version safely. The session warns at every tracked old
|
|
caller site; recompiling one either updates it or gives a normal type error.
|
|
|
|
plan.org's dev/release table (~671-682) also lists "Structs: version word"
|
|
for dev builds — implying a similar versioned-layout plan existed for
|
|
structs too. But docs/BUILT.md:1223-1230 says none of this is built for
|
|
functions either ("no function versions, no trampolines... the refusal
|
|
stays, because the alternative to refusing is not the new design, it is a
|
|
silent argument mismatch. It is a stopgap"). So today BOTH functions and
|
|
globals/structs just hard-refuse; the versioned/warn-and-keep-old-value
|
|
design exists on paper for functions but nowhere for structs/globals, and
|
|
isn't implemented for either.
|
|
|
|
Action: look at what SBCL actually does on struct/type redefinition (it
|
|
warns rather than refusing, and instances of the old layout get an
|
|
"obsolete instance"-style condition on next access rather than corrupting
|
|
memory) as a model for what flan's struct/global version-word plan could
|
|
be, versus the current hard-refuse stopgap.
|
|
|
|
** need a value-producing array constructor, usable as a defvar initializer
|
|
Wanted to fill `grid` with 255 as part of its defvar declaration, not as a
|
|
separate mutation step after. Doesn't work today: dotimes returns Unit, not
|
|
an array value, so it can only mutate an already-existing place — it can't
|
|
be the initializer expression itself, which has to produce the whole typed
|
|
value in one go. Had to declare grid zeroed then mutate it in main instead.
|
|
|
|
(array 4 rl/Vector2) (Ast.ArrayOf, "the one position with no type slot",
|
|
docs/BUILT.md ~3475) already exists but only zero-fills — no way to give it
|
|
a fill value or a generator. Want something like (array-fill n v) or a
|
|
repeat/generate form that IS an expression (produces the array value
|
|
directly, works nested for 2D), so it composes as a defvar initializer the
|
|
same way a bracket literal does. Not designed or implemented.
|
|
|
|
** revamp the flan buffers: *flan-dev* has no compilation-mode / jump-to-error
|
|
Related to the earlier "compiler messages need a persistent buffer" item but
|
|
distinct — that one was about eval-time messages; this is *flan-dev*, the
|
|
daemon's own startup/build output (emacs/flan.el:729-765,
|
|
flan--start-daemon), which is where a `main` that fails to compile shows its
|
|
errors. It's a plain buffer — get-buffer-create with no major mode, raw
|
|
process output appended via make-process. No compilation-mode, no
|
|
compilation-shell-minor-mode, so no next-error / M-g M-n / jump-to-source.
|
|
|
|
The fix is probably small: flan's own diagnostics already print in the
|
|
ordinary path:line:col: message shape (every error pasted in this session
|
|
has been that format) — the same shape Emacs' built-in
|
|
compilation-error-regexp-alist already parses (it's the GCC/Clang shape
|
|
compile-mode was built around). So this likely isn't a custom-regex job,
|
|
just turning on compilation-minor-mode in *flan-dev* (or a dedicated
|
|
flan-daemon-mode derived from compilation-mode). Umbrella task: revisit all
|
|
the flan-* buffers (*flan-dev*, *flan-output*, error overlays, the eval
|
|
result overlay/minibuffer item) together rather than patching each one
|
|
separately. Not implemented.
|
|
|
|
** implicit numeric conversions with a warning flag, instead of hard errors
|
|
Not liking the strictness; want implicit typing with an opt-in warning flag
|
|
for implicit conversions instead of a hard type error.
|
|
|
|
Worth being clear-eyed first: "no implicit widening/narrowing anywhere" is
|
|
not an oversight, it's a core invariant repeated all over the codebase —
|
|
lib/check.ml:1774 ("this language has no implicit narrowing anywhere"),
|
|
:4736, :5640, :6575-6579 ("no implicit widening, so one side has to decide
|
|
it"), docs/BUILT.md:809. It's the Odin/Rust-style bet against C's decades of
|
|
silent-precision-loss bugs, and it's load-bearing in how binary-op checking
|
|
picks which side "decides" the type. A warn-instead-of-refuse mode isn't a
|
|
flag on top of that — it's a second type-checking mode that would need
|
|
threading through every one of those sites, not a small change.
|
|
|
|
That said, real middle-ground precedent exists: C/C++'s -Wconversion is
|
|
exactly "implicit, but warn" bolted on after the fact. Something similar
|
|
here would mean: allow widening/narrowing between numeric types silently at
|
|
the type-check level, but have the checker also emit a separate warning
|
|
list (surfaced same as any other diagnostic) for every site where a
|
|
conversion happened that wasn't an explicit cast. Not designed, and a real
|
|
philosophy question for the language, not just an implementation task.
|
|
|
|
** CORRECTION: pos?/neg?/zero? already exist, and generics already work
|
|
I was wrong on both counts in the original entry below (kept struck-through
|
|
for the record rather than deleted).
|
|
|
|
pos?/neg?/zero? are already in lib/prelude.ml:493-506, and they're already
|
|
generic: `(defn pos? [x $t] bool {:where (numeric? $t)} (> x 0))` — same for
|
|
neg?/zero?. My "nothing named pos?/neg?" search was wrong because my own
|
|
grep pattern used bare `pos?`/`neg?` — the `?` is an unescaped regex
|
|
quantifier there ("s is optional"), not a literal character, so the search
|
|
silently missed every hit. Should have quoted/escaped it.
|
|
|
|
And generics DO exist and are a real, working, monomorphized feature —
|
|
Odin-style: `$t` in a defn's parameter vector binds a type variable, bare
|
|
`t` in the body/return type reads it, `{:where (pred? $t)}` after the return
|
|
type constrains it, and the checker instantiates a fresh copy per concrete
|
|
type actually used at a call site (an instantiation cache, like Odin's
|
|
gen_procs — lib/check.ml:137-162). swap/reverse/sort/min-of/max-of/
|
|
or-else/reduce/filter/pos?/neg?/zero? in the prelude all use exactly this.
|
|
|
|
The "generic code over the type variable %s... milestone 5" message I
|
|
quoted also looks to have been rewritten since — lib/check.ml:1069-1092 now
|
|
explicitly says "generics were milestone 5 work. They are not" and gives a
|
|
much clearer message naming the fix ("write $%s in the parameter vector to
|
|
introduce one"). Also added since: `foreign_spelling` (lib/check.ml:301-312)
|
|
maps common other-language names (integer/uint/long/double/str/etc.) to
|
|
flan's real names — though note `int` and `float` themselves specifically
|
|
are still NOT in that list, so those two still fall through to the
|
|
type-variable-not-introduced message rather than a direct "unknown type int
|
|
— Flan spells it i32". Real change happened, but not 100% coverage of what
|
|
was originally reported.
|
|
|
|
Original (wrong) entry, kept for the record:
|
|
~~Nothing named pos?/neg?/zero?/sign anywhere in the prelude. Trivial to write
|
|
per-type ((defn pos? [x i64] bool (> x 0))), but that's the problem: we want
|
|
these (and inc/dec from earlier) to work across every numeric type without
|
|
writing one copy per type.
|
|
|
|
This is blocked on real generics, which don't exist yet — "generic code over
|
|
the type variable %s is not implemented yet — milestone 5" (lib/check.ml:
|
|
776-778, hit earlier when `int` fell through to the type-variable path).
|
|
min/max already do this at the builtin level (special-cased in the checker,
|
|
not written as generic Flan functions), which is the current workaround for
|
|
"needs to work over any numeric type" — but that doesn't scale to arbitrary
|
|
user-defined functions like pos?/neg?/inc/dec. Two related asks logged
|
|
together: (1) add pos?/neg?/zero?, (2) make milestone-5 generics real so
|
|
functions like these don't need special-casing into the compiler to be
|
|
generic.~~
|
|
|
|
** println output goes to *flan-output*, not inline in the repl
|
|
Deliberate per emacs/flan-repl.el:40-44: "a value and the program's output
|
|
are different things and arrive by different routes... showing them in one
|
|
place would be convenient and wrong." Same split in flan-mode's C-x C-e.
|
|
|
|
Not sure I like it — printed output feels disconnected from the eval that
|
|
produced it when it's in a separate buffer/window. Revisit.
|
|
|
|
** main cannot actually be redefined
|
|
Confirmed. emit_main (lib/emit.ml:3858-3861) calls the flan-level `main` via
|
|
`fname "main"` — a direct symbol call. Every other call site goes through
|
|
`body_of` (lib/emit.ml:1980-1994) instead, which loads from the dev-build
|
|
indirection cell so a C-c C-c redefinition takes effect. emit_main skips
|
|
that, so flan_program_main (what M-x flan-rerun re-enters) always runs the
|
|
body main had at the initial build, no matter what's redefined into main's
|
|
cell afterward. Not documented as a known limitation anywhere. Should
|
|
probably route through body_of too, or say plainly that main is special.
|
|
|
|
** the "queued; the program is parked..." note is annoying
|
|
lib/dev.ml:778-790. Fires on every C-c C-c redefine while parked — and a
|
|
finished program is always parked, so re-evaling main after each run always
|
|
shows this long note. Message is correct, just too verbose/frequent for the
|
|
common case. Maybe shorten, or only show once per park / make it toggleable?
|
|
|
|
** (rl/with-drawing ()) fails with a confusing error
|
|
`()` is never a valid expression (lib/parse.ml:278). with-drawing splices its
|
|
arg verbatim (vendor/raylib/modes.flan:79-84), so `(rl/with-drawing ())`
|
|
expands to `(do (begin-drawing) () (end-drawing))` and the bare `()` blows up
|
|
deep in the expansion. Workaround: use `(rl/with-drawing (do))` — `(do)`
|
|
with zero forms is a valid no-op (used elsewhere, e.g.
|
|
examples/core-input-gamepad.flan:268).
|
|
|
|
The macro does have an empty-body guard ((< (len args) 1) ->
|
|
with-drawing-takes-a-body) but it only catches zero arguments
|
|
((rl/with-drawing)), not one argument that is itself (). Guard should
|
|
probably also treat a single bare-() arg as "no body".
|
|
|
|
`()` is reserved as the type-position spelling of Unit (Types.to_string
|
|
Unit = "()", lib/types.ml:125) and has no value-position meaning today — the
|
|
unit value only comes from `(do)`, a form with no body, etc. Position
|
|
already disambiguates Vec/Map literals from their type spellings (parse.ml
|
|
comments near line 266-273); worth checking whether () could get the same
|
|
treatment — type in type position, unit value in expression position —
|
|
instead of being refused outright everywhere.
|
|
|
|
** error inside a macro-expanded body points at the macro call site, not the real line
|
|
Hit with do-grid: "unknown function neg?" reported at the do-grid call line,
|
|
not the actual (unless (neg? cell-val) ...) line inside the spliced ~@body.
|
|
|
|
Already known/tracked, not a fresh gap: the Form wire format a macro
|
|
receives and returns (lib/expand.ml:96-101) is Form.value, not Form.t — it
|
|
structurally has no loc field, "a macro cannot invent a source location and
|
|
the image has no room for one." So EVERY node a macro returns, including
|
|
~@body forms spliced through completely unchanged (my own original source,
|
|
not compiler-generated), gets the call site's location stamped on it
|
|
(lib/prelude.ml:1863-1868 says the same thing). The comment names the real
|
|
fix directly: preserving locations through a macro needs "the
|
|
structured-error rewrite," which doesn't exist yet — today's stamping is
|
|
explicitly the stopgap "that can be had now without" it.
|
|
|
|
So: not asking for something new, just noting I hit the documented
|
|
limitation and it's genuinely annoying for macros like do-grid that splice
|
|
a large body through — every error in the body mislocates to the macro call.
|
|
|
|
** built-in comment
|
|
No (comment ...) exists. Trivial as a one-line user macro today —
|
|
(defmacro comment [args] `(do)) — and it already has the useful property:
|
|
macro args are raw unparsed Form, never checked as expressions, so whatever
|
|
is inside never needs to type-check. Want it built in (prelude or special
|
|
form) rather than something every project defines for itself.
|
|
|
|
** #_ (discard) isn't syntax-highlighted, though it works correctly
|
|
Compiler side is fine and deliberate — lib/reader.ml:16-24, 202-262 reads
|
|
and throws away the next form, Clojure-style, including #_#_ counting-free
|
|
chaining. Purely an editor gap: flan-font-lock-keywords
|
|
(emacs/flan-mode.el:126-146) has no rule for #_ at all, and
|
|
flan-mode-syntax-table (line 180+, derived from lisp-mode-syntax-table) has
|
|
no notion of it either since Common Lisp doesn't have this construct. So a
|
|
discarded form renders as plain text, not grayed out/comment-styled.
|
|
|
|
real clojure-mode.el's approach is precedent: a syntax-propertize-function
|
|
that marks a discarded form's characters with the comment syntax class, so
|
|
font-lock renders it as a comment for free. Not implemented here.
|
|
|
|
** syntax highlighting: defmacro/when/etc missing, and macros aren't dynamic
|
|
Two separate gaps.
|
|
|
|
Easy: flan--definers (emacs/flan-mode.el:114-118) is missing `defmacro`
|
|
entirely, even though it introduces a top-level name like defn does.
|
|
flan--special (120-124) is missing when/cond/and/or/break/continue/recur/
|
|
handler-bind/handler-case/restart-case/invoke-restart — all real lib/parse.ml
|
|
special forms, just never added to the static lists. Plain oversights,
|
|
trivial to fix.
|
|
|
|
Harder: highlighting a user-defined macro like do-grid can't be a static
|
|
list — and the daemon doesn't even have the info today. A macro compiles
|
|
down to an ordinary Tast.fn (defmacro m [args] body is exactly
|
|
defn m [args [Form]] Form body) — macro-ness is erased by check time.
|
|
Confirmed: defs (lib/dev.ml:1080-1133) reports every function with
|
|
kind:"fn", no "macro" kind exists anywhere, because Tast.fn has no field
|
|
recording it came from a defmacro. So even flan--defs (the live cache
|
|
already driving eldoc/completion) can't tell do-grid is a macro right now.
|
|
|
|
Would need: (1) plumb a macro flag through Check/Tast so defs can report a
|
|
real "macro" kind, (2) have flan-mode treat flan--defs as a dynamic
|
|
font-lock source — filter macro-kind names, font-lock-add-keywords +
|
|
font-lock-flush/fontify-buffer whenever the cache refreshes (same points
|
|
flan-refresh-defs already runs at: connect, after an accepted eval). CIDER
|
|
does exactly this for clojure-mode off a live nREPL connection — real
|
|
working precedent. Not designed or implemented here.
|
|
|