Metel Error Code Reference
All Metel errors carry a code. Codes are prefixed by phase:
| Prefix | Phase |
|---|---|
P | Parse — invalid source text |
T | Type — type-checker rejection |
R | Runtime — error during execution |
I | Internal — bug in the interpreter (please report) |
Parse errors (P)
P0001 — Syntax error
The source text does not match the Metel grammar.
Fix: correct the syntax at the indicated position.
Tested by (18)
1// Negative: a duplicate label in a record *type* is rejected, like the value case.
2fun f(p: { x: i64, x: f64 }) -> i64 { 0 }
parse errorP0001“duplicate label `x` in record type”
1// This file contains a deliberate syntax error on line 3.
2// The parser should report an error at line 3, col 1.
3@@@
parse errorP0001at 3:1“neg_01_syntax_error.mtl”
1aspect Display {
2 fun show(&self) -> String;
3}
4
5impl Display for Counter {
6 fun show(&self) -> String { "x" }
7}
parse errorP0001
1pub struct Counter {
2 pub value: i64,
3}
4
5fun main() {
6 let mut counter = Counter { value = 0 };
7 let ptr: &mut Counter = &mut counter;
8 ptr.value += 1;
9}
parse errorP0001
Also demonstratesClosures L4
1fun main() {
2 let f = fun(x: i64) -> i64 { return x + 1; };
3}
parse errorP0001
Also demonstratesAspect bounds on function type… L1
1// RFC-0130: the anonymous type-position keyword is `extends Aspect`. The old
2// `impl Aspect` spelling is a hard parse error (`impl` stays reserved, like
3// `mut`/`pub` after RFC-0098) -- no compatibility alias.
4fun print_it(x: impl Display) -> String {
5 "${x}"
6}
7
8fun main() {}
parse errorP0001
Also demonstratesImmutable bindings L1, Mutable bindings L1
1// RFC-0136: `let`/`var` declarations, plain reassignment, and associated-type
2// definition use `:=`. The old `=` separator is a hard parse error — no alias.
3// (Compound `+=` etc. and struct-field `=` are unaffected; see neg_15.)
4fun main() {
5 let x = 1;
6}
parse errorP0001
Also demonstratesMutable bindings L1
1// RFC-0136: plain reassignment uses `:=`. Old `x = e` is a parse error.
2fun main() {
3 var x := 0;
4 x = 1;
5}
parse errorP0001
Also demonstratesPattern matching L3
1// RFC-0156: a `match` scrutinee must be parenthesized, like `if`/`while`/`for`.
2// The bare `match x { ... }` form is a hard parse error — no alias.
3fun main() -> i64 {
4 match 1 {
5 1 => 10,
6 _ => 0,
7 }
8}
parse errorP0001
Also demonstratesRow bounds L2
1// Negative (RFC-0118 §2): a negative row bound names labels that must be absent, so there
2// is no rest to quantify over and `..` is meaningless. Rejected rather than ignored.
3fun f<record T: !{ x, .. }>(v: T) -> i64 { 1 }
4
5fun main() { }
parse errorP0001“negative row bound takes no”
Also demonstratesFile header ordering L1
1fun main() -> i64 { 0 }
2import helper::answer;
parse errorP0001
Also demonstratesType ascription L3
1// RFC-0021 §4: an expression accepts at most one type ascription.
2let value := 1 : i64 : i64;
parse errorP0001
P0002 — Invalid integer literal
An integer literal is out of range for i64 (−9,223,372,036,854,775,808 to 9,223,372,036,854,775,807).
Fix: use a value that fits in i64, or split the computation.
Tested by
P0003 — Invalid float literal
A float literal cannot be represented as an f64.
[P0003] parse error in main.mtl at 4..12: invalid float literal '1e9999'
Fix: use a value within the f64 range (~±1.8 × 10³⁰⁸).
Exempt from fixture coverage — untestable: Neither documented route is reachable: the grammar has no exponent notation, so a literal like 1e9999 is actually P0001, not P0003; and a literal long enough to overflow f64 in plain decimal notation silently saturates to infinity instead of erroring.
Type errors (T)
T0001 — Type mismatch, or an impl that is not allowed
Two types that must be equal are not.
Fix: ensure the expression produces the expected type. Add an explicit cast if widening (e.g. x as f64).
The same code also covers an extend block the language does not permit, which is a
distinct situation sharing one code:
- a target that cannot carry the impl at all —
extend { … }: Dropon an anonymous record; - a target with nowhere to register, so its methods could never be found — a tuple, an
anonymous record, a
funtype, or an array whose element is not one of the impl's own type parameters. Onlyextend<T> T[]: Aspect— the array's element spelled exactly as one of the impl's own generics — is implemented today; - a
dropbody, while destructor invocation is not yet implemented.
Fix: each message names the way forward — usually a named struct, or the generic form where one exists.
Tested by (50)
Also demonstratesPartial moves L1, Narrowing L4
1struct Pair {
2 left: String,
3 right: i64,
4}
5
6fun take(pair: Pair) -> i64 {
7 pair.right
8}
9
10fun main() {
11 let pair := Pair { left = "a", right = 1 };
12 let left: String := pair.left;
13 let value: i64 := take(pair);
14}
typecheck errorT0001“partially-moved `Pair`”
Also demonstratesNarrowing L1
1fun take(r: { left: String, right: i64 }) -> i64 {
2 r.right
3}
4
5fun main() {
6 let r := { left = "a".to_string(), right = 1 };
7 let left: String := r.left;
8 let value: i64 := take(r);
9}
typecheck errorT0001“cannot unify”
Also demonstratesPartial moves L3, Narrowing L1
1struct Two {
2 left: String,
3 right: String,
4}
5
6fun take(t: Two) -> i64 {
7 t.left.len()
8}
9
10fun main() {
11 var t := Two { left = "a", right = "b" };
12 let taken_left := t.left;
13 let taken_right := t.right;
14 t.left := "c";
15 // t.right is still moved out -- reassigning only one of two moved fields does
16 // not restore whole-value status; `t` stays partially moved.
17 let n := take(t);
18}
typecheck errorT0001“partially-moved `Two`”
1// #646's row-bound rest support is scoped to a row-bounded generic type parameter --
2// a concrete anonymous record scrutinee (no row bound at all) still has no open-row
3// support, since `unify`'s InferType::Record arm is exact-match only.
4fun main() {
5 let p := { x = 1.0, y = 2.0 };
6 match (p) {
7 { x, .. } => println("x is ${x}"),
8 }
9}
typecheck errorT0001at 7
Also demonstratesMatching through a reference L5
1// RFC-0108 §3: reference-transparency is scoped to match scrutinees only. A `&Colour`
2// value that matches fine as a scrutinee does not silently widen to `Colour` in a
3// call-argument position -- that is an ordinary type mismatch, not a peel.
4
5enum Colour { Red, Green, Blue }
6
7fun takes_by_value(c: Colour) -> String {
8 match (c) {
9 Colour::Red => "red",
10 Colour::Green => "green",
11 Colour::Blue => "blue",
12 }
13}
14
15fun main() {
16 let c := Colour::Green;
17 let r: &Colour := &c;
18 println(takes_by_value(r));
19}
typecheck errorT0001at 18
Also demonstratesDyn aspect L1
1// Regression (metel-core#865, RFC-0008 slice 1): `dyn Aspect` cannot be an
2// `extend` target -- it's existential, there is no one concrete type to
3// register an impl against (the same reason `impl Aspect` can't be an impl
4// target either). Joins the existing tuple/record/fun/array-target rejections
5// this pass already covers.
6
7aspect Local {
8 fun f(&self) -> i64;
9}
10
11extend dyn Local: Local { // ERROR[T0001]
12}
13
14fun main() {}
typecheck errorT0001“cannot `extend` a `dyn Aspect` type”
Also demonstratesNarrowing L2, Passing a residual to a function L1
1// Regression (metel-core#857, RFC-0137 slice 1): this is the actual motivating bug
2// -- Self.{ fd } used to accept a bare anonymous record literal of the same shape
3// exactly as readily as a value actually derived from a real Handle, since the
4// projection resolved to an unbranded record type. Now rejected: a struct's own
5// projection is branded, and a same-shaped anonymous record never carries that
6// brand.
7
8struct Handle { fd: i64, name: String }
9
10extend Handle {
11 fun describe(h: Self.{ fd }) -> i64 { h.fd }
12}
13
14fun main() {
15 let _ := Handle::describe({ fd = 3 });
16}
typecheck errorT0001
Also demonstratesNarrowing L4
1// RFC-0137 slice 2 (metel-core#858): once a field is moved out, the value's type
2// is the residual -- `Handle.{ fd }` -- not the whole `Handle`. Passing it where
3// the whole struct is required is a plain type error at inference time, no longer
4// only a `--move-check` finding.
5
6struct Handle { fd: i64, name: String }
7
8fun wants_full(h: Handle) -> i64 { h.fd }
9
10fun main() {
11 let h := Handle { fd = 3, name = "x" };
12 let taken := h.name;
13 let _ := wants_full(h); // rejected: `h` is `Handle.{ fd }`
14 println(taken);
15}
typecheck errorT0001“partially-moved `Handle`”
Also demonstratesNarrowing L1, Narrowing L4
1// RFC-0117 (metel-core#789): once a field is moved out of an anonymous record,
2// the record's type is the narrower row -- `{ right: i64 }`, not `{ left, right }`.
3// Passing it where the whole record is required is a plain type error at
4// inference time, no longer only a `--move-check` finding. A narrowed record has
5// no distinct type marker, so the diagnostic is the ordinary record-shape
6// mismatch.
7
8fun wants_full(r: { left: String, right: i64 }) -> i64 { r.right }
9
10fun main() {
11 let r := { left = "a".to_string(), right = 1 };
12 let taken := r.left;
13 let _ := wants_full(r);
14 println(taken);
15}
typecheck errorT0001“cannot unify”
Also demonstratesNarrowing L1, Narrowing L4
1// metel-core#958: the join is the *union* of the arms' moves. Only the `then`
2// arm moves `rec.gone`; after the `if`, `rec` is narrowed to `{ keep: i64 }` on
3// every path (the move is joined in even though the `else` path didn't run it),
4// so a whole-value use at the wider row is rejected.
5fun whole(r: { gone: String, keep: i64 }) -> i64 { r.keep }
6
7fun main() {
8 let cond := true;
9 let rec := { gone = "z".to_string(), keep = 1 };
10 if (cond) {
11 let a := rec.gone;
12 }
13 whole(rec) // rec : { keep: i64 } here -- wider row required
14}
typecheck errorT0001“cannot unify”
Also demonstratesRow bounds L7
1// #646: a closed row bound's fields are fully known, but a record pattern without `..`
2// must still name every one of them -- the same completeness rule an anonymous record or
3// named struct pattern already enforces.
4fun get_x<record T: { x: f64, y: f64 }>(p: T) -> f64 {
5 match (p) {
6 { x } => x,
7 }
8}
9
10fun main() {
11 println(get_x({ x = 1.0, y = 2.0 }));
12}
typecheck errorT0001at 6
Also demonstratesRow bounds L7
1// #646: an open row bound's full field set isn't known here, so a record pattern that
2// doesn't end in `..` can never be exhaustive against it.
3fun describe<record T: { x: f64, .. }>(p: T) -> f64 {
4 match (p) {
5 { x } => x,
6 }
7}
8
9fun main() {
10 println(describe({ x = 1.0 }));
11}
typecheck errorT0001at 5
1// #266: diagnostics printed raw inference variables (`?t18`), and the number
2// shifted whenever *anything* unrelated was added to the file, since it was
3// the TypeVar's own global, monotonically-increasing id. Paired with
4// stability_02 (identical body, one unrelated declaration prepended) — both
5// fixtures assert the exact same message text via their .toml sidecar's
6// `contains`, pinning that the message is unaffected by declarations that
7// have nothing to do with the error. Now shows `T`, `Wrap`'s own declared
8// parameter name, rather than even a stable placeholder — the fuller half of
9// #266's ask, covered for struct/enum type parameters specifically.
10struct Wrap<T> { v: T }
11enum E { A }
12
13fun main() {
14 let w := Wrap { v = 1 };
15 let n: E := w; // ERROR[T0001]
16}
typecheck errorT0001“cannot unify Wrap<T> with E”
1// #266 — see stability_01's comment. Identical body to stability_01, with one
2// unrelated generic struct prepended that the error does not mention at all.
3// Before the fix, adding this alone shifted the reported label (`?t18` ->
4// `?t19`) purely because the global TypeVar counter had run further by the
5// time the error site was reached — nothing about the error itself changed.
6// The .toml sidecar asserts the exact same `contains` string as stability_01.
7struct Unrelated<A> { a: A }
8struct Wrap<T> { v: T }
9enum E { A }
10
11fun main() {
12 let w := Wrap { v = 1 };
13 let n: E := w; // ERROR[T0001]
14}
typecheck errorT0001“cannot unify Wrap<T> with E”
1// #266, second half: a var that traces back to a declared generic parameter
2// is now shown under that name, not a placeholder. `Pair<A, B>`'s `second`
3// field is fully resolved (`String`, from the literal); `first` is not
4// (`i64`, never constrained against anything concrete) -- the message must
5// show the declared name `A` for the unresolved one and the concrete type
6// for the resolved one, side by side.
7struct Pair<A, B> { first: A, second: B }
8
9fun main() {
10 let p := Pair { first = 5, second = "x" };
11 let n: boolean := p; // ERROR[T0001]
12}
typecheck errorT0001“cannot unify Pair<A, String> with boolean”
1// #266: function-level generic variables preserve their declared names at a
2// call site's fresh instantiation. Paired with stability_05 (one unrelated
3// declaration prepended) asserting the identical message.
4fun pair<A, B>(a: A, b: B) -> (A, B) { (a, b) }
5
6fun main() {
7 let x := pair(1, "s");
8 let n: boolean := x; // ERROR[T0001]
9}
typecheck errorT0001“cannot unify (A, String) with boolean”
1// #266 — see stability_04's comment. Identical body, one unrelated generic
2// struct prepended. Asserts the same declared-name diagnostic as stability_04.
3struct Unrelated<A> { a: A }
4
5fun pair<A, B>(a: A, b: B) -> (A, B) { (a, b) }
6
7fun main() {
8 let x := pair(1, "s");
9 let n: boolean := x; // ERROR[T0001]
10}
typecheck errorT0001“cannot unify (A, String) with boolean”
1// #266: generic instance-method dispatch instantiates both the receiver's
2// parameter and map's own U through the shared context helper.
3struct Box1<T> { v: T }
4
5extend<T> Box1<T> {
6 fun map<U>(self, f: |T| -> U) -> Box1<U> { Box1 { v = f(self.v) } }
7}
8
9fun main() {
10 let b := Box1 { v = 1 };
11 let mapped := b.map(|x: i64| -> String { x.to_string() });
12 let n: boolean := mapped; // ERROR[T0001]
13}
typecheck errorT0001“cannot unify Box1<String> with boolean”
Also demonstratesAspect bounds on function type… L14
1// Negative: divergent branches should fail with T0001
2// RFC §1.1 example - function with different concrete types on different branches
3
4aspect Display {
5 fun display(&self) -> String;
6}
7
8struct MyInt {
9 value: i64,
10}
11
12struct MyString {
13 value: String,
14}
15
16extend MyInt: Display {
17 fun display(&self) -> String {
18 self.value.to_string()
19 }
20}
21
22extend MyString: Display {
23 fun display(&self) -> String {
24 self.value.clone()
25 }
26}
27
28// RFC §1.1 - divergent branches returning different concrete types
29fun bad(flag: boolean) -> extends Display {
30 if (flag) { // ERROR[T0001]
31 MyInt { value = 42 }
32 } else {
33 MyString { value = "hello".to_string() }
34 }
35}
36
37fun main() {
38 let result := bad(true);
39}
typecheck errorT0001at 30
Also demonstratesStructural aspect bounds L2
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// RFC-0061 grants structural impl targets, but only the generic form is
6// registered. A concrete one has nowhere to key on, so its methods could never
7// be found — rejected rather than accepted-and-unreachable (metel-core#581).
8extend i64[]: Area {
9 fun area(&self) -> i64 { return 1; }
10}
11
12fun main() {
13}
typecheck errorT0001“could never be found”
Also demonstratesStructural aspect bounds L2
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// RFC-0061 grants structural impl targets, but only the generic array form is
6// registered. A concrete one has nowhere to key on, so its methods could never
7// be found — rejected rather than accepted-and-unreachable (metel-core#581).
8extend |i64| -> i64: Area {
9 fun area(&self) -> i64 { return 1; }
10}
11
12fun main() {
13}
typecheck errorT0001“could never be found”
Also demonstratesStructural aspect bounds L2
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// RFC-0061 grants structural impl targets, but only the generic form is
6// registered. A concrete one has nowhere to key on, so its methods could never
7// be found — rejected rather than accepted-and-unreachable (metel-core#581).
8extend { w: i64, h: i64 }: Area {
9 fun area(&self) -> i64 { return 1; }
10}
11
12fun main() {
13}
typecheck errorT0001“could never be found”
Also demonstratesStructural aspect bounds L2
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// RFC-0061 grants structural impl targets, but only the generic form is
6// registered. A concrete one has nowhere to key on, so its methods could never
7// be found — rejected rather than accepted-and-unreachable (metel-core#581).
8extend (i64, i64): Area {
9 fun area(&self) -> i64 { return 1; }
10}
11
12fun main() {
13}
typecheck errorT0001“could never be found”
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// The generic form is what makes an array impl work, but for a function type
6// it typechecks and then has no effect in either the call or the bound position
7// (metel-core#239). Rejected rather than accepted-and-inert, on the same grounds
8// as the concrete form in metel-core#581.
9extend<A, B> |A| -> B: Area {
10 fun area(&self) -> i64 { return 1; }
11}
12
13fun main() {
14}
typecheck errorT0001“could never be found”
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// The generic form is what makes an array impl work, but for a tuple or record
6// it typechecks and then has no effect in either the call or the bound position
7// (metel-core#239). Rejected rather than accepted-and-inert, on the same grounds
8// as the concrete form in metel-core#581.
9extend<T> { w: T }: Area {
10 fun area(&self) -> i64 { return 1; }
11}
12
13fun main() {
14}
typecheck errorT0001“could never be found”
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// The generic form is what makes an array impl work, but for a tuple or record
6// it typechecks and then has no effect in either the call or the bound position
7// (metel-core#239). Rejected rather than accepted-and-inert, on the same grounds
8// as the concrete form in metel-core#581.
9extend<A, B> (A, B): Area {
10 fun area(&self) -> i64 { return 1; }
11}
12
13fun main() {
14}
typecheck errorT0001“could never be found”
1aspect Area {
2 fun area(&self) -> i64;
3}
4
5// `T` is declared but does not name the array's element — `array_target_generic_name`
6// only recognizes `extend<T> T[]`, so this registers nothing and would otherwise be
7// exactly as inert as `extend i64[]: Area` (found by adversarial review of #296: the
8// first cut's exemption checked only "is an array with generics", not this).
9extend<T> i64[]: Area {
10 fun area(&self) -> i64 { return 99; }
11}
12
13fun main() {
14}
typecheck errorT0001“not one of the impl's own type parameters”
1struct Point { x: i64 }
2
3aspect Describe {
4 fun describe(&self) -> String;
5}
6
7extend Point: Describe {
8 fun describe(&self) -> String { return "point"; }
9 fun stowaway(&self) -> i64 { return self.x; }
10}
11
12fun main() {}
typecheck errorT0001“not declared by aspect”
Also demonstratesFixed size arrays L3, Fixed size arrays L4
1// [expr; N] with annotation [T; M] where N ≠ M must be rejected.
2let x: [i64; 3] := [0; 4]; // ERROR[T0001]
typecheck errorT0001at 2
Also demonstratesFixed size arrays L3, Fixed size arrays L4
1// Element type mismatch in repeat construction: [boolean; 3] cannot satisfy [i64; 3].
2let x: [i64; 3] := [true; 3]; // ERROR[T0001]
typecheck errorT0001at 2
Also demonstratesFixed size arrays L2
1// A dynamic array has no statically known length and cannot satisfy [T; N].
2fun main() {
3 let dynamic: i64[] := [1, 2, 3];
4 let fixed: [i64; 3] := dynamic; // ERROR[T0001]
5 println(fixed[0].to_string());
6}
typecheck errorT0001at 4
Also demonstratesFixed size arrays L9
1// Every literal index is out of bounds for an empty fixed-size array.
2fun main() {
3 let empty: [i64; 0] := [];
4 let x := empty[0]; // ERROR[T0001]
5 println(x.to_string());
6}
typecheck errorT0001at 4
Also demonstratesLiterals L3
1fun main() {
2 // An unsuffixed `5` could adopt f64 here; the i64 suffix makes this literal concrete.
3 let _value: f64 := 5i64;
4}
typecheck errorT0001“cannot unify i64 with f64”
1fun take(point: { x: i64 }) -> i64 {
2 point.x
3}
4
5let wider := { x = 1, y = 2 };
6let result := take(wider);
typecheck errorT0001“cannot unify”
1// Negative: a narrower record where a wider one is expected. Exactness holds in
2// both directions — this is the mirror of stage5_neg_08.
3fun take(p: { x: i64, y: i64 }) -> i64 { p.x + p.y }
4
5let narrow := { x = 1 };
6let result := take(narrow);
typecheck errorT0001“cannot unify”
Also demonstratesAnonymous records L3
1// Negative: anonymous records have no nominal owner, so no inherent methods.
2extend { x: i64 } {
3 fun get(&self) -> i64 { self.x }
4}
typecheck errorT0001“cannot have inherent methods”
Also demonstratesAnonymous records L3
1// Negative: anonymous records cannot carry custom teardown logic.
2extend { x: i64 }: Drop {
3 fun drop(&self) {}
4}
typecheck errorT0001“cannot implement `Drop`”
1enum Token {
2 Text { value: String },
3 End,
4}
5
6extend Token: Copy;
typecheck errorT0001“variant `Token::Text`”
1struct Handle {
2 fd: u64,
3}
4
5extend Handle: Copy;
6
7extend Handle: Drop {
8 fun drop(&var self) {}
9}
typecheck errorT0001“cannot implement both `Copy` and `Drop`”
Also demonstratesCopy and drop are mutually excl… L1
1// RFC-0071 §4 across two *conditional* impls (issue #302).
2//
3// Neither impl target is closed, so the declaration-site check in
4// `typechecker::inference` cannot evaluate either one — it is `coherence`'s
5// cross-aspect overlap check that rejects this. The bounds are not disjoint:
6// `i64` is both `Copy` and `Display`, so `Overlap<i64>` would have both
7// aspects, which §4 forbids.
8
9struct Overlap<T> {
10 val: T,
11}
12
13extend<T: Copy> Overlap<T>: Copy;
14
15extend<T: Display> Overlap<T>: Drop {
16 fun drop(&var self) {}
17}
18
19fun main() {}
typecheck errorT0001“cannot implement both `Copy` and `Drop`”
Also demonstratesCopy and drop are mutually excl… L1
1// RFC-0071 §4 where a `Copy` blanket and a concrete `Drop` impl meet at one
2// instantiation (issue #302).
3//
4// `i64` is `Copy`, so the blanket reaches `Reach<i64>` — the exact type the
5// `Drop` impl targets. Contrast the accepted case in
6// `evaluator/structs/95_copy_and_drop_non_overlapping_impls.mtl`, which is
7// this program with `String` in place of `i64`: the rejection turns on
8// whether the concrete argument satisfies the blanket's bound, not on the
9// two impls merely sharing a target constructor.
10
11struct Reach<T> {
12 val: T,
13}
14
15extend<T: Copy> Reach<T>: Copy;
16
17extend Reach<i64>: Drop {
18 fun drop(&var self) {}
19}
20
21fun main() {}
typecheck errorT0001“cannot implement both `Copy` and `Drop`”
1// The rejecting half of issue #303. `Inner` has no `Copy` impl at all, so
2// `Inner<T>` is not `Copy` for any `T` and `Outer<T>` is not eligible —
3// recognising conditional impls must not degrade into accepting whatever it
4// cannot evaluate.
5//
6// The expected message also pins the diagnostic: the field type is named as
7// written, `Inner<T>`, not as the inference variable `Inner<?t16>` that the
8// struct's field entry actually stores.
9
10struct Inner<T> {
11 value: T,
12}
13
14struct Outer<T> {
15 inner: Inner<T>,
16}
17
18extend<T: Copy> Outer<T>: Copy;
19
20fun main() {}
typecheck errorT0001“field `inner` has type `Inner<T>` which is not `Copy`”
1// The assumption set an impl's own generics contribute (issue #303) is
2// exactly its positive bounds — an unbounded parameter contributes nothing.
3//
4// This guards the direction that would be dangerous to get wrong: `T` must
5// answer `false` for `Copy` here, rather than falling through to whatever an
6// unrelated declaration named `T` might satisfy, or being treated as
7// unknown-therefore-fine.
8
9struct Outer<T> {
10 value: T,
11}
12
13extend<T> Outer<T>: Copy;
14
15fun main() {}
typecheck errorT0001“field `value` has type `T` which is not `Copy`”
1// The assumption set an impl's generics contribute (issue #303) must never be
2// consulted for a real type that merely shares a parameter's name.
3//
4// `Holder`'s own scope declares one parameter, `U`. Field `inner: T` is
5// therefore the *struct* `T`, which holds a `String` and is not `Copy`, even
6// though the impl below names its parameter `T` as well.
7//
8// This program was once accepted, because the assumptions were keyed by name
9// and the field type `T` matched the entry meant for the parameter. They are
10// keyed by type variable now — a parameter is not a named type — so the two
11// cannot be confused whatever they are called.
12//
13// The companion positive case is in
14// `evaluator/structs/97_copy_param_shadows_same_named_type.mtl`, where the
15// substituted position genuinely is the parameter and shadowing must win.
16
17struct T {
18 s: String,
19}
20
21struct Holder<U> {
22 inner: T,
23 marker: U,
24}
25
26extend<T: Copy> Holder<T>: Copy;
27
28fun main() {}
typecheck errorT0001“field `inner` has type `T` which is not `Copy`”
1struct Handle {
2 fd: u64,
3}
4
5extend Handle: Drop {
6 fun drop(&var self) {
7 println("closing");
8 }
9}
typecheck errorT0001“a `drop` body cannot run yet”
Also demonstratesThe operator L1
1// ? on a non-Result type should fail.
2fun might_fail() -> i64 {
3 42? // ERROR[T0001]
4}
typecheck errorT0001“cannot unify an integer literal with”
Also demonstratesType ascription L2
1// Stage 8 negative: ascription with incompatible type is a type error.
2// `1 : f64` is an error — use `1 as f64` to convert.
3
4let z: f64 := 1 : f64; // ERROR[T0001]
typecheck errorT0001at 4
Also demonstratesStruct patterns L1
1struct Token { kind: i64, span: i64, offset: i64 }
2
3fun main() {
4 let t := Token { kind = 1, span = 2, offset = 3 };
5 match (t) {
6 Token { kind, span } => println(kind + span),
7 }
8}
typecheck errorT0001at 6
Also demonstratesTurbofish L2
1fun identity<T>(x: T) -> T { x }
2
3fun main() {
4 let x := identity::<i64>("hello");
5 println(x);
6}
typecheck errorT0001at 4
T0002 — Annotation required
The type checker cannot infer a type without an explicit annotation.
Fix: annotate the binding: let x: i64 = ....
The same code also covers dereferencing (*expr) an operand that isn't a reference type at
all — not an inference gap, but sharing the code with the annotation case above since both
are "the checker has nothing to work with here":
Fix: remove the *, or check that the operand actually has reference type (&T / &var T).
Tested by
Also demonstratesUnqualified variant constructors L4
1// metel-core#285: a bare variant that never resolves must be reported, not silently
2// accepted. Pass 1 defers it (RFC-0111 §3.1) and only pass 2 resolves it against an
3// expected type -- but an uncalled closure's body is never constructed, so no expected
4// type ever arrives and nothing used to notice. Checked after the final solve instead.
5enum Colour { Red, Green }
6
7fun main() {
8 let f := || { Red }; // ERROR[T0002]
9}
typecheck errorT0002at 8
T0003 — Undefined name
A name is used but not defined in the current scope.
Fix: define the variable or function before use, or correct the spelling.
Tested by (29)
Also demonstratesNative functions standard libra… L1
1// `native` is a stdlib-only construct; declaring one in a user module (any
2// module whose path does not begin with `std`) is rejected.
3native(@std.core.println) fun shout(x: String); // ERROR[T0003]
4
5fun main() {}
typecheck errorT0003“native”
Also demonstratesType aliases L2
1// RFC-0160 OQ4: a transparent alias may not be recursive — direct or through a
2// chain. There is no finite expansion.
3type Json := Wrap<Json>;
4type Wrap<T> := T;
5fun main() { }
typecheck errorT0003“recursive type alias”
Also demonstratesScoping and shadowing L2
1fun main() {
2 let _before := value;
3 let value := 42;
4}
typecheck errorT0003“undefined name `value`”
Also demonstratesTuples L1
1// TYPECHECK_ERROR[out of bounds]
2fun main() {
3 let t := (1, 2);
4 let _x := t.5;
5}
typecheck errorT0003“out of bounds”
Also demonstratesDyn aspect L1
1// Regression (metel-core#865, RFC-0008 slice 1): `dyn` reuses the same
2// aspect-name resolution `impl Aspect` already uses (`aspect_type`) -- an
3// unresolvable name is rejected the same way, not silently accepted.
4
5fun a(x: dyn NotReal) -> i64 { 0 }
6
7fun main() {}
typecheck errorT0003“unknown aspect”
Also demonstratesRow bounds L7
1// #646: `..` discards fields the pattern doesn't name -- it doesn't let the pattern name
2// a field the bound never promised is there, even one a particular caller happens to pass.
3fun describe<record T: { x: f64, .. }>(p: T) -> f64 {
4 match (p) {
5 { x, z, .. } => x + z,
6 }
7}
8
9fun main() {
10 println(describe({ x = 1.0, z = 2.0 }));
11}
typecheck errorT0003at 5
1aspect Marker { }
2
3// `Self` is meaningful inside an extend block, not as the block's target.
4extend Self: Marker { }
5
6fun main() { }
typecheck errorT0003“unknown type `Self`”
Also demonstratesFirst class functions L2
1// Negative, deliberately out of scope (RFC-0138 §5): a generic function passed to
2// a parameter position that is itself still generic in the callee -- rank-2
3// polymorphism -- stays call-only. #736/RFC-0138 only lifted the restriction for a
4// bare reference and a higher-order argument whose *receiving* parameter is
5// concrete (see stage10_10/stage10_11); here `apply_twice`'s own `f: F` is not.
6
7fun identity<T>(x: T) -> T { return x; }
8fun apply_twice<F, T>(f: F, x: T) -> T { return f(x); }
9
10fun main() {
11 apply_twice(identity, 5);
12}
typecheck errorT0003at 11
1struct Handle {
2 fd: i64,
3 tag: i64,
4}
5
6fun take(handle: Handle.{ nope }) -> i64 { handle.nope }
7
8fun main() {
9 let handle := Handle { fd = 7, tag = 9 };
10 take(handle.{ fd });
11}
typecheck errorT0003“has no field `nope`”
1enum Status {
2 Ready,
3}
4
5fun take(status: Status.{ code }) -> i64 { status.code }
typecheck errorT0003“only structs have a row to project”
1// Negative: the projection is wrong, but nothing exercises it — `bad` is never called and
2// its body never touches the field. The dedicated projection pass catches it anyway,
3// rather than waiting for unification to stumble over a stand-in type.
4struct Handle { fd: i64, tag: i64 }
5
6fun bad(h: Handle.{ nope }) -> i64 { 0 }
7
8fun main() { }
typecheck errorT0003“has no field `nope`”
1// Negative: a struct field may not project a struct declared later. Field types are
2// converted while the registry is still being built, so the target does not exist yet.
3// Reported directly, with the fix, rather than surfacing later as a bare unify failure.
4struct A { r: B.{ x } }
5
6struct B { x: i64, y: i64 }
7
8fun main() { }
typecheck errorT0003“is declared later in this module”
1fun get_z<record T: { x: f64 }>(p: T) -> f64 {
2 p.z
3}
4
5fun main() {
6 let _ := get_z({ x = 3.0 });
7}
typecheck errorT0003“no field `z` on type parameter (bounds: { x: f64 })”
1fun get_z<record T: { x: f64, .. }>(p: T) -> f64 {
2 p.z
3}
4
5fun main() {
6 let _ := get_z({ x = 3.0, z = 9.0 });
7}
typecheck errorT0003“hint: an open row bound only makes its explicitly listed fields accessible”
1import helper::nonexistent;
2fun main() -> i64 { return nonexistent(); }
typecheck errorT0003
1import helper::ping;
2
3// `Token` is declared by a loaded module but is not imported here.
4fun unused(value: Token) { }
5
6fun main() { }
typecheck errorT0003“unknown type `Token`”
1import helper::ping;
2
3// Loading a module through one item must not make its other declarations visible.
4fun unused_type(value: helper::Token) { }
5fun unused_bound<T: helper::Marker>(value: T) { }
6
7fun main() { }
typecheck errorT0003“unknown type `helper::Token`”
1import helper::ping;
2
3// Loading one item must not make qualified aspect names visible either.
4fun unused<T: helper::Marker>(value: T) { }
5
6fun main() { }
typecheck errorT0003“unknown aspect `helper::Marker`”
1// An unused generic declaration must not leave an unknown aspect deferred.
2fun unused<T: Missing>(value: T) { }
3
4fun main() { }
typecheck errorT0003“unknown aspect `Missing`”
1fun take(value: Missing) { }
2
3fun main() {
4 take(1);
5}
typecheck errorT0003“unknown type `Missing`”
1struct Holder {
2 value: Missing,
3}
4
5fun main() {
6 let holder := Holder { value = 1 };
7}
typecheck errorT0003“unknown type `Missing`”
1// An unused enum variant still carries a valid type annotation.
2enum Holder {
3 Value { value: Missing },
4}
5
6fun main() { }
typecheck errorT0003“unknown type `Missing`”
1struct Holder { value: i64 }
2
3extend Holder: Missing { }
4
5fun main() { }
typecheck errorT0003“unknown aspect `Missing`”
1// This used to blame the literal during unification rather than the missing name.
2fun main() {
3 let value: Missing := 1;
4}
typecheck errorT0003“unknown type `Missing`”
1// Nested type expressions must validate every named component.
2fun unused(values: Missing[]) { }
3
4fun main() { }
typecheck errorT0003“unknown type `Missing`”
1// An uncalled signature must still reject an undeclared type.
2fun unused(value: Missing) { }
3
4fun main() { }
typecheck errorT0003“unknown type `Missing`”
1// Return annotations are diagnosed as unresolved names, not as a later mismatch.
2fun unused() -> Missing { 0 }
3
4fun main() { }
typecheck errorT0003“unknown type `Missing`”
T0004 — Arity mismatch
A function is called with the wrong number of arguments.
Fix: pass the exact number of arguments the function declares.
Tested by
Also demonstratesType aliases L1
1// RFC-0160 §3: an alias use must supply exactly the alias's declared number of
2// type arguments.
3type Pair<A, B> := (A, B);
4fun main() -> Pair<i64> { (1, 2) }
typecheck errorT0004“type argument”
T0005 — Invalid operand types
An operator is applied to operands it does not support. Three forms share this code:
- Mismatched operands. The two sides of a binary operator disagree, e.g.
1 == "x". The message names the operator and both types. - Binary arithmetic/ordering on unsupported types.
- Equality (
==,!=) on anything other than a numeric type,boolean,Stringorchar.==does not yet dispatch through theEqaspect, so structs, enums, arrays, tuples and references are rejected; use.eq(..)on a type that implementsEq.
Since v0.12.0: address-of (
&,&var) applied to a non-addressable expression — a literal, a call result, a struct/enum construction — is no longer one of this code's cases. Both forms now get temporary lifetime extension instead of being rejected; see Expressions — References.
Fix: use compatible types, cast one operand, or bind the value to a name so it has an address.
Tested by
1// Sibling of neg_01/neg_02, one level of indirection removed (#236 follow-up):
2// the numeric literal here is recovered through a generic struct field rather
3// than written directly at the `+` site. The T0005 message must name the
4// concrete type this resolves to (`i64`), not the internal TypeVar it was
5// unified with — see .toml sidecar.
6struct Pair<A, B> { first: A, second: B }
7
8fun main() {
9 let p := Pair { first = 1, second = "x" };
10 let _bad := p.first + "y"; // ERROR[T0005]
11}
typecheck errorT0005“got `i64` and `String`”
T0006 — Assignment to immutable binding
A write operation targets a let binding. This covers three forms:
- Direct reassignment:
x = newValue - Field assignment through an immutable binding:
point.x = 1 - Taking a mutable reference to an immutable binding:
&var x
Fix: change the binding declaration to var.
Tested by (4)
1// Calling a method whose receiver is `&var self` requires the receiver
2// expression itself to be a mutable (`var`) binding -- an immutable `let`
3// binding of the same struct type is rejected.
4struct Counter {
5 value: i64,
6}
7
8extend Counter {
9 fun increment(&var self) {
10 self.value += 1;
11 }
12}
13
14fun main() {
15 let counter := Counter { value = 0 };
16 counter.increment();
17}
typecheck errorT0006at 16:5
1aspect Bump {
2 fun bump(&var self);
3}
4
5struct C {
6 v: i64,
7}
8
9extend C: Bump {
10 fun bump(&var self) {
11 self.v := self.v + 1;
12 }
13}
14
15// Resolving the method through `&T` must not also grant mutable access:
16// a `&var self` method is not reachable through a shared reference.
17fun mutate_through_shared<T: Bump>(x: &T) {
18 x.bump();
19}
20
21fun main() {
22 var c := C { v = 0 };
23 mutate_through_shared(&c);
24}
typecheck errorT0006“through a shared reference”
1aspect Bump {
2 fun bump(&var self);
3}
4
5struct C {
6 v: i64,
7}
8
9extend C: Bump {
10 fun bump(&var self) {
11 self.v := self.v + 1;
12 }
13}
14
15// The receiver is `pair.0`, not an identifier. The guard cannot fall back to
16// asking whether a *binding* is writable, because there is no binding — so the
17// rule has to be about the reference chain itself.
18fun sneak<T: Bump>(pair: (&T, i64)) {
19 pair.0.bump();
20}
21
22fun main() {
23 var c := C { v = 0 };
24 sneak((&c, 1));
25}
typecheck errorT0006“through a shared reference”
Also demonstratesImmutable bindings L1
1// Compound assignment to an immutable binding is T0006, same as plain assign.
2let n: i64 := 10;
3n += 5; // ERROR[T0006]
typecheck errorT0006at 3
T0007 — Invalid cast
A as cast between incompatible types.
Fix: only cast between numeric types (i64 as f64). Use an explicit conversion function for other types.
Tested by
Also demonstratesThe operator L2
1// ? with mismatched error types and no From impl must fail with T0007.
2// METEL-80 routes ? through From-based coercion; when no From impl exists,
3// the coercion is invalid and the typechecker emits T0007 (invalid cast).
4// From coercion for arbitrary type pairs is deferred to #13.
5
6fun inner() -> Result<i64, String> {
7 Result::Ok { value = 42 }
8}
9
10fun outer() -> Result<i64, i64> {
11 let x := inner()?; // ERROR[T0007]
12 Result::Ok { value = x }
13}
14
15fun main() {}
typecheck errorT0007at 11:21
T0008 — Non-exhaustive match
A match expression does not cover all possible values of the scrutinee type.
Fix: add the missing arms, or add a wildcard arm _ => ....
Tested by (2)
Also demonstratesUnqualified variant patterns L4
1// RFC-0107 §2: a bare variant tag is rewritten before exhaustiveness checking;
2// it is not a catch-all binding.
3enum Colour { Red, Blue }
4
5fun name(c: Colour) -> String {
6 match (c) {
7 Red => "red",
8 }
9}
10
11fun main() {}
typecheck errorT0008at 6:5“non-exhaustive match”
Also demonstratesFixed size arrays L7
1// Exact-count pattern [a, b] on a [i64; 3] scrutinee has the wrong element count.
2// The constraint [i64; 3] ~ [i64; 2] fails, leaving the match non-exhaustive.
3fun main() {
4 let sized: [i64; 3] := [1, 2, 3];
5 let _ := match (sized) { [a, b] => a + b, }; // ERROR[T0008]
6}
typecheck errorT0008at 5
T0012 — Aspect bound not satisfied
A generic type parameter's bound is not satisfied by the concrete type at the call
site or construction site. Covers both directions: a positive bound (T: Aspect)
requires an implementation that isn't reachable, or a negative bound (T: !Aspect,
RFC-0072) is violated because the concrete type does implement the aspect. Also
covers a conditional extend block's own where-clause bounds (RFC-0036) failing at a
use site — the same check as an ordinary function bound, just reached through an
implementation block's
condition instead of a function's generic parameter.
A type satisfying T: Copy automatically satisfies T: !Drop (RFC-0072 §2.3) even
though it implements Drop — this is a narrow, Copy/Drop-specific exception, not a
general rule.
Fix: implement the required aspect for the type, or (for a negative bound) remove the conflicting positive implementation.
Tested by (52)
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun main() {
4 let x: dyn Display := Rock { };
5}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun show(x: dyn Display) -> String {
4 x.to_string()
5}
6
7fun main() {
8 show(Rock { });
9}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun main() {
4 var shapes: List<dyn Display> := List::new();
5 shapes.push(Rock { });
6}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun show(x: &dyn Display) -> String { x.to_string() }
4
5fun main() {
6 show(&Rock { });
7}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { n: i64 }
2
3aspect Counter {
4 fun increment(&var self);
5}
6
7fun bump(c: &var dyn Counter) { }
8
9fun main() {
10 var r := Rock { n = 0 };
11 bump(&var r);
12}
typecheck errorT0012“does not implement `Counter`”
Also demonstratesDyn aspect L6
1struct Rock { }
2struct Holder { item: dyn Display }
3
4fun main() {
5 let h := Holder { item = Rock { } };
6}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun main() {
4 var x: dyn Display := 42;
5 x := Rock { };
6}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun main() {
4 let arr: dyn Display[] := [Rock { }];
5}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun make() -> dyn Display { Rock { } }
4
5fun main() {
6 make();
7}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun main() {
4 let x: dyn Display := loop {
5 break Rock { };
6 };
7}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2
3fun main() {
4 let x := (Rock { } : dyn Display);
5}
typecheck errorT0012“does not implement `Display`”
Also demonstratesDyn aspect L6
1struct Rock { }
2struct Pebble { }
3
4extend Rock: Display {
5 fun to_string(&self) -> String { "rock".to_string() }
6}
7
8fun main() {
9 let arr: dyn Display[] := [Rock { }, Pebble { }];
10}
typecheck errorT0012“does not implement `Display`”
Also demonstratesRow bounds L4, Narrowing L3
1// Regression (metel-core#857, RFC-0137 slice 1's own normalization rule, and
2// RFC-0137 sec3's worked example): a projection naming every field a struct
3// declares normalizes back to the plain struct type rather than staying a
4// distinct branded residual. Confirms the normalization doesn't accidentally
5// earn row-bound eligibility -- h.{ fd, name }, full width, is rejected by a row
6// bound the exact same way a bare `Handle` value already is.
7
8struct Handle { fd: i64, name: String }
9
10fun wants_a_record<record T: { fd: i64, name: String, .. }>(t: T) -> i64 { t.fd }
11
12fun main() {
13 let h := Handle { fd = 3, name = "x" };
14 let _ := wants_a_record(h.{ fd, name });
15}
typecheck errorT0012“struct never satisfies a row bound”
Also demonstratesNarrowing L2
1// Regression (metel-core#857, RFC-0137 slice 1): a genuine (non-full-width) branded
2// residual never satisfies a row bound either -- eligibility for structural
3// matching is scoped to the brand alone (RFC-0137 sec3), and a struct's brand is
4// never visible to matching regardless of how narrow its current row is.
5
6struct Handle { fd: i64, name: String, extra: i64 }
7
8fun wants_a_record<record T: { fd: i64, .. }>(t: T) -> i64 { t.fd }
9
10fun main() {
11 let h := Handle { fd = 3, name = "x", extra = 9 };
12 let _ := wants_a_record(h.{ fd });
13}
typecheck errorT0012“is not a record”
Also demonstratesRow bounds L2
1// RFC-0118 §2: a negative row bound written in a `where` clause is enforced
2// the same as one written inline -- a record carrying the forbidden label is
3// rejected, not silently accepted because the bound lives in a separate
4// clause from the parameter's own declaration.
5fun f<record T: { x, y: i64, .. }>(value: T) -> i64
6where record T: !{ z } {
7 value.y
8}
9
10fun main() {
11 let bad := { x = 1, y = 2, z = 3 };
12 let result := f(bad);
13}
typecheck errorT0012at 12:20“negative row bound `!{ z }`”
Also demonstratesAspect bounds on function type… L7, Aspect bounds on function type… L8
1// Negative: generic function called with a type that does NOT implement
2// the required bound. Expect T0012.
3
4aspect Printable {
5 fun print(self);
6}
7
8struct Plain { x: i64 }
9
10// Plain does not implement Printable.
11
12fun print_it<T: Printable>(x: T) {
13 x.print()
14}
15
16fun bad(p: Plain) {
17 print_it(p) // ERROR[T0012]
18}
typecheck errorT0012at 17:13
Also demonstratesDefault methods L1
1// Negative: a required method (no default body) must still be provided even when
2// the impl provides other methods that have defaults in the aspect.
3
4struct Widget {
5 id: i64,
6}
7
8aspect Render {
9 fun draw(self) -> String;
10
11 fun preview(self) -> String {
12 return "preview: " + self.draw();
13 }
14}
15
16extend Widget: Render { // ERROR[T0012]
17 fun preview(self) -> String {
18 return "overriding the default";
19 }
20}
typecheck errorT0012“does not implement”
Also demonstratesAspect bounds on function type… L4
1// Negative: `extends Aspect` param called with a type that does NOT implement
2// the required aspect. Expect T0012.
3
4aspect Printable {
5 fun print(self);
6}
7
8struct Plain { x: i64 }
9
10// Plain does not implement Printable.
11
12fun print_it(x: extends Printable) {
13 x.print()
14}
15
16fun bad(p: Plain) {
17 print_it(p) // ERROR[T0012]
18}
typecheck errorT0012at 17
Also demonstratesAssociated types L7
1// Negative: RFC-0082 §4 equality constraint (`Aspect<AssocType = ConcreteType>`)
2// is violated when the impl's actual associated type does not match. Expect
3// T0012.
4
5aspect Container {
6 type Item: Display;
7 fun get(self) -> Item;
8}
9
10struct StrBox {
11 value: String,
12}
13
14extend StrBox: Container {
15 type Item := String;
16 fun get(self) -> String {
17 return self.value;
18 }
19}
20
21fun needs_int_item<T: Container<Item = i64>>(x: T) -> i64 {
22 return x.get();
23}
24
25fun main() {
26 let b := StrBox { value = "hi" };
27 let _ := needs_int_item(b); // ERROR[T0012]
28}
typecheck errorT0012at 27
Also demonstratesAspect bounds on struct and enu… L2
1// Negative: enum with aspect bound constructed with a type that does NOT
2// implement the bound. Expect T0012.
3
4aspect Printable {
5 fun print(self);
6}
7
8enum Container<T: Printable> {
9 Some { value: T },
10 Empty {},
11}
12
13struct Plain { x: i64 }
14
15// Plain does not implement Printable.
16
17fun bad(p: Plain) -> Container<Plain> {
18 Container::Some { value = p } // ERROR[T0012]
19}
typecheck errorT0012
Also demonstratesNegative bounds L4
1// Negative: generic function with negative bound, called with a type that
2// DOES implement the negated aspect. Expect T0012.
3
4aspect Drop {
5 fun drop(self);
6}
7
8struct Resource { x: i64 }
9
10extend Resource: Drop {
11 fun drop(self) {}
12}
13
14fun move_out<T: !Drop>(x: T) -> T {
15 return x;
16}
17
18fun bad(r: Resource) {
19 move_out(r) // ERROR[T0012]
20}
typecheck errorT0012at 19:13“bound not satisfied”
Also demonstratesNegative bounds L8
1// Test that a negative bound (!Drop) correctly rejects when the type DOES implement Drop via conditional impl
2aspect Drop { fun drop(self); }
3struct Arena<T: !Drop> { items: T[] }
4struct Pair<A, B> { first: A, second: B }
5// Pair implements Drop when BOTH A and B implement Drop
6extend<A: Drop, B: Drop> Pair<A, B>: Drop { fun drop(self) {} }
7struct Resource { x: i64 }
8extend Resource: Drop { fun drop(self) {} }
9
10// This should FAIL: Arena<Pair<Resource, Resource>> should be rejected because
11// Pair<Resource, Resource> DOES implement Drop via the conditional impl,
12// violating the !Drop bound. Before the fix, this would incorrectly succeed.
13fun bad(r: Resource) -> Arena<Pair<Resource, Resource>> {
14 Arena { items = [Pair { first = r, second = r }] } // ERROR[T0012]
15}
16
17fun main() {}
typecheck errorT0012at 14:5“bound not satisfied”
Also demonstratesImplementing an aspect L11
1// RFC-0036 §4: conditional impl bound not satisfied at use site -> T0012.
2
3aspect Printable {
4 fun print(self) -> String;
5}
6
7struct Pair<A, B> {
8 first: A,
9 second: B,
10}
11
12struct NoPrint {
13 value: i64,
14}
15
16extend<A: Printable, B: Printable> Pair<A, B>: Printable {
17 fun print(self) -> String {
18 return "pair";
19 }
20}
21
22fun main() {
23 let p := Pair { first = 5, second = NoPrint { value = 0 } };
24 let s: String := p.print(); // ERROR[T0012]
25}
typecheck errorT0012at 24
Also demonstratesImplementing an aspect L11
1// RFC-0036 §4: two-param conditional impl, one bound satisfied, one not -> T0012.
2
3aspect Printable {
4 fun print(self) -> String;
5}
6
7extend i64: Printable {
8 fun print(self) -> String { return "i64"; }
9}
10
11struct Pair<A, B> {
12 first: A,
13 second: B,
14}
15
16struct NoPrint {
17 value: i64,
18}
19
20extend<A: Printable, B: Printable> Pair<A, B>: Printable {
21 fun print(self) -> String {
22 return "pair";
23 }
24}
25
26fun main() {
27 let p := Pair { first = NoPrint { value = 0 }, second = 10 };
28 let s: String := p.print(); // ERROR[T0012]
29}
typecheck errorT0012at 28
Also demonstratesImplementing an aspect L5
1// RFC-0036 §2.2: mirror of stage17_05 with a type argument that genuinely
2// does NOT satisfy the conditional impl's bound -> must still be rejected
3// when passed through an unrelated generic function's own bound.
4
5aspect Printable {
6 fun print(self) -> String;
7}
8
9struct NoPrint {
10 value: i64,
11}
12
13struct Pair<A, B> {
14 first: A,
15 second: B,
16}
17
18extend<A: Printable, B: Printable> Pair<A, B>: Printable {
19 fun print(self) -> String {
20 return "pair";
21 }
22}
23
24fun describe<T: Printable>(x: T) -> String {
25 return x.print();
26}
27
28fun main() {
29 let p := Pair { first = NoPrint { value = 1 }, second = NoPrint { value = 2 } };
30 let s: String := describe(p); // ERROR[T0012]
31}
typecheck errorT0012at 30
Also demonstratesStructural aspect bounds L2, Structural aspect bounds L3
1// Negative: an array's conditional impl is gated on the element type, not
2// granted unconditionally to every array. `Opaque[]` does not implement
3// `Display` because `Opaque` itself does not (RFC-0061 §2-3) -- this is the
4// array counterpart to stage19_neg_01 (tuple) and stage19_neg_02 (function).
5
6struct Opaque {
7 value: i64,
8}
9
10fun show<T: Display>(x: T) -> String {
11 return x.to_string();
12}
13
14fun main() {
15 let items := [Opaque { value = 1 }, Opaque { value = 2 }];
16 let _ := show(items); // ERROR[T0012]
17}
typecheck errorT0012at 16
Also demonstratesStructural aspect bounds L10
1// Negative: function pointers do not implement `Eq` -- function equality is
2// undecidable in general (RFC-0061 §7.3). Expect T0012, the function-type
3// sibling of stage19_neg_02 (which covers `Display`) for a different aspect
4// from the "aspects function pointers do not implement" table.
5
6fun show_eq<T: Eq>(a: T, b: T) -> boolean {
7 return a.eq(&b);
8}
9
10fun plus_one(x: i64) -> i64 {
11 return x + 1;
12}
13
14fun minus_one(x: i64) -> i64 {
15 return x - 1;
16}
17
18fun main() {
19 let _ := show_eq(plus_one, minus_one); // ERROR[T0012]
20}
typecheck errorT0012at 19
Also demonstratesImplementing an aspect L12
1struct Point { x: i64 }
2
3aspect Describe {
4 fun describe(&self) -> String;
5}
6
7extend Point: Describe {
8 fun describe(&self, suffix: String) -> String { return suffix; }
9}
10
11fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
1struct Wrapper<T> { value: T }
2
3aspect Greet {
4 fun hello(&self) -> String;
5}
6
7extend<T: Copy> Wrapper<T>: Greet {
8 fun hello(&self, suffix: String) -> String { return suffix; }
9}
10
11fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
1struct Point { x: i64 }
2
3aspect Describe {
4 fun describe(&var self) -> String;
5}
6
7extend Point: Describe {
8 fun describe(&self) -> String { return "point"; }
9}
10
11fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
1struct Point { x: i64 }
2
3aspect Describe {
4 fun describe(&self) -> String;
5}
6
7extend Point: Describe {
8 fun describe(self) -> String { return "point"; }
9}
10
11fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
Also demonstratesImplementing an aspect L13, Implementing an aspect L14
1// RFC-0129 legality-13: an aspect implementation may not *strengthen* a method
2// generic constraint. `Guess::pick` declares `<T>` with no bound; the impl
3// requires `T: Copy`, which rejects instantiations the aspect admits.
4
5aspect Guess {
6 fun pick<T>(&self, a: T, b: T) -> T;
7}
8
9struct Chooser {}
10
11extend Chooser: Guess {
12 fun pick<T: Copy>(&self, a: T, b: T) -> T { return a; }
13}
14
15fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
Also demonstratesImplementing an aspect L13
1// RFC-0129 legality-13 (the metel-core#616 fix): record kind is part of the
2// generic-constraint comparison. `Store::keep` declares `<T>`; the impl requires
3// `<record T>`, so a caller with only a `Store` bound could call `keep(1)` while
4// the impl needs a record. Adding the record kind is strengthening -> rejected.
5
6aspect Store {
7 fun keep<T>(&self, value: T) -> T;
8}
9
10struct Box {}
11
12extend Box: Store {
13 fun keep<record T>(&self, value: T) -> T { return value; }
14}
15
16fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
Also demonstratesImplementing an aspect L13
1// RFC-0129 legality-13, conservative wrong-no. Dropping a bound (`<T: Copy>` ->
2// `<T>`) is a *safe widening* -- the impl accepts every instantiation the aspect
3// admits -- but RFC-0129's minimal interim rule compares constraint conjunctions
4// for structural equality, so it rejects this too. RFC-0129's deferred
5// admissible-domain-inclusion extension accepts the widening; update this
6// expectation to a positive fixture when that lands.
7
8aspect Relaxed {
9 fun pass<T: Copy>(&self, value: T) -> T;
10}
11
12struct Passthrough {}
13
14extend Passthrough: Relaxed {
15 fun pass<T>(&self, value: T) -> T { return value; }
16}
17
18fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
Also demonstratesImplementing an aspect L13
1// RFC-0129 legality-13, conservative wrong-no. `<record T>` -> `<T>` is a safe
2// widening: a plain `<T>` impl accepts every record the aspect admits. The
3// minimal structural-equality rule still rejects it; RFC-0129's deferred
4// admissible-domain-inclusion extension will accept it. Flip to a positive
5// fixture when that lands.
6
7aspect Inspect {
8 fun keep<record T>(&self, value: T) -> T;
9}
10
11struct Holder {}
12
13extend Holder: Inspect {
14 fun keep<T>(&self, value: T) -> T { return value; }
15}
16
17fun main() {}
typecheck errorT0012“does not match the signature declared by aspect”
Also demonstratesAnonymous records L1
1// Negative (RFC-0116 §3): an anonymous record has no nominal owner, so it satisfies no
2// impl-based aspect. It must be rejected at the call site, like a tuple or a struct
3// without the impl — not accepted and then blown up at run time.
4fun show<T: Display>(x: T) -> String { x.to_string() }
5
6fun main() {
7 let r := { x = 1 };
8 let s := show(r);
9}
typecheck errorT0012“anything impl-based needs a nominal type”
1fun exact<record T: { x: i64 }>(_value: T) -> i64 { 0 }
2
3fun main() {
4 let _ := exact({ x = 1, y = 2 });
5}
typecheck errorT0012“requires exactly these labels”
1fun need_y<record T: { y, .. }>(_value: T) -> i64 { 0 }
2
3fun main() {
4 let _ := need_y({ x = 1 });
5}
typecheck errorT0012“requires label `y`”
1fun need_float<record T: { x: f64, .. }>(_value: T) -> i64 { 0 }
2
3fun main() {
4 let _ := need_float({ x = 1 });
5}
typecheck errorT0012“label `x` to have type `f64`”
1fun forbid_x<record T: !{ x }>(_value: T) -> i64 { 0 }
2
3fun main() {
4 let _ := forbid_x({ x = 1 });
5}
typecheck errorT0012“negative row bound `!{ x }`”
1fun forbid_float_x<record T: !{ x: f64 }>(_value: T) -> i64 { 0 }
2
3fun main() {
4 let _ := forbid_float_x({ x = 1.0 });
5}
typecheck errorT0012“negative row bound `!{ x: f64 }`”
1fun bad<T: { x: i64 }>(_value: T) -> i64 { 0 }
2
3fun main() {
4 let _ := bad({ x = 1 });
5}
typecheck errorT0012“add `record` before the type parameter”
Also demonstratesRow bounds L4
1struct Point {
2 x: i64,
3}
4
5fun need_record<record T: { x: i64, .. }>(_value: T) -> i64 { 0 }
6
7fun main() {
8 let _ := need_record(Point { x = 1 });
9}
typecheck errorT0012“struct never satisfies a row bound”
1fun any_record<record T>(_value: T) -> i64 { 0 }
2
3fun main() {
4 let _ := any_record(1);
5}
typecheck errorT0012“only records satisfy a `record` type parameter”
1fun need_copy<T: Copy>(x: T) -> T {
2 x
3}
4
5fun main() {
6 let bad := need_copy((1, "nope"));
7}
typecheck errorT0012“tuples implement `Copy` only when every element does”
1fun need_copy<T: Copy>(x: T) -> T {
2 x
3}
4
5fun main() {
6 let seed: [String; 2] := ["a", "b"];
7 let bad := need_copy(seed);
8}
typecheck errorT0012“fixed-size arrays implement `Copy` only when their element type `String` does”
1fun need_copy<T: Copy>(x: T) -> T {
2 x
3}
4
5fun main() {
6 var n := 1;
7 let shared: &var i64 := &var n;
8 let bad := need_copy(shared);
9}
typecheck errorT0012“does not implement `Copy`”
Also demonstratesBuilt in functions L1
1// println/print require Display (METEL-181): passing a type with no
2// Display impl is a compile-time error, not a runtime panic.
3
4struct Test {
5 attr: i8,
6}
7
8fun main() {
9 let x := Test { attr = 1i8 };
10 println(x); // ERROR[T0012]
11}
typecheck errorT0012“does not implement `Display`”
Also demonstratesImplementing an aspect L4
1// Cross-module conditional impl regression (RFC-0036 / merge_from boundary):
2// The conditional impl requires A: Display AND B: Display. Pair<i64, Pair<i64, i64>>
3// violates the bound for B (Pair itself has no Display impl) — greet() must fail
4// with T0012.
5
6import pair_module::{Pair, Greet};
7
8fun main() {
9 let inner: Pair<i64, i64> := Pair { first = 1, second = 2 };
10 let p := Pair { first = 5, second = inner };
11 let s: String := p.greet(); // ERROR[T0012]
12}
typecheck errorT0012at 11
Also demonstratesNegative impls L5
1// RFC-0081: a blanket generic negative impl is part of the accepted surface,
2// not just concrete `impl !Aspect for Foo<i64> {}` cases. It must override a
3// blanket positive impl for every matching instantiation, so `Foo<boolean>`
4// fails a positive `Marker` bound here.
5
6aspect Marker {
7 fun mark(self) -> String;
8}
9
10struct Foo<T> {
11 value: T,
12}
13
14extend<T> Foo<T>: Marker {
15 fun mark(self) -> String { return "blanket"; }
16}
17
18extend<T> Foo<T>: !Marker;
19
20fun needs_marker<U: Marker>(x: U) {}
21
22fun main() {
23 let f := Foo { value = true };
24 needs_marker(f); // ERROR[T0012]
25}
typecheck errorT0012at 24
Also demonstratesNegative impls L5, Structural aspect bounds L2
1// RFC-0081 + RFC-0061: a blanket negative impl on a structural target (`T[]`)
2// must override a blanket positive impl the same way it already does for a
3// named generic target (see generic_negative_impl_blocks_positive_bound) --
4// negative-impl priority over a blanket positive is a property of the
5// aspect/target pair, not something that should depend on whether the
6// target happens to be `Named` or structural.
7
8aspect Marker {
9 fun mark(self) -> String;
10}
11
12extend<T> T[]: Marker {
13 fun mark(self) -> String { return "blanket"; }
14}
15
16extend<T> T[]: !Marker;
17
18fun needs_marker<U: Marker>(x: U) {}
19
20fun main() {
21 let xs := [1, 2, 3];
22 needs_marker(xs); // ERROR[T0012]
23}
typecheck errorT0012at 22
Also demonstratesNegative impls L5
1// RFC-0081 §2.4: a negative impl applies only to its own target. `Rc` meets
2// the negative bound, while the separately positive `Arc` must not inherit it.
3aspect Send {
4 fun send(self);
5}
6
7struct Rc { value: i64 }
8struct Arc { value: i64 }
9
10extend Rc: !Send;
11extend Arc: Send { fun send(self) {} }
12
13fun needs_not_send<T: !Send>(value: T) {}
14
15fun main() {
16 needs_not_send(Rc { value = 1 });
17 needs_not_send(Arc { value = 2 }); // ERROR[T0012]
18}
typecheck errorT0012at 17:19“bound not satisfied”
T0013 — Ambiguous aspect method/associated-type resolution
Two different aspects define the same method name on the same receiver type, so a
call like value.method() does not have a unique static target — or (RFC-0082 §3a)
two different aspects bound on the same generic type parameter both declare an
associated type of the same name, so a bare projection like T::AssocName doesn't
have a unique target either.
Fix (method case): rename one of the methods, remove one of the conflicting impls, or change the design so the receiver type does not expose two indistinguishable aspect methods.
Fix (associated-type case): bind the associated type to a fresh type parameter via
an equality-constrained bound instead of projecting it directly — e.g.
fun f<T: Deref<Target = U> + Convert, U>(x: &T) -> U — which resolves unambiguously
since U is an ordinary type parameter, not a projection.
Tested by (2)
Also demonstratesAssociated types L6
1aspect Deref {
2 type Target;
3 fun deref(&self) -> Target;
4}
5
6aspect Convert {
7 type Target;
8 fun convert(&self) -> Target;
9}
10
11fun ambiguous<T: Deref + Convert>(x: &T) -> T::Target {
12 x.deref()
13}
14
15fun main() {}
typecheck errorT0013at 11
1// Regression: two distinct aspects define the same method name on the same receiver type.
2// Static dispatch must reject the ambiguous call at compile time instead of silently
3// picking whichever impl elaboration visits first.
4
5aspect A {
6 fun label(self) -> i64;
7}
8
9aspect B {
10 fun label(self) -> i64;
11}
12
13struct S { value: i64 }
14
15extend S: A {
16 fun label(self) -> i64 { 1 }
17}
18
19extend S: B {
20 fun label(self) -> i64 { 2 }
21}
22
23fun main() {
24 let s := S { value = 0 };
25 let _n := s.label();
26}
typecheck errorT0013“ambiguous aspect method `label` on type `S`”
T0014 — Orphan implementation
An extend Type: Aspect block where neither Aspect nor Type's outermost type
constructor is declared in the current module (or std::core, for built-ins).
Fix: move the extend block into the module that declares the aspect or the type, or (for
two foreign types) into std::core if this is genuinely a standard-library concern.
Tested by (5)
Also demonstratesAnonymous records L3
1// Negative: `Display` is a standard-library aspect (not local to this module),
2// so it cannot be implemented for an anonymous record.
3extend { x: i64 }: Display {
4 fun to_string(&self) -> String { "x" }
5}
typecheck errorT0014“orphan implementation”
Also demonstratesAspect implementation coherence L2
1// RFC-0097: a bare-parameter blanket impl can never satisfy the orphan rule via
2// the target side, since bare `T` has no declaring module at all. Using a
3// foreign aspect must therefore be rejected.
4
5extend<T> T: Display { // ERROR[T0014]
6 fun to_string(self) -> String {
7 return "?";
8 }
9}
10
11fun main() {}
typecheck errorT0014at 5
Also demonstratesImplementing an aspect L10
1// RFC-0036 §3.3: a conditional impl obeys the orphan rule just as an
2// unconditional impl does. This module owns neither imported half.
3import greet_aspect::Greet;
4import widget::Widget;
5
6extend<T: Display> Widget<T>: Greet { // ERROR[T0014]
7 fun greet(self) -> String { return "hi"; }
8}
9
10fun main() {}
typecheck errorT0014at 6:1“orphan implementation”
Also demonstratesNegative impls L6
1// RFC-0081 §3: "Negative impls follow the same orphan rules as positive impls" --
2// this module owns neither Marker nor Widget, so even a negative impl is rejected.
3import marker_aspect::Marker;
4import widget::Widget;
5
6extend Widget: !Marker; // ERROR[T0014]
7
8fun main() {}
typecheck errorT0014at 6:1“orphan implementation”
Also demonstratesAspect implementation coherence L5
1// Regression test (RFC-0060/issue #238): orphan rule. `main.mtl` declares
2// neither `Greet` (from greet_aspect.mtl) nor `Widget` (from widget.mtl) — an
3// impl here owns no half of the pair, so it must be rejected as an orphan
4// implementation regardless of what the impl body does.
5
6import greet_aspect::Greet;
7import widget::Widget;
8
9extend Widget: Greet { // ERROR[T0014]
10 fun greet(self) -> String {
11 return "hi";
12 }
13}
14
15fun main() {}
typecheck errorT0014at 9
T0015 — Conflicting implementation
Two implementations of the same aspect cover the same concrete type — either two
identical extend blocks, or a positive and a negative impl (see Negative Impls in the declarations
reference) for the same concrete type.
Fix: remove the duplicate extend block, or narrow one block's type arguments so the two no
longer overlap.
Tested by (4)
Also demonstratesAspect implementation coherence L3
1// RFC-0097 §3: overlap between two bare-parameter blankets of the same local aspect
2// needs no new machinery -- it is caught by the same overlap detection RFC-0060 §2
3// already uses for named targets. `Copy` implies `Clone` (RFC-0080), so any `T: Copy`
4// also satisfies `T: Clone`, and these two blankets are not syntactically disjoint.
5
6aspect Marker {}
7
8extend<T: Copy> T: Marker {}
9extend<T: Clone> T: Marker {}
10
11fun main() {}
typecheck errorT0015at 9
Also demonstratesAspect implementation coherence L6
1// RFC-0060 §2: a blanket impl and a concrete impl of the same aspect
2// conflict when the concrete instantiation is already covered by the
3// blanket -- the pre-#244 overlap check only ever compared identically-
4// shaped canonical targets, so this shape-crossing pair silently missed
5// each other. Fixed by treating TypeParam as a wildcard when comparing
6// canonicalized targets (issue #244).
7
8aspect Marker {
9 fun mark(self) -> String;
10}
11
12struct Foo<T> {
13 value: T,
14}
15
16extend<T> Foo<T>: Marker {
17 fun mark(self) -> String { return "blanket"; }
18}
19
20extend Foo<i64>: Marker { // ERROR[T0015]
21 fun mark(self) -> String { return "concrete"; }
22}
23
24fun main() {}
typecheck errorT0015at 20
Also demonstratesImplementing an aspect L7, Implementing an aspect L9
1// RFC-0036§3 and RFC-0036§3.2: unconditional impl + conditional impl for the same aspect/type
2// constructor → T0015 conflict. The unconditional impl has all-empty scoped
3// bounds, so provably_disjoint can never return true against it.
4
5aspect Display {
6 fun display(self) -> String;
7}
8
9aspect Printable {
10 fun print(self) -> String;
11}
12
13struct Pair<A, B> {
14 first: A,
15 second: B,
16}
17
18// Unconditional blanket impl — no bounds, scoped bounds are all-empty.
19extend<A, B> Pair<A, B>: Printable {
20 fun print(self) -> String { return "unconditional"; }
21}
22
23// Conditional impl — Display bound at position 0.
24extend<C: Display> Pair<C, C>: Printable { // ERROR[T0015]
25 fun print(self) -> String { return "conditional"; }
26}
27
28fun main() {}
typecheck errorT0015at 24:1“conflicting implementation”
Also demonstratesAspect implementation coherence L6
1// Regression test (RFC-0060/issue #238): overlap detection. Two impls of the
2// same aspect for the same concrete type conflict, independent of whether
3// their method bodies agree or disagree.
4
5aspect Describe {
6 fun describe(self) -> String;
7}
8
9struct Crate {
10 x: i64,
11}
12
13extend Crate: Describe {
14 fun describe(self) -> String {
15 return "a";
16 }
17}
18
19extend Crate: Describe { // ERROR[T0015]
20 fun describe(self) -> String {
21 return "b";
22 }
23}
24
25fun main() {}
typecheck errorT0015at 19
T0016 — Non-diverging -> ! function
A function declared -> ! (RFC-0078) contains a reachable path that doesn't
diverge — most commonly an ordinary return <expr> where <expr> isn't itself
!-typed. A -> ! function promises never to return; the compiler verifies
every control-flow path ends in a diverging expression (a panic, a loop
with no reachable break, or a return/tail expression whose own value is
already !-typed).
Fix: make every path genuinely diverge (panic(msg), loop { }, or a
recursive/other !-returning call), or drop the -> ! annotation if the
function is meant to return normally.
Tested by
Also demonstratesNever type L11
1// Negative: RFC-0078 §6 -- a function declared `-> !` containing a reachable,
2// ordinary `return` (one whose value is NOT itself `!`-typed) is a type error:
3// the function actually returns, which `-> !` forbids.
4
5fun bad() -> ! { // ERROR[T0016]
6 return 5;
7}
8
9fun main() {}
typecheck errorT0016at 5
T0017 — Missing associated type definition
An extend Type: Aspect block omits a type Name = ConcreteType; definition for an
associated type the aspect declares (RFC-0082 §2). Every implementation of an aspect with
associated types must define all of them.
Fix: add the missing type Item = ConcreteType; definition to the extend block.
Tested by
1// RFC-0082 §2: every implementation of an aspect with associated types must
2// define all of them -- `IntBox`'s extend block omits `type Item`.
3aspect Container {
4 type Item;
5 fun get(&self) -> Item;
6}
7
8struct IntBox { v: i64 }
9
10extend IntBox: Container {
11 fun get(&self) -> i64 { self.v }
12}
13
14fun main() {}
typecheck errorT0017“associated type”
T0018 — Naming the concrete type of an opaque return value
A function returning extends Aspect (RFC-0037) hides its concrete return type. Using the
result in a position that pins it to a specific type — annotating it, or unifying it with a
concrete type — defeats that, and is rejected.
Fix: keep the value opaque — annotate it as extends Aspect too, or accept it through a
generic parameter with the same bound.
Tested by
Also demonstratesAspect bounds on function type… L16
1// Negative: caller cannot name concrete type should fail with T0018
2// Attempting to assign opaque return to concrete type variable
3
4aspect Display {
5 fun display(&self) -> String;
6}
7
8struct MyInt {
9 value: i64,
10}
11
12extend MyInt: Display {
13 fun display(&self) -> String {
14 self.value.to_string()
15 }
16}
17
18// Function returning extends Display
19fun make_int() -> extends Display {
20 MyInt { value = 42 }
21}
22
23fun main() {
24 let int_val := make_int();
25 // This should fail - cannot name the concrete type of opaque return
26 let concrete: MyInt := int_val; // ERROR[T0018]
27}
typecheck errorT0018at 26
T0019 — Use of moved value
Since v0.12.0, under
--move-checkonly. Move checking is off by default in this release.
An ownership rule from RFC-0071 §1/§7 was violated. Seven distinct situations share this code, each with its own message:
- a value used after it was moved;
- a partially moved value used as a whole;
- a partial move out of a type that implements
Drop, which is never allowed; - a move out of an array element, which is banned outright;
- a move of a non-
Copyelement out of a borrowedT[]view; - a
&varbinding moved by a use that is not a reborrow; - a value moved out of a reference — by calling a by-value
selfmethod through it, in general assignment or by-value argument position, or by reading a field through it with no explicit*at all. A reference only grants access, never ownership, so its pointee cannot be moved out this way, unless the pointee's own type isCopy(in which case the read is a copy, exactly asT: Copyalready permits at read-copy positions per §3a).
Each message names the binding and the location of the move. When the move happened on an earlier iteration of an enclosing loop, the message says so — a loop-carried move is usually the same expression as the use, one iteration later, so naming only its location would point back at the line you are already reading.
Fix: depending on the rule — borrow instead of moving (&x), clone the value, move the
whole value rather than a field of a Drop type, or index-and-copy rather than moving an
element out of an array.
Tested by (54)
Also demonstratesValues move by default L1
1fun main() {
2 let s := "hello";
3 let moved := s;
4 let again := s;
5}
typecheck errorT0019“use of moved value `s`”
Also demonstratesDrop L1, Partial moves L2
1struct Handle {
2 name: String,
3 fd: i64,
4}
5
6extend Handle: Drop {
7 fun drop(&var self) { }
8}
9
10fun main() {
11 let handle := Handle { name = "x", fd = 1 };
12 let name := handle.name;
13}
typecheck errorT0019“belongs to a `Drop` type”
Also demonstratesWhich constructs support partia… L1
1fun main() {
2 let pair := ("x", 1);
3 let left := pair.0;
4 let again := pair.0;
5}
typecheck errorT0019“`pair.0` was moved”
Also demonstratesWhich constructs support partia… L1
1enum MaybeText {
2 Empty,
3 Full { text: String },
4}
5
6fun main() {
7 let value := MaybeText::Full { text = "x" };
8 let n := match (value) {
9 MaybeText::Full { text } => text.len(),
10 MaybeText::Empty => 0,
11 };
12 let again := value;
13}
typecheck errorT0019“use of moved value `value`”
Also demonstratesWhich constructs support partia… L1
1fun main() {
2 let xs := ["x"];
3 let first := xs[0];
4}
typecheck errorT0019“array element moves are not allowed”
Also demonstratesWhich constructs support partia… L1, Structural aspect bounds L11
1fun main() {
2 let s := "hello";
3 let f := [s] once || -> String { return s; };
4 let again := s;
5}
typecheck errorT0019“use of moved value `s`”
1fun main() {
2 let s := "hello";
3 if (true) {
4 let moved := s;
5 } else {
6 let keep := 0;
7 }
8 let again := s;
9}
typecheck errorT0019“use of moved value `s`”
1fun main() {
2 let s := "hello";
3 loop {
4 let moved := s;
5 break;
6 }
7 let again := s;
8}
typecheck errorT0019“use of moved value `s`”
Also demonstratesReferences and moves L1
1struct Counter {
2 value: i64,
3}
4
5fun bump(r: &var Counter) { }
6
7fun main() {
8 var c := Counter { value = 0 };
9 let r := &var c;
10 let q := r;
11 bump(r);
12}
typecheck errorT0019“non-reborrow use”
1fun main() {
2 let xs := ["x"];
3 let n := match (xs[0]) {
4 s => s.len(),
5 };
6}
typecheck errorT0019“array element moves are not allowed”
Also demonstratesPartial moves L4
1struct Handle {
2 name: String,
3 fd: i64,
4}
5
6extend Handle: Drop {
7 fun drop(&var self) { }
8}
9
10fun main() {
11 let handle := Handle { name = "x", fd = 1 };
12 let n := match (handle.name) {
13 name => name.len(),
14 };
15}
typecheck errorT0019“belongs to a `Drop` type”
1struct Token { }
2
3fun duplicate<T>(value: T) -> T {
4 let moved := value;
5 value
6}
7
8fun main() { }
typecheck errorT0019“use of moved value `value`”
1struct Box<T> {
2 value: T,
3}
4
5extend<T> Box<T> {
6 fun take_twice(self) -> T {
7 let first := self.value;
8 self.value
9 }
10}
11
12fun main() { }
typecheck errorT0019“use of moved value `self`”
1fun main() {
2 let duplicate := |value| {
3 let moved := value;
4 value
5 };
6}
typecheck errorT0019“use of moved value `value`”
1extend<T> T[] {
2 fun take_first_twice(self) -> T {
3 let first := self[0];
4 self[0]
5 }
6}
7
8fun main() { }
typecheck errorT0019“cannot move from `self[_]`: array element moves are not allowed”
1// Two nominal generic impls provide the same method name under disjoint bounds.
2// Move checking must reconstruct each body with that declaration's own scheme,
3// not the last name-keyed scheme registered for `Box::describe`.
4aspect NonCopyTake {
5 fun describe(self) -> String;
6}
7
8aspect CopyTake {
9 fun describe(self) -> String;
10}
11
12struct Box<T> {
13 value: T,
14}
15
16extend<T: !Copy> Box<T>: NonCopyTake {
17 fun describe(self) -> String {
18 let first := self.value;
19 let second := self.value;
20 "non-copy"
21 }
22}
23
24extend<T: Copy> Box<T>: CopyTake {
25 fun describe(self) -> String {
26 let first := self.value;
27 let second := self.value;
28 "copy"
29 }
30}
31
32fun main() { }
typecheck errorT0019“use of moved value `self`”
1// Structural array methods share the same name-keyed registry pressure as
2// nominal methods. The !Copy body must not inherit the later Copy bound.
3aspect NonCopyTake {
4 fun take_twice(self) -> i64;
5}
6
7aspect CopyTake {
8 fun take_twice(self) -> i64;
9}
10
11extend<T: !Copy> T[]: NonCopyTake {
12 fun take_twice(self) -> i64 {
13 let first := self[0];
14 let second := self[0];
15 0
16 }
17}
18
19extend<T: Copy> T[]: CopyTake {
20 fun take_twice(self) -> i64 {
21 let first := self[0];
22 let second := self[0];
23 0
24 }
25}
26
27fun main() { }
typecheck errorT0019“cannot move from `self[_]`: array element moves are not allowed”
1fun sink<T>(value: T) { }
2
3fun move_twice<T>(value: T) -> T {
4 sink(value);
5 value
6}
7
8fun main() { }
typecheck errorT0019“use of moved value `value`”
1extend<T> T[] {
2 fun take_first(self) -> T {
3 self[0]
4 }
5}
6
7fun main() { }
typecheck errorT0019“cannot move from `self[_]`: array element moves are not allowed”
1aspect Inspect {
2 fun inspect(&self);
3}
4
5fun inspect_then_move_twice<T: Inspect>(value: T) -> T {
6 value.inspect();
7 let moved := value;
8 value
9}
10
11fun main() { }
typecheck errorT0019“use of moved value `value`”
1aspect Consume {
2 fun consume(self);
3}
4
5fun consume_twice<T: Consume>(value: T) {
6 value.consume();
7 value.consume();
8}
9
10fun main() { }
typecheck errorT0019“use of moved value `value`”
1aspect Merge {
2 fun merge(self, other: Self) -> Self;
3}
4
5fun merge_then_reuse_argument<T: Merge>(left: T, right: T) -> T {
6 let merged := left.merge(right);
7 right
8}
9
10fun main() { }
typecheck errorT0019“use of moved value `right`”
1aspect IntoItem {
2 type Item;
3 fun into_item(self) -> Item;
4}
5
6fun consume_twice<T: IntoItem>(value: T) {
7 let first := value.into_item();
8 let second := value.into_item();
9}
10
11fun main() { }
typecheck errorT0019“use of moved value `value`”
1aspect GenericSink {
2 fun sink<U>(&self, other: U);
3}
4
5fun sink_then_reuse<T: GenericSink, U>(value: T, other: U) -> U {
6 value.sink(other);
7 other
8}
9
10fun main() { }
typecheck errorT0019“use of moved value `other`”
1aspect Marker {
2 fun mark(self) -> String;
3}
4
5extend<T> T: Marker {
6 fun mark(self) -> String {
7 let moved := self;
8 let moved_again := self;
9 "ok"
10 }
11}
12
13fun main() { }
typecheck errorT0019“use of moved value `self`”
1aspect Consume {
2 fun consume(self);
3}
4
5aspect Container {
6 type Item: Consume;
7 fun get(self) -> Item;
8}
9
10fun consume_item_twice<C: Container>(container: C) {
11 let item := container.get();
12 item.consume();
13 item.consume();
14}
15
16fun main() { }
typecheck errorT0019“use of moved value `item`”
Also demonstratesArrays L1
1fun first<T>(items: T[]) -> T {
2 for (item in items) {
3 return item;
4 }
5 panic("empty")
6}
7
8fun main() { }
typecheck errorT0019“it is borrowed from a `T[]` view”
1fun main() {
2 let s := "hello";
3 var i := 0;
4 loop {
5 i += 1;
6 let moved := s;
7 if (i == 2) { break; }
8 }
9}
typecheck errorT0019“use of moved value `s`: `s` was moved here on an earlier iteration”
1fun peek(s: &String) -> i64 { 1 }
2
3fun main() {
4 let s := "hello";
5 var i := 0;
6 while (peek(&s) > i) {
7 i += 1;
8 let moved := s;
9 }
10}
typecheck errorT0019“on an earlier iteration”
1fun main() {
2 let s := "hello";
3 let values: i64[] := [1, 2, 3];
4 for (value in values) {
5 let moved := s;
6 }
7}
typecheck errorT0019“use of moved value `s`: `s` was moved here on an earlier iteration”
1fun main() {
2 let s := "hello";
3 var i := 0;
4 loop {
5 i += 1;
6 if (i == 1) { continue; }
7 let moved := s;
8 if (i > 3) { break; }
9 }
10}
typecheck errorT0019“use of moved value `s`: `s` was moved here on an earlier iteration”
Also demonstratesReferences and moves L1
1// #648: a shared reference only ever grants access, never ownership (RFC-0071
2// SS7.1) -- moving `String` (non-Copy) out of `*p` is illegal on the *first*
3// call, not just a repeated one. Before #648 this compiled and only the
4// second `eat(*p)` was rejected, as an ordinary use-after-move -- the wrong
5// diagnosis, since the first move was never legal to begin with.
6fun eat(s: String) -> i64 { 1 }
7
8fun main() {
9 let s := "hello";
10 let p := &s;
11 let first := eat(*p);
12}
typecheck errorT0019“cannot move `(*p)` out of a reference”
1fun main() {
2 let s := "original";
3 var i := 0;
4 loop {
5 i += 1;
6 let moved := s;
7 let s := "replacement";
8 if (i == 2) { break; }
9 }
10}
typecheck errorT0019“use of moved value `s`”
1fun main() {
2 let s := "original";
3 loop {
4 let moved := s;
5 let s := "replacement";
6 break;
7 }
8 let again := s;
9}
typecheck errorT0019“use of moved value `s`”
1aspect Consume {
2 fun eat(self) -> String;
3}
4
5struct B {
6 v: String,
7}
8
9extend B: Consume {
10 fun eat(self) -> String {
11 return self.v;
12 }
13}
14
15// A by-value `self` method reached through a shared reference has nowhere to
16// take the value from: `r` only borrows `b`, so `eat` cannot consume it. This
17// is rejected at the *first* call, not the second (#348) — moving out of a
18// reference is illegal regardless of how many times it is attempted.
19fun main() {
20 let b := B { v = "owned" };
21 let r := &b;
22 let first := r.eat();
23}
typecheck errorT0019“cannot move `(*r)` out of a reference”
1aspect Consume {
2 fun eat(self) -> String;
3}
4
5struct B {
6 v: String,
7}
8
9extend B: Consume {
10 fun eat(self) -> String {
11 return self.v;
12 }
13}
14
15// Same rule as #56, spelled with the deref written out. Auto-deref and an
16// explicit `*` dispatch to the same method and must be rejected identically —
17// checking only the receiver's static type and missing an explicit `Deref`
18// projection would leave this spelling as an unguarded sibling.
19fun main() {
20 let b := B { v = "owned" };
21 let r := &b;
22 let first := (*r).eat();
23}
typecheck errorT0019“cannot move `(*r)` out of a reference”
1aspect Consume {
2 fun eat(self) -> String;
3}
4
5struct B {
6 v: String,
7}
8
9extend B: Consume {
10 fun eat(self) -> String {
11 return self.v;
12 }
13}
14
15// The `&var` form used to be rejected too, but on the second call only, as a
16// reuse of the moved *reference* rather than a move of its pointee (`&var T`
17// is not `Copy`, unlike `&T`). Rejected at the first call now, for the actual
18// reason: a by-value method cannot be called through any reference.
19fun main() {
20 var b := B { v = "owned" };
21 let r := &var b;
22 let first := r.eat();
23}
typecheck errorT0019“cannot move `(*r)` out of a reference”
1aspect Consume {
2 fun eat(self) -> String;
3}
4
5struct B {
6 v: String,
7}
8
9extend B: Consume {
10 fun eat(self) -> String {
11 return self.v;
12 }
13}
14
15// The concrete and generic paths must agree — the mistake #347 had to correct
16// in the other direction. `consume_method_receiver` is the single place both
17// resolve through, so there is no second copy of this rule to drift.
18fun twice<T: Consume>(x: &T) -> String {
19 let a := x.eat();
20 return x.eat();
21}
22
23fun main() {
24 let b := B { v = "owned" };
25 let result := twice(&b);
26}
typecheck errorT0019“cannot move `(*x)` out of a reference”
1aspect Consume {
2 fun eat(self) -> String;
3}
4
5struct B {
6 v: String,
7}
8
9extend B: Consume {
10 fun eat(self) -> String {
11 return self.v;
12 }
13}
14
15// The receiver is `pair.0`, not an identifier — the same non-identifier shape
16// #347 found unguarded for `&var self`. The check here is keyed on the
17// receiver's type and place, not on `Expr::Ident`, so it needs no separate
18// case for this.
19fun consume_sneak<T: Consume>(pair: (&T, i64)) -> String {
20 return pair.0.eat();
21}
22
23fun main() {
24 let b := B { v = "owned" };
25 let result := consume_sneak((&b, 1));
26}
typecheck errorT0019“cannot move `(*pair.0)` out of a reference”
1aspect Consume {
2 fun eat(self) -> String;
3}
4
5struct B {
6 v: String,
7}
8
9extend B: Consume {
10 fun eat(self) -> String {
11 return self.v;
12 }
13}
14
15// The rule is specifically about a reference in the way, not about by-value
16// receivers in general: an owned value can still be consumed by value, and
17// using it again afterward is the ordinary use-after-move check, unaffected
18// by #348.
19fun main() {
20 let b := B { v = "owned" };
21 let out := b.eat();
22 let again := b.v;
23}
typecheck errorT0019“use of moved value `b`”
1// #648: `self` in a `&self` method is a reference like any other -- moving a
2// field's value out of it was never actually checked before this fix.
3struct Name { value: String }
4struct Item { name: Name, count: i64 }
5
6extend Item {
7 fun peek(&self) -> String {
8 let v := self.name.value;
9 v
10 }
11}
12
13fun main() {
14 let item := Item { name = Name { value = "n" }, count = 1 };
15 let _ := item.peek();
16}
typecheck errorT0019“cannot move `self.name.value` out of a reference”
1// #648, no `self` involved: an ordinary `&T` parameter has the same rule.
2struct Name { value: String }
3struct Item { name: Name, count: i64 }
4
5fun peek(item: &Item) -> String {
6 let v := item.name.value;
7 v
8}
9
10fun main() {
11 let item := Item { name = Name { value = "n" }, count = 1 };
12 let _ := peek(&item);
13}
typecheck errorT0019“cannot move `item.name.value` out of a reference”
Also demonstratesReferences and moves L1
1// #648, RFC-0071 SS7.1's own named example: `let x: B = *r;`.
2struct B { v: String }
3
4fun main() {
5 let b := B { v = "x" };
6 let r := &b;
7 let x: B := *r;
8}
typecheck errorT0019“cannot move `(*r)` out of a reference”
Also demonstratesReferences and moves L1
1// #648, RFC-0071 SS7.1's other named example: `f(*r)`.
2struct B { v: String }
3
4fun takes(b: B) -> String {
5 b.v
6}
7
8fun main() {
9 let b := B { v = "x" };
10 let r := &b;
11 let n := takes(*r);
12}
typecheck errorT0019“cannot move `(*r)` out of a reference”
1// #648: the field-read form of the argument-position gap -- `f(r.field)`, no
2// explicit `*` anywhere in sight.
3struct Name { value: String }
4struct Item { name: Name }
5
6fun takes(v: String) -> i64 {
7 v.len()
8}
9
10fun main() {
11 let item := Item { name = Name { value = "x" } };
12 let r := &item;
13 let n := takes(r.name.value);
14}
typecheck errorT0019“cannot move `r.name.value` out of a reference”
1// #648: a different manifestation of the same gap, reached through the
2// by-value method receiver's own fallback path. #602's own
3// receiver_place_is_behind_a_reference only inspects the immediate
4// receiver's own type/place, so it misses a receiver reached via auto-deref
5// through an interior reference-typed field (`inner: &Middle` here). The
6// fallback still routes through the same checked path this fix corrects,
7// so it's closed as a side effect.
8aspect Consume { fun eat(self) -> String; }
9struct B { v: String }
10extend B: Consume { fun eat(self) -> String { return self.v; } }
11struct Middle { payload: B }
12struct Outer { inner: &Middle }
13
14fun main() {
15 let b := B { v = "owned" };
16 let middle := Middle { payload = b };
17 let outer := Outer { inner = &middle };
18 let taken := outer.inner.payload.eat();
19}
typecheck errorT0019“cannot move `outer.inner.payload` out of a reference”
Also demonstratesWidening L1, References and moves L1
1// RFC-0137 slice 2 (metel-core#858): narrowing and widening apply only to an
2// owned binding. A non-`Copy` field cannot be moved out of a value reached
3// through a reference (RFC-0071 §7.1), so there is never a residual to narrow to
4// or widen from behind one — this rule is unchanged by RFC-0137.
5//
6// Needs move_check = true: the move-out-of-a-reference ban is a move-checker
7// rule, not one of the always-on typecheck rules.
8
9struct Handle { fd: i64, name: String }
10
11fun consume_name(h: &var Handle) -> String {
12 let n := h.name; // rejected: moving `name` out through `&var Handle`
13 n
14}
15
16fun main() {
17 var h := Handle { fd = 1, name = "x" };
18 println(consume_name(&var h));
19}
typecheck errorT0019“reference”
Also demonstratesFirst class functions L3
1// v0.13.0 cross-feature (integration session, metel-core#956): a struct-pattern
2// binding of a written-function-type field (RFC-0154) is move-only (RFC-0166).
3// Using it by value twice inside the arm is a use-after-move under --move-check.
4struct Pair { op: |i64| -> i64, n: i64 }
5
6fun main() {
7 let p := Pair { op = |x: i64| { x * 2 }, n = 5 };
8 let r := match (p) {
9 Pair { op, n } => {
10 let a := op;
11 let b := op; // use after move
12 a(n) + b(n)
13 },
14 };
15 println("unreachable");
16}
typecheck errorT0019“moved”
1// Registry merging prepends this module's method variants ahead of dependency
2// variants. Declaration-identity lookup must still pair boxed.mtl's body with
3// boxed.mtl's !Copy scheme.
4import boxed::*;
5
6aspect CopyTake {
7 fun describe(self) -> String;
8}
9
10extend<T: Copy> Box<T>: CopyTake {
11 fun describe(self) -> String {
12 "copy"
13 }
14}
15
16fun main() { }
typecheck errorT0019“use of moved value `self`”
1import contracts::*;
2
3fun consume_twice<T: Consume>(value: T) {
4 value.consume();
5 value.consume();
6}
7
8fun main() { }
typecheck errorT0019“use of moved value `value`”
Also demonstratesClosures D10
1// v0.13.0 closure cluster (RFC 0134 dynamics-10): a `once` call consumes the
2// callee place at the call expression; a second call is the ordinary
3// moved-value error.
4//
5// Needs move_check = true (general moved-value check, RFC 0071).
6fun main() {
7 let s := "hello";
8 let take := [s] once || { s };
9 let first := take();
10 let second := take(); // moved-value error -- `take` was already consumed
11}
typecheck errorT0019“moved”
Also demonstratesClosures D5
1// v0.13.0 closure cluster (RFC 0157 D5): `[s]` moves a non-Copy binding into
2// the closure, consuming the outer binding. Using it afterward is the
3// ordinary moved-value error (RFC 0134 §2 cites T0019's existing shape).
4//
5// Needs move_check = true: this is the general affine-move check (RFC 0071),
6// not one of the closure-specific always-on checks (ADR-0052 §1).
7fun main() {
8 let s := "hello";
9 let greet := [s] once || { s };
10 println(s); // moved-value error -- `s` was moved into `greet`
11}
typecheck errorT0019“moved”
Also demonstratesFirst class functions L3
1// v0.13.0 (RFC-0166): a parameter whose written type is a function type is
2// move-only inside the callee, regardless of what the caller passed. `add_one`
3// is a copyable named function, but `consume` sees `f: |i64| -> i64` as
4// move-only -- moving it into `a` leaves nothing for the second `let`.
5//
6// This is the migration case RFC-0166 calls out: a body that used a
7// bare-typed callback by value more than once. Needs move_check = true.
8
9fun add_one(x: i64) -> i64 { x + 1 }
10
11fun consume(f: |i64| -> i64) -> i64 {
12 let a := f; // moves `f`
13 let b := f; // moved-value error
14 a(1) + b(2)
15}
16
17fun main() {
18 println(consume(add_one));
19}
typecheck errorT0019“moved”
Also demonstratesFirst class functions L3, First class functions L4
1// v0.13.0 (RFC-0166): a binding whose *written* type is a function type `|i64|
2// -> i64` is move-only, regardless of what was assigned to it. `add_one` is a
3// copyable named function, but the annotation erases that: `f` may be moved
4// once, and the second `let` is an ordinary use-after-move (RFC-0071 / T0019).
5//
6// Needs move_check = true -- this is the general affine-move check, not a
7// closure-specific always-on rule.
8
9fun add_one(x: i64) -> i64 { x + 1 }
10
11fun main() {
12 let f: |i64| -> i64 := add_one;
13 let a := f; // moves `f`
14 let b := f; // moved-value error -- `f` is move-only under RFC-0166
15 println(a(1) + b(2));
16}
typecheck errorT0019“moved”
T0021 — break/continue with no enclosing loop
break or continue appeared with no enclosing loop of any kind (loop, while, for,
or for-in) to bind to. This includes a break/continue written inside a closure body
— a closure is never considered to be "inside" whatever loop happens to lexically
surround its definition, since the closure may be called long after that loop has exited,
or from somewhere the loop never ran at all.
Fix: remove the keyword, or move it inside the loop it is meant to control. If it is meant to control a loop that encloses the call site of a closure rather than the closure's own definition, restructure the code — a closure cannot break or continue a loop it does not itself contain.
Tested by
T0022 — extends Aspect outside parameter or return position
extends Aspect was written somewhere other than a function parameter's type or a
function's return type — for example, a let/var annotation, a struct or enum
variant field, a cast target (x as extends P), or a generic bound. Parameter position is
lowered to a fresh bounded type parameter, and return position is RFC-0037's opaque
return type; every other position is not part of this language version.
Fix: name a concrete type instead, or restructure the code so the aspect bound is expressed through a parameter or return type.
Tested by (2)
Also demonstratesAspect bounds on function type… L6
1// Negative: `extends Aspect` is not permitted inside a local let annotation.
2aspect P { fun p(&self); }
3
4struct L { t: String }
5
6extend L: P { fun p(&self) {} }
7
8fun main() {
9 let values: extends P[] := [L { t = "x" }]; // ERROR[T0022]
10}
typecheck errorT0022at 9
Also demonstratesAspect bounds on function type… L5
1// Negative: `extends Aspect` is not permitted in a struct field type.
2aspect P { fun p(&self); }
3
4struct Holder {
5 values: extends P[], // ERROR[T0022]
6}
7
8fun main() {}
typecheck errorT0022at 5
T0023 — Assignment through a non-owning view
An index assignment targets a T[] value. Since RFC-0126, T[] is an unconditionally
Copy, non-owning view — it never grants write access through its indices, independent
of whether the binding holding it is let or var. This is a different failure shape
than T0006 (all three of T0006's forms are about a let binding that declaring it var
would fix); no annotation or binding-mutability change can fix this one.
Fix: use [T; N] (a fixed-size array) or List<T> (a growable, owned collection)
instead of T[] for storage that needs index-write access.
Tested by
1// RFC-0126: `T[]` is an unconditionally Copy, non-owning view -- it never
2// grants write access through its indices, regardless of let/var.
3fun main() {
4 let a: i64[] := [1, 2, 3];
5 a[0u64] := 5;
6}
typecheck errorT0023“array views are immutable”
T0024 — Read-copy of a non-Copy value out of a reference
Since v0.12.1.
RFC-0067a §3a's "read-copy": a let/mut binding, return/break value, tail
expression, or explicit ascription (expr: T) whose own declared type differs from
its initializer's reference type (&U/&var U) implicitly copies the referent out —
but only when U is Copy. A reference only grants access, never ownership, so
reading a non-Copy value out this way would silently duplicate it with no move and
no explicit clone.
Checked once against the fully-dereferenced type at the end of a reference chain, not
each intermediate layer — let x: i64 = rr; where rr: &&i64 is unaffected, since
i64 is Copy regardless of how many reference layers it's read through.
Fix: call .clone() if the type implements Clone, or restructure the code to
take ownership of the value directly instead of reading it through a reference.
The closure cluster reserves the contiguous T0026–T0030 block, split below so each code's own coverage is visible rather than folded into one shared entry (an implementation gap in only one of the five would otherwise hide behind the other four).
Tested by
Also demonstratesReading a value out of a refere… L1
1// TYPECHECK_ERROR[T0024]
2// #649: RFC-0067a §3a's read-copy requires the referent to be `Copy` -- reading a
3// non-`Copy` value out of a shared reference at a `let` binding must be rejected,
4// not silently duplicated.
5struct NotCopy { v: String }
6
7fun main() {
8 let owned := NotCopy { v = "x" };
9 let r: &NotCopy := &owned;
10 let copy: NotCopy := r;
11}
typecheck errorT0024
T0026 — Capture list required, incomplete, or incompatible
Since: v0.13.0.
A capture list is required, incomplete, or uses an incompatible capture form.
Fix: use the capture list the closure body's captures actually require.
Tested by (2)
Also demonstratesClosures L6
1// v0.13.0 closure cluster (RFC 0050 legality-6): a closure must carry a
2// capture list if its body references a free non-Copy local binding.
3//
4fun main() {
5 let name := "log";
6 let greet := || { name }; // missing capture list for `name`
7 assert(greet() == "log");
8}
typecheck errorT0026“capture”
Also demonstratesClosures L19
1// v0.13.0 closure cluster (RFC 0050 legality-19): verification runs in a
2// fixed stage order -- capture classification first, then use_multiplicity,
3// then `once`, then `var`. A body that both omits a required capture list AND
4// mutates that binding is reported at stage 1 ("add a capture list"), not at
5// stage 4 ("add `var`").
6//
7fun main() {
8 var msg := "hi";
9 let f := || { msg := "bye"; }; // no capture list; `msg` is a free non-Copy local
10 f();
11}
typecheck errorT0026“capture”
T0027 — Consuming capture without once
Since: v0.13.0.
A closure body consumes a capture but the literal/type is not once.
Fix: mark the closure once.
Tested by
Also demonstratesClosures L8
1// v0.13.0 closure cluster (RFC 0134 legality-8): a closure whose body moves
2// a non-Copy capture out must be written `once`; omitting it is a compile
3// error at the definition site.
4//
5fun main() {
6 let s := "hello";
7 let take := [s] || { s }; // moves `s` out; missing `once`
8 take();
9}
typecheck errorT0027“once”
T0028 — Mutating capture without var
Since: v0.13.0.
A closure body mutates a capture, or uses [&var x], but is not var.
Fix: mark the closure var.
Tested by (2)
Also demonstratesClosures L25
1// v0.13.0 closure cluster (RFC 0153 legality-25 / metel-core#959): a `[&var x]`
2// capture over a `var` binding still requires the `var` qualifier on the closure
3// literal itself. The diagnostic names the pipe spelling introduced by RFC-0154
4// -- `[...] var |...| { ... }` -- not the parenthesized form that RFC removed.
5fun main() {
6 var count := 0;
7 let bump := [&var count] || { count := count + 1; }; // missing `var` qualifier
8 bump();
9}
typecheck errorT0028“var |...| { ... }”
Also demonstratesClosures L25
1// v0.13.0 closure cluster (RFC 0153 legality-25): a closure whose body
2// assigns to a by-value capture must be written `var`; omitting it is a
3// compile error at the definition site.
4//
5fun main() {
6 let n := 0;
7 let bump := [n] || { n := n + 1; n }; // mutates `n`; missing `var`
8 bump();
9}
typecheck errorT0028“var”
T0029 — var closure called through a shared reference
Since: v0.13.0.
A var closure is called through a shared reference.
Fix: call through an owned binding or an exclusive (&var) reference instead.
Tested by
Also demonstratesClosures L10
1// v0.13.0 closure cluster (RFC 0153 legality-10): a `mutating` call needs
2// exclusive access to the callee for the call's duration. Calling one through
3// a shared `&` reference (here a `&Cell` receiver) is a compile error.
4//
5struct Cell {
6 go: var || -> i64,
7}
8
9fun peek(c: &Cell) -> i64 {
10 (c.go)() // error (T0024): `var` closure called through a shared `&` reference
11}
12
13fun main() {
14 let n := 0;
15 let c := Cell { go = [n] var || { n := n + 1; n } };
16 assert(peek(&c) == 1);
17}
typecheck errorT0029“shared”
T0030 — Inner closure borrows an outer by-value capture
An inner closure borrows an enclosing closure's by-value capture.
Fix: restructure the code until RFC-0122 supplies the necessary borrow analysis.
Exempt from fixture coverage — blocked on RFC-0122: requires RFC-0122's borrow analysis, not yet implemented
Runtime errors (R)
R0001 — No main function defined
Execution requires a main function but none was found.
Fix: add fun main() { ... } to your program.
Tested by
R0002 — main is not a valid entry point
main exists but is generic or is not a function.
Fix: main must be a concrete, non-generic function with no parameters.
Note: also raised for a generic closure invoked with no call-site type context, with a different message — this entry covers the
maincase only.
Tested by
1// `main` exists but is a binding, not a function -- R0002's other documented
2// trigger (the sibling case is a generic main, see error-codes.md).
3let main := 5;
runtime errorR0002“not a function”
R0003 — Undefined variable at runtime
A variable name is not found in the current environment. This can occur when a variable is used before it is defined in a branch that the type-checker did not flag.
[R0003] runtime error in main.mtl at 10..15: undefined variable `x`
Exempt from fixture coverage — blocked on metel-core#986: Confirmed live raise sites (lvalue.rs, mod.rs). #986's follow-up round tried #712's exact precedent (nested-fun forward reference) across 5 more statement positions -- var initializer, if-branch, call-argument, match-arm, struct-literal-field, while-condition -- all resolved correctly, meaning #712's original let-initializer fix was thorough, not narrow. Still no repro found across either investigation round, and still not confirmed unreachable either.
R0004 — Index out of bounds
An array index is negative or ≥ the array length.
Fix: check that the index is within 0..array.len() before access.
Tested by
1// RUNTIME_ERROR[out of bounds]
2fun main() {
3 let arr := [1, 2];
4 let _x := arr[5];
5}
runtime errorR0004“out of bounds”
R0005 — Tuple index out of bounds
A tuple element is accessed by an index that does not exist.
[R0005] runtime error in main.mtl at 5..10: tuple index 3 out of bounds
Fix: tuple indices are fixed at compile time; verify the index against the tuple's declared length.
Note: not confirmed reachable from ordinary source. A tuple index is always a literal token, never a computed expression, so an out-of-range index was caught as
T0003statically in every construction tried. UnlikeP0003above, the raise site is real code — just unconfirmed.
Exempt from fixture coverage — untestable: Checked and found unreachable via ordinary source -- tuple indices are fixed at compile time and out-of-range access is caught statically, not deferred to runtime.
R0006 — Non-exhaustive match at runtime
A match expression reached its end without any arm matching. This indicates a pattern that the type checker approved as exhaustive but that is not, which is a known limitation.
[R0006] runtime error in main.mtl at 2..30: match: no arm matched scrutinee
Exempt from fixture coverage — blocked on metel-core#986: A known limitation (the type checker approving a match as exhaustive when it is not). #986's follow-up round read check_match_exhaustiveness end to end (typechecker/construction/patterns.rs) -- the Boolean/Named-enum/Never/SizedArray cases, is_variant_uninhabited's RFC-0078 uninhabited-payload check, and pattern_covers_variant's enum+variant name matching all look sound on inspection. No construction attempted this round (unlike R0003/R0009) since no plausible gap surfaced worth testing against.
R0007 — Arithmetic error
Integer division or remainder by zero, or integer overflow on +, -, *, or
/ (RFC-0007 D3, amended 2026-08-26 — panics unconditionally in every build; there
is no debug/release distinction). Floating-point arithmetic never raises this code —
float overflow and division by zero follow IEEE 754 (inf/-inf/NaN), never a
panic.
Fix: guard with a zero check before dividing, or ensure operands stay in range before an operation that could overflow.
Tested by
Also demonstratesSized numeric types D1
1// RUNTIME_ERROR[overflow]
2fun main() {
3 let a: i8 := 127i8 + 1i8;
4 let _ := a;
5}
runtime errorR0007“overflow”
R0008 — Field not found
A struct or enum value does not have the accessed field.
[R0008] runtime error in main.mtl at 5..12: no field `colour` on value
Fix: check the field name against the type definition.
Exempt from fixture coverage — blocked on metel-core#986: Confirmed live raise site, but every attempted repro (a generic function reading an unconstrained field) was caught statically as T0002 instead. #986's follow-up round: field access resolves directly against the accessed value's own concrete fields (lvalue.rs's TypedPlace::Field), not through a bare-name-keyed lookup table the way aspect methods do -- so R0009's newly-found collision bug (metel-core#989) doesn't obviously carry over here. No new construction attempted this round.
R0009 — Method not found
A method call cannot be resolved for the receiver type.
[R0009] runtime error in main.mtl at 5..20: no method `draw` on `Circle`
Fix: define the method in an extend block for the type.
Exempt from fixture coverage — blocked on metel-core#989: Confirmed live raise site. #986's follow-up round found a real root-cause bug in this exact dispatch machinery: metel-core#989, two same-named aspects in different modules corrupt each other's dispatch resolution (TypeRegistry::aspect_decl_modules is keyed by bare aspect name, not a qualified path). In the variant tried (colliding aspects with differently-named methods) this surfaced as a false static T0003 rejection, not a runtime R0009 -- construction-time method lookup apparently consults the same corrupted map before dispatch is ever elaborated. A same-method-name variant (also tried) resolved correctly in both import orderings tried, so this mechanism isn't yet confirmed to reach R0009 specifically -- revisit once #989 is fixed.
R0010 — Call on non-callable value
A call expression (f(...)) is applied to a value that is not a function or closure.
[R0010] runtime error in main.mtl at 3..8: call: expected a closure or builtin
Exempt from fixture coverage — blocked on metel-core#986: Confirmed live raise site, but calling a plain i64 variable was caught statically as T0001. #986's follow-up round: calling a value generically/dynamically has no route to try at all in v0.13.0 -- RFC-0161's dyn Callable / Callable aspect (the mechanism that would make a call target's callability depend on a runtime value rather than a static function type) is deferred in full to a later milestone, so there is currently no dynamic-dispatch angle to test against this code.
R0011 — Invalid for-in iterator
A for x in expr loop where expr does not evaluate to an Array, a Range, or a type
implementing Iterable.
[R0011] runtime error in main.mtl at 1..20: for-in: expected Array or Range
Fix: ensure the iterable is an array literal, a range (a..b), a value of those types,
or a type with its own Iterable implementation (see expressions.md, "for-in").
Exempt from fixture coverage — blocked on metel-core#986: Confirmed live raise sites (evaluator/mod.rs), but a plain non-iterable typed value (e.g. for (x in n) where n: i64) is caught statically as T0001 before reaching this runtime path. #986's follow-up round: the user-defined-Iterable dispatch this code guards resolves through the receiver value's own runtime type id (resolve_value_type_id + get_regular_method), not a bare-name-keyed table -- unlike R0009's aspect-method path (metel-core#989), this one isn't obviously vulnerable to the same class of collision bug. No construction attempted this round on that basis.
R0012 — Assertion failed
assert(cond) or assert(cond, msg) is called with cond evaluating to
false. The panic message is the fixed string "assertion failed" for the
one-argument form, or the caller-supplied msg for the two-argument form.
Fix: this is not a bug in the interpreter — it means the asserted condition was actually false at runtime. Fix the condition, or the code that led to it.
Tested by
Also demonstratesBuilt in functions D2
1fun main() {
2 assert(false, "custom assertion failure");
3}
runtime errorR0012“custom assertion failure”
R0013 — Unwrap on None/Err
.yolo() is called on a Perhaps<T> that is None, or a Result<T, E> that is
Err. For Result, the panic message includes the Err value's debug
representation.
Fix: this is not a bug in the interpreter — .yolo() is meant only for cases
where None/Err represents a logic error that should never occur in correct
code. Use match, .unwrap_or, .unwrap_or_else, or (for Result) ? to
handle the expected case instead.
Tested by
1// RUNTIME_ERROR[R0013]
2// issue #232: .yolo() on None panics with R0013.
3fun main() {
4 let none: Perhaps<i64> := Perhaps::None;
5 let _ := none.yolo();
6}
runtime errorR0013“yolo”
R0014 — Explicit panic
panic(msg) (RFC-0078) is called. Always panics unconditionally with msg.
Fix: this is not a bug in the interpreter — panic is meant for logic
errors that should never occur in correct code. Handle the expected case with
ordinary control flow instead of reaching the panic call.
Tested by
Also demonstratesNever type D1, Panics D1
1// RUNTIME_ERROR[boom]
2// RFC-0078: panic(msg) always panics (R0014) with the given message.
3fun main() {
4 panic("boom");
5}
runtime errorR0014“boom”
R0015 — Re-entrant mutating closure call
Since: v0.13.0.
A var closure tried to call the same closure value again before its current invocation
finished. This is an uncatchable assertion-class runtime error.
Fix: restructure the callback/control flow so a mutating closure is not re-entered.
Tested by
Also demonstratesClosures D9
1// v0.13.0 closure cluster (RFC 0153 dynamics-9): for the extent of a
2// `mutating` call the callee is exclusively borrowed. A second `mutating`
3// call on the same closure value reached from inside the first is rejected --
4// before the borrow checker lands, as a runtime error (R0015).
5//
6struct Cell {
7 go: var || -> i64,
8}
9
10fun main() {
11 var c := Cell { go = || { 0 } };
12 c.go := [&var c] var || {
13 (c.go)() + 1 // re-enters the same `var` closure while its first call is live
14 };
15 (c.go)(); // runtime error R0015 -- re-entrant call to a mutating closure
16}
runtime errorR0015“re-entrant”
Internal errors (I)
I0001 — Internal interpreter error
The interpreter reached an impossible state. This is a bug in the interpreter — the typechecker should have caught it before execution.
[I0001] internal error: binop: unsupported operand types (typechecker should have caught this)
What to do: please file a bug report at the Metel issue tracker with the source program that triggered this error.
Exempt from fixture coverage — untestable: Forcing an internal-error state deliberately isn't meaningfully the same kind of check as an ordinary trigger -- a real repro would mean finding an actual interpreter bug, not demonstrating a language rule (not attempted).
I0002 — Not implemented
The program uses a language feature that is not yet supported in this version of the interpreter.
[I0002] internal error: generic functions are not supported in v0.1
What to do: check the changelog for the current supported feature set and the release plan for the planned implementation milestone.
Note:
I0002and itsnot_implemented()constructor are kept as scaffolding — the intended way to report a recognized but not-yet-built construct while a feature is under development. There is no live raise site today. metel-core#992 tracks removing the variant and constructor if they stay unused.
Exempt from fixture coverage — untestable: Kept as scaffolding for reporting a recognized-but-unimplemented construct during feature development; there is no live raise site today, so nothing to trigger. metel-core#992 tracks removal if it stays unused.