Investigation: Five prelude combinators are silently skipped from every WASM compile
Component: WASM backend (codegen) + standard prelude
Severity: Medium — silent feature gap, not a crash
Vera version: 0.0.138 (git 20f3ccb)
Found via: vera compile --target browser on any program
Note on framing
In an earlier conversation I dismissed the warning about result_map being
"skipped" as irrelevant prelude noise that didn't affect the program I was
compiling. That dismissal was technically correct for that program but was
the wrong inference about the underlying state of the compiler. On a closer
look, five prelude functions are skipped from every WASM compile, and
the reasons split into two distinct backend gaps. This is worth a separate
look — both as a real feature gap for anyone writing browser-target Vera that
does monadic Option/Result work, and as a quality signal about how much of
the standard prelude actually round-trips through the WASM backend.
Reproducer
Any non-trivial Vera program will surface these. Even the minimum reproducer
from the string-interpolation report (hello\n printer, 14 lines) emits
five [E602] warnings during browser compile:
$ vera compile --target browser hello.vera -o /tmp/out/
Function 'option_unwrap_or' has unsupported parameter type — skipped.
warning: [E602] Warning at hello.vera, line 20, column 9:
Function 'option_map' body contains unsupported expressions — skipped.
The WASM backend does not yet support all Vera expression types. This
function will not appear in the compiled output.
Function 'option_and_then' body contains unsupported expressions — skipped.
Function 'result_unwrap_or' has unsupported parameter type — skipped.
Function 'result_map' body contains unsupported expressions — skipped.
(The line/column numbers point into the user's file, not the prelude, which
made the warnings feel like noise about user code rather than about the
prelude itself. That framing also probably contributed to my initial dismissal.)
The five affected functions
Definitions extracted verbatim from vera/prelude.py:
private forall<T> fn option_unwrap_or(@Option<T>, @T -> @T)
requires(true) ensures(true) effects(pure)
{
match @Option<T>.0 { None -> @T.0, Some(@T) -> @T.0 }
}
private forall<A, B> fn option_map(@Option<A>, @OptionMapFn<A, B> -> @Option<B>)
requires(true) ensures(true) effects(pure)
{
match @Option<A>.0 {
None -> None,
Some(@A) -> Some(apply_fn(@OptionMapFn<A, B>.0, @A.0))
}
}
private forall<A, B> fn option_and_then(@Option<A>, @OptionBindFn<A, B> -> @Option<B>)
requires(true) ensures(true) effects(pure)
{
match @Option<A>.0 {
None -> None,
Some(@A) -> apply_fn(@OptionBindFn<A, B>.0, @A.0)
}
}
private forall<T, E> fn result_unwrap_or(@Result<T, E>, @T -> @T)
requires(true) ensures(true) effects(pure)
{
match @Result<T, E>.0 { Ok(@T) -> @T.0, Err(@E) -> @T.0 }
}
private forall<A, B, E> fn result_map(@Result<A, E>, @ResultMapFn<A, B> -> @Result<B, E>)
requires(true) ensures(true) effects(pure)
{
match @Result<A, E>.0 {
Ok(@A) -> Ok(apply_fn(@ResultMapFn<A, B>.0, @A.0)),
Err(@E) -> Err(@E.0)
}
}
Two distinct failure modes
| Function |
Failure |
Likely cause |
option_unwrap_or |
parameter type |
bare type variable @T as a parameter |
result_unwrap_or |
parameter type |
bare type variables @T, @E as parameters |
option_map |
body |
apply_fn call inside a match arm |
option_and_then |
body |
apply_fn call inside a match arm |
result_map |
body |
apply_fn call inside a match arm |
The split between the columns is clean. The two _unwrap_or variants don't
contain apply_fn and don't get a "body contains unsupported expressions"
warning — they fail earlier, on the parameter type. The signatures are the
distinguishing feature: _unwrap_or takes a bare @T as its second
argument, whereas _map / _and_then take a concrete generic wrapper type
(@OptionMapFn<A, B> etc.) which the WASM backend can apparently handle as a
parameter.
So the two gaps are:
-
Bare type variable in WASM parameter position.
forall<T> fn(@Option<T>, @T -> @T) cannot be lowered because @T on its
own has no monomorphic WASM representation. The WASM backend appears to
require type variables to appear only inside a known wrapper type with a
uniform representation (a pointer pair, presumably).
-
apply_fn codegen. The README in vera/wasm/ actually mentions this
directly:
"Missing cases (e.g. IndexExpr, IfExpr, apply_fn calls) return
None, which cascades to E602 (unsupported expressions)."
— vera/wasm/README.md:643
Higher-order function application via apply_fn is the mechanism the
prelude uses to invoke the closure passed to _map / _and_then. Without
this in the WASM codegen, no map/bind combinator over Option or Result
will compile.
Why this is more than cosmetic
For Python-target Vera the prelude works in full — these warnings only fire
on --target browser (and presumably any future native target sharing the
WASM codegen). The practical impact:
- Any browser-target program that wants to do
option_map(opt, fn) or
result_map(res, fn) will silently lose those calls from its WASM module
and produce a link/runtime failure when the user calls the missing function.
- Anyone teaching Vera's effect/Result story to LLMs as "use the standard
prelude combinators" will hit a wall at the browser target.
- The warnings are emitted but not gated — the build still produces a
partial WASM bundle with a happy "Browser bundle: ..." message, so it's
easy to miss them entirely. (I did, on first read.)
Suggested next steps
- Decide intent. Are bare type variables and
apply_fn planned for
WASM codegen, or is the prelude being held back from the actual supported
subset? If the latter, the prelude could be rewritten in a WASM-compatible
shape (e.g., monomorphised at a few common types like
option_map_int, option_map_string — ugly, but compilable).
- Promote the warnings. If a prelude function is silently dropped, that
should arguably be an error at compile time for browser target, or at
least a single highlighted summary line ("5 prelude functions skipped:
..."), rather than five interleaved [E602] warnings that look like they're
pointing at user code.
- Document the gap. The browser-target docs (or a section in
vera/wasm/README.md) should list which prelude items don't survive the
WASM lowering, so users writing in this target know to avoid them.
- Track
apply_fn codegen as a single feature. It's mentioned in the
README as a known gap, but landing it would unblock all three of
option_map, option_and_then, result_map — high leverage.
What I should have done the first time
When I saw the warning, I confirmed the program I was working on didn't use
those functions and moved on. The right move would have been to surface the
warning to the user with one extra sentence — "your program doesn't depend
on these but the prelude isn't fully compiling, worth flagging" — rather
than calling it "irrelevant noise." Filing this report is the version of
that I should have done the first time round.
Investigation: Five prelude combinators are silently skipped from every WASM compile
Component: WASM backend (codegen) + standard prelude
Severity: Medium — silent feature gap, not a crash
Vera version:
0.0.138(git20f3ccb)Found via:
vera compile --target browseron any programNote on framing
In an earlier conversation I dismissed the warning about
result_mapbeing"skipped" as irrelevant prelude noise that didn't affect the program I was
compiling. That dismissal was technically correct for that program but was
the wrong inference about the underlying state of the compiler. On a closer
look, five prelude functions are skipped from every WASM compile, and
the reasons split into two distinct backend gaps. This is worth a separate
look — both as a real feature gap for anyone writing browser-target Vera that
does monadic Option/Result work, and as a quality signal about how much of
the standard prelude actually round-trips through the WASM backend.
Reproducer
Any non-trivial Vera program will surface these. Even the minimum reproducer
from the string-interpolation report (
hello\nprinter, 14 lines) emitsfive
[E602]warnings during browser compile:(The line/column numbers point into the user's file, not the prelude, which
made the warnings feel like noise about user code rather than about the
prelude itself. That framing also probably contributed to my initial dismissal.)
The five affected functions
Definitions extracted verbatim from
vera/prelude.py:Two distinct failure modes
option_unwrap_or@Tas a parameterresult_unwrap_or@T,@Eas parametersoption_mapapply_fncall inside amatcharmoption_and_thenapply_fncall inside amatcharmresult_mapapply_fncall inside amatcharmThe split between the columns is clean. The two
_unwrap_orvariants don'tcontain
apply_fnand don't get a "body contains unsupported expressions"warning — they fail earlier, on the parameter type. The signatures are the
distinguishing feature:
_unwrap_ortakes a bare@Tas its secondargument, whereas
_map/_and_thentake a concrete generic wrapper type(
@OptionMapFn<A, B>etc.) which the WASM backend can apparently handle as aparameter.
So the two gaps are:
Bare type variable in WASM parameter position.
forall<T> fn(@Option<T>, @T -> @T)cannot be lowered because@Ton itsown has no monomorphic WASM representation. The WASM backend appears to
require type variables to appear only inside a known wrapper type with a
uniform representation (a pointer pair, presumably).
apply_fncodegen. The README invera/wasm/actually mentions thisdirectly:
Higher-order function application via
apply_fnis the mechanism theprelude uses to invoke the closure passed to
_map/_and_then. Withoutthis in the WASM codegen, no map/bind combinator over Option or Result
will compile.
Why this is more than cosmetic
For Python-target Vera the prelude works in full — these warnings only fire
on
--target browser(and presumably any future native target sharing theWASM codegen). The practical impact:
option_map(opt, fn)orresult_map(res, fn)will silently lose those calls from its WASM moduleand produce a link/runtime failure when the user calls the missing function.
prelude combinators" will hit a wall at the browser target.
partial WASM bundle with a happy "Browser bundle: ..." message, so it's
easy to miss them entirely. (I did, on first read.)
Suggested next steps
apply_fnplanned forWASM codegen, or is the prelude being held back from the actual supported
subset? If the latter, the prelude could be rewritten in a WASM-compatible
shape (e.g., monomorphised at a few common types like
option_map_int,option_map_string— ugly, but compilable).should arguably be an error at compile time for browser target, or at
least a single highlighted summary line ("5 prelude functions skipped:
..."), rather than five interleaved [E602] warnings that look like they're
pointing at user code.
vera/wasm/README.md) should list which prelude items don't survive theWASM lowering, so users writing in this target know to avoid them.
apply_fncodegen as a single feature. It's mentioned in theREADME as a known gap, but landing it would unblock all three of
option_map,option_and_then,result_map— high leverage.What I should have done the first time
When I saw the warning, I confirmed the program I was working on didn't use
those functions and moved on. The right move would have been to surface the
warning to the user with one extra sentence — "your program doesn't depend
on these but the prelude isn't fully compiling, worth flagging" — rather
than calling it "irrelevant noise." Filing this report is the version of
that I should have done the first time round.