c218d875 · core f63c1e69 · built 2026-10-26 22:39 UTCMetel 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 (30)
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
1// A whole keyword is never an identifier: `ident` is `!keyword ~ ...`.
2fun main() {
3 let if := 1;
4}
parse errorP0001“neg_keyword_as_identifier.mtl”
Also demonstratesOpen rows L1
1struct Handle { fd: i64, name: String }
2
3struct Holder<row R> { v: Handle.{ ..R } } // ERROR[P0001]
4
5fun main() {}
parse errorP0001at 3“is accepted only as a function parameter type”
Also demonstratesOpen rows L1
1struct Handle { fd: i64, name: String }
2
3fun main() {
4 let x: Handle.{ ..R } := Handle { fd = 1, name = "n".to_string() }; // ERROR[P0001]
5}
parse errorP0001at 4“is accepted only as a function parameter type”
Also demonstratesOpen rows L1
1struct Handle { fd: i64, name: String }
2
3fun keep<row R>(h: Handle.{ fd, ..R }) -> Handle.{ fd, ..R } { // ERROR[P0001]
4 h
5}
6
7fun main() {}
parse errorP0001at 3“is accepted only as a function parameter type”
Also demonstratesOpen rows L1
1struct Handle { fd: i64, name: String }
2
3fun read<row R>(h: &Handle.{ fd, ..R }) -> i64 { h.fd } // ERROR[P0001]
4
5fun main() {}
parse errorP0001at 3“is accepted only as a function parameter type”
1// RFC-0121 (Open Rows, metel-core#1301): a struct's own residual-projection
2// tail is grammar-legal on a method too (`extend` blocks reuse `fun_decl`'s
3// own param list), but this installment's structural resolution only runs
4// for a plain free function -- rejected explicitly at parse time, exactly
5// like sec1's generic open-row-tailed record parameter.
6
7struct Handle { fd: i64 }
8
9extend Handle {
10 fun get_fd<row R>(self, extra: Handle.{ fd, ..R }) -> i64 { extra.fd }
11}
12
13fun main() {}
parse errorP0001“not yet supported on a method's parameter”
Also demonstratesRecords L4
1// The rule applies to a generic record too, whatever the field's type (metel-core#1440).
2record Box<row R> {
3 public data: { ..R },
4}
5
6fun main() {}
parse errorP0001at 3:5“`public` is not written on field `data` of `record Box`”
Also demonstratesRecords L4
1// RFC-0120 §5 (metel-core#1440): a `record` publishes its row, so every field is public
2// by construction and takes no visibility modifier. A `public` written on one is a hard
3// syntax error, reported at the modifier. A `struct` has no such restriction.
4
5record Handle {
6 public fd: i64,
7 name: String,
8}
9
10fun main() {}
parse errorP0001at 6:5“`public` is not written on field `fd` of `record Handle`”
1// RFC-0121 item 2 (metel-core#1310): `type_args` is shared, so a row splice
2// is grammar-legal in an `extend` block's own aspect-bound type argument
3// list too -- rejected explicitly at parse time rather than silently
4// dropped.
5
6aspect Bar<T> {
7 fun bar(self) -> i64;
8}
9
10struct Foo { x: i64 }
11
12extend<row R> Foo: Bar<..R> {
13 fun bar(self) -> i64 { self.x }
14}
15
16fun main() {}
parse errorP0001“not supported in an aspect's own type argument list”
1// RFC-0121 item 2 (metel-core#1310): `type_args` is a shared grammar
2// production, so a row splice is grammar-legal in an explicit turbofish
3// call's type argument list too -- rejected explicitly at parse time
4// rather than silently dropped (it has no row-polymorphic meaning designed
5// for this position at all, unlike generic-argument position itself).
6
7fun foo<row R>() -> i64 { 0 }
8
9fun main() {
10 let _ := foo::<..R>();
11}
parse errorP0001“not supported in an explicit call's type argument list”
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”
1// RFC-0121 sec3: a decomposition equation with no tail at all is pointless
2// -- identical to an ordinary bound -- so the grammar accepts a bare
3// `type_expr` right-hand side (matching the RFC's own literal production)
4// only to reject it explicitly at parse time, rather than silently failing
5// to match the `row_equation` rule.
6
7fun split<row R>(p: { token: i64, ..R }) -> i64
8where R = i64
9{
10 p.token
11}
12
13fun main() {}
parse errorP0001“must be an open record type with a `..Rest` tail”
1// RFC-0121 (Open Rows, metel-core#1301) item 6: an open-row-tailed parameter
2// is supported on an *instance* method (one with a receiver), whose row bound
3// travels on the method's scheme. A static method (no receiver) registers
4// under a joined, name-keyed path that carries none of that bound data, so it
5// is rejected explicitly at parse time rather than silently mishandled.
6
7struct Point { x: f64, y: f64 }
8
9extend Point {
10 fun get_x<row R>(extra: { x: f64, ..R }) -> f64 { extra.x }
11}
12
13fun main() {}
parse errorP0001“not yet supported on a static method's parameter”
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.
When both are records, the message names the field that differs instead of printing the two
types: the record is missing field y(expected{ x: i64, y: i64 }, found { x: integer }),
field `z` is not part of `{ x: i64, y: i64 }` , or field `x`: expected `i64`, found `String` .
An unresolved numeric literal prints as integer or float and an open tail as ... The same
mismatch reached through a row bound is T0012, in the same words.
Fix: ensure the expression produces the expected type. Add an explicit cast if widening (e.g. x as f64).
A ? whose operand is Result<T, E> or Option<T> but whose enclosing function returns the
other one is also this code, with the message "? on Option<String> needs the enclosing
function to return Option<_>, but it returns Result<Config, ConfigError>" and a help line
naming the bridge: .ok_or(...) for an Option in a Result function, .ok() for a Result
in an Option function (RFC-0182).
When the expected type is the referent of a reference-typed expression (&T found where T
is expected), the error carries = help: read the value out with *r (copies a Copy referent) (RFC-0185). A reference is never converted to its referent implicitly.
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 (97)
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“the record is missing field `left` (expected `{ left: String, right: i64 }`, found `{ right: integer }`)”
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`”
Also demonstratesOpen rows L5
1// metel-core#1478: the fixed fields of an anonymous-tail annotation are real types. `x` is a `u8`,
2// so reading it where an `i32` is required is a mismatch.
3fun main() {
4 let c: { x: u8, .. } := { x = 200, y = 2 };
5 let e: i32 := c.x; // ERROR[T0001]
6}
typecheck errorT0001at 5“cannot unify u8 with i32”
Also demonstratesOpen rows L5
1// metel-core#1478: a closure with an open-row parameter still requires the named fields at the call.
2fun main() {
3 let get := |p: { x: i64, .. }| { p.x };
4 let _ := get({ x = 1 });
5 let _ := get({ y = 1 }); // ERROR[T0001]
6}
typecheck errorT0001at 5“the record is missing field `x` (expected `{ x: i64, .. }`, found `{ y: integer }`)”
Also demonstratesOpen rows L5
1// metel-core#1478: the annotation still requires its fixed fields.
2fun main() {
3 let _ := 1;
4 let a: { x: i64, .. } := { z = 1 }; // ERROR[T0001]
5}
typecheck errorT0001at 4“the record is missing field `x` (expected `{ x: i64, .. }`, found `{ z: integer }`)”
Also demonstratesNarrowing D1
1// metel-core#1455: a projection moves its non-`Copy` fields, so there is nothing to lend; `&h.{ name }`
2// is rejected until a borrowing form exists (RFC-0109 / RFC-0122).
3struct Handle { fd: i64, name: String }
4
5extend Handle {
6 fun nm(h: &Self.{ name }) -> i64 { 1 }
7}
8
9fun main() {
10 var h := Handle { fd = 7, name = "a".to_string() };
11 let _n := Handle::nm(&h.{ name });
12}
typecheck errorT0001
Also demonstratesNarrowing L4
1struct S { a: String }
2fun anonymous(r: {}) {}
3fun main() {
4 let s := S { a = "a" };
5 let x := s.a;
6 anonymous(s); // ERROR[T0001]
7}
typecheck errorT0001at 6
Also demonstratesNarrowing L4
1struct S { a: String }
2fun anonymous(r: {}) {}
3fun main() {
4 let s := S { a = "a" };
5 let x := s.a;
6 anonymous(s); // ERROR[T0001]
7}
typecheck errorT0001at 6
Also demonstratesNarrowing L3
1struct A { value: String }
2struct B { value: String }
3fun take(x: B.{}) {}
4fun main() {
5 let a := A { value = "owned" };
6 let value := a.value;
7 take(a); // ERROR[T0001]
8}
typecheck errorT0001at 7
Also demonstratesNarrowing L3
1struct A { value: String }
2struct B { value: String }
3fun take(x: B.{}) {}
4fun main() {
5 let a := A { value = "owned" };
6 let value := a.value;
7 take(a); // ERROR[T0001]
8}
typecheck errorT0001at 7
Also demonstratesNarrowing L4, Widening D1
1struct Pair { left: String, right: String }
2fun rebuild(empty: Pair.{}) -> Pair {
3 var restored := empty;
4 restored.left := "new";
5 restored // ERROR[T0001]
6}
7fun main() {}
typecheck errorT0001
Also demonstratesWidening L1
1struct Pair { left: String, right: String }
2fun invalid(empty: Pair.{}) {
3 var restored := empty;
4 restored.left := 3; // ERROR[T0001]
5}
6fun main() {}
typecheck errorT0001
Also demonstratesNarrowing L4
1fun take(x: ()) {}
2fun main() {
3 let r := { value = "owned" };
4 let value := r.value;
5 take(r); // ERROR[T0001]
6}
typecheck errorT0001at 5
Also demonstratesNarrowing L4
1fun take(x: ()) {}
2fun main() {
3 let r := { value = "owned" };
4 let value := r.value;
5 take(r); // ERROR[T0001]
6}
typecheck errorT0001at 5
Also demonstratesNarrowing L4
1struct S { a: String }
2fun full(s: S) {}
3fun main() {
4 let s := S { a = "a" };
5 let x := s.a;
6 full(s); // ERROR[T0001]
7}
typecheck errorT0001at 6
Also demonstratesNarrowing L4
1struct S { a: String }
2fun full(s: S) {}
3fun main() {
4 let s := S { a = "a" };
5 let x := s.a;
6 full(s); // ERROR[T0001]
7}
typecheck errorT0001at 6
1// metel-core#1241: a blanket impl over a record-kinded type parameter would be accepted and
2// then never apply (the receiver is a bare type variable no method lookup reaches), so the
3// declaration is rejected; the supported spelling is a row target.
4aspect A { fun f(self) -> i64; }
5
6extend<record T: { x: f64, .. }> T: A {
7 fun f(self) -> i64 { 1 }
8}
9
10fun main() {
11}
typecheck errorT0001“record-kinded type parameter `T`”
Also demonstratesOpen rows L1
1// metel-core#1473 (RFC-0180): a closed literal row names exactly those fields; a value with an
2// extra field does not match it.
3record Req<row R> { data: { ..R } }
4
5fun main() {
6 let r: Req<..{ host: String }> := Req { data = { host = "x", port = 1 } }; // ERROR[T0001]
7}
typecheck errorT0001at 6“cannot unify”
Also demonstratesOpen rows L1
1// metel-core#1473 (RFC-0180): a parameter typed with a literal row requires its fields at the call.
2record Req<row R> { data: { ..R } }
3
4fun host_of<row R>(r: Req<..{ host: String, ..R }>) -> String { r.data.host }
5
6fun main() {
7 let r := Req { data = { port = 1 } };
8 let _ := host_of(r); // ERROR[T0001]
9}
typecheck errorT0001at 8“cannot unify”
1// The annotation fixes the phantom parameter; it is not a license to change it (#1432).
2struct Witness<T> { count: i64 }
3
4fun main() {
5 let text: Witness<String> := Witness { count = 1 };
6 let number: Witness<i64> := text;
7}
typecheck errorT0001“cannot unify”
1// A hinted field is still checked: a String literal for a u64 field is a user type
2// error, not an internal one (#1438).
3struct Counter<T> { n: u64, v: T }
4
5fun main() {
6 let c := Counter { n = "one", v = 2 };
7}
typecheck errorT0001“cannot unify”
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 demonstratesReading a value out of a refere… L1
1// TYPECHECK_ERROR[T0001]
2// RFC-0185 (formerly #649): a reference is never implicitly read out as its referent,
3// whether or not the referent is `Copy` -- a `let` annotation does not dereference.
4struct NotCopy { v: String }
5
6fun main() {
7 let owned := NotCopy { v = "x" };
8 let r: &NotCopy := &owned;
9 let copy: NotCopy := r;
10}
typecheck errorT0001“read the value out with `*r`”
Also demonstratesReading a value out of a refere… L1
1// TYPECHECK_ERROR[T0001]
2// RFC-0185: even a `Copy` referent is not read out implicitly. The diagnostic names
3// the fix: `*r`.
4fun main() {
5 let x := 42;
6 let r: &i64 := &x;
7 let y: i64 := r;
8}
typecheck errorT0001“expected i64, found &i64”
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“the record is missing field `left` (expected `{ left: String, right: i64 }`, found `{ right: integer }`)”
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“the record is missing field `gone` (expected `{ gone: String, keep: i64 }`, found `{ keep: integer }`)”
1// metel-core#1617 (v0.14.1 integration session): a record literal checked against a named record reports a record diff, not an inference variable.
2
3record P { x: i64, y: i64 }
4
5fun main() {
6 let p: P := { x = 1 };
7}
typecheck errorT0001“missing field `y`”
Also demonstratesScoping and shadowing L1, Scoping and shadowing L7
1// metel-core#1578: once a local `f` shadows the function `f`, `f()` calls the local, which is an
2// integer: the shadow hides the function for calls as well as for plain uses.
3fun f() -> i64 {
4 4
5}
6
7fun main() {
8 let f := 9;
9 let x := f();
10}
typecheck errorT0001“cannot unify”
1// metel-core#1556: the parameter is real: a Box built from an i64 is not a Box<String>.
2fun main() {
3 struct Box<T> { x: T }
4 let b := Box { x = 1 };
5 let c: Box<String> := b;
6}
typecheck errorT0001“cannot unify”
1// Until destructors are invoked, a `std::core::Drop` impl may only declare an
2// empty `drop` body: cleanup code here would silently never run.
3struct Guard {
4 v: i64,
5}
6
7extend Guard: Drop {
8 fun drop(&var self) {
9 let _unused := self.v;
10 }
11}
12
13fun main() {}
typecheck errorT0001
1// RFC-0121 (Open Rows, metel-core#1301) sec2: `Handle.{ fd, ..R }` requires
2// the argument's own brand to match exactly -- `Other` is a different struct,
3// even though it happens to declare a field with the same name.
4
5struct Handle { fd: i64 }
6struct Other { fd: i64 }
7
8fun get_fd<row R>(h: Handle.{ fd, ..R }) -> i64 { h.fd }
9
10fun main() {
11 let o := Other { fd = 1 };
12 let _ := get_fd(o);
13}
typecheck errorT0001“expected `Handle` (or a residual of it), found `Other`”
Also demonstratesThe operator L1
1// TYPECHECK_ERROR[T0001]
2// RFC-0182: no mixed use -- an `Option` operand in a `Result`-returning function names the
3// bridge, `.ok_or(...)`.
4fun load(o: Option<i64>) -> Result<i64, String> {
5 let x := o?;
6 Ok { value = x }
7}
8
9fun main() {}
typecheck errorT0001“needs the enclosing function to return `Option<_>`”
Also demonstratesType aliases L1
1type Pair<T> := (T, T);
2fun f(p: Pair<i64>) -> i64 { p.0 }
3fun main() { f((1, "a")); } // ERROR[T0001]
typecheck errorT0001at 3“cannot unify”
Also demonstratesFixed size arrays L1
1fun main() {
2 let xs := [1, 2, 3];
3 let y := xs["a"]; // ERROR[T0001]
4}
typecheck errorT0001at 3“array index must be u64”
Also demonstratesFixed size arrays L1
1fun main() {
2 let xs := [1, "a"]; // ERROR[T0001]
3}
typecheck errorT0001at 2“cannot unify”
Also demonstratesFixed size arrays L1
1fun main() {
2 let n := 5;
3 for (x in n) { } // ERROR[T0001]
4}
typecheck errorT0001at 3“cannot unify”
Also demonstratesRecord rest patterns L1, Narrowing L4
1record Token { value: String }
2fun take(t: Token.{}) -> i64 { 1 }
3fun main() {
4 let t := Token { value = "x".to_string() };
5 let { value: _, ..rest } := t;
6 take(rest); // ERROR[T0001]
7}
typecheck errorT0001at 6“a record rest pattern (`..rest`) drops the nominal brand”
Also demonstratesRecord rest patterns L1, Narrowing L4
1record Tok { value: String, id: i64, n: i64 }
2fun take(t: Tok.{ id, n }) -> i64 { t.id + t.n }
3fun main() {
4 let t := Tok { value = "x".to_string(), id = 1, n = 2 };
5 let { value: _, ..rest } := t;
6 assert(take(rest) == 3); // ERROR[T0001]
7}
typecheck errorT0001at 6“an anonymous record is not `Tok.{ id, n }`”
Also demonstratesRigid type parameters L1, Type aliases L1
1type Envelope<T> := (T, i64);
2fun collapse<T>(value: Envelope<T>) -> Envelope<i64> { value } // ERROR[T0001]
3fun main() {}
typecheck errorT0001“a declared type parameter is opaque inside its own definition”
Also demonstratesRigid type parameters L1, Record pattern bindings L1
1fun collapse<T>(value: { payload: T }) -> i64 {
2 let { payload, ..rest } := value;
3 payload // ERROR[T0001]
4}
5fun main() {}
typecheck errorT0001“a declared type parameter is opaque inside its own definition”
Also demonstratesFixed size arrays L1
1record Samples { values: [i64; 3] }
2fun main() {
3 let s := Samples { values = [1, 2] }; // ERROR[T0001]
4}
typecheck errorT0001at 3
Also demonstratesWidening D1
1struct Pair { left: String, right: String }
2fun main() {
3 var pair := Pair { left = "old", right = "gone" };
4 let left := pair.left;
5 let right := pair.right;
6 let callback := [pair] once var || -> Pair { // ERROR[T0001]
7 pair.left := "new";
8 pair
9 };
10}
typecheck errorT0001at 6
Also demonstratesThe operator L1
1// TYPECHECK_ERROR[T0001]
2// RFC-0182: a `Result` operand in an `Option`-returning function would discard the error;
3// the diagnostic names `.ok()`.
4fun parse(r: Result<i64, String>) -> Option<i64> {
5 let x := r?;
6 Some { value = x }
7}
8
9fun main() {}
typecheck errorT0001“help: discard the error visibly first: `.ok()`”
Also demonstratesRigid type parameters L1
1// RFC-0173 D1: an associated-type binding is a fact while the body is checked.
2aspect Container { type Item; fun get(self) -> Item; }
3
4struct IntBox { value: i64 }
5extend IntBox: Container { type Item := i64; fun get(self) -> i64 { self.value } }
6
7fun wrong<T: Container<Item = i64>>(value: T) -> String {
8 value.get() // ERROR[T0001]
9}
10
11fun main() { wrong(IntBox { value = 1 }); }
typecheck errorT0001at 8
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
Also demonstratesRecord rest patterns L1, Open rows L4
1fun take<row R>(a: { x: i64, ..R }, b: { y: i64, ..R }) -> { ..R } {
2 let { x: _, ..rest } := a;
3 rest
4}
5fun main() {
6 println(take({ x = 1, z = true }, { y = 2, z = 8 }).z); // ERROR[T0001]
7}
typecheck errorT0001at 6
1// RFC-0121 §2 (metel-core#1310): the derived `Rest` is `{ extra: i64 }`, so an
2// annotation naming any other remainder is a type mismatch.
3
4record Session<row R> { id: i64, data: { ..R } }
5
6fun authenticate<row R, row Rest>(s: Session<..R>) -> Session<..Rest>
7where R = { token: String, ..Rest } {
8 panic("unimplemented")
9}
10
11fun main() {
12 let s: Session<{ token: String, extra: i64 }> :=
13 Session { id = 1, data = { token = "t", extra = 2 } };
14 let bad: Session<{ other: i64 }> := authenticate(s);
15}
typecheck errorT0001“row decomposition mismatch”
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”
1// Negative (RFC-0173 D3): a bare type parameter is not callable. The rank-2 shape
2// this fixture used to probe (a generic function passed to a parameter that is itself
3// still generic in the callee, RFC-0138 §5) can no longer be written, because
4// `apply_twice`'s own `f: F` cannot be called: the spelling is `f: |T| -> T`.
5
6fun identity<T>(x: T) -> T { return x; }
7fun apply_twice<F, T>(f: F, x: T) -> T { return f(x); } // ERROR[T0001]
8
9fun main() {
10 apply_twice(identity, 5);
11}
typecheck errorT0001at 7
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 (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// A record target is a row condition on its receiver (RFC-0121 sec3), and the
6// field types it writes must be spelled out: `T` here is the impl's own type
7// parameter, which no receiver's field type can equal, so the impl would typecheck
8// and then never apply (metel-core#239). Rejected rather than accepted-and-inert.
9extend<T> { w: T }: Area {
10 fun area(&self) -> i64 { return 1; }
11}
12
13fun main() {
14}
typecheck errorT0001“name the impl's own type parameters”
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“field `y` is not part of `{ x: i64 }`”
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“the record is missing field `y` (expected `{ x: i64, y: i64 }`, found `{ x: integer }`)”
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 demonstratesRecords L1
1import left::Packet as Left;
2import right::Packet as Right;
3fun consume(value: Left<{ count: i64 }>) -> i64 { value.data.count }
4fun main() {
5 consume(Right { data = { count = 4 } }); // ERROR[T0001]
6}
typecheck errorT0001
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
1// metel-core#1458: a `let` annotation naming the narrowed type does not narrow its initializer.
2struct Strings { a: String, b: String, c: String }
3
4fun main() {
5 let s := Strings { a = "a".to_string(), b = "b".to_string(), c = "c".to_string() };
6 let t: Strings.{ a, c } := s;
7}
typecheck errorT0001“is a whole value, but this position requires the narrowed”
1// metel-core#1458: nothing was moved here. A whole `Strings` is passed where the narrowed
2// `Strings.{ a, c }` is required, and the message says so, not that a partially-moved value
3// is used where the whole is required (the opposite direction, covered by neg_48).
4struct Strings { a: String, b: String, c: String }
5
6fun take(t: Strings.{ a, c }) -> i64 { 1 }
7
8fun main() {
9 let s := Strings { a = "a".to_string(), b = "b".to_string(), c = "c".to_string() };
10 take(s);
11}
typecheck errorT0001“is a whole value, but this position requires the narrowed”
1// metel-core#1483: a closed record parameter and an argument that lacks one of its fields.
2fun f(p: { x: i64, y: i64 }) -> i64 { 1 }
3
4fun main() {
5 f({ x = 1 });
6}
typecheck errorT0001“the record is missing field `y` (expected `{ x: i64, y: i64 }`, found `{ x: integer }`)”
1// metel-core#1483: an extra field for a closed target.
2fun main() {
3 let a: { x: i64, y: i64 } := { x = 1, y = 2, z = 3 };
4}
typecheck errorT0001“field `z` is not part of `{ x: i64, y: i64 }`”
1// metel-core#1483: the field with the wrong type is named, not the whole record.
2fun f(p: { x: i64 }) -> i64 { 1 }
3
4fun main() {
5 f({ x = "s" });
6}
typecheck errorT0001“field `x`: expected `i64`, found `String`”
Also demonstratesNarrowing D1
1// metel-core#1455 / RFC-0137: there is no implicit truncation at the call boundary, so a whole
2// `&Handle` does not coerce to `&Handle.{ name }`.
3struct Handle { fd: i64, name: String }
4
5extend Handle {
6 fun nm(h: &Self.{ name }) -> i64 { 1 }
7}
8
9fun main() {
10 let h := Handle { fd = 7, name = "a".to_string() };
11 let _n := Handle::nm(&h);
12}
typecheck errorT0001
T0002 — Annotation required
The type checker cannot infer a type without an explicit annotation.
Fix: annotate the binding: let x: i64 = ....
A closure parameter written without a type is T0002 only when nothing determines it: the message
is "closure parameter x has no determined type; add an annotation or give the closure an
expected function type". An expected function type or the body normally fixes it.
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 (2)
1// With nothing to fix a phantom parameter, no type is guessed (#1432).
2struct Witness<T> { count: i64 }
3
4fun main() {
5 let witness := Witness { count = 2 };
6}
typecheck errorT0002“cannot infer type”
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. The same code reports a method call on a type that has no such method, including a function type, which implements no aspect and so has no methods (f.clone(), or "${f}", which calls to_string).
Naming Perhaps, the former name of the optional type, where nothing declares it is also this code. The message says Perhaps is now Option (Types — Option<T>).
Fix: define the variable or function before use, or correct the spelling.
Tested by (62)
Also demonstratesDyn aspect L3
1aspect Mapper { fun identity<T>(&self, value: T) -> T; fun ordinary(&self) -> i64; }
2struct Item {}
3extend Item: Mapper {
4 fun identity<T>(&self, value: T) -> T { value }
5 fun ordinary(&self) -> i64 { 1 }
6}
7fun main() {
8 let value: dyn Mapper := Item {};
9 value.identity(1); // ERROR[T0003]
10}
typecheck errorT0003“generic method `identity` is excluded from `dyn Mapper` dispatch”
Also demonstratesDyn aspect L3
1aspect Mapper { fun identity<T>(&self, value: T) -> T; fun ordinary(&self) -> i64; }
2fun rejected(value: &dyn Mapper) {
3 value.identity(1); // ERROR[T0003]
4}
5fun main() {}
typecheck errorT0003“generic method `identity` is excluded from `dyn Mapper` dispatch”
Also demonstratesDyn aspect L3
1aspect Mapper {
2 fun ordinary(&self) -> i64;
3 fun identity<T>(&self, value: T) -> T { value }
4}
5struct Item {}
6extend Item: Mapper { fun ordinary(&self) -> i64 { 1 } }
7fun main() {
8 let value: (dyn Mapper, i64) := (Item {}, 0);
9 value.0.identity(1); // ERROR[T0003]
10}
typecheck errorT0003“generic method `identity` is excluded from `dyn Mapper` dispatch”
Also demonstratesNarrowing L4
1fun main() {
2 let r := { left = "l", right = "r" };
3 let x := r.left;
4 let y := r.right;
5 println(r.left); // ERROR[T0003]
6}
typecheck errorT0003at 5
Also demonstratesNarrowing L4
1fun main() {
2 let r := { left = "l", right = "r" };
3 let x := r.left;
4 let y := r.right;
5 println(r.left); // ERROR[T0003]
6}
typecheck errorT0003at 5
Also demonstratesWidening D1
1fun main() {
2 var r := { left = "l", right = "r" };
3 let left := r.left;
4 r.left := r.right;
5 assert(r.left == "r");
6 let missing := r.right; // ERROR[T0003]
7}
typecheck errorT0003at 6
Also demonstratesWidening D1
1fun main() {
2 var r := { left = "l", right = "r" };
3 let left := r.left;
4 r.left := r.right;
5 assert(r.left == "r");
6 let missing := r.right; // ERROR[T0003]
7}
typecheck errorT0003at 6
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”
1// A fieldful enum variant has no bare-value form -- Metel has no
2// positional/tuple-variant syntax, only `Variant { field: Type, ... }`, so
3// referencing one without braces (as a value, or as a call) is a type
4// error, not a first-class constructor reference (metel-core#1108).
5
6enum Colour {
7 Custom { r: i64, g: i64, b: i64 },
8}
9
10let f := Colour::Custom;
typecheck errorT0003“fieldful variant and has no bare-value form”
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”
1// metel-core#1561: restoring a shadowed type must not keep the inner one alive: after the inner
2// block `P` is the outer struct, which has no field `y`.
3fun main() {
4 struct P { x: i64 }
5 if (true) {
6 struct P { y: i64 }
7 }
8 let p := P { y = 1 };
9}
typecheck errorT0003“no field `y` on `P`”
1// metel-core#1565: a struct pattern on a local struct is checked against that struct's fields.
2fun main() {
3 struct Point { x: i64 }
4 let p := Point { x = 1 };
5 match (p) {
6 Point { nope } => { },
7 }
8}
typecheck errorT0003“no field `nope` on `Point`”
1// metel-core#1617 (v0.14.1 integration session): a method call on a function value reaches the T0003 wording even through a local binding (#1242 x #1578).
2
3fun f() -> i64 { 1 }
4
5fun main() {
6 let g := f;
7 let h := g.clone();
8}
typecheck errorT0003“no method `clone`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): the removed name used as a bare expression name.
2fun main() {
3 let a := Perhaps;
4}
typecheck errorT0003“`Perhaps` is now `Option`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): importing the removed name from std::core is T0003 and says Perhaps is now Option.
2import std::core::Perhaps;
3
4fun main() { }
typecheck errorT0003“`Perhaps` is now `Option`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): the removed name written as a parameter type.
2fun f(x: Perhaps<i64>) -> i64 { 0 }
3
4fun main() { }
typecheck errorT0003“`Perhaps` is now `Option`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): the removed name as the leading segment of a path.
2fun main() {
3 let a := Perhaps::None;
4}
typecheck errorT0003“`Perhaps` is now `Option`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): the removed name as the enum of a pattern.
2fun main() {
3 let v: Option<i64> := None;
4 match (v) {
5 Perhaps::None => { },
6 Perhaps::Some { value } => { },
7 }
8}
typecheck errorT0003“`Perhaps` is now `Option`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): the removed name as the enum of a variant literal.
2fun main() {
3 let a := Perhaps::Some { value = 1 };
4}
typecheck errorT0003“`Perhaps` is now `Option`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): the removed name qualified through std::core in a type position.
2fun main() {
3 let a: std::core::Perhaps<i64> := None;
4}
typecheck errorT0003“`Perhaps` is now `Option`”
Also demonstratesPerhaps t L2
1// RFC-0184 (metel-core#1625): the removed name qualified through std::core in an expression path.
2fun main() {
3 let a := std::core::Perhaps::None;
4}
typecheck errorT0003“`Perhaps` is now `Option`”
1// RFC-0121 (Open Rows, metel-core#1301) sec2: `..R`'s row variable must name
2// a declared `row` parameter, exactly as sec1's generic open-row-tailed
3// record parameter already requires -- `get_fd` declares no generics at all,
4// so `R` is undefined.
5
6struct Handle { fd: i64 }
7
8fun get_fd(h: Handle.{ fd, ..R }) -> i64 { h.fd }
9
10fun main() {}
typecheck errorT0003“undefined row variable `R`”
1// RFC-0121 sec3: the tail of a record target names one of the impl's own `row`
2// generics.
3
4aspect Area { fun area(&self) -> i64; }
5
6extend { ..R }: Area {
7 fun area(&self) -> i64 { 1 }
8}
9
10fun main() {
11}
typecheck errorT0003“undefined row variable `R`”
Also demonstratesFixed size arrays L1
1fun main() {
2 let xs := [1, 2, 3];
3 xs.push(4); // ERROR[T0003]
4}
typecheck errorT0003at 3“no method `push` on array type”
1// RFC-0121 item 2 (metel-core#1310): the spliced row variable must be a
2// declared generic in scope -- `foo` declares none, so `R` is undefined.
3
4record Builder<row R> { data: { ..R } }
5
6fun foo(b: Builder<..R>) -> i64 { 0 }
7
8fun main() {}
typecheck errorT0003“undefined row variable `R`”
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
1// RFC-0121 sec3: the decomposition's own remainder variable must name a
2// declared `row` parameter, exactly as `R` itself must -- `split` declares
3// only `row R`, so `Rest` is undefined.
4
5fun split<row R>(p: { token: i64, ..R }) -> i64
6where R = { extra: i64, ..Rest }
7{
8 p.token
9}
10
11fun main() {}
typecheck errorT0003“undefined row variable `Rest`”
1// RFC-0121 / #1380: reusable open-row types still validate their row tail.
2fun bad() -> { x: i64, ..Q } {
3 { x = 1, extra = "ok" }
4}
5
6fun main() {}
typecheck errorT0003“undefined row variable `Q`”
1// RFC-0121 (metel-core#1324): a tail-only `{ ..Q }` in a struct field whose row
2// variable `Q` is not declared must report T0003, not panic in the type converter.
3
4record Wrap<row R> { data: { ..Q } }
5
6fun main() {}
typecheck errorT0003“undefined row variable”
1// RFC-0121 §2 (metel-core#1310): the RFC's own typestate example. `authenticate`
2// removes `token` from the row, so the field is gone from the result.
3
4record Session<row R> { id: i64, data: { ..R } }
5
6extend<row R, row Rest> Session<..R> where R = { token: String, ..Rest } {
7 fun authenticate(self) -> Session<..Rest> { panic("unimplemented") }
8}
9
10fun main() {
11 let s: Session<{ token: String, extra: i64 }> :=
12 Session { id = 1, data = { token = "t", extra = 2 } };
13 let r := s.authenticate();
14 println(r.data.token);
15}
typecheck errorT0003“token”
1// RFC-0121 §2 (metel-core#1310): `Rest` is `R` without `token`, so the field the
2// equation removed is gone from the result.
3
4record Session<row R> { id: i64, data: { ..R } }
5
6fun authenticate<row R, row Rest>(s: Session<..R>) -> Session<..Rest>
7where R = { token: String, ..Rest } {
8 panic("unimplemented")
9}
10
11fun main() {
12 let s: Session<{ token: String, extra: i64 }> :=
13 Session { id = 1, data = { token = "t", extra = 2 } };
14 let r := authenticate(s);
15 println(r.data.token);
16}
typecheck errorT0003“token”
1// RFC-0121 (Open Rows, metel-core#1301) sec1: "A bare identifier in any of
2// these type positions is a type variable, never a row" -- but `..R`'s row
3// variable must still name a declared `row` parameter. `get_x` declares no
4// generics at all, so `R` is undefined.
5
6fun get_x(p: { x: f64, ..R }) -> f64 { p.x }
7
8fun main() {}
typecheck errorT0003“undefined row variable `R`”
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`”
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”
Also demonstratesInstantiation L1
1// A non-empty enum variant cannot omit its constructor fields.
2
3enum Choice {
4 Value { value: i64 },
5}
6
7let choice: Choice := Choice::Value;
typecheck errorT0003“fieldful variant and has no bare-value form”
1// RFC-0121 sec3: a record-target impl applies to anonymous records and nominal `record`s,
2// never to a `struct`. A struct reaches neither the impl's methods nor its aspect bound.
3aspect Mark { fun mark(&self) -> i64; }
4extend<row R> { ..R }: Mark { fun mark(&self) -> i64 { 1 } }
5
6struct V { public v: i64 }
7
8fun main() {
9 let v := V { v = 1 };
10 let _ := v.mark();
11}
typecheck errorT0003“no method `mark` on `V`”
Also demonstratesType aliases L3, Imports L1
1import model::EmptyPacket;
2
3fun take(value: EmptyPacket) -> i64 { value.id }
4fun main() {
5 let forbidden := Packet { id = 7, data = {} }; // ERROR[T0003]
6}
typecheck errorT0003“unknown struct `Packet`”
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`”
1// Construction is not required before a field annotation is validated.
2struct Holder {
3 value: Missing,
4}
5
6fun main() { }
typecheck errorT0003“unknown type `Missing`”
1// metel-core#1242: a function type satisfies no aspect, so `.clone()` has nothing to dispatch to.
2// The message names that, not "cannot infer receiver type ... add a type annotation", which sent the
3// user to an annotation the receiver already had.
4fun main() {
5 let f: |i64| -> i64 := |x: i64| -> i64 { x + 1 };
6 let g: |i64| -> i64 := f.clone();
7}
typecheck errorT0003“a function type implements no aspect”
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 (2)
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`”
Also demonstratesRigid type parameters L3
1// Negative (RFC-0173 D5): no aspect grants an arithmetic or ordering operator yet, so
2// such an operator on a bare declared parameter is rejected (GAP-TYPES-005). The
3// workaround is to pass the operation in, or to write the function for the concrete types.
4fun add<T>(a: T, b: T) -> T { a + b } // ERROR[T0005]
5
6fun main() { add(1, 2); }
typecheck errorT0005
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 (4)
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”
1// metel-core#1557: exhaustiveness is checked against a block-local enum's own variants.
2fun main() {
3 enum Mode { A, B }
4 var s := 0;
5 match (Mode::A) {
6 Mode::A => { s := 1; },
7 }
8}
typecheck errorT0008“non-exhaustive match”
Also demonstratesRecord rest patterns L1
1enum E { A, B, C }
2fun main() { let e := E::A; match (e) { E::A => 1, E::B => 2 }; } // ERROR[T0008]
typecheck errorT0008at 2“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 (104)
1// RFC-0123 sec1: the enclosing generic's `all R: Copy` is checked at its call: a row with a
2// `String` field does not instantiate it.
3fun consume<row R>(p: { x: i64, ..R }) -> i64 { p.x }
4
5fun outer<row R>(s: { x: i64, ..R }) -> i64 where all R: Copy { consume(s) + 1 }
6
7fun main() {
8 let _ := outer({ x = 1, k = "s".to_string() });
9}
typecheck errorT0012“field `k`”
1// RFC-0173 D6: `all R: Tag` is an entitlement for the fields of R. Passing an
2// open row to a callee that requires the entitlement must be rejected in this
3// generic definition when it does not declare the same `all` bound.
4
5aspect Tag {}
6
7fun require_tagged<row R>(value: { ..R }) -> i64 where all R: Tag { 1 }
8
9fun forward<row R>(value: { ..R }) -> i64 {
10 require_tagged(value) // ERROR[T0012]
11}
typecheck errorT0012at 10
Also demonstratesField wise row constraints L1
1// RFC-0123 sec1 (metel-core#1302): `where all R: Tag` is checked at the call --
2// `label: String` has no `Tag` impl, so the call is rejected and the error names
3// the field.
4
5aspect Tag { fun tag(self) -> i64; }
6extend i64: Tag { fun tag(self) -> i64 { 1 } }
7
8fun id_of<row R>(p: { id: i64, ..R }) -> i64 where all R: Tag { p.id }
9
10fun main() {
11 let _ := id_of({ id = 1, count = 2, label = "x".to_string() });
12}
typecheck errorT0012“field `label`”
Also demonstratesField wise row constraints L1
1record Packet<row R> { data: { ..R } }
2fun count<row R>(p: &Packet<..R>) -> i64 where all R: Copy { 1 }
3fun main() {
4 let p := Packet { data = { a = "s".to_string() } };
5 let _ := count(&p); // ERROR[T0012]
6}
typecheck errorT0012at 5“field `a` of type `String` does not implement `Copy`”
Also demonstratesField wise row constraints L1
1aspect Mark { fun mark(&self) -> i64; }
2record Packet<row R> { data: { ..R } }
3extend<row R> Packet<..R>: Mark where all R: Copy { fun mark(&self) -> i64 { 1 } }
4fun main() {
5 let p := Packet { data = { a = "s".to_string() } };
6 let _ := p.mark(); // ERROR[T0012]
7}
typecheck errorT0012at 6“field `a` of type `String` does not implement `Copy`”
1// RFC-0123 sec1 (metel-core#1302): `all R` must name the row tail of an open-row
2// parameter (or a decomposition's remainder). A row generic used any other way has
3// no fields to walk yet, and the constraint is rejected rather than silently ignored.
4
5fun lonely<row R>(x: i64) -> i64 where all R: Copy { x }
6
7fun main() {
8 let _ := lonely(1);
9}
typecheck errorT0012“must name the row tail”
1// RFC-0123 sec1 / metel-core#1302: `where all R: ..` on an `extend` must name the row
2// tail of a record target; on any other impl it would be silently ignored, so it is
3// rejected.
4
5aspect Label { fun label(&self) -> String; }
6
7extend<T> [T]: Label where all T: Label {
8 fun label(&self) -> String { "x".to_string() }
9}
10
11fun main() {
12}
typecheck errorT0012“must name the row tail of its record target”
Also demonstratesField wise row constraints L1
1// RFC-0123 sec1 (metel-core#1302): in a decomposition, `all R: Copy` covers the
2// decomposed labels too (here `name: String`), where `all Rest: Copy` would not.
3
4fun pick<row R, row Rest>(p: { ..R }) -> i64
5where R = { id: i64, name: String, ..Rest }, all R: Copy
6{
7 p.id
8}
9
10fun main() {
11 let _ := pick({ id = 1, name = "x".to_string() });
12}
typecheck errorT0012“field `name`”
Also demonstratesField wise row constraints L1
1record Packet<row R> { data: { ..R } }
2fun unrelated<row R>(n: i64) -> i64 where all R: Copy { n } // ERROR[T0012]
3fun main() {}
typecheck errorT0012at 2“must name the row tail”
1// metel-core#1472: flattening nested rows must not hide a duplicate label. Adding `host` a second
2// time is still rejected by the `!{ host }` bound, with or without a wrapper in between.
3record Req<row R> { data: { ..R } }
4
5fun add_host<row R: !{ host }>(r: Req<..R>, h: String) {
6 Req { data = { host = h, ..r.data } }
7}
8
9fun main() {
10 let first := add_host(Req { data = {} }, "a".to_string());
11 let second := add_host(first, "b".to_string());
12}
typecheck errorT0012
Also demonstratesRecord rest patterns L1, Open rows L4
1fun strip_two<row R, row Mid, row Rest>(d: { ..R }) -> { ..Rest }
2where R = { token: String, ..Mid }, Mid = { user: i64, ..Rest } {
3 let { token: _, user: _, ..rest } := d;
4 rest
5}
6fun main() {
7 strip_two({ token = "t", active = true }); // ERROR[T0012]
8}
typecheck errorT0012at 7
Also demonstratesOpen rows L1
1// metel-core#1493: the field-wise bound on a literal row's tail is enforced at the call, so a
2// remainder holding a non-Copy field is rejected.
3record Req<row R> { data: { ..R } }
4
5fun id_of<row R>(r: Req<..{ id: i64, ..R }>) -> i64 where all R: Copy { r.data.id }
6
7fun main() {
8 let r := Req { data = { id = 4, name = "n".to_string() } };
9 let _ := id_of(r); // ERROR[T0012]
10}
typecheck errorT0012at 9“does not implement `Copy`”
Also demonstratesOpen rows L1
1// metel-core#1473 (RFC-0180): a label written in a literal row must be provably absent from the
2// tail, exactly as for a spread; `R` needs the bound `R: !{ host }`.
3record Req<row R> { data: { ..R } }
4
5fun add<row R>(r: Req<..R>, h: String) -> Req<..{ host: String, ..R }> {
6 Req { data = { host = h, ..r.data } } // ERROR[T0012]
7}
8
9fun main() {}
typecheck errorT0012at 6“bound the row parameter with `!{ host }`”
Also demonstratesOpen rows L1
1// metel-core#1473 (RFC-0180): a literal row where an ordinary type parameter is expected is a
2// kind error.
3record Box<T> { value: T }
4
5fun f(b: Box<..{ x: i64 }>) -> i64 { 0 } // ERROR[T0012]
6
7fun main() {}
typecheck errorT0012at 5“ordinary type, not a `row`”
1// RFC-0121 item 6: a method's open-row-tailed parameter is a positive row
2// bound like a free function's -- an argument missing a required field is
3// rejected at the call site (checked through the method's scheme).
4
5struct Handle { fd: i64 }
6
7extend Handle {
8 fun add<row R>(self, t: { x: i64, ..R }) -> i64 { self.fd + t.x }
9}
10
11fun main() {
12 let h := Handle { fd = 1 };
13 let r := { y = 3 };
14 let _ := h.add(r);
15}
typecheck errorT0012“requires label `x`”
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, Records L2
1// Regression (metel-core#857, RFC-0137 slice 1): a genuine (non-full-width) branded
2// residual of a plain `struct` never satisfies a row bound either -- eligibility
3// for structural matching is scoped to the brand alone (RFC-0137 sec3), and a
4// `struct`'s brand is never visible to matching regardless of how narrow its
5// current row is. A `record`'s residual is eligible (RFC-0120 sec3/sec-declarations-
6// records-legality-2) -- that is tested separately, not a contradiction of this one.
7
8struct Handle { fd: i64, name: String, extra: i64 }
9
10fun wants_a_record<record T: { fd: i64, .. }>(t: T) -> i64 { t.fd }
11
12fun main() {
13 let h := Handle { fd = 3, name = "x", extra = 9 };
14 let _ := wants_a_record(h.{ fd });
15}
typecheck errorT0012“struct never satisfies a row bound”
1// metel-core#1556: the same for a local enum's variant constructor.
2aspect Tag { fun t(&self) -> i64; }
3struct No {}
4
5fun main() {
6 enum Wrap<T: Tag> { Has { v: T }, Empty }
7 let w := Wrap::Has { v = No {} };
8}
typecheck errorT0012“does not implement `Tag`”
1// metel-core#1556: a bound on a local type's parameter is checked where the type is constructed.
2aspect Tag { fun t(&self) -> i64; }
3struct No {}
4
5fun main() {
6 struct Box<T: Tag> { x: T }
7 let b := Box { x = No {} };
8}
typecheck errorT0012“does not implement `Tag`”
1// #1350: a record with a non-Clone field does not receive Clone.
2record NotClone { values: List<i64> }
3
4fun main() {
5 let value := NotClone { values = List::new() };
6 let _copy := value.clone();
7}
typecheck errorT0012“Clone”
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 demonstratesRecord pattern bindings L1
1record Pair<row R> { id: i64, data: { ..R } }
2fun incomplete<row R>(p: Pair<..R>) -> i64 {
3 let { id } := p; // ERROR[T0012]
4 id
5}
6fun main() { println(incomplete(Pair { id = 1, data = {} })); }
typecheck errorT0012at 3
1// RFC-0121 (Open Rows, metel-core#1301) sec2: `Handle.{ fd, ..R }` checks the
2// argument's *current* row structurally (reusing RFC-0118/0120's existing
3// positive row-bound checking unchanged) -- a residual that has already
4// narrowed away `fd` is rejected at compile time.
5
6struct Handle { fd: i64, flags: i64 }
7
8fun get_fd<row R>(h: Handle.{ fd, ..R }) -> i64 { h.fd }
9
10fun main() {
11 let h := Handle { fd = 1, flags = 0 };
12 let r := h.{ flags };
13 let _ := get_fd(r);
14}
typecheck errorT0012“requires label `fd`”
1// RFC-0121 (Open Rows, metel-core#1301) sec2: `..R` needs `R` declared
2// `row R`, not a plain type parameter -- exactly the same binder-kind
3// distinction sec1's generic open-row-tailed record parameter already draws.
4
5struct Handle { fd: i64 }
6
7fun get_fd<R>(h: Handle.{ fd, ..R }) -> i64 { h.fd }
8
9fun main() {}
typecheck errorT0012“not a row parameter”
1// metel-core#1338: the record `Display` impl requires every field to be `Display`
2// (`where all R: Display`); a field of a struct with no impl is rejected at the call.
3struct Opaque { v: i64 }
4
5fun main() {
6 let p := { a = 1, o = Opaque { v = 2 } };
7 let _ := p.to_string();
8}
typecheck errorT0012“field `o`”
Also demonstratesField wise row constraints L1
1// RFC-0123 sec1 / metel-core#1302: `String` has no `Mark` impl, so the record
2// `{ a, s }` does not satisfy `all R: Mark` and the impl does not apply to it.
3
4aspect Label { fun label(&self) -> String; }
5aspect Mark { fun mark(&self) -> i64; }
6
7extend i64: Mark { fun mark(&self) -> i64 { 1 } }
8
9extend<row R> { ..R }: Label where all R: Mark {
10 fun label(&self) -> String { "all marked".to_string() }
11}
12
13fun main() {
14 let _ := { a = 1, s = "x".to_string() }.label();
15}
typecheck errorT0012“field `s`”
1// RFC-0121 sec3: report the row fact a record-target method requires.
2
3aspect Area { fun area(&self) -> i64; }
4
5extend<row R: { w: i64, .. }> { ..R }: Area {
6 fun area(&self) -> i64 { self.w }
7}
8
9fun main() {
10 let t := { h = 3 };
11 let _ := t.area(); // ERROR[T0012]
12}
typecheck errorT0012at 11“missing required label `w`”
1// RFC-0121 sec3: a record-target impl satisfies an aspect bound only for the records
2// whose row meets its condition. `{ h }` has no `w`.
3
4aspect Area { fun area(&self) -> i64; }
5
6extend<row R: { w: i64, .. }> { ..R }: Area {
7 fun area(&self) -> i64 { self.w }
8}
9
10fun total<T: Area>(x: T) -> i64 { x.area() }
11
12fun main() {
13 let _ := total({ h = 3 });
14}
typecheck errorT0012“does not implement `Area`”
1// metel-core#1337: `T: Copy` is satisfied by a record only when every field is `Copy`.
2fun dup<T: Copy>(v: T) -> T { v }
3
4fun main() {
5 let a := { x = 1, s = "hi".to_string() };
6 let _ := dup(a);
7}
typecheck errorT0012“does not implement `Copy`”
Also demonstratesField wise row constraints L1, Type aliases L1
1type Pair<T> := (T, T);
2fun duplicate<row R>(value: { ..R }) -> Pair<{ ..R }> where all R: Copy {
3 (value, value)
4}
5fun main() { duplicate({ text = "owned" }); } // ERROR[T0012]
typecheck errorT0012“field `text` of type `String` does not implement `Copy`”
Also demonstratesRecord rest patterns L1
1record P { x: i64, y: i64 }
2fun main() { let p := P { x = 1, y = 2 }; let { x: _, ..r1 } := p; let { y: _, ..r2 } := r1; let { y: _, ..r3 } := r2; } // ERROR[T0012]
typecheck errorT0012at 2“not known to be present”
Also demonstratesRecord rest patterns L1
1record P { x: i64 }
2fun main() {
3 let p := P { x = 1 };
4 let { y, ..rest } := p; // ERROR[T0012]
5}
typecheck errorT0012at 4“not known to be present”
Also demonstratesOpen rows L2
1fun need_id<row R>(v: { id: i64, ..R }) -> i64 { v.id }
2fun main() {
3 need_id({ name = "x" }); // ERROR[T0012]
4}
typecheck errorT0012at 3“requires label `id`”
Also demonstratesField wise row constraints L1
1struct Opaque { v: i64 }
2record Wrap { o: Opaque }
3fun main() {
4 let w := Wrap { o = Opaque { v = 1 } };
5 println("${w}"); // ERROR[T0012]
6}
typecheck errorT0012at 5“field `o` of type `Opaque` does not implement `Display`”
Also demonstratesRigid type parameters L2
1// RFC-0173 D6(2): a caller cannot pass an unconstrained declared parameter
2// to a callee whose corresponding parameter requires Tag.
3
4aspect Tag { fun tag(self) -> i64; }
5
6fun needs_tag<T: Tag>(value: T) -> i64 { value.tag() }
7
8fun forward<U>(value: U) -> i64 {
9 needs_tag(value) // ERROR[T0012]
10}
typecheck errorT0012at 9
Also demonstratesRigid type parameters L2
1fun absent<row R: !{token}>(values: [{..R}]) -> i64 { values.len() }
2
3fun caller<row T>() -> i64 {
4 absent::<T>([]) // ERROR[T0012]
5}
6
7fun main() {}
typecheck errorT0012at 4
1// RFC-0121 item 2 (metel-core#1310): `where R = { auth: String, .. }` is a
2// real bound on the row-kinded parameter, checked at the call site.
3
4record Builder<row R> { data: { ..R } }
5
6fun auth_of<row R>(b: Builder<..R>) -> String where R = { auth: String, .. } {
7 b.data.auth
8}
9
10fun main() {
11 let b := Builder { data = { retries = 3 } };
12 auth_of(b);
13}
typecheck errorT0012
1// RFC-0121 item 2 (metel-core#1310): a `row`-kinded parameter takes a record
2// type (or `..R`), not an ordinary type like `i64`.
3
4record Builder<row R> { data: { ..R } }
5
6fun foo(b: Builder<i64>) -> i64 { 0 }
7
8fun main() {}
typecheck errorT0012“row”
1// RFC-0121 item 2 (metel-core#1310): a `..R` argument is only valid for a
2// `row`-kinded type parameter; `Wrapper`'s `T` is an ordinary type parameter.
3
4struct Wrapper<T> { v: T }
5
6fun foo<row R>(w: Wrapper<..R>) -> i64 { 0 }
7
8fun main() {}
typecheck errorT0012“row”
1// RFC-0121 item 5 (metel-core#833): a row-conditional aspect impl satisfies a
2// generic bound only for a row that meets its condition. The error is at the call
3// that needs the bound, not inside the generic body.
4
5aspect Describe { fun describe(self) -> i64; }
6
7record Session<row R> { id: i64, data: { ..R } }
8
9extend<row R: { token: String, .. }> Session<..R>: Describe {
10 fun describe(self) -> i64 { self.id }
11}
12
13fun show<T: Describe>(t: T) -> i64 { t.describe() }
14
15fun main() {
16 let anon: Session<{ extra: i64 }> := Session { id = 1, data = { extra = 1 } };
17 println(show(anon));
18}
typecheck errorT0012“does not implement”
1// Issue #1386: when disjoint row candidates all fail, retain every alternative.
2
3aspect Label { fun label(self) -> String; }
4
5extend<row R: { tag: i64, .. }> { ..R }: Label {
6 fun label(self) -> String { "integer" }
7}
8
9extend<row R: { tag: String, .. }> { ..R }: Label {
10 fun label(self) -> String { "text" }
11}
12
13fun main() {
14 { tag = true }.label() // ERROR[T0012]
15}
typecheck errorT0012at 14“candidate 2 requires { tag: String, .. }”
1// RFC-0121 item 5 (metel-core#833): the impl's negative row bound `!{ token }`
2// forbids `token`, so a receiver whose row has it has no such method.
3
4record Session<row R> { id: i64, data: { ..R } }
5
6extend<row R: !{ token }> Session<..R> {
7 fun send_data(self) -> i64 { self.id }
8}
9
10fun main() {
11 let s: Session<{ token: String }> := Session { id = 1, data = { token = "t" } };
12 println(s.send_data());
13}
typecheck errorT0012“token”
1// RFC-0121 item 5 (metel-core#833): the impl's row bound requires `token`, so a
2// receiver whose row has none cannot call its methods.
3
4record Session<row R> { id: i64, data: { ..R } }
5
6extend<row R: { token: String, .. }> Session<..R> {
7 fun with_token(self) -> i64 { self.id }
8}
9
10fun main() {
11 let s: Session<{ extra: i64 }> := Session { id = 1, data = { extra = 2 } };
12 println(s.with_token());
13}
typecheck errorT0012“token”
1// RFC-0121 sec3 (metel-core#1306 item 3): the decomposition equation's own
2// named fields are real requirements, checked structurally like any other
3// row bound -- a record missing one is rejected at compile time.
4
5fun split<row R, row Rest>(p: { token: i64, ..R }) -> i64
6where R = { extra: i64, ..Rest }
7{
8 p.token
9}
10
11fun main() {
12 let r := { token = 1 };
13 let _ := split(r);
14}
typecheck errorT0012“requires label `extra`”
1// RFC-0121 sec3: `Rest` needs `row Rest` declared, not a plain type
2// parameter -- the same binder-kind distinction `R` itself already draws.
3
4fun split<row R, Rest>(p: { token: i64, ..R }) -> i64
5where R = { extra: i64, ..Rest }
6{
7 p.token
8}
9
10fun main() {}
typecheck errorT0012“not a row parameter”
1// RFC-0121 §2 (metel-core#1306 item 7): the equation requires `extra`, which `Handle`
2// does not have, so no `Handle` can be passed. This used to be accepted: the equation
3// was silently ignored on a residual-projection tail.
4
5struct Handle { public fd: i64, public other: i64 }
6
7fun use_it<row R, row Rest>(h: Handle.{ fd, ..R }) -> i64
8where R = { extra: i64, ..Rest } {
9 h.fd
10}
11
12fun main() {
13 let h := Handle { fd = 1, other = 2 };
14 println(use_it(h));
15}
typecheck errorT0012“requires label”
1// RFC-0121 §2 (metel-core#1306 item 7): `extra` exists but at a different type than
2// the equation names, so the equation's requirement is not met.
3
4struct Handle { public fd: i64, public extra: String }
5
6fun use_it<row R, row Rest>(h: Handle.{ fd, ..R }) -> i64
7where R = { extra: i64, ..Rest } {
8 h.fd
9}
10
11fun main() {
12 let h := Handle { fd = 1, extra = "s" };
13 println(use_it(h));
14}
typecheck errorT0012“to have type”
Also demonstratesRecord pattern bindings L1, Record rest patterns L1
1fun bad<row R>(r: { ..R }) -> i64 {
2 let { id: _, ..rest } := r; // ERROR[T0012]
3 0
4}
5
6fun main() -> i64 {
7 bad({ other = 1 })
8}
typecheck errorT0012at 2:9
1// RFC-0121 §2 (metel-core#1310): the impl's equation `R = { token: String, ..Rest }`
2// bounds `R`, so a receiver whose row has no `token` cannot call the method.
3
4record Session<row R> { id: i64, data: { ..R } }
5
6extend<row R, row Rest> Session<..R> where R = { token: String, ..Rest } {
7 fun authenticate(self) -> Session<..Rest> { panic("unimplemented") }
8}
9
10fun main() {
11 let s: Session<{ extra: i64 }> := Session { id = 1, data = { extra = 2 } };
12 let r := s.authenticate();
13}
typecheck errorT0012“token”
1// RFC-0121 (Open Rows, metel-core#1301): the named fields in an open-row-tailed
2// parameter are a positive row bound, checked structurally (RFC-0118/0120)
3// against the argument's current row -- a record missing a required field is
4// rejected at compile time, not a runtime field-access crash.
5
6fun get_x<row R>(p: { x: f64, ..R }) -> f64 { p.x }
7
8fun main() {
9 let r := { y = 4.0 };
10 let _ := get_x(r);
11}
typecheck errorT0012“requires label `x`”
1// RFC-0121 (Open Rows, metel-core#1301): an open-row-tailed parameter desugars
2// to a record-kinded row bound, so a plain `struct` argument is rejected exactly
3// as it already is for an explicit `<record T: { x: f64, .. }>` parameter
4// (RFC-0137/0120) -- a struct's row is never visible to row-bound satisfaction.
5
6struct Point { x: f64, y: f64 }
7
8fun get_x<row R>(p: { x: f64, ..R }) -> f64 { p.x }
9
10fun main() {
11 let pt := Point { x = 3.0, y = 4.0 };
12 let _ := get_x(pt);
13}
typecheck errorT0012“struct never satisfies a row bound”
1// RFC-0121 (Open Rows, metel-core#1301) sec1: `..R` needs `R` declared `row R`,
2// not a plain (or `record`-kinded) type parameter -- the binder's own kind
3// marker, same distinction RFC-0118/0120 already draw between a plain `T` and
4// a `record T`.
5
6fun get_x<R>(p: { x: f64, ..R }) -> f64 { p.x }
7
8fun main() {}
typecheck errorT0012“not a row parameter”
Also demonstratesOpen rows L4, Row bounds L2
1// metel-core#1456: `!{ token: String }` only rules out a `String` `token`; the row may still hold
2// `token` at another type, so it is not an absent-label fact and a spread cannot rely on it
3// (spec.types.generics.open-rows.legality-4).
4fun add_token<row R: !{ token: String }>(d: { ..R }) -> { token: String, ..R } {
5 { token = "secret".to_string(), ..d } // ERROR[T0012]
6}
7
8fun main() {}
typecheck errorT0012at 5“cannot prove field `token` absent from the spread row”
Also demonstratesOpen rows L4, Row bounds L2
1// The same rule for a negative bound written in a `where` clause.
2fun add_token<row R>(d: { ..R }) -> { token: String, ..R } where R: !{ token: String } {
3 { token = "secret".to_string(), ..d } // ERROR[T0012]
4}
5
6fun main() {}
typecheck errorT0012at 3“cannot prove field `token` absent from the spread row”
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 satisfies an impl-based aspect only through an
2// `extend` on a record target (`std::core`'s `Display` for a record needs every field to be
3// `Display`). A record with a field that has no `Display` is rejected at the call site, like a
4// tuple or a struct without the impl -- not accepted and then blown up at run time.
5struct Opaque { v: i64 }
6
7fun show<T: Display>(x: T) -> String { x.to_string() }
8
9fun main() {
10 let r := { x = Opaque { v = 1 } };
11 let s := show(r);
12}
typecheck errorT0012“does not implement `Display`”
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`”
1// RFC-0121 sec3: a `struct` does not satisfy an aspect bound through a record-target impl.
2aspect Mark { fun mark(&self) -> i64; }
3extend<row R> { ..R }: Mark { fun mark(&self) -> i64 { 1 } }
4
5struct V { public v: i64 }
6
7fun show<T: Mark>(x: &T) -> i64 { x.mark() }
8
9fun main() {
10 let v := V { v = 1 };
11 let _ := show(&v);
12}
typecheck errorT0012“`V` does not implement `Mark`”
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 (7)
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
Also demonstratesAspect implementation coherence L5
1// Regression: naming `Describe` *through* `importer` (which only imports it) used to
2// intern a placeholder `(importer, Describe)` symbol; coherence then resolved the
3// aspect inside `importer` to that placeholder, judged it local, skipped T0014, and
4// the program failed later with a misleading T0013 ("`Describe` and `Describe`").
5// An explicit import must win, so the orphan impl is reported as T0014.
6
7import describe_aspect::Describe;
8import importer::Describe as ImportedAgain;
9
10fun main() {}
typecheck errorT0014at 4
Also demonstratesAspect implementation coherence L5
1// RFC-0060 §1 / RFC-0121 §3: two modules cannot offer conflicting implementations of an
2// aspect for the same record shape. The foreign module's row impl is an orphan
3// (T0014) -- reported there, before any overlap (T0015) or method-ambiguity (T0013)
4// question can arise between it and `describe_aspect`'s own impl.
5
6import describe_aspect::Describe;
7import other_row_impl::touch;
8
9fun main() {
10 touch();
11 let r := { w = 1, h = 2 };
12 println(r.describe());
13}
typecheck errorT0014at 5
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 (7)
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
1// RFC-0121 sec3: two impls of one aspect on the same closed row overlap.
2
3aspect Describe { fun describe(&self) -> String; }
4
5extend { w: i64 }: Describe {
6 fun describe(&self) -> String { "a".to_string() }
7}
8
9extend { w: i64 }: Describe {
10 fun describe(&self) -> String { "b".to_string() }
11}
12
13fun main() {
14}
typecheck errorT0015“conflicting implementation”
1// RFC-0121 sec3 / `spec.types.generics.row-conditional-impls.legality-3`: two row
2// conditions that no label contradicts can both hold of one record (here `{ w, h }`),
3// so the impls overlap and are rejected exactly as two overlapping ordinary impls are.
4
5aspect Describe { fun describe(&self) -> String; }
6
7extend<row R: { w: i64, .. }> { ..R }: Describe {
8 fun describe(&self) -> String { "a".to_string() }
9}
10
11extend<row R: { h: i64, .. }> { ..R }: Describe {
12 fun describe(&self) -> String { "b".to_string() }
13}
14
15fun main() {
16}
typecheck errorT0015“conflicting implementation”
1// RFC-0121 item 5 (metel-core#833, legality-3): `{ a, .. }` and `{ b, .. }` mention
2// no shared label, so one row can satisfy both -- they overlap, exactly as two
3// ordinary overlapping impls do.
4
5aspect Describe { fun describe(self) -> i64; }
6
7record Session<row R> { id: i64, data: { ..R } }
8
9extend<row R: { a: i64, .. }> Session<..R>: Describe {
10 fun describe(self) -> i64 { 1 }
11}
12
13extend<row R: { b: i64, .. }> Session<..R>: Describe {
14 fun describe(self) -> i64 { 2 }
15}
16
17fun main() {}
typecheck errorT0015“conflicting implementation”
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, with one exception: using a field that was already moved out of a nominal struct or record (reading it, or projecting it again) is reported without the flag, because the narrowing that makes the field unavailable is a type-level fact. Move checking is off by default in this release.
An ownership rule from RFC-0071 §1/§7 was violated. Eight distinct situations share this code, each with its own message:
- a value used after it was moved;
- a field used after it was moved out of a nominal struct or record, including a second projection of it;
- 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 borrowed[T]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 (92)
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”
1// metel-core#1226: move-check's symbolic construction of a generic body
2// used to fail outright on a row-bound field access (the placeholder it
3// sampled for `T` had no fields at all), silently skipping the whole body
4// -- including the use-after-move below -- instead of analyzing it.
5fun f<record T: { y: i64, .. }>(value: T) -> (T, T, i64) {
6 let n := value.y;
7 let a := value;
8 let b := value;
9 (a, b, n)
10}
11
12fun main() {
13 let r := { y = 1 };
14 let p := f(r);
15}
typecheck errorT0019“use of moved value `value`”
Also demonstratesValues move by default L1
1aspect Source { type Item; fun take(self) -> Item; }
2fun invalid<S: Source<Item = { token: String }>>(source: S) -> {} {
3 let { token: _, ..rest } := source.take();
4 let again := source.take(); // ERROR[T0019]
5 rest
6}
7fun main() {}
typecheck errorT0019“use of moved value `source`”
Also demonstratesValues move by default L1
1fun rejected<row R>(value: &{ token: String, ..R }) -> String {
2 value.token // ERROR[T0019]
3}
4fun main() {}
typecheck errorT0019“cannot move `value.token` out of a reference”
Also demonstratesNarrowing L4, Closures L7
1struct Pair { left: String, right: String }
2fun invalid(empty: Pair.{}) -> String {
3 let callback := [empty] once || -> String { empty.left }; // ERROR[T0019]
4 callback()
5}
6fun main() {}
typecheck errorT0019
Also demonstratesNarrowing L4
1record S { a: String, b: String }
2fun main() {
3 let s := S { a = "a", b = "b" };
4 let x := s.a;
5 let y := s.b;
6 println(s.a); // ERROR[T0019]
7}
typecheck errorT0019at 6
Also demonstratesNarrowing L4
1record S { a: String, b: String }
2fun main() {
3 let s := S { a = "a", b = "b" };
4 let x := s.a;
5 let y := s.b;
6 println(s.a); // ERROR[T0019]
7}
typecheck errorT0019at 6
Also demonstratesWidening D1
1struct Pair { left: String, right: String }
2fun main() {
3 var p := Pair { left = "l", right = "r" };
4 let l := p.left;
5 let r := p.right;
6 p.left := "back";
7 let missing := p.right; // ERROR[T0019]
8}
typecheck errorT0019at 7
Also demonstratesWidening D1
1struct Pair { left: String, right: String }
2fun main() {
3 var p := Pair { left = "l", right = "r" };
4 let l := p.left;
5 let r := p.right;
6 p.left := "back";
7 let missing := p.right; // ERROR[T0019]
8}
typecheck errorT0019at 7
Also demonstratesNarrowing L4
1struct S { a: String, b: String }
2fun main() {
3 let s := S { a = "a", b = "b" };
4 let x := s.a;
5 let y := s.b;
6 println(s.a); // ERROR[T0019]
7}
typecheck errorT0019at 6
Also demonstratesNarrowing L4
1struct S { a: String, b: String }
2fun main() {
3 let s := S { a = "a", b = "b" };
4 let x := s.a;
5 let y := s.b;
6 println(s.a); // ERROR[T0019]
7}
typecheck errorT0019at 6
Also demonstratesNarrowing L4
1struct Pair { left: String, right: String }
2fun take(p: Pair.{}) {}
3fun main() {
4 let p := Pair { left = "l", right = "r" };
5 let l := p.left;
6 let r := p.right;
7 take(p);
8 take(p); // ERROR[T0019]
9}
typecheck errorT0019at 8
Also demonstratesNarrowing D1
1// Projecting every field moves the non-`Copy` ones too; only `Copy` fields stay readable.
2struct Handle { fd: i64, name: String }
3
4fun main() {
5 let h := Handle { fd = 3, name = "log".to_string() };
6 let all := h.{ fd, name };
7 println(h.name); // ERROR[T0019]
8}
typecheck errorT0019at 7“use of moved value”
Also demonstratesValues move by default L1, Rigid type parameters L1
1fun add<row R: !{token}>(r: { ..R }) -> { token: String, ..R } {
2 { ..r, token = "secret" }
3}
4
5fun forward<row R: !{token}>(r: { ..R }) -> i64 {
6 let callback := [r] once || {
7 let added := add(r);
8 let again := r; // ERROR[T0019]
9 7
10 };
11 callback()
12}
13
14fun main() {}
typecheck errorT0019“use of moved value `r`”
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”
1aspect Marker {}
2struct Witness<T> { count: i64 }
3extend<T> Witness<T>: Copy;
4extend<T> Witness<T>: !Copy where T: Marker;
5
6fun denied<T: Marker>(value: Witness<T>) {
7 let first := value;
8 let again := value; // ERROR[T0019]
9}
10
11fun main() {}
typecheck errorT0019“use of moved value `value`”
Also demonstratesPartial moves L2
1aspect Marker {}
2struct Guard<T> { name: String, tag: T }
3extend<T> Guard<T>: Drop where T: Marker {
4 fun drop(&var self) {}
5}
6
7fun denied<T: Marker>(value: Guard<T>) -> String {
8 value.name // ERROR[T0019]
9}
10
11fun main() {}
typecheck errorT0019“belongs to a `Drop` type”
1// The body is analysed, not skipped: a double move beside `assert` calls is still
2// reported (#1433).
3fun twice<T>(value: T) {
4 assert(true);
5 assert(true, "analysed");
6 let first := value;
7 let second := value;
8}
9
10fun main() {
11 twice(1);
12}
typecheck errorT0019“use of moved value `value`”
1// An inferred-signature parameter used as an operator operand is still analysed
2// at definition time: the operator only observes it, so the later moves are
3// what the checker must see (#273).
4fun twice_move(v) {
5 let sum := v + v;
6 let a := v;
7 let b := v;
8 sum
9}
10
11fun main() {
12 assert(twice_move(1) == 2);
13}
typecheck errorT0019“use of moved value `v`”
1// A let-polymorphic closure is checked from its retained definition-time body, so a
2// double move of its unannotated parameter is rejected even though no call fixes a type
3// that would make it `Copy` (#273).
4let dup := |v| {
5 let first := v;
6 let second := v;
7 first
8};
9
10fun main() {
11 let kept := dup(1);
12}
typecheck errorT0019“use of moved value `v`”
1struct Box<T> { value: T }
2extend<T> Box<T> {
3 fun twice(self) {
4 let first := self;
5 let again := self; // ERROR[T0019]
6 }
7}
8
9fun main() {}
typecheck errorT0019“use of moved value `self`”
1// metel-core#1556: a record is Copy only if its fields are, so Box<String> moves.
2fun main() {
3 record Box<T> { x: T }
4 let a := Box { x = "s" };
5 let b := a;
6 let c := a;
7}
typecheck errorT0019“use of moved value `a`”
1struct Pair { left: String, right: String }
2fun bad() {
3 var pair := Pair { left = "moved", right = "kept" };
4 let left := pair.left;
5 let read := [pair] once || -> String { pair.left }; // ERROR[T0019]
6}
typecheck errorT0019at 5
1// metel-core#1553: two block-local records with the same name and the same field names are
2// different types. `a`'s record is `Copy` (all fields are), `b`'s is not (`String`), so `b`'s second
3// `let v := p` is a use-after-move. Construction used to stamp `b`'s record with `a`'s identity, so
4// the checker judged it by `a`'s field types and accepted the move.
5fun a() -> i64 {
6 record P { x: i64 }
7 let p := P { x = 1 };
8 let u := p;
9 let v := p;
10 u.x + v.x
11}
12
13fun b() -> i64 {
14 record P { x: String }
15 let p := P { x = "s" };
16 let u := p;
17 let v := p;
18 0
19}
20
21fun main() {
22 assert(a() == 2);
23 assert(b() == 0);
24}
typecheck errorT0019“use of moved value `p`”
1// metel-core#1555: two block-local types with the same name and no fields cannot be told apart by
2// their fields, and a zero-field `record` is `Copy` while a zero-field `struct` is not. `a`'s
3// record is `Copy`; `b`'s struct is not, so `b`'s second `let v := e` is a use-after-move. The type
4// is identified by its own declaration, not by name and fields, so `b` is judged as a struct.
5fun a() -> i64 {
6 record E {}
7 let e := E {};
8 let u := e;
9 let v := e;
10 0
11}
12
13fun b() -> i64 {
14 struct E {}
15 let e := E {};
16 let u := e;
17 let v := e;
18 0
19}
20
21fun main() {
22 assert(a() == 0);
23 assert(b() == 0);
24}
typecheck errorT0019“use of moved value `e`”
1// metel-core#1555 (the shape #1553 fixed): an inner block-local record shadows an outer one with the
2// same name and field names. The outer one is `Copy`; the inner one has a `String` field and is not,
3// so moving `inner` twice is a use-after-move.
4fun main() {
5 record P { x: i64 }
6 let outer := P { x = 1 };
7 let o1 := outer;
8 let o2 := outer;
9 if (true) {
10 record P { x: String }
11 let inner := P { x = "s" };
12 let i1 := inner;
13 let i2 := inner;
14 }
15}
typecheck errorT0019“use of moved value `inner`”
Also demonstratesNarrowing D1
1// metel-core#1454: `h.{ name }` moves the non-`Copy` field out of `h` like a plain move does, so
2// reading `h.name` afterwards is the same T0019 as after `let a := h.name;` (it used to be an
3// internal error, I0001, from the construction pass).
4struct Handle { fd: i64, name: String }
5
6fun main() {
7 let h := Handle { fd = 3, name = "log".to_string() };
8 let p := h.{ name };
9 println(h.name); // ERROR[T0019]
10}
typecheck errorT0019at 9“use of moved value”
Also demonstratesNarrowing D1
1// metel-core#1455: the non-`Copy` field was moved by the first projection, so it cannot be projected again.
2struct Handle { fd: i64, name: String }
3
4fun main() {
5 var h := Handle { fd = 7, name = "a".to_string() };
6 let _p := h.{ name };
7 let _q := h.{ name };
8}
typecheck errorT0019
Also demonstratesNarrowing D1
1// metel-core#1455: projecting a non-`Copy` field out of a reference would move out of it.
2struct Handle { fd: i64, name: String }
3
4fun f(h: &Handle) { let _p := h.{ name }; }
5
6fun main() {}
typecheck errorT0019
1// metel-core#1337: a record with a field that is not `Copy` does not get `Copy` from the
2// blanket impl (`where all R: Copy` fails on `s: String`), so `let b := a` moves it and the
3// later use of `a` is a use-after-move.
4fun main() {
5 let a := { x = 1, s = "hi".to_string() };
6 let b := a;
7 println(a.x);
8}
typecheck errorT0019“use of moved value `a`”
Also demonstratesValues move by default L1, Rigid type parameters L1
1fun dup<T>(x: T) -> (T, T) { (x, x) } // ERROR[T0019]
2fun main() {}
typecheck errorT0019at 1“use of moved value”
Also demonstratesValues move by default L1, Fixed size arrays L1
1record Row { name: String }
2fun main() {
3 let rows: [Row] := [Row { name = "a".to_string() }];
4 for (r in rows) { let n := r.name; } // ERROR[T0019]
5}
typecheck errorT0019at 4“borrowed from a `[T]` view”
Also demonstratesOpen rows L3
1// Whole-record open rows x ownership (RFC-0179): the argument moves into the parameter whole, so
2// a later read of one of its non-Copy fields is a use of a moved value.
3fun take<row R>(p: { x: i64, ..R }) -> i64 { p.x }
4
5fun main() {
6 let r := { x = 1, name = "s".to_string() };
7 let _ := take(r);
8 let _ := r.name; // ERROR[T0019]
9}
typecheck errorT0019at 8“moved value”
Also demonstratesValues move by default L1, Rigid type parameters L1, Record pattern bindings L1
1fun duplicate_rest<row R, row Rest>(value: { ..R }) -> ({ ..Rest }, { ..Rest })
2where R = { token: String, ..Rest } {
3 let { token: _, ..rest } := value;
4 let first := rest;
5 (first, rest) // ERROR[T0019]
6}
7fun main() {}
typecheck errorT0019“use of moved value `rest`”
Also demonstratesClosures D5, Narrowing L4
1struct Pair { left: String, right: String }
2fun take(value: Pair.{}) {}
3fun main() {
4 var pair := Pair { left = "old", right = "gone" };
5 let left := pair.left;
6 let right := pair.right;
7 take(pair);
8 let callback := [pair] once var || -> Pair { // ERROR[T0019]
9 pair.left := "new";
10 pair.right := "back";
11 pair
12 };
13}
typecheck errorT0019at 8
Also demonstratesClosures D5
1struct Pair { left: String, right: String }
2fun take(value: Pair.{}) {}
3fun main() {
4 var pair := Pair { left = "old", right = "gone" };
5 let left := pair.left;
6 let right := pair.right;
7 let callback := [pair] once |extra| { extra };
8 take(pair); // ERROR[T0019]
9}
typecheck errorT0019at 8
Also demonstratesNarrowing L4
1struct Pair { left: String, right: String }
2fun main() {
3 let pair := Pair { left = "gone", right = "kept" };
4 let left := pair.left;
5 let callback := [pair] once || -> String { pair.left }; // ERROR[T0019]
6}
typecheck errorT0019at 5
Also demonstratesClosures D5
1struct Pair { left: String, right: String }
2fun take(value: Pair.{}) {}
3fun main() {
4 var pair := Pair { left = "old", right = "gone" };
5 let left := pair.left;
6 let right := pair.right;
7 let callback := [pair] once || -> i64 {
8 let pair := 3;
9 pair
10 };
11 take(pair); // ERROR[T0019]
12}
typecheck errorT0019at 11
Also demonstratesClosures D5, Narrowing L4
1struct Pair { left: String, right: String }
2fun take(value: Pair.{}) {}
3fun main() {
4 var pair := Pair { left = "old", right = "gone" };
5 let left := pair.left;
6 let right := pair.right;
7 let callback := [pair] once var || -> Pair {
8 pair.left := "new";
9 pair.right := "back";
10 pair
11 };
12 take(pair); // ERROR[T0019]
13}
typecheck errorT0019at 12
Also demonstratesRecord rest patterns L1
1fun consume<row R>(value: { ..R }) {}
2fun main() {
3 let source := { token = "gone", owned = "kept" };
4 let { token: _, ..rest } := source;
5 assert(rest.owned == "kept");
6 consume(source); // ERROR[T0019]
7}
typecheck errorT0019at 6
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 (2)
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”
Also demonstratesArrays L1
1fun set_first(xs: [i64]) {
2 xs[0] := 9; // ERROR[T0023]
3}
4fun main() {}
typecheck errorT0023at 2“cannot assign through `[T]`”
T0024 — Read-copy of a non-Copy value out of a reference
Since v0.12.1.
Retired (RFC-0185). This code no longer fires. It rejected the implicit read-copy of a non-
Copyvalue out of a reference (RFC-0067a §3a), a conversion that no longer exists: a reference is never read out implicitly, solet x: T := r;is T0001 with a= help: read the value out with*r`` line, for aCopyreferent and a non-Copyone alike. An explicit*rof a non-Copyreferent is a move out of a reference, rejected by the move checker (T0019). The number is never reused.
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).
T0026 — Capture list required, incomplete, or incompatible
Availability: 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 (8)
Also demonstratesClosures L7, Rigid type parameters D1
1fun helper() -> i64 { 4 }
2
3fun invalid<T>(value: T) -> i64 {
4 let helper := 3;
5 let callback := [value] once || { helper }; // ERROR[T0026]
6 0
7}
8
9fun main() {}
typecheck errorT0026at 5“`helper` is captured but not listed”
Also demonstratesClosures L7
1fun main() {
2 let s := "x".to_string();
3 let f := || { s }; // ERROR[T0026]
4 f();
5}
typecheck errorT0026at 3“add a capture list”
Also demonstratesClosures L7, Rigid type parameters L1, Record rest patterns L1
1type Callback := once || -> i64;
2fun invoke(f: Callback) -> i64 { f() }
3
4fun transition<row R: !{token}>(r: { ..R }) -> { token: String, ..R } {
5 { ..r, token = "secret" }
6}
7
8fun copied_tail<row R: !{token}>(r: { id: i64, ..R }) -> i64 where all R: Copy {
9 let { id, ..rest } := r;
10 let copy := rest;
11 let callback := [copy] once || { // ERROR[T0026]
12 let extended := transition(copy);
13 let { token, ..again } := extended;
14 id + token.len() as i64
15 };
16 invoke(callback)
17}
18
19fun main() {
20 copied_tail({ id = 3, count = 7 });
21}
typecheck errorT0026at 11“`id` is captured but not listed”
Also demonstratesClosures L7, Rigid type parameters D1
1// Capture completeness is checked even when the generic function is never called.
2fun invalid<T>(value: T) -> i64 {
3 let id := 7;
4 let callback := [value] once || { id }; // ERROR[T0026]
5 0
6}
7
8fun main() {}
typecheck errorT0026at 4“`id` is captured but not listed”
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”
Also demonstratesClosures L6
1// RFC-0050 legality-6 for a declared type parameter: `A` has no `Copy` bound, so
2// the implicit capture is rejected at the definition even though the only
3// instantiation (`i64`) is `Copy` (#273).
4fun swap_vals<A>(a: A) -> |i64| -> (i64, A) {
5 |b: i64| -> (i64, A) { (b, a) }
6}
7
8fun main() {
9 let f := swap_vals(1);
10 let pair := f(2);
11}
typecheck errorT0026“add a capture list”
Also demonstratesClosures L6
1// RFC-0050 legality-6: a closure with no capture list may capture only `Copy`
2// locals. `a` is an unannotated (inferred) parameter, so it has no `Copy` grant
3// at the definition; whether some later call would pass a `Copy` type is not
4// consulted (#273).
5let swap_vals := |a| { |b| { (b, a) } };
6
7fun main() {
8 let pair := swap_vals(1)(2);
9}
typecheck errorT0026“add a capture list”
T0027 — Consuming capture without once
Availability: 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
Availability: 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
Availability: 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
T0031 — Invalid program entry point
The root module does not declare exactly one function named main, non-generic,
callable with zero arguments, with a body the type checker can type
(RFC-0167).
Replaces retired R0001/R0002's main-entry cases — see those entries below.
Fix: add fun main() { ... } (or fix its shape) to your program's root module.
Tested by (4)
Also demonstratesProgram entry point L1
1// RUNTIME_ERROR[no main]
2let x := 1;
typecheck errorT0031“no main”
Also demonstratesProgram entry point L1
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;
typecheck errorT0031“not a function”
Also demonstratesProgram entry point L1
1// `main` exists and is a function, but is itself generic -- RFC-0167's other
2// documented `main`-shape violation (the sibling case is `neg_08`'s "not a
3// function" one). The program entry point must be non-generic.
4fun main<T>() {}
typecheck errorT0031“generic”
Also demonstratesProgram entry point L1
1// `main` exists, is a function, and is non-generic, but takes an argument --
2// spec.functions.program-entry-point.legality-1 requires it be callable with
3// zero arguments.
4fun main(x: i64) {
5 let _ := x;
6}
typecheck errorT0031“zero arguments”
T0032 — Anonymous row in this position
An anonymous row — the splice Builder<..> (RFC-0121 §5) or an open record with an
anonymous tail, { x: T, .. } — is a fresh, nameless row variable at each occurrence. It is
accepted in a function or method parameter, a let or var annotation, and a closure
parameter (a closure with such a parameter generalizes, so one closure accepts records of
different shapes). The splice Builder<..> is also accepted in a return type and in a written
function type.
It is T0032 in a record or struct field, and an open record { x: T, .. } is T0032 in a
return type and in a written function type, positions whose meaning is not settled
(RFC-0181). A named splice (Builder<..R>, R declared row) and a
tail-only { ..R } are supported everywhere. An undeclared row variable is T0003, and a
..R argument for a non-row parameter (or a non-record argument for a row
one) is T0012
(metel-core#1310,
#1478).
Fix: name the row with a row-kinded generic parameter and splice it as ..R, or take the
open record as a parameter, a let annotation or a closure parameter. What works instead depends on the position, and the message says so:
- a free function's return type: omit the return type so the exact record is inferred
(private functions only; a
publicfunction needs an explicit type,T0010), write the record in full (-> { x: i64, y: i64 }), or name arowparameter when the row comes from an argument (-> { x: i64, ..R }); - a method's return type, or an aspect method declaration's: write the record in full or
name a
rowparameter. Leaving the type out does not infer it there: the method returns(); - a closure's return annotation: omit the annotation, and the exact record is inferred, or write the record in full. A closure cannot name a row parameter;
- a written function type: share one
Rbetween the parameter and the result when the callback maps records of one shape.
Tested by (6)
Also demonstratesOpen rows L5
1// metel-core#1478: nor does one in a written function type (RFC-0181).
2fun apply(f: |{ x: i64, .. }| -> i64) -> i64 { // ERROR[T0032]
3 1
4}
5fun main() { let _ := 1; }
typecheck errorT0032at 2“share the one `R` between the parameter and the result”
Also demonstratesOpen rows L5
1// metel-core#1478: an anonymous open record in a return type has no settled meaning (RFC-0181).
2fun make() -> { x: i64, .. } { // ERROR[T0032]
3 { x = 1, y = 2 }
4}
5fun main() { let _ := make(); }
typecheck errorT0032at 2“omit the return type so the exact record is inferred”
Also demonstratesOpen rows L1
1// metel-core#1473 (RFC-0180, RFC-0181): an anonymous tail inside a literal row follows the same
2// positional rule as any anonymous open record; in a return type it is T0032.
3record Req<row R> { data: { ..R } }
4
5fun add<row R: !{ host }>(r: Req<..R>) -> Req<..{ host: String, .. }> { // ERROR[T0032]
6 Req { data = { host = "x".to_string(), ..r.data } }
7}
8
9fun main() {}
typecheck errorT0032at 5“return type”
Also demonstratesOpen rows L5
1// metel-core#1484: and so does an aspect method declaration.
2aspect Source {
3 fun view(&self) -> { x: i64, .. }; // ERROR[T0032]
4}
5
6fun main() { let _ := 1; }
typecheck errorT0032at 3“An aspect method declaration does not infer its return type”
Also demonstratesOpen rows L5
1// metel-core#1484: and a closure's return annotation.
2fun main() {
3 let f := |a: i64| -> { x: i64, .. } { { x = a, y = 1 } }; // ERROR[T0032]
4 let _ := f(1);
5}
typecheck errorT0032at 3“omit the closure's return annotation so the exact record is inferred”
Also demonstratesOpen rows L5
1// metel-core#1484: a method's return type gets the same T0032 text as a free function's,
2// naming the alternatives that work for a method: omitting the type returns (), it does not infer.
3struct Box { n: i64 }
4
5extend Box {
6 fun view(&self) -> { x: i64, .. } { // ERROR[T0032]
7 { x = self.n, y = 1 }
8 }
9}
10
11fun main() { let _ := 1; }
typecheck errorT0032at 6“A method does not infer its return type”
T0033 — A record spread through a reference needs every field Copy
Spreading a record through a reference ({ ..r } where r is &{ .. }) copies its
fields, so it is accepted only when every field is Copy (RFC-0178). RFC-0179 removed the
by-value case this code used to report: an open-row parameter now takes its whole argument,
so nothing is forgotten at a call.
Fix: make the fields Copy, clone the record explicitly, or spread an owned record.
Tested by
Also demonstratesOpen rows L4
1fun bad(r: &{ data: String }) -> { data: String } {
2 { ..r } // ERROR[T0033]
3}
4
5fun main() {}
typecheck errorT0033at 2“through a reference”
Runtime errors (R)
T0034 — Duplicate method definition
A method name is defined twice where both definitions can apply to the same receiver: twice
in one extend block (inherent or an aspect impl), twice in one aspect declaration, or in
two inherent extend blocks of one type whose coverage overlaps. Without this error the
later definition would silently replace the earlier one at dispatch. Methods are not
overloaded, so different parameter types do not make two same-named methods distinct.
Blocks whose coverage is provably disjoint may reuse a name — extend W<i64> and
extend W<String>, bounds that contradict (T: Tag and T: !Tag), or row bounds that
contradict on a shared label (row R: { token, .. } and row R: !{ token }) — and a call
dispatches to the block the receiver satisfies. The overlap test is the one
T0015 applies to aspect impls.
Fix: rename one of the methods, merge the two definitions, or narrow one block so the two no longer overlap.
Tested by (6)
1// metel-core#1229: the same method name declared twice in one `aspect`.
2aspect Tag {
3 fun t(&self) -> i64;
4 fun t(&self) -> i64;
5}
6struct S {}
7extend S: Tag { fun t(&self) -> i64 { 1 } }
8fun main() { let s := S {}; println("${s.t()}"); }
typecheck errorT0034“in this `aspect` declaration”
1// metel-core#1229: a method defined twice in one aspect impl. The later definition used to
2// replace the earlier one silently, so `s.t()` answered 2.
3aspect Tag { fun t(&self) -> i64; }
4struct S {}
5extend S: Tag {
6 fun t(&self) -> i64 { 1 }
7 fun t(&self) -> i64 { 2 }
8}
9fun main() { let s := S {}; println("${s.t()}"); }
typecheck errorT0034“in this `extend` block”
1// metel-core#1322: a method defined twice in one `extend` block. The later definition
2// used to replace the earlier one silently at dispatch.
3
4struct Foo { public v: i64 }
5
6extend Foo {
7 fun f(self) -> i64 { 1 }
8 fun f(self) -> i64 { 2 }
9}
10
11fun main() {}
typecheck errorT0034“in this `extend` block”
1// metel-core#1322: two blanket blocks of one generic type both apply to every
2// instantiation, so a shared method name is a duplicate.
3
4struct Box<T> { public v: T }
5
6extend<T> Box<T> {
7 fun f(self) -> i64 { 1 }
8}
9
10extend<T> Box<T> {
11 fun f(self) -> i64 { 2 }
12}
13
14fun main() {}
typecheck errorT0034“another `extend` block”
1// metel-core#1322: the same method name in two inherent `extend` blocks of one type.
2
3struct Foo { public v: i64 }
4
5extend Foo {
6 fun f(self) -> i64 { 1 }
7}
8
9extend Foo {
10 fun f(self) -> i64 { 2 }
11}
12
13fun main() {}
typecheck errorT0034“another `extend` block”
1// metel-core#1229: methods are not overloaded, so a second `m` with other parameter types is a
2// duplicate, not an overload.
3struct S {}
4extend S {
5 fun m(&self, x: i64) -> i64 { 1 }
6 fun m(&self, x: String) -> i64 { 2 }
7}
8fun main() { let s := S {}; println("${s.m(5)}"); }
typecheck errorT0034“in this `extend` block”
T0035 — Use not granted by the declared bounds
A generic definition used its type parameter in a way its declared bounds do not grant (RFC-0173): a method call on a parameter that has no bounds, or whose bounds do not declare the method, or a field access on a parameter with no row bound that names the field. The message names the parameter and, for a method, the bounds it has.
aspect Show { fun show(self) -> i64; }
fun f<T>(x: T) -> i64 { x.show() } // T has no Show bound
Inside its own definition a declared parameter is opaque: it supports only what its bounds entail. Passing it where a callee requires a bound it lacks is T0012; an operator on a bare parameter is T0005; using it as a concrete type, or as another parameter, is T0001.
Fix: add the bound that grants the use (fun f<T: Show>(x: T)), or take a concrete type.
Tested by (7)
Also demonstratesRigid type parameters L2
1// RFC-0173 D6: an abstract record row cannot use a record-target conditional
2// method unless its declared all-fields entitlement entails the impl condition.
3
4aspect Mark {}
5aspect Label { fun label(&self) -> String; }
6
7extend<row R> { ..R }: Label where all R: Mark {
8 fun label(&self) -> String { "marked".to_string() }
9}
10
11fun use<row R>(value: { ..R }) -> String {
12 value.label() // ERROR[T0035]
13}
typecheck errorT0035at 12
Also demonstratesRigid type parameters L2
1// RFC-0173 D6(3) / #1323: a conditional inherent method is unavailable in
2// a generic body until the receiver argument's declared bounds entail the
3// impl condition.
4
5aspect Tag {
6 fun tag(self) -> i64;
7}
8
9struct Box<T> {
10 value: T,
11}
12
13extend i64: Tag {
14 fun tag(self) -> i64 { 7 }
15}
16
17extend<T: Tag> Box<T> {
18 fun tagged(self) -> i64 { 1 }
19}
20
21fun use_box<U>(box: Box<U>) -> i64 {
22 box.tagged() // ERROR[T0035]
23}
typecheck errorT0035at 22
Also demonstratesField wise row constraints L1, Rigid type parameters L1
1struct NotClone {}
2
3fun clone_head<row R>(value: { fixed: NotClone, ..R })
4where all R: Clone {
5 value.clone() // ERROR[T0035]
6}
7
8fun main() {}
typecheck errorT0035
Also demonstratesField wise row constraints L1, Rigid type parameters L2, Row conditional impls L3
1aspect Mark { fun mark(&self) -> i64; }
2extend<row R> { ..R }: Mark where all R: Copy {
3 fun mark(&self) -> i64 { 7 }
4}
5fun unchecked<row R>(value: &{ ..R }) -> i64 { value.mark() } // ERROR[T0035]
6fun main() {}
typecheck errorT0035“conditional implementation requirements are not entailed”
1// Issue #1386: abstract rows report all requirements and the missing entailment.
2
3aspect Label { fun label(self) -> String; }
4
5extend<row R: { tag: i64, .. }> { ..R }: Label {
6 fun label(self) -> String { "integer" }
7}
8
9extend<row R: { tag: String, .. }> { ..R }: Label {
10 fun label(self) -> String { "text" }
11}
12
13fun use<row R>(value: { ..R }) -> String {
14 value.label() // ERROR[T0035]
15}
typecheck errorT0035at 14“candidate 2 requires { tag: String, .. }”
Also demonstratesRigid type parameters L2, Row conditional impls L3
1// RFC-0173 D6(3): a row-conditional inherent method is unavailable in a
2// generic body until the row parameter's declared bounds entail the condition.
3
4record Session<row R> {
5 id: i64,
6 data: { ..R },
7}
8
9extend<row R: { token: String, .. }> Session<..R> {
10 fun with_token(self) -> i64 { self.id }
11}
12
13fun use_session<row R>(session: Session<..R>) -> i64 {
14 session.with_token() // ERROR[T0035]
15}
typecheck errorT0035at 14
Also demonstratesRow conditional impls L1
1record Packet<row R> { data: { ..R } }
2extend<row R: !{ token }> Packet<..R> { fun cleared(&self) -> i64 { 4 } }
3fun rejected<row R: !{ token: String }>(packet: &Packet<..R>) -> i64 {
4 packet.cleared() // ERROR[T0035]
5}
6fun main() {}
typecheck errorT0035“no row-conditional implementation matches”
T0036 — By-value open-row parameter over a Drop brand
A by-value open-row parameter over a nominal type that has a Drop impl
(fun f<row R>(h: Handle.{ fd, ..R }) given a Handle) would drop a narrowed
Handle.{ fd } in the callee. Drop dispatch against a narrowed residual is not
implemented (RFC-0137 §5, #949), so the call is rejected (RFC-0179 §1.5). A plain record
that only contains a Drop value is not affected.
Fix: take the value by reference, or give the parameter the whole type.
Tested by
Also demonstratesOpen rows L3
1// RFC-0179 section 1.5: passing a `Drop` brand to a projection-tailed parameter would drop a
2// narrowed `Handle.{ fd }`, which needs drop dispatch against a narrowed residual (#949).
3struct Handle { fd: i64, name: String }
4
5extend Handle: Drop {
6 fun drop(&var self) { }
7}
8
9fun read_fd<row R>(h: Handle.{ fd, ..R }) -> i64 { h.fd }
10
11fun main() {
12 let h := Handle { fd = 3, name = "x".to_string() };
13 let _ := read_fd(h); // ERROR[T0036]
14}
typecheck errorT0036at 13“drop dispatch against a narrowed residual”
T0037 — Duplicate top-level declaration
A top-level name is declared twice in one namespace of one module. Structs, enums,
aspects and type aliases share the type namespace; functions, let and var share the
value namespace. A type and a value may share a name (struct Foo and fun Foo
coexist); two types, or a binding and a function, may not. Two funs of one name are an
overload set, so repeating a function is T0011 only when their
parameter types are identical. Without this error the second declaration shared the first
one's identity and went unseen.
let _ := ... binds nothing, so repeating it is allowed.
Fix: rename one of the declarations, or merge them.
Tested by (5)
Also demonstratesScoping and shadowing L5
1// metel-core#1560: `let`, `var` and `fun` share the value namespace. Two `fun`s of one name are an
2// overload set (their duplicates are T0011), so only a `fun` beside a binding is reported here.
3let x := 1;
4fun x() -> i64 { 2 }
5
6fun main() {}
typecheck errorT0037“duplicate declaration of value `x`”
Also demonstratesScoping and shadowing L5
1// metel-core#1560
2struct Foo { x: i64 }
3aspect Foo { fun f(&self) -> i64; }
4
5fun main() {}
typecheck errorT0037“duplicate declaration of type `Foo`”
Also demonstratesScoping and shadowing L5
1// metel-core#1560: structs, enums, aspects and type aliases share one namespace.
2struct Foo { x: i64 }
3enum Foo { A }
4
5fun main() {}
typecheck errorT0037“duplicate declaration of type `Foo`”
Also demonstratesScoping and shadowing L5
1// metel-core#1560: two top-level declarations of one name in the type namespace. The second used to
2// share the first one's SymbolId and silently go unseen.
3struct Foo { x: i64 }
4struct Foo { y: i64 }
5
6fun main() {}
typecheck errorT0037“duplicate declaration of type `Foo`”
Also demonstratesScoping and shadowing L5
1// metel-core#1560: an alias is expanded away before name resolution, so it is checked against the
2// module's other types where it is collected.
3struct Foo { x: i64 }
4type Foo := i64;
5
6fun main() {}
typecheck errorT0037“duplicate declaration of type `Foo`”
T0038 — Declaring a type with the name of an intrinsic type
A struct, enum, aspect, type alias, or block-local struct or enum is declared with the name of an
intrinsic type: boolean, String, Char, i8, i16, i32, i64, u8, u16, u32, u64,
f32, f64, Range or RangeInclusive. The language provides these types and std::core declares
none of them, so a user type of the same name would capture part of the compiler's lookups by name:
with struct String { ... } declared, println of an ordinary string failed because the Display
impl belongs to the primitive. Only the type namespace is reserved. A function or let may use
the name, and the types std::core declares (List, Option, Result, ...) are not covered.
Fix: rename the type.
Tested by (5)
Also demonstratesScoping and shadowing L8
1// metel-core#1582: a block-local type may not take the name either.
2fun main() {
3 struct u8 {
4 x: i64,
5 }
6 let a := u8 { x = 1 };
7}
typecheck errorT0038“`u8` is the name of an intrinsic type”
Also demonstratesScoping and shadowing L8
1// metel-core#1582: the intrinsic types are provided by the language and declared nowhere, so a user
2// type of the same name would capture part of the compiler's lookups by name. `println(s)` on an
3// ordinary string used to fail with "String does not implement Display" once this was accepted.
4struct String {
5 x: u8,
6}
7
8fun main() {
9 let s := "a";
10 println(s);
11}
typecheck errorT0038“`String` is the name of an intrinsic type”
Also demonstratesScoping and shadowing L8
1// metel-core#1582: an alias is expanded before name resolution, so it is checked where it is collected.
2type boolean := i64;
3
4fun main() {}
typecheck errorT0038“`boolean` is the name of an intrinsic type”
Also demonstratesScoping and shadowing L8
1// metel-core#1582
2aspect f64 {
3 fun twice(&self) -> i64;
4}
5
6fun main() {}
typecheck errorT0038“`f64` is the name of an intrinsic type”
Also demonstratesScoping and shadowing L8
1// metel-core#1582
2enum Char {
3 A,
4}
5
6fun main() {}
typecheck errorT0038“`Char` is the name of an intrinsic type”
R0001 — No main function defined
Retired, replaced by
T0031(RFC-0167). Themain-existence check now runs during typechecking, not lazily at evaluator startup; this code no longer fires. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md) — seeT0031above.
Formerly: execution required a main function but none was found.
R0002 — main is not a valid entry point
Retired, split three ways by RFC-0167. This code no longer fires:
mainexists but has the wrong shape is nowT0031above; an ordinary (non-main) call on a non-callable value is nowI0007below (merged with whatR0010became); a generic closure/method with no call-site type context is nowI0009below, its own code, unrelated tomain. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md).
Formerly: main existed but was generic or was not a function.
R0003 — Undefined variable at runtime
Retired, replaced by
I0003(RFC-0167). This code no longer fires. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md) — seeI0003below.
Formerly: a variable name was not found in the current environment.
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
Retired, replaced by
I0004(RFC-0167). This code no longer fires. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md) — seeI0004below.
Formerly: a match expression reached its end without any arm matching.
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
Retired, replaced by
I0005(RFC-0167). This code no longer fires. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md) — seeI0005below.
Formerly: a struct or enum value did not have the accessed field.
R0009 — Method not found
Retired, replaced by
I0006(RFC-0167). This code no longer fires. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md) — seeI0006below.
Formerly: a method call could not be resolved for the receiver type.
R0010 — Call on non-callable value
Retired, replaced by
I0007(RFC-0167). This code no longer fires, and merges withR0002's ordinary (non-main) call-target case. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md) — seeI0007below.
Formerly: a call expression (f(...)) was applied to a value that is not a function
or closure.
R0011 — Invalid for-in iterator
Retired, replaced by
I0008(RFC-0167). This code no longer fires. Kept documented, never reused, per this project's rigor-block-id convention (STYLEGUIDE.md) — seeI0008below.
Formerly: a for x in expr loop where expr did not evaluate to an Array, a
Range, or a type implementing Iterable.
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 (2)
Also demonstratesBuilt in functions D2
1fun main() {
2 assert(false, "custom assertion failure");
3}
runtime errorR0012“custom assertion failure”
Also demonstratesBuilt in functions D2
1// The paired control: an `assert` reached through a generic body still fails when its
2// condition is false, with the assertion's own message (#1433).
3fun must_hold<T>(value: T) { assert(1 == 2, "generic assertion failed"); }
4
5fun main() {
6 must_hold(1);
7}
runtime errorR0012“generic assertion failed”
R0013 — Unwrap on None/Err
.yolo() is called on an Option<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: Option<i64> := Option::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
Availability: 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”
R0016 — Invalid Unicode scalar value
Char::from(n) is called with a u32 that is not a Unicode scalar value, including a
surrogate or a value above 0x10FFFF.
Fix: pass a Unicode scalar value to Char::from.
Tested by
Also demonstratesChar methods D1
1// RUNTIME_ERROR[R0016]
2// Char::from rejects u32 values outside the Unicode scalar range.
3fun main() {
4 let _c := Char::from(55296u32);
5}
runtime errorR0016“not a valid Unicode scalar”
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.
I0003 — Scoping/hoisting invariant violated
A variable name was not found in the current environment at runtime
(RFC-0167).
Whether a name resolves is a purely static scoping fact; if this fires, the type
checker approved a program it should not have. Replaces retired R0003 (see above).
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 — blocked on metel-core#986: Confirmed live raise sites (lvalue.rs, mod.rs), carried over unchanged from retired R0003's own finding. #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.
I0004 — Exhaustiveness-checker invariant violated
A match expression reached its end without any arm matching
(RFC-0167).
Whether a match's arms are exhaustive is a purely static property of the pattern set
against the scrutinee type; if this fires, check_match_exhaustiveness approved a
match it should not have. Replaces retired R0006 (see above).
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 — blocked on metel-core#986: A known limitation (the type checker approving a match as exhaustive when it is not), carried over unchanged from retired R0006's own finding. #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.
I0005 — Field-resolution invariant violated
A struct or enum value does not have the accessed field
(RFC-0167).
Whether a type has a given field is a structural fact, checked at the point of the
struct/enum declaration; if this fires, that check was wrong. Replaces retired
R0008 (see above).
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 — blocked on metel-core#986: Confirmed live raise site, carried over unchanged from retired R0008's own finding, 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 collision bug (metel-core#989) doesn't obviously carry over here. No new construction attempted this round.
I0006 — Method-dispatch invariant violated
A method call cannot be resolved for the receiver type
(RFC-0167).
Whether a type has a given method is a structural fact, checked at the point of the
relevant extend block; if this fires, that check was wrong. Replaces retired R0009
(see above).
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 — blocked on metel-core#989: Confirmed live raise site, carried over unchanged from retired R0009's own finding. #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 this raise site -- 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 this code specifically -- revisit once #989 is fixed.
I0007 — Callability invariant violated
A call expression (f(...)) is applied to a value that is not a function or closure
(RFC-0167).
Whether a value is callable is a static type fact; in v0.13.0 there is not even a
dynamic-dispatch mechanism (dyn Callable, RFC-0161) that could make it otherwise. If
this fires, the type checker approved a call it should not have. Replaces retired
R0010, merged with retired R0002's ordinary (non-main) call-target case (see both
above) — both raise sites make the exact same claim, just from different call paths.
Note: if RFC-0161 (
dyn Callable) ships, this code's classification should be revisited — a dynamically-dispatched call target's callability would then genuinely depend on a runtime value again.
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 — blocked on metel-core#986: Confirmed live raise sites (merged from retired R0010's own finding and R0002's non-main call-target case), 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.
I0008 — Iterator-dispatch invariant violated
A for x in expr loop where expr does not evaluate to an Array, a Range, or a
type implementing Iterable
(RFC-0167).
Whether a type implements Iterable is a static bound fact, checked wherever the
iteration is typed; if this fires, that check was wrong. Replaces retired R0011
(see above).
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 — blocked on metel-core#986: Confirmed live raise sites (evaluator/mod.rs), carried over unchanged from retired R0011's own finding, 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.
I0009 — Generic construction-at-call-time invariant violated
A generic closure or method's construction-at-call-time machinery (metel-core#286)
was invoked with no call-site type context available
(RFC-0167).
Unrelated to call-target shape or main entirely — a construction-machinery plumbing
failure, not a callability question. Replaces retired R0002's other orphan use (see
above); this is a wholly new code, not a merge into any existing one.
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: This code is new with RFC-0167 -- no prior R0002-era reachability finding to carry over. Its two raise sites (call.rs) fire only when a generic closure/method's own captured type_ctx is None, which every construction path that builds a ClosureBody::Untyped value populates unconditionally (Pass 1b and hoist_nested_funs both always pass env.type_ctx.clone()); no construction was found that reaches ClosureBody::Untyped with type_ctx already None.
I0010 — Generic definition and construction disagree
The checker accepted a generic definition, but reconstructing that generic body for a concrete call rejected it (RFC-0173). The definition-time check is the contract, so this signals an implementation invariant violation rather than an error at the call site.
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: I0010 reports a disagreement between two checker passes. No source fixture can correctly require such an implementation-invariant failure; the differential check that exercises the invariant remains tracked by metel-core#1364.
I0011 — Move checking could not analyze a body
Under --move-check, the move checker could not analyze at least one generic body, so it
cannot vouch for the program (ADR-0063, abstract generic bodies).
This is not a move violation: nothing was shown to be used after a move. It is reported
as its own error because an incomplete analysis must never authorize execution, and a
program that passes --move-check has had every body checked. Default mode, which does
not run the move checker, is unaffected.
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: I0011 is a defensive guard against an implementation gap or a feature that lacks ownership integration. Every valid body in the implemented language is analyzed, so no source fixture can correctly require it. The guard is exercised by unit tests that remove a body's retained facts, and a corpus test asserts that no fixture reaches it.