6c6
< version: 0.1.5
---
> version: 0.1.6
32c32,33
< - Any Motoko reserved keyword as a declared identifier -- Before writing, check parameter, variable, function, type, field, and label names against Motoko's reserved words. `query` and `label` are reserved and must never be identifiers. Rename a colliding domain term instead of relying on its position or inferred meaning.
---
> - Any Motoko reserved keyword as a declared identifier -- Before writing, check parameter, variable, function, type, field, and label names against the full list in [references/reserved-keywords.md](references/reserved-keywords.md). `query` and `label` are reserved and must never be identifiers. Rename a colliding domain term instead of relying on its position or inferred meaning.
> - Type annotations on an inline `func` passed as a **call argument** -- write `xs.filter(func x = x > 1)`, not `xs.filter(func(x : Nat) : Bool { x > 1 })`. The call supplies the types. If a generic cannot be inferred, instantiate the call (`map<In, Out>`), never the lambda. This applies only in argument position — named declarations still carry full signatures. **One exception:** keep `: async ()` on an async callback (`func() : async () { ... }`) — it is what makes the body async, and removing it fails with M0096
54c55,56
< - Never hand-edit `mops.toml` or `mops.lock`; use the `mops` CLI so dependency metadata and the lockfile stay atomic.
---
> - Never hand-edit dependency entries in `mops.toml`, and never touch `mops.lock`; use the `mops` CLI so dependency metadata and the lockfile stay atomic. (`[toolchain]` has a CLI too: `mops toolchain use <tool> [version]`.)
> - Leave `[moc] args` alone. Compiler flags are a one-time project-setup concern, and many platforms own `mops.toml` and set them for you — do not inspect or change them while writing code. If you are setting up a project yourself, see [references/project-setup.md](references/project-setup.md).
65c67
< - **`mops check --fix`** (fast — use for iteration) — Auto-fixes warnings (dot-notation, redundant type instantiation, redundant implicit arguments) and reports remaining compile errors. Exit 0 = success. Error format: `file:startLine.startCol-endLine.endCol: severity [code], message`. Iterate on this until it passes.
---
> - **`mops check --fix`** (fast — use for iteration) — Reports compile errors and auto-fixes the style warnings, where the project has them enabled (dot-notation, redundant type instantiation, redundant implicit arguments). Follow this skill's rules whether or not `--fix` enforces them. Exit 0 = success. Error format: `file:startLine.startCol-endLine.endCol: severity [code], message`. Iterate on this until it passes.
94c96,97
< // Two-arg functions are NOT dot notation
---
> // Equality: Principal declares `equal` with a self parameter, so it is dot notation too
> a.equal(b) // PREFERRED
96d98
< a == b // also OK
99a102,105
> **Prefer `equal` / `compare` over `==`.** `==` is compiler-generated structural equality and exists only for **shared** types, so one `var` field takes a record out of shared and `==` stops compiling (M0060). Use `==` only for the numeric primitives that have no receiver form: `Nat`, `Int`, `Float`, and the sized int types declare `equal(x, y)` without a `self` parameter, so `myNat.equal(other)` fails with M0070 and `a == b` is the right call. Other receiver methods on those types (`myNat.toText()`) are fine.
>
> Your own records and variants get nothing derived — a record `compare` must be an explicit function, and custom variants need both `equal` and `compare` written out. See [references/equality.md](references/equality.md).
>
191c197
< let user = users.find(func(u : User) : Bool { u.id == caller })
---
> let user = users.find(func u = u.id == caller)
221c227,229
< Compiler infers comparison functions automatically:
---
> `Map` and `Set` operations take the comparison function as an **implicit** argument. `Map.empty()` itself takes no arguments — the comparator is resolved at the operations that need it (`add`, `get`, `remove`, …), not at construction.
>
> Inference works by finding a `compare` in the module imported **for the key type**. So the import is what makes it work:
223a232,234
> import Map "mo:core/Map";
> import Nat "mo:core/Nat"; // this import is what supplies Nat.compare
>
225c236,251
< map.add(5, "hello"); // Nat.compare inferred
---
> map.add(5, "hello"); // compare resolved from the imported Nat
> ```
>
> Without `import Nat`, the same code fails — the type is known, but there is no module to take `compare` from:
>
> ```text
> type error [M0230], Cannot determine implicit argument `compare` of type (Nat, Nat) -> Order
> note: Did you mean to import mo:core/Int or mo:core/Nat?
> ```
>
> Do **not** pass the comparator explicitly when it can be inferred; that is M0237, which `mops check --fix` removes:
>
> ```motoko
> ages.add("Alice", 30); // CORRECT
> ages.add(Text.compare, "Alice", 30); // WRONG (M0237)
> ```
227,228c253
< let ages = Map.empty<Text, Nat>();
< ages.add(Text.compare, "Alice", 30); // explicit when needed
---
> A custom key type works the same way — give its module a `compare` and it is inferred:
230c255
< // Define module with compare for custom types → auto-inferred
---
> ```motoko
236d260
<
238a263,264
> Type instantiation on `empty()` follows the usual rule — needed only when the binding is unannotated. `let m : Map.Map<Nat, Text> = Map.empty();` infers it, and `Map.empty<Nat, Text>()` there would be M0223.
>
275a302,305
> ### The Actor Must Come Last
>
> Imports and `type`/`let` declarations may precede the actor. Nothing may follow it — the actor is the file's result, so a trailing declaration makes the actor a non-`()` statement and fails with M0096 (`expression of type actor {...} cannot produce expected type ()`). Prefer keeping shared types in `types.mo` regardless.
>
294c324
< { self with likedBy = Set.toArray(self.likedBy) };
---
> { self with likedBy = self.likedBy.toArray() };
342c372
< - If a value is an array (`[T]`) or came from `.toArray()` / `.filter(...)`, then `.map(...)` already returns an array; do not append `.toArray()` to that array-map result. (`List.List<T>.map(...)` may still need explicit type instantiation and `.toArray()` when mapping records to another type.)
---
> - If a value is an array (`[T]`) or came from `.toArray()` / `.filter(...)`, then `.map(...)` already returns an array; do not append `.toArray()` to that array-map result. (`List.List<T>.map(...)` returns a `List`, so it still needs `.toArray()` when the caller expects an array.)
348,350c378
< let matched = leftTags.any(func(left : Text) : Bool {
< rightTags.any(func(right : Text) : Bool { left == right })
< });
---
> let matched = leftTags.any(func left = rightTags.any(func right = left == right));
358c386,412
< When a predicate uses a block body, declare the return type as `: Bool`. Do not write `func(todo : Types.Todo) { todo.id == id }`; that block can compile as a `()`-returning callback in argument position. Use `func(todo : Types.Todo) : Bool { todo.id == id }`.
---
> **An inline `func` passed as a call argument takes no type annotations.** The call already fixes the parameter and result types, so annotating repeats them and lets them drift as the code changes. Use the expression form `func x = <expr>`:
>
> ```motoko
> todos.find(func todo = todo.id == targetId); // CORRECT
> todos.find(func(todo : Types.Todo) : Bool { todo.id == targetId }); // WRONG: annotated
> ```
>
> This is about **argument position only**. A named declaration still carries its full signature, and a lambda bound on its own has nothing to infer from — `let f = func x = x > 1` fails with M0103 (`cannot infer type of variable`).
>
> ```motoko
> public func toView(t : Types.Todo) : Types.TodoView { ... }; // annotated, as always
> ```
>
> When the types are not obvious to a reader, or a generic cannot be inferred, say it **on the call** rather than on the lambda — it reads better and keeps one source of truth:
>
> ```motoko
> photos.map(func p = { id = p.id; url = p.url.toText() }); // inferred — preferred
> photos.map<PhotoInternal, Photo>(func p = { ... }); // when M0098 demands it
> ```
>
> Add `<In, Out>` only when the compiler actually reports M0098; adding it when inference already succeeded is M0223 (redundant type instantiation).
>
> The one exception is a callback that must return `async`. There `: async ()` is load-bearing — it is what makes the body async, and there is no unannotated form (`func() = async { ... }` does not work either). Without it the lambda infers `() -> ()` and the call fails with M0096:
>
> ```motoko
> Timer.recurringTimer<system>(#seconds(3600), func() : async () { cleanup() });
> ```
362c416
< switch (todos.find(func(todo : Types.Todo) : Bool { todo.id == targetId })) {
---
> switch (todos.find(func todo = todo.id == targetId)) {
408c462
< switch (numbers.find(func(n : Nat) : Bool { n > 5 })) {
---
> switch (numbers.find(func n = n > 5)) {
421c475
< let sorted = all.sort(func(a : Types.Todo, b : Types.Todo) : { #less; #equal; #greater } {
---
> let sorted = all.sort(func (a, b) =
425,427c479,481
< });
< sorted.map<Types.Todo, Types.TodoView>(func(todo) {
< { id = todo.id; text = todo.text; completed = todo.completed; createdAt = todo.createdAt }
---
> );
> sorted.map(func todo = {
> id = todo.id; text = todo.text; completed = todo.completed; createdAt = todo.createdAt
431,432c485,486
< all.sort(func(a, b) { Int.compare(b.createdAt, a.createdAt) });
< all.map<Types.Todo, Types.TodoView>(func(todo) { ... });
---
> all.sort(func (a, b) = Int.compare(b.createdAt, a.createdAt));
> all.map(func todo = { ... });
442,444c496,498
< numbers.contains(3); // implicit Nat.equal
< friends.contains(Principal.equal, p); // explicit equality
< todos.find(func(todo : Types.Todo) : Bool { todo.id == targetId }); // returns ?Todo
---
> numbers.contains(3); // equal inferred from the imported Nat
> friends.contains(p); // likewise from Principal — passing Principal.equal here is M0237
> todos.find(func todo = todo.id == targetId); // returns ?Todo
457a512,531
> ### Joining Text
>
> `join` takes the **iterator as its receiver and the separator as its argument** — easy to invert. Use dot notation; the module form is an M0236 violation that `mops check --fix` rewrites for you.
>
> ```motoko
> ["a", "b"].values().join(", "); // CORRECT → "a, b"
> Text.join(["a", "b"].values(), ", "); // WRONG (M0236)
> ```
>
> Note the receiver is an **iterator**, not an array: call `.values()` on an array first.
>
> ### Variant Tag Arguments
>
> Always parenthesize a variant tag's argument. A tag binds only to the atom immediately after it, tighter than any operator, so an unparenthesized argument silently loses everything past the first term:
>
> ```motoko
> #tag(n + 1) // CORRECT
> #tag n + 1 // WRONG: parses as (#tag n) + 1 → M0060, operator is not defined for operand types
> ```
>
460c534
< When `.map()` transforms to a **different** type, provide type parameters explicitly (M0098 without):
---
> Let inference work first. With unannotated lambdas the compiler resolves `.map()` to a different type on its own, so write the plain call:
463,464c537,538
< let photos = internalPhotos.map<PhotoInternal, Photo>(
< func(p) { { id = p.id; url = p.url; uploadedBy = p.uploadedBy.toText() } }
---
> let photos = internalPhotos.map(
> func p = { id = p.id; url = p.url; uploadedBy = p.uploadedBy.toText() }
465a540
> ```
466a542,545
> Add explicit type parameters **only** when the compiler reports M0098 (`no best choice for type parameter`):
>
> ```motoko
> let photos = internalPhotos.map<PhotoInternal, Photo>(func p = { ... });
468a548,549
> Adding them when inference already succeeded is a warning of its own — M0223, redundant type instantiation — which `mops check --fix` strips. Annotating the lambda instead of instantiating the call is always wrong.
>
524c605
< let user = users.find(func(u : User) : Bool { u.id == caller })
---
> let user = users.find(func u = u.id == caller)
528c609
< let label = optLabel ?? "(untitled)";
---
> let caption = optLabel ?? "(untitled)"; // not `label` — reserved word
532c613
< users.find(func(u : User) : Bool { u.name == name });
---
> users.find(func u = u.name == name);
536c617
< switch (todos.find(func(todo : Types.Todo) : Bool { todo.id == targetId })) {
---
> switch (todos.find(func todo = todo.id == targetId)) {
670,671c751
< | `unexpected token '<name>'` at an identifier declaration | Reserved word used as an identifier | Rename the identifier consistently across its contract and callers |
< | `unexpected token 'break'` | `break` reserved | Use helper function with early return |
---
> | `unexpected token '<name>'` at an identifier declaration | Reserved word used as an identifier | Rename it consistently across its contract and callers; see [references/reserved-keywords.md](references/reserved-keywords.md) |
672a753,762
> | `M0219` implicitly transient | Actor not persistent | Write `persistent actor`; see [references/project-setup.md](references/project-setup.md) |
> | `M0220` actor should be declared `persistent` | Actor not persistent | Write `persistent actor`; see [references/project-setup.md](references/project-setup.md) |
> | `M0218` redundant `stable` keyword | `stable` under EOP | Remove `stable` — a plain `let`/`var` is already stable |
> | `M0064` misplaced `'!'` | `!` outside an option block | Wrap in `do ? { ... }` |
> | `M0145` `does not cover value` | Non-exhaustive switch | Add the missing cases or a `case _` |
> | `M0060` operator not defined for `{#tag : T}` | Unparenthesized variant tag | `#tag(x)`, never `#tag x` |
> | `M0060` operator not defined, on `==` | `==` on a record with a `var` field (not shared) | Use an `equal` function instead |
> | `M0230` cannot determine implicit argument `compare` | Record/variant key with no findable `compare` | Add `compare` to the type's module; or `import` the module for a primitive key |
> | `M0070` expected object type, produces `Nat` | Receiver `.equal`/`.compare` on a number | Use `==` or `Nat.equal(a, b)` |
> | `M0096` actor cannot produce expected type `()` | Declaration after the actor | The actor must be the last declaration in the file |
707a798
> 11. Inline `func` arguments carry no type annotations (except `: async ()` on async callbacks); instantiate the call instead, and only when the compiler reports M0098
711c802,804
< - **Control flow**: [references/control-flow.md](references/control-flow.md) — `??`, switch statements, loops, `break` / `continue`
---
> - **Control flow**: [references/control-flow.md](references/control-flow.md) — `??`, `do ? { ... }` option chaining, switch statements, loops, `break` / `continue`
> - **Reserved keywords**: [references/reserved-keywords.md](references/reserved-keywords.md) — full list to check identifiers against
> - **Equality & comparison**: [references/equality.md](references/equality.md) — which types support receiver `.equal`, and when `==` differs from `equal`
712a806
> - **Project setup**: [references/project-setup.md](references/project-setup.md) — one-time `[moc] args` flags. Skip this if your platform manages `mops.toml`
Upstream diff:
caffeinelabs/skills6173cbc→eeb232aCommit:
eeb232ac0b18To sync: create branch
chore/sync-upstream-skills-eeb232a, follow theUpstream Sync Strategy
in CLAUDE.md, run
npm run validate, and open a PR that closes this issue.Before applying: check
.claude/upstream.mdfor icskills-owned sections.Do NOT overwrite those sections from upstream. Also check whether any owned
section is now covered by the upstream changes — if so, drop the icskills copy
and remove it from the owned list to avoid duplicating content.
writing-motoko← upstreamwriting-motokoSKILL.mdShow diff (- old upstream, + new upstream)
examples.mdShow diff (- old upstream, + new upstream)
references/control-flow.mdShow diff (- old upstream, + new upstream)
references/equality.mdShow diff (- old upstream, + new upstream)
references/project-setup.mdShow diff (- old upstream, + new upstream)
references/reserved-keywords.mdShow diff (- old upstream, + new upstream)
migrating-motoko-actors— no changestroubleshooting-motoko-migrations— no changes