ADR-323: A Grammar Describes a Type as Data — meta::Type, From Which the Compiler Writes a Block’s Parameters and Result

Status: Accepted Date: October 6, 2026 Answers: what the type of meta::column(name, type) and meta::parameter(name, type) is, when a type is not a value in this language (#114) Related: ADR-299 D2, D12-D14 (a statement’s row type, and how its columns are named); ADR-296 D5 (a DSL’s deferred parameters become one value whose type the compiler writes); ADR-304 (reflection reads a type’s shape; no macro, no code as text); ADR-177 D4 (what a grammar run at build time hands back has to cross); ADR-311 (what crosses)

1. Question

How does a grammar that runs while the program is built tell the compiler the type of something it found — a column, a placeholder — when nothing in the source writes that type?

A driver’s grammar learns from a statement and a schema that a row has name: String and email: String?. The compiler has to build that type. A type is not a value in this language, there are no type arguments at a call (f[String]()), and what a grammar run hands back crosses as data (ADR-177 D4). So the type has to travel as a description.

2. How others do it

Language A type from data What it costs there
Zig @Type(info) turns a description into a type; types are build-time values throughout the whole language carries types as values, and an anytype failure lands in the body
F# type providers a library run by the compiler supplies types from a schema a compiler interface of its own
Rust sqlx::query! a macro reads the schema and writes a struct as tokens code generated behind the reader’s back
TypeScript with Prisma a generator writes type files before compiling a second build step

What transfers: Zig’s description as data. What does not: types as values everywhere, and generated source.

3. Options, weighed

Changed by ADR-330 D9: type arguments at a call are allowed, with a warning.

Option Why it lost
Scalars only (Text, I64, … and nullable) enough for SQL, a regex’s named groups and a CSV header; not for JSON, Protobuf or GraphQL, which nest, repeat and name
The type as text ("String?") a misspelling surfaces when the grammar runs for a user, not when the driver is compiled
Type arguments at a call (meta::column[String?]("email")) a new form for the whole language, for one use
Any comptime may compute a type (comptime type Config = …) a construct of its own — a type as the result of any build-time computation — with its own questions of naming and identity; every format named above is read by a grammar, which this record covers

4. Decision

D1 — std::meta::Type describes a type as data.

Changed by ADR-330 D3, D6: the parts are types, Map, Struct, Enum and Opaque are kinds of it, and it exists only while the program is built; Record stays for a grammar; D7: Named is gone.

pub enum Type {
    I64, I32, U8, U64, U32, F64, Bool, Text, Bytes,
    Maybe(Type),            // T?
    List(Type),             // Vec[T]
    Record(Vec[Field]),     // a struct the compiler writes
    Named(ref String),      // a type the program declares
}
pub struct Field { name: ref String, ty: Type }

The type checker checks a description when the grammar is compiled, so a driver’s mistake is the driver’s, at its line, and a shape that is not a type cannot be written.

D2 — meta::column(name, type) and meta::parameter(name, type) take a meta::Type.

A grammar calls them while it runs at build time; the calls are recorded and handed back with the grammar’s result, as data that crosses (ADR-311).

D3 — The compiler writes the type, and only it.

From the columns a block’s grammar declared, the result’s row type; from its parameters, the parameter type (ADR-296 D5). Text is String, Maybe(T) is T?, List(T) is Vec[T], Record is a struct with those fields.

Named("Mode"), a type the program declares, resolved where the block stands, is gone: ADR-330 D7.

D4 — A written type says where it came from.

A message about a type the compiler wrote names the block and the grammar that declared its members — email is a column of the query at line 12 — and --comptime prints the type each block was given. The type is data the reader can ask for, never source the reader did not write (ADR-304).

5. Consequences

6. What this does not decide

This site is open source. Improve this page.