You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Summary
Automated bump of `.sources/motoko` from `v1.8.2` to `v1.9.0`.
- Ran `npm run sync:motoko` — synced docs from the new release
- Build passed ✓
## Checklist
- [ ] Review synced content for breaking changes
- [ ] Check [release
notes](https://github.com/caffeinelabs/motoko/releases/tag/1.9.0) for
API or syntax changes that affect hand-written docs
- [ ] Verify any new sections or renamed files are handled correctly
## Sync recommendation
`sync from caffeinelabs/motoko doc/md`
Co-authored-by: pr-automation-bot-public[bot] <pr-automation-bot-public[bot]@users.noreply.github.com>
Copy file name to clipboardExpand all lines: docs/languages/motoko/fundamentals/implicit-parameters.md
+168Lines changed: 168 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -191,6 +191,11 @@ The compiler searches for implicit arguments in the following order, stopping at
191
191
1. Local values in the current scope.
192
192
2. Module fields (e.g., `Array.compare<T>`).
193
193
3. Fields of unimported modules (requires `--implicit-package`).
194
+
3.**Structural**: structural combiners (`__record`, `__tuple` convention) applied to record or tuple types (see [Structural derivation](#structural-derivation) below):
195
+
1. Local values in the current scope.
196
+
2. Module fields.
197
+
3. Fields of unimported modules (requires `--implicit-package`).
198
+
194
199
Within each tier, if multiple candidates match, the compiler picks the most specific one (by subtyping). If no unique best candidate exists, the call is rejected as ambiguous.
195
200
196
201
This ordering guarantees that direct matches are always preferred over derived ones, and local definitions take precedence over imported or unimported module definitions.
@@ -223,6 +228,168 @@ The resolution depth is bounded to guarantee termination. If you encounter a dep
223
228
224
229
When derivation is attempted but fails (for example, because an inner implicit can't be resolved), the compiler reports which inner implicits were missing and, when applicable, a hint about which module to import.
225
230
231
+
### Structural derivation
232
+
233
+
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.
234
+
235
+
Two structural kinds are supported, distinguished by the combiner's parameter name:
236
+
237
+
| 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|
240
+
|`__tuple`|`[() -> E] -> R`|`(A, B, ...) -> R` or `((A,B,...), (A,B,...)) -> R` (≥ 2 elements) | Tuple: one implicit per element |
241
+
|`__variant`|: |: | Reserved for future extension |
242
+
243
+
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.
244
+
245
+
The search label used to resolve per-element implicits is the same as the implicit parameter name at the call site.
246
+
247
+
:::caution
248
+
Motoko has no type abstraction (no newtypes or private types), so a named type that expands to a record: including stdlib containers like `Map`, `Set`, or `Buffer`: is structurally indistinguishable from a plain data record and may be decomposed into its internal fields by structural derivation; provide a dedicated instance (e.g. `MapJson`) to take precedence over structural synthesis for such types.
249
+
:::
250
+
251
+
#### Unary record derivation (`__record`)
252
+
253
+
When the compiler is looking for an implicit of type `SomeRecord -> R` and finds a unique structural combiner for `R` (parameter named `__record`, type `[(Text, () -> E)] -> R`), it:
254
+
255
+
1. Decomposes `SomeRecord` into its fields (in lexicographic order).
256
+
2. For each field `name : FieldType`, resolves a per-field implicit of type `FieldType -> E` using the same search label.
The compiler finds `Json.encode(__record)` as the unique structural combiner for `Json`, resolves per-field `encode` instances from `TextJson` and `IntJson`, and synthesizes the wrapper automatically.
299
+
300
+
#### Binary record derivation
301
+
302
+
When the compiler is looking for an implicit of type `(Rec, Rec) -> R` where `Rec` is a record type and both arguments have the same type, it searches for a `__record` combiner for `R`: the same combiner that handles the unary case. The arity is determined entirely by the implicit argument's type; the combiner itself is unaware of it.
Each per-field implicit has type `(FieldType, FieldType) -> E`, resolved recursively with the same search label. This allows binary operations like comparison or equality to be derived field-by-field from a single `__record` combiner.
311
+
312
+
##### Example: lexicographic comparison
313
+
314
+
```motoko
315
+
import Array "mo:core/Array";
316
+
import Nat "mo:core/Nat";
317
+
import Text "mo:core/Text";
318
+
import Order "mo:core/Order";
319
+
320
+
// __record combiner: fold field-wise Order values, short-circuiting at first non-equal.
321
+
// Thunks enable genuine short-circuiting — remaining fields are never evaluated.
// Fields resolved: age → Nat.compare, name → Text.compare (lexicographic order).
334
+
let people : [Person] = [{ name = "Carol"; age = 30 }, { name = "Bob"; age = 25 }];
335
+
let sorted = people.sort();
336
+
// sorted[0] = { name = "Bob"; age = 25 } (age 25 < 30)
337
+
```
338
+
339
+
Nested record types are handled automatically: a `Team` with a `Person` field will derive `compare` for `Team` by first deriving `compare` for `Person` at depth+1.
340
+
341
+
#### Tuple derivation (`__tuple`)
342
+
343
+
When the compiler is looking for an implicit of type `(A, B, ...) -> R` (a tuple domain with at least two elements), it searches for a structural combiner whose parameter is named `__tuple` and has type `[() -> E] -> R`.
Each per-element implicit has type `ElemType_i -> E`, resolved positionally using the same search label.
352
+
353
+
#### Binary tuple derivation
354
+
355
+
Like `__record`, the `__tuple` combiner also supports binary implicit arguments. When the implicit argument has type `((A, B, ...), (A, B, ...)) -> R` (two arguments of the same tuple type with ≥ 2 elements), the compiler synthesizes a binary wrapper:
Each per-element implicit has type `(ElemType_i, ElemType_i) -> E`. This enables element-wise binary operations like comparison or equality over tuples.
362
+
363
+
##### Example: tuple description
364
+
365
+
```motoko
366
+
// __tuple combiner: join per-element descriptions (evaluates all thunks)
367
+
func describe(__tuple : [() -> Text]) : Text {
368
+
var s = "("; var first = true;
369
+
for (t in __tuple.vals()) {
370
+
if (not first) { s #= ", " };
371
+
s #= t(); first := false
372
+
};
373
+
s #= ")"; s
374
+
};
375
+
376
+
module TextDesc { public func describe(self : Text) : Text = self };
377
+
module NatDesc { public func describe(self : Nat) : Text = debug_show self };
378
+
379
+
func inspect<T>(x : T, describe : (implicit : T -> Text)) : Text = describe(x);
#### Disambiguation: binary vs unary when both `__record` and `__tuple` are in scope
385
+
386
+
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:
387
+
388
+
-`implicit : (X, X) -> T`: the inline tuple `(X, X)` is flattened into two separate args. The compiler sees a **two-argument** function, checks that both args are the same type, and uses the binary path: `__record` if `X` is a record type, `__tuple` if `X` is a tuple type (≥ 2 elements).
389
+
-`implicit : P -> T` where `P` is a **type alias** for `(A, B, ...)`: `P` is not a tuple in the source, so it stays as a single arg. The compiler sees a **one-argument** function, promotes `P` to a tuple, and uses the `__tuple` combiner (unary path).
390
+
391
+
In practice: write `(X, X) -> T` directly as two args to trigger the binary path. Going through a type alias `type Pair = (R, R)` and writing `Pair -> T` will route to `__tuple` (unary) instead.
392
+
226
393
### Supported types
227
394
228
395
The core library provides comparison functions for common types:
@@ -327,6 +494,7 @@ There is no need to update existing code unless you want to take advantage of th
327
494
Implicit arguments are resolved at compile time.
328
495
- For direct matches, the resulting code is identical to explicitly passing the argument.
329
496
- For derived implicits, the compiler synthesizes a wrapper function at each call site. This creates a small overhead per call site, which could be mitigated by caching in the future. For now, if this becomes a performance issue, consider defining the function explicitly so all call sites share a single definition.
497
+
- For `__record` structural derivation, the synthesized wrapper invokes one implicit per record field (two invocations per field for the binary path), so runtime cost scales linearly with record width. For `__tuple`, cost scales with tuple arity. For hot paths with wide types, consider writing the combiner explicitly.
Copy file name to clipboardExpand all lines: docs/languages/motoko/reference/changelog.md
+9-1Lines changed: 9 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,14 @@ sidebar:
8
8
9
9
# Motoko compiler changelog
10
10
11
+
## 1.9.0 (2026-06-02)
12
+
13
+
* motoko (`moc`)
14
+
15
+
* feat: Structural implicit derivation for records and tuples via `__record` and `__tuple` combiners. Per-field results are lazy thunks, enabling short-circuiting for operations like `compare` (#5903).
16
+
17
+
* feat: `--experimental-multi-value` flag enables function-level multi-value Wasm codegen. Off by default (#6113).
18
+
11
19
## 1.8.2 (2026-05-21)
12
20
13
21
* motoko (`moc`)
@@ -906,7 +914,7 @@ sidebar:
906
914
ensures that no cleanup is required.
907
915
908
916
The relevant security best practices are accessible at
Copy file name to clipboardExpand all lines: docs/languages/motoko/reference/language-manual.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2389,6 +2389,12 @@ the expanded function call expression `<parenthetical>? <exp1> <T0,…,Tn>? <
2389
2389
If the derivable candidate's own implicit parameters can be recursively resolved (up to a configurable depth limit), the compiler synthesizes a wrapper function that calls the candidate with the resolved inner implicits.
2390
2390
This allows, for example, an implicit `compare : ([Nat], [Nat]) -> Order` to be derived from `Array.compare<Nat>` when `Nat.compare` is in scope. The derivation depth is bounded by the `--implicit-derivation-depth` flag.
2391
2391
2392
+
**Structural derivation**: When derivation also fails, the compiler additionally searches for *structural combiners*: first among local values, then module fields, then library fields (gated on `--implicit-package`). The combiner's parameter name determines the structural kind:
2393
+
2394
+
- `__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.
2395
+
- `__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.
2396
+
- `__variant` is reserved for future extension.
2397
+
2392
2398
The call expression `<exp1> <T0,…,Tn>? <exp2>` evaluates `<exp1>` to a result `r1`. If `r1` is `trap`, then the result is `trap`.
2393
2399
2394
2400
Otherwise, `<exp3>` (the hole expansion of `<exp2>`) is evaluated to a result `r2`. If `r2` is `trap`, the expression results in `trap`.
0 commit comments