diff --git a/.sources/VERSIONS b/.sources/VERSIONS index 5a66c74..33ff89a 100644 --- a/.sources/VERSIONS +++ b/.sources/VERSIONS @@ -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 diff --git a/.sources/motoko b/.sources/motoko index cd63c2e..38528ec 160000 --- a/.sources/motoko +++ b/.sources/motoko @@ -1 +1 @@ -Subproject commit cd63c2e6707e3d45313c9d11ab4b9eb86c64c334 +Subproject commit 38528ecbf7f2b83dc784481f8f79f228d022fe8e diff --git a/docs/languages/motoko/fundamentals/implicit-parameters.md b/docs/languages/motoko/fundamentals/implicit-parameters.md index d76098f..aa882ec 100644 --- a/docs/languages/motoko/fundamentals/implicit-parameters.md +++ b/docs/languages/motoko/fundamentals/implicit-parameters.md @@ -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`). 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`). @@ -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. @@ -381,6 +381,42 @@ func inspect(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(x : T, show : (implicit : T -> Text)) : Text = show(x); + +type Shape = { #circle : Nat; #named : Text }; +assert inspect(#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: diff --git a/docs/languages/motoko/reference/changelog.md b/docs/languages/motoko/reference/changelog.md index 65ee6f1..ef5a3b0 100644 --- a/docs/languages/motoko/reference/changelog.md +++ b/docs/languages/motoko/reference/changelog.md @@ -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 ` as before (#6279). + + * bugfix: implement the new Candid subtyping rule `service <: 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`) diff --git a/docs/languages/motoko/reference/compiler-ref.md b/docs/languages/motoko/reference/compiler-ref.md index 8a62451..dbb675f 100644 --- a/docs/languages/motoko/reference/compiler-ref.md +++ b/docs/languages/motoko/reference/compiler-ref.md @@ -56,7 +56,7 @@ You can use the following options with the `moc` command. | `--implicit-derivation-depth ` | 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 ` | Set maximum number of pages available for library `ExperimentStableMemory.mo` (default 65536). | +| `--max-stable-pages ` | 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 ` | Specifies the output file. | diff --git a/docs/languages/motoko/reference/language-manual.md b/docs/languages/motoko/reference/language-manual.md index 96f1b52..fc2e426 100644 --- a/docs/languages/motoko/reference/language-manual.md +++ b/docs/languages/motoko/reference/language-manual.md @@ -2396,7 +2396,7 @@ the expanded function call expression `? ? < - `__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 ` ? ` evaluates `` to a result `r1`. If `r1` is `trap`, then the result is `trap`.