Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .sources/VERSIONS
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ chain-fusion-signer v0.4.0
papi v0.1.1 168bc9d
ic-pub-key v1.0.1 f89fa55
icp-cli v1.1.0 fe4af7c
motoko v1.13.0 cd63c2e
motoko v1.14.0 38528ec
motoko-core v2.4.0 cd37dbf
cdk-rs ic-cdk v0.20.1 / ic-cdk-timers v1.0.0 / ic-cdk-executor v2.0.0 317f55c
candid 2025-12-18 # candid v0.10.20, didc v0.5.4 2e4a2cf
Expand Down
2 changes: 1 addition & 1 deletion .sources/motoko
Submodule motoko updated 40 files
+1 −1 .github/CODEOWNERS
+1 −1 .github/workflows/build.yml
+38 −0 .github/workflows/docs-site.yml
+1 −1 .github/workflows/release.yml
+22 −0 Changelog.md
+40 −4 doc/md/fundamentals/implicit-parameters.md
+1 −1 doc/md/reference/compiler-ref.md
+1 −1 doc/md/reference/language-manual.md
+6 −6 doc/site/package-lock.json
+10 −2 doc/site/sidebar.mjs
+9 −9 flake.lock
+2 −0 rts/motoko-rts/src/idl.rs
+14 −3 src/codegen/compile_classical.ml
+14 −3 src/codegen/compile_enhanced.ml
+1 −1 src/exes/moc.ml
+1 −1 src/ir_passes/tailcall.ml
+3 −1 src/lowering/desugar.ml
+2 −1 src/mo_config/flags.ml
+25 −4 src/mo_frontend/typing.ml
+1 −1 test/check-error-codes.py
+11 −0 test/fail/implicit-derivation-structural-variant-missing.mo
+7 −0 test/fail/ok/implicit-derivation-structural-variant-missing.tc-human.ok
+0 −0 test/fail/ok/implicit-derivation-structural-variant-missing.tc-human.ret.ok
+4 −0 test/fail/ok/implicit-derivation-structural-variant-missing.tc.ok
+0 −0 test/fail/ok/implicit-derivation-structural-variant-missing.tc.ret.ok
+1 −1 test/run-drun/region0-stable-mem-max.mo
+1 −0 test/run-drun/stable-mem-max.mo
+45 −0 test/run/idl-service-principal.mo
+62 −0 test/run/implicit-derivation-structural-variant.mo
+19 −2 test/run/issue-5050.mo
+13 −0 test/run/issue-6285.mo
+0 −5 test/run/ok/issue-5050.comp.ok
+2 −0 test/run/ok/issue-5050.run-ir.ok
+2 −5 test/run/ok/issue-5050.run-low.ok
+2 −0 test/run/ok/issue-5050.run.ok
+2 −0 test/run/ok/issue-5050.wasm-run.ok
+2 −0 test/run/ok/issue-6285.run-ir.ok
+2 −0 test/run/ok/issue-6285.run-low.ok
+2 −0 test/run/ok/issue-6285.run.ok
+2 −0 test/run/ok/issue-6285.wasm-run.ok
44 changes: 40 additions & 4 deletions docs/languages/motoko/fundamentals/implicit-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ The compiler searches for implicit arguments in the following order, stopping at
1. Local values in the current scope.
2. Module fields (e.g., `Array.compare<T>`).
3. Fields of unimported modules (requires `--implicit-package`).
3. **Structural**: structural combiners (`__record`, `__tuple` convention) applied to record or tuple types (see [Structural derivation](#structural-derivation) below):
3. **Structural**: structural combiners (`__record`, `__tuple`, `__variant` convention) applied to record, tuple, or variant types (see [Structural derivation](#structural-derivation) below):
1. Local values in the current scope.
2. Module fields.
3. Fields of unimported modules (requires `--implicit-package`).
Expand Down Expand Up @@ -230,15 +230,15 @@ When derivation is attempted but fails (for example, because an inner implicit c

### Structural derivation

When an implicit is needed for a **record or tuple type**, the compiler can synthesize it automatically using a *structural combiner*: a function whose single parameter name begins with `__` and encodes the structural decomposition kind. Structural combiners must not have implicit parameters.
When an implicit is needed for a **record, tuple, or variant type**, the compiler can synthesize it automatically using a *structural combiner*: a function whose single parameter name begins with `__` and encodes the structural decomposition kind. Structural combiners must not have implicit parameters.

Two structural kinds are supported, distinguished by the combiner's parameter name:
Three structural kinds are supported, distinguished by the combiner's parameter name:

| Parameter name | Combiner type | Implicit argument type | Description |
|----------------|----------------------------|------------------------------------------|------------------------------------------------|
| `__record` | `[(Text, () -> E)] -> R` | `Rec -> R` or `(Rec, Rec) -> R` | Record: one or two records, arity from implicit|
| `__tuple` | `[() -> E] -> R` | `(A, B, ...) -> R` or `((A,B,...), (A,B,...)) -> R` (≥ 2 elements) | Tuple: one implicit per element |
| `__variant` |: |: | Reserved for future extension |
| `__variant` | `(Text, () -> E) -> R` | `Var -> R` | Variant: the active case `(tag, payload thunk)`|

Each per-field/element result is wrapped in a **thunk** (`() -> E`), giving the combiner full control over evaluation order. Combiners that need all values (like serialization) simply call every thunk. Combiners that can short-circuit (like comparison) can stop early: remaining thunks are never evaluated.

Expand Down Expand Up @@ -381,6 +381,42 @@ func inspect<T>(x : T, describe : (implicit : T -> Text)) : Text = describe(x);
assert inspect(("hello", 42 : Nat)) == "(hello, 42)";
```

#### Variant derivation (`__variant`)

When the compiler is looking for an implicit of type `Var -> R` where `Var` is a variant type `{ #t1 : T1; ...; #tn : Tn }`, it searches for a structural combiner whose parameter is named `__variant` and has type `(Text, () -> E) -> R`.

Unlike a record or tuple, a variant value is exactly **one** of its cases at runtime. The compiler synthesizes a wrapper that switches on the active case and applies the combiner once to its `(tag, payload thunk)`:

```
func($v) {
combiner(switch ($v) {
case (#t1 x) ("t1", func() { inst1(x) });
...
case (#tn x) ("tn", func() { instn(x) });
})
}
```

Each per-case implicit has type `Ti -> E`, resolved by the same search label. A no-payload case `#t` has payload type `()`, so it needs a `() -> E` instance: the same rule that applies to every component type of a record or tuple.

```motoko
// __variant combiner: serialise the active case as "#tag(payload)".
func show(__variant : (Text, () -> Text)) : Text {
let (tag, payload) = __variant;
"#" # tag # "(" # payload() # ")"
};

module TextShow { public func show(self : Text) : Text = self };
module NatShow { public func show(self : Nat) : Text = debug_show self };

func inspect<T>(x : T, show : (implicit : T -> Text)) : Text = show(x);

type Shape = { #circle : Nat; #named : Text };
assert inspect<Shape>(#named "hi") == "#named(hi)";
```

Only the unary form (`Var -> R`) is supported. Binary operations over variants (`(Var, Var) -> R`, e.g. `compare`) are not derived structurally, because the two values may be in different cases; write such combiners explicitly.

#### Disambiguation: binary vs unary when both `__record` and `__tuple` are in scope

Having `__record` and `__tuple` combiners in scope simultaneously is safe: the compiler picks the right path by inspecting the **number of arguments** in the implicit argument's function type. The dispatch depends on where the tuple appears in the source, not on what the type expands to:
Expand Down
22 changes: 22 additions & 0 deletions docs/languages/motoko/reference/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ sidebar:

# Motoko compiler changelog

## 1.14.0 (2026-08-11)

* motoko (`moc`)

* feat: Structural implicit derivation now supports variants via the `__variant` combiner (`(Text, () -> E) -> R`).
The synthesized wrapper switches on the active case and applies the combiner to its `(tag, payload thunk)`,
deriving operations like serialization for any variant whose case payloads have instances (#6192).

* feat: the default maximum for stable memory (`--max-stable-pages`) is now 100 GiB
(was 4 GiB), raising the default ceiling for the `Region` library.
Override with `--max-stable-pages <n>` as before (#6279).

* bugfix: implement the new Candid subtyping rule `service <actortype> <: principal`
(dfinity/candid#748): service references now decode at type `Principal`, both when
decoded directly and in deferred subtype checks on function references (#6275).

* bugfix: a `class` in expression position lowered to unit instead of its
constructor (#6291).

* bugfix: a self tail call whose argument is a tuple-returning expression
crashed the compiler (or miscompiled, with the IR check off) (#6292).

## 1.13.0 (2026-08-03)

* motoko (`moc`)
Expand Down
2 changes: 1 addition & 1 deletion docs/languages/motoko/reference/compiler-ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ You can use the following options with the `moc` command.
| `--implicit-derivation-depth <n>` | Maximum recursion depth for [implicit](../fundamentals/implicit-parameters.md) argument derivation (default 100). Raise if a complex derivation is rejected as depth-limited. |
| `--legacy-persistence` | Use legacy (classical) persistence. This also enables the usage of --copying-gc, --compacting-gc, and --generational-gc. Deprecated in favor of the new enhanced orthogonal persistence, which is default. Legacy persistence will be removed in the future.|
| `--map` | Outputs a JavaScript source map. |
| `--max-stable-pages <n>` | Set maximum number of pages available for library `ExperimentStableMemory.mo` (default 65536). |
| `--max-stable-pages <n>` | Set maximum number of pages available to stable memory via the `Region` library (default 1638400, i.e. 100 GiB). |
| `-no-system-api` | Disables system API imports. |
| `-no-timer` | Disables timer API imports and hides timer primitives. |
| `-o <file>` | Specifies the output file. |
Expand Down
2 changes: 1 addition & 1 deletion docs/languages/motoko/reference/language-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -2396,7 +2396,7 @@ the expanded function call expression `<parenthetical>? <exp1> <T0,…​,Tn>? <

- `__record` (parameter type `[(Text, () -> E)] -> R`): handles both unary holes (`SomeRecord -> R`) and binary holes (`(SomeRecord, SomeRecord) -> R` where both args are the same record type). For a unary hole it synthesizes `func($r) { combiner([("f", func() = inst($r.f)), ...]) }` with per-field implicits `FieldType -> E`. For a binary hole it synthesizes `func($r1, $r2) { combiner([("f", func() = inst($r1.f, $r2.f)), ...]) }` with per-field implicits `(FieldType, FieldType) -> E`. Per-field thunks let the combiner short-circuit (e.g. comparison). The arity is determined by the hole type, not the combiner.
- `__tuple` (parameter type `[() -> E] -> R`): handles both unary holes (`(A, B, ...) -> R` with at least two elements) and binary holes (`((A, B, ...), (A, B, ...)) -> R` where both args are the same tuple type with ≥ 2 elements). For a unary hole it synthesizes `func($t) { combiner([func() = inst0($t.0), func() = inst1($t.1), ...]) }` with per-element implicits `ElemType_i -> E`. For a binary hole it synthesizes `func($t1, $t2) { combiner([func() = inst0($t1.0, $t2.0), ...]) }` with per-element implicits `(ElemType_i, ElemType_i) -> E`. Tuples with fewer than two elements are not synthesized: single-element tuples reduce to the element type, and unit `()` is treated as a scalar.
- `__variant` is reserved for future extension.
- `__variant` (parameter type `(Text, () -> E) -> R`): handles unary holes (`SomeVariant -> R`) only. Since a variant value is exactly one of its cases, it synthesizes `func($v) { combiner(switch $v { case (#t x) ("t", func() = inst(x)); ... }) }` with per-case implicits `CaseType -> E`. Binary holes (`(SomeVariant, SomeVariant) -> R`) are not synthesized, since the two values may inhabit different cases.

The call expression `<exp1> <T0,…​,Tn>? <exp2>` evaluates `<exp1>` to a result `r1`. If `r1` is `trap`, then the result is `trap`.

Expand Down
Loading