using the compiler

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 —

diagnostic
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.

hoisted.as
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
}
diagnostic
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.

suspicious.as
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;
}
diagnostics
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.

orphaned.as
function fallback(): f32 {
    return 1.0;
}

const seed: f32 = 3.0;

export function apply(v: f32): f32 {
    return v * 2.0;
}
diagnostics
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.