VIP-13: error declarations
Status: accepted by Will, 2026-10-28.
Decision
// VibeLang
error NotFound { id: string }
error Timeout // an error with no fields
// exactly this Effect TS
class NotFound extends Data.TaggedError("NotFound")<{ readonly id: string }> {}
class Timeout extends Data.TaggedError("Timeout") {}
// using one (VIP-4, VIP-5, VIP-7)
throw new NotFound({ id }) // in an effect function: fails with NotFound
fetchUser(id) catch (e) => match (e) { when NotFound: guest; }error Name { fields }declares exactly Effect'sData.TaggedErrorclass. The tag is always the name, and every field is readonly.- Amended 2026-10-30: payload key erasure. Plain
errorpayload fields areData.TaggedError's generic type argument. Their computed type keys cause no runtime lookup, evaluation or TDZ read; declaration output retains the type. Admittedschema errorfield keys belong to the runtime schema object and evaluate normally, while actual methods/getters retain TypeScript's computed-name evaluation. A shared runtime-key rule never makes a plain payload type into an object initializer. See Error declarations. - The body may hold methods and getters, as any class can, for example
get message() { return \no user ${this.id}` }. It may not hold a constructor or a_tag` override. - Amended 2026-10-29: no
extends. Anerrordeclaration always extendsData.TaggedError, soerror UserNotFound extends NotFound { … }is a compile error. Errors are grouped with a union, as intype LookupError = NotFound | Timeout, because tagged errors are matched by_tagand a subclass would match two tags. Recorded under Will's standing approval of recommendations. - The base is
Data.TaggedError, which can't be decoded from JSON. Serializable, Schema-based errors are left to theschemaVIP. - Two packages that both declare
error NotFoundmatch each other, as with Effect'scatchTag, because VIP-7's matcher tests_tag. - Ambiguity: none.
erroris a contextual keyword on the same line as its name, soconst error = …andcatch (error)still work. - Amended 2026-10-29: no user-written
staticmembers or setters inerrororschema errorbodies, for now. Sent to Will on Telegram 2026-10-29 11:28 under his standing rule 'assume I accept'; no objection by 12:00. - Amended 2026-10-29: no type parameters.
error Invalid<T> { value: T }is a compile error, for now. Sent to Will on Telegram 2026-10-29 11:28 under his standing rule 'assume I accept'; no objection by 12:00. - Amended 2026-10-29: exactly VIP-13.
errordeclarations allow noextendsand no_tagoverride; EK2117 enforces it. Sent to Will on Telegram 2026-10-29 12:28 under his standing rule 'assume I accept'; no objection by 13:00.
Corrected 2026-10-29: the example ends every match arm with ;, the last one included, as the pinned TC39 grammar requires; a newline is not a separator (found by the round-3 review).
- Amended 2026-10-30: computed payload key admission. Plain
errorpayload fields use the native TypeScriptTypeLiteralmember checks ofData.TaggedError's payload type. Expression shape matters as well as key type: an identifier or property access of typenumbercan be admitted while a call returning a literal, a parenthesized key or element access receives nativeTS1170. Only admitted payload keys are erased; actual runtime member names keep their ordinary evaluation. The runtime Schema fields object in VIP-29 has its own native key admission.
Amendment: member diagnostic priority
A getter, setter or constructor rejected for an Effect modifier receives the applicable Functions diagnostics without an additional error-body category diagnostic, for both error and schema error. In particular, async effect get x() { } receives both EK0104 and EK0105 (VIP-1). Other rejected members use the first applicable member rule; the general category rule is the fallback.
History
Every finding was about error classes combined with match exhaustiveness: round 2 M7 and round 8 M1 (majors), plus five minors, all lego-kind. VIP-7's custom matcher, which tests _tag, settles them. The declaration syntax itself drew no findings.
Prior art
Zig's error{NotFound}, Swift's enum: Error, Rust's thiserror, Effect's Data.TaggedError.
Will's words
"Ok accept. Your best judgement on the questions is correct"
(The questions: the base, Data rather than Schema; and the body, methods and getters but no constructor or _tag override.)