mx includes a built-in static analysis linter that catches common Modula-2 pitfalls at compile time. Warnings appear in VS Code as yellow squiggles (via the LSP) and on stderr during mx build. Warnings never block compilation.
| Code | Category | Description |
|---|---|---|
| W01 | Unsigned arithmetic | Comparison of unsigned type against 0 (CARDINAL >= 0 is always true, CARDINAL < 0 is always false) |
| W02 | Unsigned arithmetic | WHILE c >= 0 DO DEC(c) END with unsigned variable — infinite loop since unsigned can never be negative |
| W03 | Short-circuit | Pointer dereference or array index on RHS of AND/OR where short-circuit evaluation is required for safety (e.g. (p # NIL) AND (p^.x = 1)) |
| W04 | Aliasing | Same variable passed to multiple VAR parameters in one call — modifications through one alias may interfere with the other |
| W05 | Subrange | INC or DEC on a bounded subrange type ([0..15]) may overflow/underflow the declared bounds |
| W06 | Portability | FROM SYSTEM IMPORT — use of SYSTEM module reduces portability across compilers and platforms |
| W07 | Exhaustiveness | CASE on enumeration or subrange without ELSE does not cover all possible values |
| W08 | Signedness | Mixed signed (INTEGER) and unsigned (CARDINAL) operands in arithmetic — implicit conversion may produce unexpected results |
| W09 | Unsigned arithmetic | Subtraction in FOR upper bound with unsigned loop variable (FOR i := 0 TO n - 1) — underflows to MAX(CARDINAL) when n = 0 |
| W10 | Initialization | Variable may be used before being assigned a value (path-sensitive: considers all branches) |
| W11 | Nil safety | Pointer may be NIL when dereferenced — uninitialized, assigned NIL, or set to NIL after DISPOSE |
Warnings are checked at two levels:
Tier 1 (AST-level) — W01–W09. Pattern matching on the typed syntax tree. Runs instantly during editing in VS Code.
Tier 2 (CFG dataflow) — W10–W11. Forward dataflow analysis over the control flow graph. Builds HIR and CFG, then runs a worklist solver to fixpoint. Also runs in VS Code via the LSP.
Add (*!Wxx*) as a comment on the line to suppress that warning:
FROM SYSTEM IMPORT ADDRESS, ADR; (*!W06*)
Place (*!Wxx*) before the MODULE keyword to suppress file-wide:
(*!W06*)
(*!W08*)
MODULE LowLevel;
FROM SYSTEM IMPORT ADDRESS, ADR, TSIZE;
(* All W06 and W08 warnings suppressed in this file *)
- Each
(*!Wxx*)pragma suppresses exactly one warning code - Multiple codes can be suppressed with multiple pragmas
- Suppression does not affect other warning codes
- File-level suppression applies to the entire file including all procedures
(* WARNING: if count = 0, "count - 1" underflows to MAX(CARDINAL) *)
FOR i := 0 TO count - 1 DO
Process(items[i])
END;
(* FIX: guard with IF *)
IF count > 0 THEN
FOR i := 0 TO count - 1 DO
Process(items[i])
END
END;
(* WARNING: if p = NIL, p^.value crashes — AND does not guarantee
short-circuit evaluation in standard Modula-2 *)
IF (p # NIL) AND (p^.value > 0) THEN ...
(* FIX: use & (AND THEN) which guarantees short-circuit *)
IF (p # NIL) & (p^.value > 0) THEN ...
(* WARNING: x used on ELSE path without assignment *)
PROCEDURE Foo(flag: BOOLEAN): INTEGER;
VAR x: INTEGER;
BEGIN
IF flag THEN x := 10 END;
RETURN x (* W10: x may be uninitialized when flag is FALSE *)
END Foo;
(* WARNING: p is NIL after DISPOSE *)
NEW(p);
p^.val := 42;
DISPOSE(p);
RETURN p^.val (* W11: p may be NIL *)
The lint system is implemented across three layers:
src/sema.rs— Tier 1 checks: AST pattern matching during semantic analysissrc/cfg/dataflow.rs— Generic forward dataflow framework (worklist solver over CFG)src/cfg/lint.rs— Tier 2 checks:DefinitelyAssigned(W10) andNilSafety(W11) analysessrc/analyze.rs— Integration: runs both tiers, applies suppression, feeds LSP diagnosticssrc/errors.rs—ErrorKind::Warningwith optional code, never blocks compilation