Fixing Motoko Compiler Warnings
How to Run the Build Check
bash
dfx build --check 2>&1 | tee /tmp/dfx_build_output.txt
This type-checks all canisters without deploying. Redirect stderr to capture warnings. The build takes several minutes for large projects.
To count warnings by type:
bash
grep -o 'M0[0-9]*' /tmp/dfx_build_output.txt | sort | uniq -c | sort -rn
Warning Code Reference
M0194 — Unused Identifier
What it means: A variable, parameter, import, or binding is declared but never used.
How to fix: Prefix the identifier with
(e.g.,
→
). For catch bindings, use plain
(e.g.,
→
), never
.
CRITICAL PITFALLS:
-
Unused record fields in function parameter destructuring: remove the field entirely. In Motoko, record types are structural and support width subtyping — a function that expects
will accept
{ context : Blob; response : ... }
because the caller's record has
more fields than required. So the correct fix is to drop the unused field from the parameter record:
motoko
// ❌ WRONG — renaming the field changes the record type, causes M0096 type errors
public query func transform({
_context : Blob;
response : { ... };
}) : async { ... } { ... }
// ❌ ALSO WRONG — leaving it triggers M0194 warning
public query func transform({
context : Blob;
response : { ... };
}) : async { ... } { ... }
// ✅ CORRECT — remove the unused field; subtyping accepts the extra field from callers
public query func transform({
response : { ... };
}) : async { ... } { ... }
The IC runtime passes
{ context = <blob>; response = <http_response> }
. Because Motoko uses structural subtyping, a function expecting just
will accept records that also have a
field — the extra field is simply ignored.
Rule: If M0194 fires on a record field in a function parameter, remove the field from the destructuring pattern. Do NOT rename it with
prefix.
-
Word-boundary regex can rename method calls. If a variable
is unused on the same line as
, a naive
regex will rename both. Use a negative lookbehind/lookahead that excludes dots:
python
# ❌ WRONG — also renames poolActor.liquidity()
pattern = rf'\b{identifier}\b'
# ✅ CORRECT — won't match after a dot
pattern = rf'(?<![._a-zA-Z0-9]){re.escape(identifier)}(?![_a-zA-Z0-9])'
-
Unused imports are safe to prefix (e.g.,
import Time "mo:base/Time"
→
import _Time "mo:base/Time"
), but consider just deleting the import instead if it's truly not needed.
Safe categories to rename:
- → (catch bindings — always use plain , never or )
- → (let bindings, only if truly unused)
func bar(caller : Principal)
→ func bar(_caller : Principal)
(function params that are simple names, NOT record fields)
- Unused record fields in function parameter destructuring → remove the field entirely (subtyping handles it)
- → (unused imports, or just delete them)
Catch block convention: Always use
with a plain wildcard, not
or
. Since the error value is intentionally discarded,
communicates that clearly and doesn't create a named identifier.
Bulk fix approach: Extract all M0194 warnings from build output, parse file:line:identifier, apply fixes via Python script. Example:
python
import re
def fix_m0194(filepath, line_num, identifier):
"""Prefix an unused identifier with _ on a specific line."""
with open(filepath, 'r') as f:
lines = f.readlines()
line = lines[line_num - 1] # 1-indexed
# Careful regex: don't match after dots (method calls)
pattern = rf'(?<![._a-zA-Z0-9]){re.escape(identifier)}(?![_a-zA-Z0-9])'
new_line = re.sub(pattern, f'_{identifier}', line, count=1)
lines[line_num - 1] = new_line
with open(filepath, 'w') as f:
f.writelines(lines)
M0244 — Variable Could Be
What it means: A
binding is never reassigned, so it could be
(immutable).
motoko
// Before
var foo = computeSomething();
// After
let foo = computeSomething();
Also applies to
→
and
→
(though
has restrictions — only use if the value is a constant or initializer that doesn't need upgradeability).
Bulk fix approach: Parse warnings from build output (file:line:varname), then for each line replace
with
(count=1). This is safe because the compiler has already verified the variable is never reassigned.
Safe to bulk fix. These are always safe to change unless the variable is intentionally left mutable for future use.
General Strategy
- Run and capture output
- Count warnings by code to prioritize
- Fix one warning type at a time
- After bulk fixes, always rebuild to verify no new errors were introduced
- Watch for type errors (M0096) after renaming — they indicate you broke a type signature
- Record field renames in IC interface functions will cause cascading type errors
Environment Notes
- Build time varies by repository size and number of canisters.
- If a repository has pre-existing build failures unrelated to compiler warnings, document them separately and avoid treating them as warning-fix regressions.
- Compiler warnings can vary by version, so record the active compiler version when it is relevant to reproducing or triaging results.