toolchain/ast
The shape of written Vader, as data.
The public contract a build-time metaprogram reads. It lives outside vader/
because of what depends on it: a library shipping a generator must be able to
walk declarations without depending on the compiler, and this module needs
nothing from the compiler either: only toolchain/span, for where each node
came from. Everything that INTERPRETS these nodes (resolve, typecheck, lower)
stays in vader/.
It is the tree AS WRITTEN, before resolution: an IdentExpr("Id") is the name
the author typed, not the type it denotes. A generator that needs to know
whether that name is a primitive, an alias, an imported struct or a distinct
newtype cannot answer it from here, see the note on resolved declarations in
the TODO's toolchain item.
Layout note : declaration order is free, the resolver collects every
decl name in a first pass before walking types in the second, so a
union alias can sit before or after the variant structs it lists.
Type-bearing fields are renamed ty (since type was historically a
keyword) ; the dumper emits the JSON key as "type" so snapshots
stay stable.
Types
Decl :: ImportDecl | FnDecl | StructDecl | EnumDecl | TraitDecl | ImplDecl | TypeAliasDecl | ConstDecl | AssertDecl Anything that can appear at the top level of a file.
ImportBinding :: NamedNamespaceImport | DestructureImport | WildcardImport What an import binds: a namespace alias, a modifier list, or nothing.
The three forms of SPEC §Modules: X :: import "p", import "p" { a as b },
and the bare import "p" wildcard.
ForForm :: InfiniteFor | WhileFor | InFor Which of the three for shapes a loop is: for {}, for cond {}, for x in xs {}.
Expr :: IntLitExpr | FloatLitExpr | BoolLitExpr | NullLitExpr | CharLitExpr | StringLitExpr | IdentExpr | CallExpr | FieldExpr | IndexExpr | UnaryExpr | BinaryExpr | NullCoalesceExpr | IfExpr | MatchExpr | BlockExpr | LambdaExpr | StructLitExpr | SeqLitExpr | RangeExpr | CastExpr | GenericInstExpr | IntrinsicCallExpr | DotVariantExpr | FnTypeExpr | ArrayTypeExpr | AwaitExpr Any expression. Also serves as TypeExpr, see the note there.
StringLitPart :: StringLitText | StringLitInterp One piece of a string literal: literal text, or an interpolated expression.
IfElse :: IfElseIf | IfElseBlock What follows an else: another if branch, or a final block.
Pattern :: IsPattern | StructPattern | WildcardPattern | EnumVariantPattern | LiteralPattern | RangePattern | OrPattern Anything that can appear on the left of a match arm's ->.
LetBinding :: SimpleBinding | TupleBinding | WildcardBinding | RestBinding What a :: / := binds: one name, a tuple destructure, _, or a rest binding.
AssignTarget :: PlaceTarget | WildcardTarget | RestTarget One element of a [...] = value assignment: a place, _, or a ...rest tail.
PatternFieldValue :: PatternBinding | PatternLiteral Inside a struct pattern, what a field is matched against: a binding or a literal.
CoalesceFallback :: Expr | ReturnStmt | BreakStmt | ContinueStmt Right operand of ?? (null-coalescing). Either an ordinary value
expression (must be assignable to the non-null result type) or a
divergent statement (return / break / continue, typed never).
The divergent forms are statements (not Expr variants) so they are
carried here directly rather than through the Expr union.
TypeExpr :: IntLitExpr | FloatLitExpr | BoolLitExpr | NullLitExpr | CharLitExpr | StringLitExpr | IdentExpr | CallExpr | FieldExpr | IndexExpr | UnaryExpr | BinaryExpr | NullCoalesceExpr | IfExpr | MatchExpr | BlockExpr | LambdaExpr | StructLitExpr | SeqLitExpr | RangeExpr | CastExpr | GenericInstExpr | IntrinsicCallExpr | DotVariantExpr | FnTypeExpr | ArrayTypeExpr | AwaitExpr A type AS WRITTEN: the same node set as Expr, deliberately.
Vader's type grammar is a subset of its expression grammar (i32, Foo<T>,
A | B, fn(i32) -> bool, T[]), so the parser reuses the expression nodes
rather than duplicating them. What counts as a type expression is decided by
POSITION (the slot demanding a type) never by node shape; the alias exists to
say so at each slot (StructField.ty: TypeExpr).
A reader must not assume an IdentExpr in type position denotes a type it can
name: Id may be an alias, an imported struct or a distinct newtype, and only
the resolver knows which.
Visibility :: enum Whether a declaration carries export.
Variants
Public Private Program :: struct One parsed source file.
module is null for a file with no module "..." header: an error the resolver
reports rather than the parser.
Fields
file: string span: Span id: i64 = UNASSIGNED_NODE_ID module: ModuleDecl | null = null The module "..." declaration. Null when absent: the parser
is tolerant ; the resolver's scan emits R2020 on absence.
decls: Decl[] ModuleDecl :: struct The module "<name>" decl that opens every .vader file. Carries
the validated module name + its source span (the module keyword
itself lives in the node's overall span).
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span TypeParam :: struct One entry of a [T: Bound = Default] list.
is_comptime_value marks a parameter that carries a VALUE rather than a type
([N: i64]), which the comptime engine substitutes.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string bound: TypeExpr | null default: TypeExpr | null = null Default type for this parameter, from <T = Default>. Honored when an
impl supplies fewer trait-args than the trait declares: the trailing
params fill from their defaults at impl materialization
(front/resolver/materialize.vader::build_subst). Defaults on struct / fn /
alias params parse but are not yet filled at their use sites (those
instantiations error rather than default). null = no default.
is_comptime_value: bool Decorator :: struct An @name or @name(args) above a declaration.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string args: Expr[] ImportDecl :: struct An import statement, whatever its form, see ImportBinding.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID path: string binding: ImportBinding decorators: Decorator[] NamedNamespaceImport :: struct name :: import "...": namespace binding ; members reached via name.X.
Fields
name: string DestructureImport :: struct import "p" { x as y, z as _ }: a WILDCARD of p plus per-name modifiers:
every listed source name is dropped from the wildcard ; x as y also rebinds
it as y, z as _ is a pure exclusion. A plain name (no as) is an error
(R2033): the wildcard already brings every export.
Fields
names: ImportName[] trailing_comma: bool = false True when the source wrote a trailing comma before }. Drives the
formatter's Dart-style layout: present -> one name per line.
WildcardImport :: struct bare import "...", wildcard : every function of the module becomes
callable (prefix + UFCS) ; binds no name.
ImportName :: struct One name or name as alias inside an import's modifier list.
alias is null for a bare name, which is R2033: a wildcard already brings it.
Fields
name: string alias: string | null span: Span id: i64 = UNASSIGNED_NODE_ID FnDecl :: struct A function declaration, name :: fn(...) -> T { ... }.
Four flags mark functions the user did not write. sam_synthetic is a
single-abstract-method lambda the parser desugared into an impl member;
materialized a member declared for an impl from its trait (one it inherits,
inherits, or an intrinsic stub); comptime_synthetic a declaration the
comptime engine produced. And
is_expression_bodied distinguishes f :: fn() = expr from a braced body,
which the formatter needs and a generator emitting source should preserve.
Each is a plain bool defaulting to false, never bool | null (Vader has no
optional fields) and the dumper emits one only when true.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span visibility: Visibility type_params: TypeParam[] params: FnParam[] return_type: TypeExpr | null body: BlockExpr | null decorators: Decorator[] sam_synthetic: bool is_expression_bodied: bool materialized: bool true when this FnDecl was declared for an impl from its trait rather than
written in it : a member the impl inherits (inherits), or a stub of an
@intrinsic impl.
comptime_synthetic: bool = false true when this FnDecl is the __comptime_NAME helper the parser
synthesises for the NAME :: @comptime <expr> const sugar (see
desugar_comptime_const). The formatter skips it: the surface sugar
is re-emitted from the paired ConstDecl.comptime_body instead.
Defaults false : only the desugar sets it, every other constructor
leaves it off.
trailing_comma: bool = false True when the source wrote a trailing comma after the last parameter. Drives the formatter's Dart-style layout: present -> one param per line.
doc: DocComment | null = null inherits: FnDecl | null = null For a member an impl inherits from its trait, the trait's member whose default body it runs: the body was checked once in the trait, and is lowered for each impl from there. Null for any other function.
FnParam :: struct One parameter of a function, lambda or trait method.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span The name alone, where span covers the whole slot, decorators, a variadic
..., the type and the default included : what a rename rewrites. span
stays the parameter's identity, the key of the side tables.
ty: TypeExpr | null default_value: Expr | null variadic: bool decorators: Decorator[] Decorators written before the parameter (@c_pointer tv: TimeVal).
StructDecl :: struct A struct declaration, Name :: struct { ... }.
The node a generator walks most: fields in declaration order, each carrying its
written type. name_span covers the name alone, which is what a diagnostic about
the struct itself should point at.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span visibility: Visibility type_params: TypeParam[] fields: StructField[] decorators: Decorator[] doc: DocComment | null = null StructField :: struct One field of a struct.
visibility is @internal: fields are public by default, so this is the opt-out.
default backs T {} construction with the field omitted.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string ty: TypeExpr visibility: Visibility default: Expr | null doc: DocComment | null = null EnumDecl :: struct An enum declaration, Name :: enum(u8) { A, B } or
Name :: enum(Info) { A { .f = 1 } }.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span visibility: Visibility header: TypeExpr[] The parenthesised header as written : empty, (width), (data) or
(data, width). Which role a lone argument plays depends on what it
resolves to, so the typechecker decides, not the parser.
variants: EnumVariant[] decorators: Decorator[] doc: DocComment | null = null EnumVariant :: struct One variant of an enum, with its explicit value when the source gave one.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string value: i64 | null value_span: Span | null data: StructLitItem[] | null = null The { .f = v } block as written, checked against the header's data
type ; null when the variant has none.
doc: DocComment | null = null TraitDecl :: struct A trait declaration.
members are its methods, each an FnDecl; one with a body is a default
method.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span visibility: Visibility type_params: TypeParam[] requires: TypeExpr | null The supertrait bound, for either form: Numeric :: trait<T> = Add & Sub
(alias form, members empty) or Hashable :: trait<T> : Hash & Eq { … }
(methods plus composition). A parsed type expression: typically a
BinaryExpr(.BitAnd) chain over trait names, though a single trait
reference is valid too. null for a plain trait { … }.
members: FnDecl[] decorators: Decorator[] doc: DocComment | null = null ImplDecl :: struct An implements block, or its one-method short form.
for_type is the implementing type as written, trait_name the trait, which is
a bare string rather than a node because a trait is always named, never computed.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID type_params: TypeParam[] for_type: TypeExpr trait_name: string trait_name_span: Span trait_args: TypeExpr[] members: FnDecl[] decorators: Decorator[] doc: DocComment | null = null TypeAliasDecl :: struct A type alias, Name :: SomeType.
A generator resolving a written type must expect to meet one of these: the alias
and its target are indistinguishable in an IdentExpr.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span visibility: Visibility type_params: TypeParam[] aliased: TypeExpr decorators: Decorator[] doc: DocComment | null = null ConstDecl :: struct A module-level constant, NAME :: value or the typed NAME: T: value.
comptime_body holds the unevaluated expression for a @comptime const, whose
value is filled in by the comptime engine.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span visibility: Visibility ty: TypeExpr | null value: Expr decorators: Decorator[] comptime_body: Expr | null = null The value a @comptime const was written with, when the parser gave it a
__comptime_NAME helper to call (through_comptime_helper) : the
formatter re-emits it rather than the call. Non-null marks such a const ;
null for every other const. Holds the raw block/expression (pre-return-
lift), so the exact source spelling round-trips.
comptime_as_decorator: bool = false True when @comptime decorated the const (@comptime above NAME :: v)
rather than following :: ; read with comptime_body.
doc: DocComment | null = null AssertDecl :: struct An @assert at the top level: a build-time invariant.
condition is evaluated by the comptime VM and the build fails with C4015 when
it is false. Nothing is emitted at runtime.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID condition: Expr message: string | null message_span: Span | null Where the message literal is written, so it prints in its own spelling.
decorators: Decorator[] LetStmt :: struct A local binding, x :: e or x := e.
mutable is the := form. Style treats a := that is never reassigned as a
defect, so a generator should emit :: unless it writes to the slot.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID mutable: bool binding: LetBinding ty: TypeExpr | null value: Expr SimpleBinding :: struct A local bound to one name: a leaf of the recursive binding tree, whose other
leaf is WildcardBinding (ignores the slot) and whose only branch is
TupleBinding (matches a tuple-typed value element-wise).
name_span covers the name alone: the precise target a rename needs, which the
whole-binding span is too wide for.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span TupleBinding :: struct A destructuring local, [a, b] :: pair.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID elements: LetBinding[] WildcardBinding :: struct A discarded local, _ :: e.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID RestBinding :: struct The ...rest tail of a destructuring local: collects every slot its sibling
SimpleBindings did not consume into a fresh array.
Valid only as the LAST element of a TupleBinding whose source is array-typed.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span DestructureAssignStmt :: struct An assignment to several existing places at once, [a, b] = pair.
Not an AssignStmt over a literal target: its elements are not expressions,
_ and ...rest only mean something here.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID targets: AssignTarget[] value: Expr PlaceTarget :: struct A place written by a destructuring assignment: a name, a field or an index.
Any expression parses here; the typechecker refuses what is not a place.
Fields
WildcardTarget :: struct A discarded element of a destructuring assignment, _.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID RestTarget :: struct The ...rest tail of a destructuring assignment: writes a fresh array of
every element its siblings did not consume into place.
Valid only as the LAST target, over an array-typed source.
Fields
AssignStmt :: struct An assignment to an existing place, target = value.
lhs op= rhs parses to one too : value is lhs op rhs over the SAME lhs
node as target, which compound_assign_op recognises.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID target: Expr value: Expr ExprStmt :: struct An expression evaluated for its effect, its value discarded.
Fields
ReturnStmt :: struct A return, with no value for a void function.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID value: Expr | null YieldStmt :: struct yield expr inside a generator fn body: emit one element and suspend.
Always carries a value (unlike return, which may be bare); the empty /
exhausted case is a bare return or falling off the end. value must
coerce to the T of the generator's -> Iterator<T> return.
Fields
ForStmt :: struct A loop in any of its three forms, see ForForm.
label is the loop-jump target of break <label> / continue <label>.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID label: string | null form: ForForm body: BlockExpr is_comptime: bool = false @comptime for x in <arr> { … }: unroll at lower time. False for
every other form.
InfiniteFor :: struct The for { ... } form: no condition, exited by break or return.
WhileFor :: struct The for cond { ... } form.
Fields
cond: Expr InFor :: struct The for x in xs { ... } form.
LoopVar :: NamedVar | DestructureVar | DiscardVar The loop variable of a for … in …. A union so the mutually exclusive
shapes can't be miscombined and no sentinel / span-geometry heuristic is
needed downstream: each pass matches on the variant. Kept structural on the
AST (not desugared at parse) so vader fmt round-trips every sugar.
NamedVar :: struct for x in …, and the explicit discard for _ in coll (name == "_").
Fields
name: string span: Span DestructureVar :: struct for [k, v] in …: the leaves bind against the iterable's element type.
Fields
pattern: LetBinding DiscardVar :: struct for <range>: no user variable is written (the iterable is the range
itself). Also the lower-synthesised varless for <coll> (while→for sugar).
BreakStmt :: struct A break, optionally naming the loop to leave.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID label: string | null ContinueStmt :: struct A continue, optionally naming the loop to resume.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID label: string | null DeferStmt :: struct A defer, running its body when the enclosing scope exits.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID body: DeferBody DeferStmtBody :: struct A defer deferring one statement.
Fields
stmt: Stmt DeferBlockBody :: struct A defer deferring a block.
Fields
block: BlockExpr Stmt :: LetStmt | AssignStmt | DestructureAssignStmt | ExprStmt | ReturnStmt | YieldStmt | ForStmt | BreakStmt | ContinueStmt | DeferStmt Anything that can appear as a statement in a block.
Declared AFTER its variant structs, as DeferBody is: the resolver collects
every decl name before resolving types, so either order works and this one
reads top-down.
DeferBody :: DeferStmtBody | DeferBlockBody What a defer defers: one statement, or a block.
IntLitExpr :: struct An integer literal.
Stored as u64 whatever the written base. The literal is FREE (it adopts the
surrounding concrete numeric type) so its node carries no width.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID value: u64 Unsigned magnitude, full u64 range (the snapshot format renders the
value as a decimal string with the trailing n so 0..u64.MAX all
round-trip losslessly).
negative: bool = false A - written against the literal: the parser reads -5 as one value,
so -128 is checked as the least i8 rather than as the negation of a
128 no i8 holds, and -5.to_string() is "-5". -(5) and - 5
stay negations.
FloatLitExpr :: struct A float literal.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID value: f64 Signed as written: the parser reads a - against the literal into it
(IntLitExpr.negative).
BoolLitExpr :: struct true or false.
Fields
NullLitExpr :: struct The null literal.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID CharLitExpr :: struct A character literal, stored as its codepoint.
Free like an integer literal: in an integer context it coerces to that width carrying the codepoint.
Fields
StringLitExpr :: struct A string literal, as the sequence of its parts.
Always a part list, even with no interpolation, so a reader never has to handle
two shapes. A generator emitting source must re-escape $, which opens an
interpolation.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID parts: StringLitPart[] StringLitText :: struct A run of literal text inside a string literal.
Fields
StringLitInterp :: struct An interpolated ${...} inside a string literal.
Fields
IdentExpr :: struct A bare name, in value or type position.
implicit_dot marks the leading-dot spelling (.Variant), where resolution
defers to the surrounding context. is_type_param_intro marks a dollar-prefixed
name introducing a type parameter rather than referring to one.
Both are plain bools defaulting to false, and the dumper emits their JSON
keys (implicitDot / isTypeParamIntro) only when true.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string implicit_dot: bool is_type_param_intro: bool CallExpr :: struct A call, callee(args).
callee is an arbitrary expression, so this covers f(x), a.f(x) (whose callee
is a FieldExpr) and P.f(x) alike: UFCS is not distinguished here.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID callee: Expr type_args: TypeExpr[] = [] The type arguments written on the call (f<i64>(x), x.f<i64>(y)) : the
callee's type parameters, first ones first. Empty when the call infers them.
args: CallArg[] trailing_comma: bool = false Source wrote a trailing comma before ): formatter keeps it exploded.
CallArg :: struct One argument of a call.
name is set for a named argument, spread for the ...xs form.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string | null value: Expr spread: bool Reserved for argument-spread f(...xs), not parsed yet, so always
false today. The consumers (printer / dump / ufcs) already honour it
so the feature only needs parse_call_args to set it.
FieldExpr :: struct A member access, target.field.
is_numeric marks a tuple index (t.0), whose "field" is a position.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID target: Expr field: string field_span: Span is_numeric: bool IndexExpr :: struct An index, target[index].
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID target: Expr index: Expr UnaryOp :: enum(u8) Which prefix operator a UnaryExpr applies.
Variants
Neg Not BitNot UnaryExpr :: struct A prefix operator applied to one operand.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID op: UnaryOp operand: Expr BinaryOp :: enum(u8) Which infix operator a BinaryExpr applies.
Variants
Add Sub Mul Div Mod Shl Shr BitAnd BitOr BitXor And Or Eq Neq Lt Lte Gt Gte Is In NotIn Same === / !== : the same object, not an equal value.
NotSame AddWrap +% / -% / *% : integer arithmetic that wraps around its width.
SubWrap MulWrap BinaryExpr :: struct An infix operator applied to two operands.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID op: BinaryOp left: Expr right: Expr NullCoalesceExpr :: struct Null-coalescing left ?? fallback. left must be typed T | null ;
the expression yields T (null subtracted). fallback supplies the
value when left is null : either a value expression assignable to T,
or a divergent statement (return / break / continue, typed
never). Lowers to _t :: left ; if _t is null { fallback } else { _t },
so the existing narrowing engine refines the result to T.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID left: Expr fallback: CoalesceFallback IfExpr :: struct An if, in statement or value position.
One node for both: in value position the blocks' trailing expressions are the result, so a generator emitting a conditional value writes this.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID cond: Expr then_block: BlockExpr else_branch: IfElse | null IfElseIf :: struct An else if, holding the next branch.
Fields
branch: IfExpr IfElseBlock :: struct A final else block.
Fields
block: BlockExpr MatchExpr :: struct A match over a scrutinee.
partial is @partial: the match is knowingly non-exhaustive; unreachable is
@unreachable: the uncovered variants are impossible and trap if reached.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID scrutinee: Expr arms: MatchArm[] partial: bool unreachable: bool = false binding: SimpleBinding | null = null match x :: e { … }: the name the scrutinee is bound to, visible in every
arm and guard and narrowed by each arm. Its name_span is the declaration
the resolver mints the symbol at.
MatchArm :: struct One arm of a match, with its optional if guard.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID pattern: Pattern guard: Expr | null body: Expr BlockExpr :: struct A braced block.
trailing is the block's value when it ends in an expression rather than a
statement, which is what makes a block usable in value position.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID stmts: Stmt[] trailing: Expr | null LambdaExpr :: struct A lambda, (x: i32) -> x + 1.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID params: FnParam[] return_type: TypeExpr | null body: BlockExpr trailing_comma: bool = false True when the source wrote a trailing comma after the last parameter.
StructLitExpr :: struct A struct literal, Point { .x = 1 }.
items mixes fields and spreads in SOURCE order, which a generator must preserve:
a later field overrides an earlier spread.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID type_name: TypeExpr items: (StructLitField | StructLitSpread)[] trailing_comma: bool = false True when the source wrote a trailing comma before }. The formatter
reads it as a "keep exploded" directive (Dart-style block layout).
StructLitItem :: StructLitField | StructLitSpread One entry of a struct literal: a field, or a spread.
StructLitField :: struct A .name = value entry of a struct literal.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span value: Expr StructLitSpread :: struct A ...expr entry of a struct literal.
Fields
SeqLitExpr :: struct An array or tuple literal, [a, b, c].
One node for both: the parser does not commit to array or tuple, and the typechecker decides from context.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID elements: Expr[] trailing_comma: bool = false True when the source wrote a trailing comma before ]. Drives the
formatter's Dart-style layout: present -> one element per line.
RangeExpr :: struct A range, a..<b or a..=b.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID inclusive: bool lower: Expr upper: Expr AwaitExpr :: struct await e: suspend the enclosing async fn until e's async result is
ready, then produce it. A single-operand prefix wrapper; carries no error
semantics of its own.
Fields
CastExpr :: struct An explicit conversion, T(value).
Only a genuine width or representation change needs one: a free literal adopts its context, so a cast around one is a style defect.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID target: TypeExpr value: Expr GenericInstExpr :: struct A generic type with its arguments written, Box<i32> : in a type, as a struct
literal's name, as the owner of a static member call (Bag<T>.make()). A call's
own type arguments are CallExpr.type_args.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID callee: Expr type_args: TypeExpr[] IntrinsicCallExpr :: struct A call to a compiler intrinsic: @size_of(T), @type_name(T).
name is the intrinsic ident WITHOUT the leading @. The recognised set lives
in vader/front/parser/intrinsics.vader, and the parser mints this node only for a
known name: an unknown @name(...) in expression position raises P1014.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span args: Expr[] trailing_comma: bool = false DotVariantExpr :: struct A bare .Variant, whose enum type comes from context.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID variant: string variant_span: Span IsPattern :: struct A type test in a pattern, is T. inner nests a further pattern.
Binds nothing: the arm reads the scrutinee, narrowed in place, or the name
the match header gives it (MatchExpr.binding).
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID ty: TypeExpr inner: Pattern | null StructPattern :: struct A struct destructuring pattern.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID fields: StructPatternField[] StructPatternField :: struct One field of a struct pattern.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID name: string name_span: Span value: PatternFieldValue PatternBinding :: struct A struct-pattern field bound to a name.
Fields
PatternLiteral :: struct A struct-pattern field matched against a literal.
Fields
value: Expr WildcardPattern :: struct The _ pattern.
On a closed union this silences the exhaustiveness check, which is why the style guide forbids it in value position there.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID EnumVariantPattern :: struct A pattern matching one enum variant, .Variant.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID variant: string LiteralPattern :: struct Match a literal value : 'a' -> …, 42 -> …, "ok" -> …, etc.
The inner value is the parsed literal as an Expr ; the lowerer
emits scrutinee == value as the arm predicate.
Fields
RangePattern :: struct Range pattern : 'a'..='z' -> …, 0..<10 -> …. Matches when a scalar
scrutinee falls in the range. inclusive = ..= (upper bound included),
else ..< (excluded). The bounds are literal scalar exprs (char / int) of
the scrutinee's type ; the lowerer emits
scrutinee >= lower && scrutinee <(=) upper. Binds nothing, and (like a
literal pattern over an open scalar domain) exhausts nothing.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID inclusive: bool lower: Expr upper: Expr OrPattern :: struct Or-pattern : .A | .B -> …, 1 | 2 | 3 -> …. Matches when ANY alternative
matches. Parsed from pipe-separated patterns in a match arm. The is A | B
form does NOT produce this: there the type parser folds A | B into a
single union IsPattern (equivalent narrowing + exhaustiveness). The
alternatives are value-level (enum variants / literals) and bind nothing.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID patterns: Pattern[] FnTypeExpr :: struct A function type as written, fn(i32) -> bool: type-only, with no value-side
counterpart.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID params: TypeExpr[] return_type: TypeExpr | null ArrayTypeExpr :: struct An array type as written, T[]: type-only.
The bracketed form [T1, T2] parses as a SeqLitExpr (tuple type) instead.
Fields
span: Span id: i64 = UNASSIGNED_NODE_ID element: TypeExpr DocComment :: struct The /// lines written right above a declaration, a struct field or an enum
variant: its vaderdoc.
Only a parse that asks for docs fills one (parse_source_with_docs in
vader/front/parser): the parse_source a build runs leaves every doc null, so
a build pays nothing for comments it never reads. A plain // or /* */ comment
between the lines and the declaration detaches them; blank lines do not.
Fields
lines: DocCommentLine[] One per /// line, in source order. Never empty.
DocCommentLine :: struct One /// line of a DocComment.
Fields
text: string What follows the three slashes, as written: the space after them included.
span: Span The whole comment, slashes included.
DocSections :: struct A vaderdoc split by its tags.
Fields
prose: string The lines outside every tag, as written, trimmed of the blank lines around them: CommonMark.
params: DocParam[] One per @param, in source order.
returns: string[] One text per @return.
errors: string[] One text per @error.
example: string[] The lines under @example, as written: indented, hidden ones included.
DocParam :: struct What one @param says.
Fields
name: string The parameter it names (doc_param_name).
text: string The rest, its continuation lines joined; "" when the name stands alone.
ExampleLine :: struct A line of an example, as a reader sees it.
Fields
text: string The line without the indentation the shown lines share, and without the
# of a hidden line.
Walk :: enum What the visitor of inspect answers for the node it was handed.
Variants
Descend Visit the node's children next.
Skip Leave the node's children out, and go on with the rest of the walk.
Stop End the whole walk.
Functions
coalesce_fallback_expr :: fn(f: CoalesceFallback) -> Expr | null The value expression a read-only expression walker should descend into
for a ?? fallback : the fallback expression itself, the value of a
return <expr>, or null for a bare return / break / continue.
Passes that also need to visit the statement node itself (id assignment,
typecheck, formatting) destructure CoalesceFallback directly instead.
Parameters
| f | CoalesceFallback | No description. |
Returns
Expr | null assign_places :: fn(s: DestructureAssignStmt) -> Expr[] The places a destructuring assignment writes, in order: every target but _.
For the walkers that visit a statement's sub-expressions and have no use for each element's kind.
Parameters
| s | DestructureAssignStmt | No description. |
Returns
Expr[] decorators_have :: fn(decs: Decorator[], name: string) -> bool Whether decs carries the decorator called name.
Here rather than in each pass: six modules open-coded this loop, and a decorator's presence is a question about the AST, which is what this module owns.
Parameters
| decs | Decorator[] | the declaration's decorators. |
| name | string | the decorator name, without the @. |
Returns
bool extern_header :: fn(decl: FnDecl, library: CLibrary | null) -> string The header that owns the prototype of an @extern declaration: its own
@c_header, else the header of the library it names. "" when none does.
The backend INCLUDES that header instead of synthesising a prototype: the header already has the real one, and a synthesised one that differs is a conflicting declaration the C compiler refuses outright.
Parameters
| decl | FnDecl | the bodyless function declaration. |
| library | CLibrary | null | the CLibrary it names, as evaluated ; null for none. |
Returns
string c_struct_spelling :: fn(decs: Decorator[], struct_name: string) -> string The C type a struct declaration mirrors, or "" when it carries no
@c_struct.
A bare @c_struct names the C type after the struct, read as a typedef
name: Color mirrors Color. The argument spells a C type whose name differs
(@c_struct("struct pollfd")). An argument that is not one plain string reads
as ""; the parser reports it (P1032, P1033).
Parameters
| decs | Decorator[] | the struct's decorators. |
| struct_name | string | the name the struct declares, never its mangled form. |
Returns
string body_source :: fn(fd: FnDecl) -> FnDecl The declaration whose body fd runs: for a member an impl inherits, the
trait's member (inherits), whose default body it is ; fd itself otherwise.
A reader of a member's body goes through it, or sees an inherited member as
bodyless.
Parameters
| fd | FnDecl | a function declaration. |
Returns
compound_assign_op :: fn(s: AssignStmt) -> BinaryOp | null The operator of a compound assignment lhs op= rhs, or null for a plain one.
target and value.left are one node, so they carry one span ; a
hand-written a = a + 1 reads an a of its own, at another position. A pass
that evaluates lhs's operands once, or prints the op= back, keys on this,
and a pass that copies an assignment keeps the two sides one node.
Parameters
| s | AssignStmt | the assignment. |
Returns
BinaryOp | null the arithmetic operator the op= spells, or null.int_literal_value :: fn(literal: IntLitExpr) -> i64 The value of an integer literal as an i64: a magnitude past i64 as its
bits, the least i64 from its magnitude 2^63.
Parameters
| literal | IntLitExpr | the literal. |
Returns
i64 int_literal_text :: fn(literal: IntLitExpr) -> string An integer literal in decimal, signed as written: -128, 300.
Parameters
| literal | IntLitExpr | the literal. |
Returns
string string_literal_text :: fn(e: Expr) -> string | null The text of e when it is a string literal of one run of text, with no
interpolation: what a name spelled out at build time reads (@field(x, "n")).
Parameters
| e | Expr | any expression. |
Returns
string | null null for anything else, an interpolation included.collect_bitor_variants :: fn(e: Expr) -> Expr[] Flatten a T | U | V | … BitOr BinaryExpr chain into its variant
list, in source order. Non-BitOr expressions are returned as a
single-element list. Used by vader/tools/fmt and vader/front/typecheck to
avoid hand-rolling the same recursion on each side.
Parameters
| e | Expr | No description. |
Returns
Expr[] doc_text :: fn(doc: DocComment | null) -> string The text of doc: its lines joined by \n, each without its /// and the one
space that usually follows.
Parameters
| doc | DocComment | null | a vaderdoc block, or null for none. |
Returns
string "" for null.decl_doc :: fn(decl: Decl) -> DocComment | null The vaderdoc written above decl.
Parameters
| decl | Decl | a declaration. |
Returns
DocComment | null null when it has none, when the parse did not keep docs, and for an import or an assert, which take none.doc_sections :: fn(text: string) -> DocSections Split the vaderdoc text by its tags.
A tag opens a line and runs until the next tag or a blank line: the lines
under it join its text, wherever they are indented. A non-tag line after a
blank one is prose again. @example runs to the end, and text on its own
line is the example's first line.
Parameters
| text | string | a vaderdoc, as doc_text gives it. |
Returns
Example
expand
import "std/io"
import "toolchain/ast"
s :: doc_sections("Add.\n\n@param a the first.\n@return the sum.")
println(s.params[0].name + ": " + s.returns[0])s :: doc_sections("Add.\n\n@param a the first.\n@return the sum.")
println(s.params[0].name + ": " + s.returns[0])doc_tag :: fn(line: string) -> string The vaderdoc tag line opens, or "": @param, @return, @error or
@example, followed by a blank or the end of the line, so @params opens
none.
Parameters
| line | string | a doc line, past its /// and its leading blanks. |
Returns
string doc_param_name :: fn(rest: string) -> string The parameter a @param names: the identifier its text opens with, "" when
it opens with none.
Parameters
| rest | string | the text after the tag, past its blanks. |
Returns
string shown_example :: fn(sections: DocSections) -> string The example of sections as a reader sees it: without the lines that open
with # , which set it up so that it compiles, and without the indentation
the shown lines share.
Parameters
| sections | DocSections | a split vaderdoc. |
Returns
string "" without an example.example_lines :: fn(sections: DocSections) -> ExampleLine[] The lines of the example of sections, hidden ones included, without the
blank lines around them.
Parameters
| sections | DocSections | a split vaderdoc. |
Returns
ExampleLine[] none without an example.inspect :: fn(n: Node, visit: fn(Node) -> Walk) -> bool Walk n and every node under it, root first, children in source order.
Every child is reached: type positions (a parameter's type, a cast's target,
an is pattern's type), decorator arguments, lambda and defer bodies, the
statement a ?? falls back to. A node shared with another field is reached
once: the place of a compound assignment x op= y is the left operand of its
value, and a @comptime const leaves its comptime_body and its type to the
helper fn the parser synthesised, which holds both. What is not a node (a
binding, a field name) is for the visitor to read on the node
that carries it.
Parameters
| n | Node | the root. |
| visit | fn(Node) -> Walk | called on every node reached ; its answer steers the walk. |
Returns
bool true when the visitor answered Stop.Example
names: string[]: []
inspect(decl, (n) -> {
if n is IdentExpr {
names.push(n.name)
}
return .Descend
})Constants
UNASSIGNED_NODE_ID: i64: 0 Sentinel id on a node the parser has just minted.
Node ids are assigned in a later pass (vader/front/parser::assign_program_ids), which
replaces this with a fresh unique value (≥ 1), so a freshly parsed tree carries
the sentinel everywhere. A generator reading a typechecked tree sees real ids;
one building nodes by hand leaves them at the default: it is every node's
.id default, so a construction may omit the field.