Warnings & Lints
Warnings that catch suspicious and dead code early, without ever changing the compiled output.
rasc carries two diagnostic severities. Errors stop a build; warnings never
do — they print from every CLI mode, and rasc check --strict opts
into failing on them (with --json for tooling).
The flagship is W3001, the dead-mutated-copy warning: a
field store into a value-class local that was copied out of a writable place
(an element access or field chain, seen through unchecked()), where
no forward path reads the local again before reassignment or scope end —
warning W3001: this writes to a copy of 'poly.vertices[i]'; the original
is never updated because value classes copy on assignment. write through
'poly.vertices[i]' or store the copy back
Idiomatic shapes stay silent: mutate-then-use, store-back, reads in a later loop iteration, and passing the copy to a call. See the alias-mutation hazard for why this exists.
A lint pass covers every user file in the import closure (never the stdlib
or vendored lib sources): unused locals and parameters, unreachable code,
assignment in if conditions, root-scope shadowing, use before
declaration — and W1007, every var declaration.
var compiles for upstream AssemblyScript compatibility, but it is
function-scoped and hoisted; rasc code uses let and
const, and the generated wasm is identical.
export function last(xs: Array<i32>): i32 {
for (var i = 0; i < xs.length; i++) {
}
return i; // legal only because var hoists: i outlives the loop
}
warning W1007: 'var' is function-scoped and hoisted; use 'let' or 'const'
Rewriting it with let makes the escape an error instead of a
surprise: i dies with the loop, and the function has to say what
it means.
A batch of suspicious-code rules rounds out the syntactic pass:
W1008 literal true/false conditions,
W1009 assigning a variable or field chain to itself,
W1010 comparisons whose two sides are the same expression,
W1011 duplicate switch case values, and
W1013 expression statements with no effect, like
a == b; where an assignment was meant.
export function tune(mode: i32, gain: f32): f32 {
let level = gain;
level = level;
if (level == level) {
level += 1.0;
}
if (false) {
level = -1.0;
}
level + 0.5;
switch (mode) {
case 1:
return level;
case 1:
return -level;
}
return level;
}
warning W1009: 'level' is assigned to itself
warning W1010: both sides of this comparison are the same
warning W1008: condition is always false
warning W1013: statement has no effect
warning W1011: duplicate case value; first on line 12
The loop idioms stay silent: while (true) and
do..while (false) never trip W1008. A deliberate NaN probe
does trip W1010 — write isNaN(x) instead of
x != x.
W1012 flags unused imports. A name used only as a type annotation, a generic constraint, or a re-export counts as used, so type-only imports never warn.
Two more warnings come from the compiler itself, because lazy compilation is already a whole-program reachability analysis: W3002, a file-local function nothing references, and W3003, a global nothing reads or writes.
function fallback(): f32 {
return 1.0;
}
const seed: f32 = 3.0;
export function apply(v: f32): f32 {
return v * 2.0;
}
warning W3002: function 'fallback' is never used
warning W3003: global 'seed' is never used
API surface never warns: exported declarations, names in an
export { ... } list, and names starting with
_. Declarations named only inside a constant-folded feature
branch — the ASC_FEATURE_* gating pattern from
SIMD — are spared too, so dual-artifact
sources stay clean in both bakes. Both rules go quiet when the compile has
errors, since reference tracking is incomplete then.