Skip to main content

qala_compiler/
typed_ast.rs

1//! the typed AST: what the type checker produces and codegen consumes.
2//!
3//! mirrors `ast.rs` node-by-node and adds a resolved [`QalaType`] annotation on
4//! every expression node and an [`EffectSet`] annotation on every function
5//! node. nothing is desugared -- [`TypedExpr::Pipeline`], [`TypedExpr::Match`],
6//! [`TypedExpr::OrElse`], and [`TypedExpr::Interpolation`] stay faithful, same
7//! rule as the untyped AST; lowering happens in Phase 4 codegen.
8//!
9//! the type checker takes `&ast::Ast` and returns a [`TypedAst`] by value plus
10//! error and warning vectors; no interior mutability, no side tables, no
11//! shared references to the untyped tree. the typed tree is built once and
12//! never mutated; codegen reads it through the uniform [`TypedExpr::ty`] /
13//! [`TypedExpr::span`] / [`TypedFnDecl::effect`] accessors.
14//!
15//! `Pattern`, `UnaryOp`, `BinOp` are re-used from `ast.rs` unchanged: in v1
16//! patterns carry no resolved type (the scrutinee's [`TypedExpr::ty`] is the
17//! relevant fact during exhaustiveness checking), and operators carry no type
18//! information either -- the typed nodes wrapping them carry the type.
19//!
20//! the node enums derive `Debug, Clone, PartialEq` and no more: no `Eq`,
21//! because float literals reach the typed AST and `f64` is not `Eq`; no
22//! `serde`, because the typed AST is in-process data the renderer never sees.
23
24use crate::ast::{BinOp, Pattern, UnaryOp};
25use crate::effects::EffectSet;
26use crate::span::Span;
27use crate::types::QalaType;
28
29/// a whole typed program: the top-level typed items in source order.
30///
31/// the mirror of `ast::Ast`. a type alias rather than a wrapper struct, for the
32/// same reason: a program's span is just the source's span, which the caller
33/// already holds.
34pub type TypedAst = Vec<TypedItem>;
35
36// ---- items -----------------------------------------------------------------
37
38/// a typed top-level declaration: a function, a struct, an enum, or an
39/// interface. the mirror of `ast::Item`. `fn Type.method` definitions are a
40/// [`TypedItem::Fn`] whose [`TypedFnDecl`] carries an optional `type_name`.
41#[derive(Debug, Clone, PartialEq)]
42pub enum TypedItem {
43    /// a typed `fn` declaration -- a free function, or a `fn Type.method`
44    /// method definition when `type_name` is set.
45    Fn(TypedFnDecl),
46    /// a typed `struct` declaration with resolved field types.
47    Struct(TypedStructDecl),
48    /// a typed `enum` declaration with resolved variant field types.
49    Enum(TypedEnumDecl),
50    /// a typed `interface` declaration -- method signatures with resolved
51    /// parameter and return types and inferred effect sets.
52    Interface(TypedInterfaceDecl),
53}
54
55impl TypedItem {
56    /// the source span of this item, opening keyword to closing brace.
57    ///
58    /// each item kind wraps a typed decl that carries the span; this delegates
59    /// to it. exhaustive over every kind, so a new item kind forces an arm here.
60    pub fn span(&self) -> Span {
61        match self {
62            TypedItem::Fn(d) => d.span,
63            TypedItem::Struct(d) => d.span,
64            TypedItem::Enum(d) => d.span,
65            TypedItem::Interface(d) => d.span,
66        }
67    }
68}
69
70/// a typed `fn` declaration. mirror of `ast::FnDecl` with the parser's
71/// [`Option<TypeExpr>`](crate::ast::TypeExpr) return type resolved to a
72/// [`QalaType`] (`void` resolves to [`QalaType::Void`]) and the parser's
73/// optional `is X` annotation replaced by an inferred [`EffectSet`] that is
74/// always present.
75#[derive(Debug, Clone, PartialEq)]
76pub struct TypedFnDecl {
77    /// the receiver type's name for a `fn Type.method` definition, else `None`.
78    pub type_name: Option<String>,
79    /// the function (or method) name.
80    pub name: String,
81    /// the typed parameter list, in order; for a method, the first may be
82    /// `self`.
83    pub params: Vec<TypedParam>,
84    /// the resolved return type. `void` is the resolved-form of an omitted
85    /// `-> T`.
86    pub ret_ty: QalaType,
87    /// the inferred effect set (or the annotated set if `is X` was present and
88    /// was a superset of the inferred). always present in the typed AST, even
89    /// when the source had no `is X` annotation.
90    pub effect: EffectSet,
91    /// the function body.
92    pub body: TypedBlock,
93    /// `fn` keyword to closing `}`.
94    pub span: Span,
95}
96
97impl TypedFnDecl {
98    /// the inferred effect set of this function.
99    ///
100    /// [`EffectSet`] is `Copy`, so returning by value is the same cost as a
101    /// reference and reads cleaner at call sites. this is the public accessor
102    /// the diagnostics renderer (Plan 5) consults when building the
103    /// `EffectViolation` message's caller-effect slot.
104    pub fn effect(&self) -> EffectSet {
105        self.effect
106    }
107}
108
109/// one typed parameter of a function or method. mirror of `ast::Param`. for the
110/// `self` first parameter of a method the type is the receiver's type
111/// (resolved by the type checker via the [`TypedFnDecl::type_name`]).
112#[derive(Debug, Clone, PartialEq)]
113pub struct TypedParam {
114    /// true if this is the `self` first parameter of a method.
115    pub is_self: bool,
116    /// the parameter name (`"self"` when `is_self`).
117    pub name: String,
118    /// the resolved parameter type. for `self`, the receiver type
119    /// ([`QalaType::Named`] of the enclosing method's `type_name`).
120    pub ty: QalaType,
121    /// the `= expr` default value, type-checked against `ty`, or `None`.
122    pub default: Option<TypedExpr>,
123    /// the parameter's source span (name to default, or name to type).
124    pub span: Span,
125}
126
127/// a typed `struct` declaration. mirror of `ast::StructDecl` with field types
128/// resolved.
129#[derive(Debug, Clone, PartialEq)]
130pub struct TypedStructDecl {
131    /// the struct's name.
132    pub name: String,
133    /// the typed fields, in declaration order.
134    pub fields: Vec<TypedField>,
135    /// `struct` keyword to closing `}`.
136    pub span: Span,
137}
138
139/// one typed field of a struct: a name and a resolved type.
140#[derive(Debug, Clone, PartialEq)]
141pub struct TypedField {
142    /// the field name.
143    pub name: String,
144    /// the field's resolved type.
145    pub ty: QalaType,
146    /// the field's source span (name to type).
147    pub span: Span,
148}
149
150/// a typed `enum` declaration. mirror of `ast::EnumDecl` with each variant's
151/// carried field types resolved.
152#[derive(Debug, Clone, PartialEq)]
153pub struct TypedEnumDecl {
154    /// the enum's name.
155    pub name: String,
156    /// the typed variants, in declaration order.
157    pub variants: Vec<TypedVariant>,
158    /// `enum` keyword to closing `}`.
159    pub span: Span,
160}
161
162/// one typed variant of an enum: a name and zero or more resolved field types.
163#[derive(Debug, Clone, PartialEq)]
164pub struct TypedVariant {
165    /// the variant name.
166    pub name: String,
167    /// the resolved types of the variant's data fields (possibly empty).
168    pub fields: Vec<QalaType>,
169    /// the variant's source span.
170    pub span: Span,
171}
172
173/// a typed `interface` declaration. mirror of `ast::InterfaceDecl` with method
174/// signatures' parameter and return types resolved and effect sets present.
175#[derive(Debug, Clone, PartialEq)]
176pub struct TypedInterfaceDecl {
177    /// the interface's name.
178    pub name: String,
179    /// the typed method signatures the interface requires.
180    pub methods: Vec<TypedMethodSig>,
181    /// `interface` keyword to closing `}`.
182    pub span: Span,
183}
184
185/// one typed method signature in an interface: like a [`TypedFnDecl`] without a
186/// body and without a `type_name`. the first parameter is typically `self`.
187#[derive(Debug, Clone, PartialEq)]
188pub struct TypedMethodSig {
189    /// the method name.
190    pub name: String,
191    /// the typed parameter list; the first is typically `self`.
192    pub params: Vec<TypedParam>,
193    /// the resolved return type. `void` is the resolved-form of an omitted
194    /// `-> T`.
195    pub ret_ty: QalaType,
196    /// the effect set required of any conforming implementation. (an interface
197    /// method's `is X` annotation, or the inferred set when conformance is
198    /// being checked.)
199    pub effect: EffectSet,
200    /// the signature's source span.
201    pub span: Span,
202}
203
204// ---- statements and blocks -------------------------------------------------
205
206/// a typed `{ ... }` block. mirror of `ast::Block`, plus the resolved type of
207/// the block's value: `ty` is the type of the trailing value expression, or
208/// [`QalaType::Void`] if there is no trailing value (the block ended in a `;`
209/// or was empty).
210#[derive(Debug, Clone, PartialEq)]
211pub struct TypedBlock {
212    /// the typed statements, in order.
213    pub stmts: Vec<TypedStmt>,
214    /// the trailing expression (no terminating `;`) that is the block's value,
215    /// or `None` for a `void` block.
216    pub value: Option<Box<TypedExpr>>,
217    /// the type of the trailing value expression, or [`QalaType::Void`] for an
218    /// empty / semicolon-ended block. the typechecker fills this; codegen reads
219    /// it to decide whether the block leaves a value on the stack.
220    pub ty: QalaType,
221    /// `{` to `}`.
222    pub span: Span,
223}
224
225/// a typed statement. mirror of `ast::Stmt`: `if`/`else`, `while`, `for`,
226/// `return`, `break`, `continue`, `defer`, and expression-statements all stay
227/// statements -- there is no `TypedExpr::If` in v1.
228#[derive(Debug, Clone, PartialEq)]
229pub enum TypedStmt {
230    /// `let name = init` or `let mut name = init`. `ty` is the resolved type
231    /// of the binding (inferred from `init` or checked against an explicit
232    /// annotation). `is_mut` is carried forward from the untyped AST so
233    /// codegen does not have to re-read the original tree.
234    Let {
235        is_mut: bool,
236        name: String,
237        ty: QalaType,
238        init: TypedExpr,
239        span: Span,
240    },
241    /// `if cond { ... }` with an optional `else` (a block, or another `if` for
242    /// an `else if` chain). a statement, not an expression.
243    If {
244        cond: TypedExpr,
245        then_block: TypedBlock,
246        else_branch: Option<TypedElseBranch>,
247        span: Span,
248    },
249    /// `while cond { ... }`.
250    While {
251        cond: TypedExpr,
252        body: TypedBlock,
253        span: Span,
254    },
255    /// `for var in iter { ... }`. `var_ty` is the resolved type of the loop
256    /// variable (the element type of the iterable). kept as a `For` node, not
257    /// lowered.
258    For {
259        var: String,
260        var_ty: QalaType,
261        iter: TypedExpr,
262        body: TypedBlock,
263        span: Span,
264    },
265    /// `return` or `return expr`. the value is optional -- a bare `return` in
266    /// a `void` function is legal.
267    Return {
268        value: Option<TypedExpr>,
269        span: Span,
270    },
271    /// `break`. no labels, no value in v1.
272    Break { span: Span },
273    /// `continue`. no labels, no value in v1.
274    Continue { span: Span },
275    /// `defer expr` -- `expr` runs at scope exit, LIFO with other defers in the
276    /// same scope.
277    Defer { expr: TypedExpr, span: Span },
278    /// an expression used as a statement (`expr ;`). its value is discarded.
279    Expr { expr: TypedExpr, span: Span },
280}
281
282impl TypedStmt {
283    /// the source span of this statement.
284    ///
285    /// every variant carries its `span` field; this match is exhaustive, which
286    /// is the proof every typed statement has one.
287    pub fn span(&self) -> Span {
288        match self {
289            TypedStmt::Let { span, .. }
290            | TypedStmt::If { span, .. }
291            | TypedStmt::While { span, .. }
292            | TypedStmt::For { span, .. }
293            | TypedStmt::Return { span, .. }
294            | TypedStmt::Break { span }
295            | TypedStmt::Continue { span }
296            | TypedStmt::Defer { span, .. }
297            | TypedStmt::Expr { span, .. } => *span,
298        }
299    }
300}
301
302/// the `else` part of a [`TypedStmt::If`]: either a final `{ ... }` block, or
303/// another `if` (the boxed [`TypedStmt`] is always a [`TypedStmt::If`]) for an
304/// `else if` chain. mirror of `ast::ElseBranch`.
305#[derive(Debug, Clone, PartialEq)]
306pub enum TypedElseBranch {
307    /// `else { ... }`.
308    Block(TypedBlock),
309    /// `else if ...` -- the boxed statement is a [`TypedStmt::If`].
310    If(Box<TypedStmt>),
311}
312
313// ---- expressions -----------------------------------------------------------
314
315/// a typed expression. mirror of `ast::Expr` with a resolved [`QalaType`]
316/// annotation on every variant.
317///
318/// nothing is desugared: [`TypedExpr::Pipeline`] stays a pipeline (not
319/// `f(x, a)`), [`TypedExpr::Interpolation`] stays a parts list (not a `+`
320/// chain), [`TypedExpr::Match`] and [`TypedExpr::Block`] produce values
321/// directly. the rule is the same as for the untyped AST -- the typechecker
322/// only types; codegen lowers.
323///
324/// every recursive position is a [`Box<TypedExpr>`]; the variant-level `ty`
325/// field is the design choice over a wrapper struct (`TypedExpr { kind, span,
326/// ty }`) -- per-variant means [`TypedExpr::ty`] is one exhaustive match that
327/// the compiler enforces.
328#[derive(Debug, Clone, PartialEq)]
329pub enum TypedExpr {
330    /// an integer literal with its resolved type ([`QalaType::I64`]).
331    Int {
332        value: i64,
333        ty: QalaType,
334        span: Span,
335    },
336    /// a float literal with its resolved type ([`QalaType::F64`]).
337    Float {
338        value: f64,
339        ty: QalaType,
340        span: Span,
341    },
342    /// a byte literal with its resolved type ([`QalaType::Byte`]).
343    Byte { value: u8, ty: QalaType, span: Span },
344    /// a string literal with its resolved type ([`QalaType::Str`]).
345    Str {
346        value: String,
347        ty: QalaType,
348        span: Span,
349    },
350    /// a boolean literal with its resolved type ([`QalaType::Bool`]).
351    Bool {
352        value: bool,
353        ty: QalaType,
354        span: Span,
355    },
356    /// an identifier reference resolved to its bound type.
357    Ident {
358        name: String,
359        ty: QalaType,
360        span: Span,
361    },
362    /// `( inner )` -- a parenthesized expression. semantically transparent; the
363    /// resolved type is the inner's type.
364    Paren {
365        inner: Box<TypedExpr>,
366        ty: QalaType,
367        span: Span,
368    },
369    /// `( e1, e2, ... )` -- a tuple. resolved type is
370    /// [`QalaType::Tuple`] of the element types.
371    Tuple {
372        elems: Vec<TypedExpr>,
373        ty: QalaType,
374        span: Span,
375    },
376    /// `[ e1, e2, ... ]` -- an array literal. resolved type is
377    /// [`QalaType::Array`] with `Some(n)` length matching `elems.len()`.
378    ArrayLit {
379        elems: Vec<TypedExpr>,
380        ty: QalaType,
381        span: Span,
382    },
383    /// `[ value; count ]` -- the repeat form of an array literal.
384    ArrayRepeat {
385        value: Box<TypedExpr>,
386        count: Box<TypedExpr>,
387        ty: QalaType,
388        span: Span,
389    },
390    /// `Name { field: e, ... }` -- a struct literal. resolved type is
391    /// [`QalaType::Named`] of the struct.
392    StructLit {
393        name: String,
394        fields: Vec<TypedFieldInit>,
395        ty: QalaType,
396        span: Span,
397    },
398    /// `obj.name` -- field access. distinct from [`TypedExpr::MethodCall`]:
399    /// this is the form with no `( ... )` after the `.name`. resolved type is
400    /// the field's type.
401    FieldAccess {
402        obj: Box<TypedExpr>,
403        name: String,
404        ty: QalaType,
405        span: Span,
406    },
407    /// `receiver.name(args)` -- a method call. distinct from a field access
408    /// followed by a call; the typechecker resolves it to the
409    /// `fn Type.name(self, ...)` declaration. resolved type is the method's
410    /// return type.
411    MethodCall {
412        receiver: Box<TypedExpr>,
413        name: String,
414        args: Vec<TypedExpr>,
415        ty: QalaType,
416        span: Span,
417    },
418    /// `callee(args)` -- a call expression. resolved type is the callee's
419    /// return type.
420    Call {
421        callee: Box<TypedExpr>,
422        args: Vec<TypedExpr>,
423        ty: QalaType,
424        span: Span,
425    },
426    /// `obj[index]` -- an index expression. resolved type is the element type
427    /// of the array.
428    Index {
429        obj: Box<TypedExpr>,
430        index: Box<TypedExpr>,
431        ty: QalaType,
432        span: Span,
433    },
434    /// `expr?` -- error propagation. resolved type is the `Ok` payload (for
435    /// `Result<T, E>`) or the `Some` payload (for `Option<T>`).
436    Try {
437        expr: Box<TypedExpr>,
438        ty: QalaType,
439        span: Span,
440    },
441    /// a unary-operator application: `!operand` or `-operand`. resolved type
442    /// is determined by the operand and the operator.
443    Unary {
444        op: UnaryOp,
445        operand: Box<TypedExpr>,
446        ty: QalaType,
447        span: Span,
448    },
449    /// a binary-operator application. resolved type is determined by the
450    /// operator (arithmetic: matches the operand type; comparison and equality:
451    /// `bool`; `&&`/`||`: `bool`). the `or` fallback is *not* here -- it is
452    /// [`TypedExpr::OrElse`].
453    Binary {
454        op: BinOp,
455        lhs: Box<TypedExpr>,
456        rhs: Box<TypedExpr>,
457        ty: QalaType,
458        span: Span,
459    },
460    /// `start .. end` or `start ..= end`. resolved type is the iterable type
461    /// the range stands for.
462    Range {
463        start: Option<Box<TypedExpr>>,
464        end: Option<Box<TypedExpr>>,
465        inclusive: bool,
466        ty: QalaType,
467        span: Span,
468    },
469    /// `lhs |> call` -- the pipeline operator. NOT desugared in the typed AST
470    /// either; desugaring is Phase 4 codegen's job. resolved type is the call's
471    /// return type.
472    Pipeline {
473        lhs: Box<TypedExpr>,
474        call: Box<TypedExpr>,
475        ty: QalaType,
476        span: Span,
477    },
478    /// `comptime body` -- compile-time evaluated. resolved type is the body's
479    /// type (the typechecker only types it; evaluation is codegen).
480    Comptime {
481        body: Box<TypedExpr>,
482        ty: QalaType,
483        span: Span,
484    },
485    /// `{ ...; trailing }` used as an expression. resolved type is the block's
486    /// trailing-value type (or `void`); duplicated at this level so the
487    /// uniform [`TypedExpr::ty`] accessor reads from a variant field rather
488    /// than peeking into the [`TypedBlock`].
489    Block {
490        block: TypedBlock,
491        ty: QalaType,
492        span: Span,
493    },
494    /// `match scrutinee { arm, ... }`. NOT desugared. resolved type is the
495    /// common type of all arm bodies; exhaustiveness is checked by the
496    /// typechecker against the scrutinee's enum type.
497    Match {
498        scrutinee: Box<TypedExpr>,
499        arms: Vec<TypedMatchArm>,
500        ty: QalaType,
501        span: Span,
502    },
503    /// `expr or fallback` -- the inline fallback for a `Result`/`Option`. NOT
504    /// desugared. resolved type is the `T` of `Result<T, E>` / `Option<T>`.
505    OrElse {
506        expr: Box<TypedExpr>,
507        fallback: Box<TypedExpr>,
508        ty: QalaType,
509        span: Span,
510    },
511    /// a string with `{expr}` interpolations. NOT desugared to a `+` chain;
512    /// the conversion-and-concatenation happens in Phase 4 codegen. resolved
513    /// type is [`QalaType::Str`].
514    Interpolation {
515        parts: Vec<TypedInterpPart>,
516        ty: QalaType,
517        span: Span,
518    },
519}
520
521impl TypedExpr {
522    /// the resolved type of this expression.
523    ///
524    /// exhaustive match: the proof every typed expression has a type. the
525    /// typechecker fills this; codegen reads it to drive instruction selection
526    /// (int add vs float add, struct-field offset, exhaustiveness, ...).
527    pub fn ty(&self) -> &QalaType {
528        match self {
529            TypedExpr::Int { ty, .. }
530            | TypedExpr::Float { ty, .. }
531            | TypedExpr::Byte { ty, .. }
532            | TypedExpr::Str { ty, .. }
533            | TypedExpr::Bool { ty, .. }
534            | TypedExpr::Ident { ty, .. }
535            | TypedExpr::Paren { ty, .. }
536            | TypedExpr::Tuple { ty, .. }
537            | TypedExpr::ArrayLit { ty, .. }
538            | TypedExpr::ArrayRepeat { ty, .. }
539            | TypedExpr::StructLit { ty, .. }
540            | TypedExpr::FieldAccess { ty, .. }
541            | TypedExpr::MethodCall { ty, .. }
542            | TypedExpr::Call { ty, .. }
543            | TypedExpr::Index { ty, .. }
544            | TypedExpr::Try { ty, .. }
545            | TypedExpr::Unary { ty, .. }
546            | TypedExpr::Binary { ty, .. }
547            | TypedExpr::Range { ty, .. }
548            | TypedExpr::Pipeline { ty, .. }
549            | TypedExpr::Comptime { ty, .. }
550            | TypedExpr::Block { ty, .. }
551            | TypedExpr::Match { ty, .. }
552            | TypedExpr::OrElse { ty, .. }
553            | TypedExpr::Interpolation { ty, .. } => ty,
554        }
555    }
556
557    /// the source span of this expression.
558    ///
559    /// every variant carries its `span` field; this match is exhaustive over
560    /// all of them, the same shape as `ast::Expr::span()`. the typechecker
561    /// copies the span from the untyped node when it constructs the typed one.
562    pub fn span(&self) -> Span {
563        match self {
564            TypedExpr::Int { span, .. }
565            | TypedExpr::Float { span, .. }
566            | TypedExpr::Byte { span, .. }
567            | TypedExpr::Str { span, .. }
568            | TypedExpr::Bool { span, .. }
569            | TypedExpr::Ident { span, .. }
570            | TypedExpr::Paren { span, .. }
571            | TypedExpr::Tuple { span, .. }
572            | TypedExpr::ArrayLit { span, .. }
573            | TypedExpr::ArrayRepeat { span, .. }
574            | TypedExpr::StructLit { span, .. }
575            | TypedExpr::FieldAccess { span, .. }
576            | TypedExpr::MethodCall { span, .. }
577            | TypedExpr::Call { span, .. }
578            | TypedExpr::Index { span, .. }
579            | TypedExpr::Try { span, .. }
580            | TypedExpr::Unary { span, .. }
581            | TypedExpr::Binary { span, .. }
582            | TypedExpr::Range { span, .. }
583            | TypedExpr::Pipeline { span, .. }
584            | TypedExpr::Comptime { span, .. }
585            | TypedExpr::Block { span, .. }
586            | TypedExpr::Match { span, .. }
587            | TypedExpr::OrElse { span, .. }
588            | TypedExpr::Interpolation { span, .. } => *span,
589        }
590    }
591}
592
593/// one typed field initializer in a struct literal: `name: value`. mirror of
594/// `ast::FieldInit`.
595#[derive(Debug, Clone, PartialEq)]
596pub struct TypedFieldInit {
597    /// the field name.
598    pub name: String,
599    /// the typed value expression.
600    pub value: TypedExpr,
601    /// the initializer's source span (name to value).
602    pub span: Span,
603}
604
605/// one piece of a typed string interpolation: either literal text, or an
606/// embedded typed expression. matches `ast::InterpPart` but holds
607/// [`TypedExpr`] instead of `Expr`.
608#[derive(Debug, Clone, PartialEq)]
609pub enum TypedInterpPart {
610    /// literal text between interpolations (may be empty).
611    Literal(String),
612    /// an embedded `{ expr }`, type-checked to a value the conversion
613    /// machinery can stringify.
614    Expr(TypedExpr),
615}
616
617/// a typed `match` arm. mirror of `ast::MatchArm`. the pattern is re-used from
618/// the untyped AST -- patterns carry no resolved type in v1 (the scrutinee's
619/// [`TypedExpr::ty`] is the relevant fact during exhaustiveness checking).
620#[derive(Debug, Clone, PartialEq)]
621pub struct TypedMatchArm {
622    /// the pattern this arm matches; reused unchanged from `ast::Pattern`.
623    pub pattern: Pattern,
624    /// the `if expr` guard, type-checked to `bool`, or `None`.
625    pub guard: Option<TypedExpr>,
626    /// the arm body -- a typed expression or a typed block.
627    pub body: TypedMatchArmBody,
628    /// the arm's source span.
629    pub span: Span,
630}
631
632/// a typed `match` arm body: a bare typed expression (`Pattern => expr`) or a
633/// typed block (`Pattern => { ... }`). mirror of `ast::MatchArmBody`.
634#[derive(Debug, Clone, PartialEq)]
635pub enum TypedMatchArmBody {
636    /// `=> expr`.
637    Expr(Box<TypedExpr>),
638    /// `=> { ... }`.
639    Block(TypedBlock),
640}
641
642#[cfg(test)]
643mod tests {
644    use super::*;
645    use crate::ast::{BinOp, Pattern, UnaryOp};
646    use crate::effects::EffectSet;
647    use crate::span::Span;
648    use crate::types::{QalaType, Symbol};
649
650    // a distinct span per call, so a wrong span() arm or a clobbered field is
651    // caught: span(n) and span(m) never compare equal for n != m. same shape
652    // as the helper in ast.rs's tests.
653    fn span(n: usize) -> Span {
654        Span::new(n, n + 1)
655    }
656
657    #[test]
658    fn nodes_are_debug_clone_and_partial_eq() {
659        // build a small typed expression tree, clone it, assert equal; build a
660        // different one, assert not equal. mirror of ast.rs's analogous test.
661        let a = TypedExpr::Binary {
662            op: BinOp::Add,
663            lhs: Box::new(TypedExpr::Int {
664                value: 1,
665                ty: QalaType::I64,
666                span: span(0),
667            }),
668            rhs: Box::new(TypedExpr::Int {
669                value: 2,
670                ty: QalaType::I64,
671                span: span(2),
672            }),
673            ty: QalaType::I64,
674            span: span(0),
675        };
676        let b = a.clone();
677        assert_eq!(a, b);
678        // Debug must be derived too (assertion messages and the typechecker
679        // tests use it).
680        let _ = format!("{a:?}");
681        let c = TypedExpr::Binary {
682            op: BinOp::Mul, // different operator
683            lhs: Box::new(TypedExpr::Int {
684                value: 1,
685                ty: QalaType::I64,
686                span: span(0),
687            }),
688            rhs: Box::new(TypedExpr::Int {
689                value: 2,
690                ty: QalaType::I64,
691                span: span(2),
692            }),
693            ty: QalaType::I64,
694            span: span(0),
695        };
696        assert_ne!(a, c);
697    }
698
699    #[test]
700    fn typed_expr_ty_returns_the_stored_type() {
701        // a sample of variants, each with a chosen ty; .ty() must return a
702        // borrow that compares equal to the chosen ty. this is the proof every
703        // variant in the .ty() exhaustive match returns the right field.
704        let cases: Vec<(TypedExpr, QalaType)> = vec![
705            (
706                TypedExpr::Int {
707                    value: 0,
708                    ty: QalaType::I64,
709                    span: span(1),
710                },
711                QalaType::I64,
712            ),
713            (
714                TypedExpr::Float {
715                    value: 0.0,
716                    ty: QalaType::F64,
717                    span: span(2),
718                },
719                QalaType::F64,
720            ),
721            (
722                TypedExpr::Byte {
723                    value: 0,
724                    ty: QalaType::Byte,
725                    span: span(3),
726                },
727                QalaType::Byte,
728            ),
729            (
730                TypedExpr::Str {
731                    value: String::new(),
732                    ty: QalaType::Str,
733                    span: span(4),
734                },
735                QalaType::Str,
736            ),
737            (
738                TypedExpr::Bool {
739                    value: true,
740                    ty: QalaType::Bool,
741                    span: span(5),
742                },
743                QalaType::Bool,
744            ),
745            (
746                TypedExpr::Ident {
747                    name: "x".to_string(),
748                    ty: QalaType::Named(Symbol("Shape".to_string())),
749                    span: span(6),
750                },
751                QalaType::Named(Symbol("Shape".to_string())),
752            ),
753            (
754                TypedExpr::Unary {
755                    op: UnaryOp::Neg,
756                    operand: Box::new(TypedExpr::Int {
757                        value: 1,
758                        ty: QalaType::I64,
759                        span: span(8),
760                    }),
761                    ty: QalaType::I64,
762                    span: span(7),
763                },
764                QalaType::I64,
765            ),
766            (
767                TypedExpr::Tuple {
768                    elems: vec![
769                        TypedExpr::Int {
770                            value: 1,
771                            ty: QalaType::I64,
772                            span: span(10),
773                        },
774                        TypedExpr::Bool {
775                            value: true,
776                            ty: QalaType::Bool,
777                            span: span(11),
778                        },
779                    ],
780                    ty: QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]),
781                    span: span(9),
782                },
783                QalaType::Tuple(vec![QalaType::I64, QalaType::Bool]),
784            ),
785            // Unknown is a legal poison type after a type error.
786            (
787                TypedExpr::Call {
788                    callee: Box::new(TypedExpr::Ident {
789                        name: "f".to_string(),
790                        ty: QalaType::Unknown,
791                        span: span(13),
792                    }),
793                    args: vec![],
794                    ty: QalaType::Unknown,
795                    span: span(12),
796                },
797                QalaType::Unknown,
798            ),
799        ];
800        for (expr, expected) in cases {
801            assert_eq!(expr.ty(), &expected, "ty() mismatch for {expr:?}");
802        }
803    }
804
805    #[test]
806    fn typed_expr_span_returns_the_stored_span() {
807        // mirror of ast.rs's expr_span_returns_the_stored_span_for_a_sample_of_variants:
808        // every faithful node form (incl. Pipeline / OrElse / Interpolation /
809        // MethodCall) is represented so a missing arm in .span() trips the test.
810        let cases: Vec<(TypedExpr, Span)> = vec![
811            (
812                TypedExpr::Int {
813                    value: 0,
814                    ty: QalaType::I64,
815                    span: span(1),
816                },
817                span(1),
818            ),
819            (
820                TypedExpr::Float {
821                    value: 0.0,
822                    ty: QalaType::F64,
823                    span: span(2),
824                },
825                span(2),
826            ),
827            (
828                TypedExpr::Byte {
829                    value: 0,
830                    ty: QalaType::Byte,
831                    span: span(3),
832                },
833                span(3),
834            ),
835            (
836                TypedExpr::Str {
837                    value: String::new(),
838                    ty: QalaType::Str,
839                    span: span(4),
840                },
841                span(4),
842            ),
843            (
844                TypedExpr::Bool {
845                    value: true,
846                    ty: QalaType::Bool,
847                    span: span(5),
848                },
849                span(5),
850            ),
851            (
852                TypedExpr::Ident {
853                    name: "x".to_string(),
854                    ty: QalaType::I64,
855                    span: span(6),
856                },
857                span(6),
858            ),
859            (
860                TypedExpr::Unary {
861                    op: UnaryOp::Neg,
862                    operand: Box::new(TypedExpr::Int {
863                        value: 1,
864                        ty: QalaType::I64,
865                        span: span(8),
866                    }),
867                    ty: QalaType::I64,
868                    span: span(7),
869                },
870                span(7),
871            ),
872            (
873                TypedExpr::MethodCall {
874                    receiver: Box::new(TypedExpr::Ident {
875                        name: "f".to_string(),
876                        ty: QalaType::FileHandle,
877                        span: span(10),
878                    }),
879                    name: "read_all".to_string(),
880                    args: vec![],
881                    ty: QalaType::Str,
882                    span: span(9),
883                },
884                span(9),
885            ),
886            (
887                TypedExpr::Pipeline {
888                    lhs: Box::new(TypedExpr::Int {
889                        value: 5,
890                        ty: QalaType::I64,
891                        span: span(12),
892                    }),
893                    call: Box::new(TypedExpr::Ident {
894                        name: "double".to_string(),
895                        ty: QalaType::Function {
896                            params: vec![QalaType::I64],
897                            returns: Box::new(QalaType::I64),
898                        },
899                        span: span(14),
900                    }),
901                    ty: QalaType::I64,
902                    span: span(11),
903                },
904                span(11),
905            ),
906            (
907                TypedExpr::OrElse {
908                    expr: Box::new(TypedExpr::Ident {
909                        name: "a".to_string(),
910                        ty: QalaType::Option(Box::new(QalaType::Str)),
911                        span: span(16),
912                    }),
913                    fallback: Box::new(TypedExpr::Str {
914                        value: "no data".to_string(),
915                        ty: QalaType::Str,
916                        span: span(18),
917                    }),
918                    ty: QalaType::Str,
919                    span: span(15),
920                },
921                span(15),
922            ),
923            (
924                TypedExpr::Interpolation {
925                    parts: vec![
926                        TypedInterpPart::Literal("fib(".to_string()),
927                        TypedInterpPart::Expr(TypedExpr::Ident {
928                            name: "i".to_string(),
929                            ty: QalaType::I64,
930                            span: span(20),
931                        }),
932                        TypedInterpPart::Literal(")".to_string()),
933                    ],
934                    ty: QalaType::Str,
935                    span: span(19),
936                },
937                span(19),
938            ),
939            (
940                TypedExpr::Match {
941                    scrutinee: Box::new(TypedExpr::Ident {
942                        name: "v".to_string(),
943                        ty: QalaType::I64,
944                        span: span(22),
945                    }),
946                    arms: vec![TypedMatchArm {
947                        pattern: Pattern::Wildcard { span: span(23) },
948                        guard: None,
949                        body: TypedMatchArmBody::Expr(Box::new(TypedExpr::Int {
950                            value: 0,
951                            ty: QalaType::I64,
952                            span: span(24),
953                        })),
954                        span: span(23),
955                    }],
956                    ty: QalaType::I64,
957                    span: span(21),
958                },
959                span(21),
960            ),
961        ];
962        for (expr, expected) in cases {
963            assert_eq!(expr.span(), expected, "span() mismatch for {expr:?}");
964        }
965    }
966
967    #[test]
968    fn typed_stmt_span_returns_the_stored_span() {
969        // mirror of ast.rs's stmt-span test.
970        let cases: Vec<(TypedStmt, Span)> = vec![
971            (
972                TypedStmt::Let {
973                    is_mut: false,
974                    name: "x".to_string(),
975                    ty: QalaType::I64,
976                    init: TypedExpr::Int {
977                        value: 1,
978                        ty: QalaType::I64,
979                        span: span(1),
980                    },
981                    span: span(0),
982                },
983                span(0),
984            ),
985            (TypedStmt::Break { span: span(2) }, span(2)),
986            (TypedStmt::Continue { span: span(3) }, span(3)),
987            (
988                TypedStmt::Return {
989                    value: None,
990                    span: span(4),
991                },
992                span(4),
993            ),
994            (
995                TypedStmt::For {
996                    var: "i".to_string(),
997                    var_ty: QalaType::I64,
998                    iter: TypedExpr::Range {
999                        start: Some(Box::new(TypedExpr::Int {
1000                            value: 0,
1001                            ty: QalaType::I64,
1002                            span: span(6),
1003                        })),
1004                        end: Some(Box::new(TypedExpr::Int {
1005                            value: 15,
1006                            ty: QalaType::I64,
1007                            span: span(8),
1008                        })),
1009                        inclusive: false,
1010                        ty: QalaType::Array(Box::new(QalaType::I64), None),
1011                        span: span(6),
1012                    },
1013                    body: TypedBlock {
1014                        stmts: vec![],
1015                        value: None,
1016                        ty: QalaType::Void,
1017                        span: span(9),
1018                    },
1019                    span: span(5),
1020                },
1021                span(5),
1022            ),
1023            (
1024                TypedStmt::Defer {
1025                    expr: TypedExpr::Call {
1026                        callee: Box::new(TypedExpr::Ident {
1027                            name: "close".to_string(),
1028                            ty: QalaType::Function {
1029                                params: vec![QalaType::FileHandle],
1030                                returns: Box::new(QalaType::Void),
1031                            },
1032                            span: span(11),
1033                        }),
1034                        args: vec![TypedExpr::Ident {
1035                            name: "f".to_string(),
1036                            ty: QalaType::FileHandle,
1037                            span: span(12),
1038                        }],
1039                        ty: QalaType::Void,
1040                        span: span(11),
1041                    },
1042                    span: span(10),
1043                },
1044                span(10),
1045            ),
1046        ];
1047        for (stmt, expected) in cases {
1048            assert_eq!(stmt.span(), expected, "span() mismatch for {stmt:?}");
1049        }
1050    }
1051
1052    #[test]
1053    fn typed_item_span_delegates_to_the_wrapped_decl() {
1054        // mirror of ast.rs's item_span_delegates_to_the_wrapped_decl: build
1055        // one of each TypedItem kind and check the span() delegates correctly.
1056        let f = TypedItem::Fn(TypedFnDecl {
1057            type_name: None,
1058            name: "main".to_string(),
1059            params: vec![],
1060            ret_ty: QalaType::Void,
1061            effect: EffectSet::io(),
1062            body: TypedBlock {
1063                stmts: vec![],
1064                value: None,
1065                ty: QalaType::Void,
1066                span: span(1),
1067            },
1068            span: span(0),
1069        });
1070        assert_eq!(f.span(), span(0));
1071        let s = TypedItem::Struct(TypedStructDecl {
1072            name: "S".to_string(),
1073            fields: vec![],
1074            span: span(2),
1075        });
1076        assert_eq!(s.span(), span(2));
1077        let e = TypedItem::Enum(TypedEnumDecl {
1078            name: "E".to_string(),
1079            variants: vec![],
1080            span: span(3),
1081        });
1082        assert_eq!(e.span(), span(3));
1083        let i = TypedItem::Interface(TypedInterfaceDecl {
1084            name: "I".to_string(),
1085            methods: vec![],
1086            span: span(4),
1087        });
1088        assert_eq!(i.span(), span(4));
1089    }
1090
1091    #[test]
1092    fn typed_fn_decl_effect_returns_the_stored_effect_set() {
1093        // every effect annotation flavor: pure (empty), single-flag (io), and
1094        // a unioned set must round-trip through .effect().
1095        let cases = [
1096            EffectSet::pure(),
1097            EffectSet::io(),
1098            EffectSet::alloc(),
1099            EffectSet::panic(),
1100            EffectSet::io().union(EffectSet::alloc()),
1101            EffectSet::full(),
1102        ];
1103        for eff in cases {
1104            let f = TypedFnDecl {
1105                type_name: None,
1106                name: "f".to_string(),
1107                params: vec![],
1108                ret_ty: QalaType::Void,
1109                effect: eff,
1110                body: TypedBlock {
1111                    stmts: vec![],
1112                    value: None,
1113                    ty: QalaType::Void,
1114                    span: span(1),
1115                },
1116                span: span(0),
1117            };
1118            assert_eq!(f.effect(), eff, "effect() mismatch for {eff:?}");
1119        }
1120    }
1121
1122    #[test]
1123    fn typed_block_ty_is_void_for_empty_block() {
1124        // an empty block has no trailing value; its resolved ty is Void.
1125        let b = TypedBlock {
1126            stmts: vec![],
1127            value: None,
1128            ty: QalaType::Void,
1129            span: span(0),
1130        };
1131        assert_eq!(b.ty, QalaType::Void);
1132        // a block with a trailing value carries that value's type.
1133        let b2 = TypedBlock {
1134            stmts: vec![TypedStmt::Let {
1135                is_mut: false,
1136                name: "x".to_string(),
1137                ty: QalaType::I64,
1138                init: TypedExpr::Int {
1139                    value: 1,
1140                    ty: QalaType::I64,
1141                    span: span(1),
1142                },
1143                span: span(0),
1144            }],
1145            value: Some(Box::new(TypedExpr::Ident {
1146                name: "x".to_string(),
1147                ty: QalaType::I64,
1148                span: span(3),
1149            })),
1150            ty: QalaType::I64,
1151            span: span(0),
1152        };
1153        assert_eq!(b2.ty, QalaType::I64);
1154        assert!(b2.value.is_some());
1155        assert_eq!(b2.stmts.len(), 1);
1156    }
1157
1158    #[test]
1159    fn typed_ast_is_a_vec_of_typed_items() {
1160        // TypedAst is a type alias over Vec<TypedItem> -- an empty source is
1161        // an empty Vec; pushing items appends as usual. mirror of ast.rs's
1162        // type alias usage pattern.
1163        let mut prog: TypedAst = vec![];
1164        assert!(prog.is_empty());
1165        prog.push(TypedItem::Struct(TypedStructDecl {
1166            name: "Point".to_string(),
1167            fields: vec![
1168                TypedField {
1169                    name: "x".to_string(),
1170                    ty: QalaType::I64,
1171                    span: span(1),
1172                },
1173                TypedField {
1174                    name: "y".to_string(),
1175                    ty: QalaType::I64,
1176                    span: span(2),
1177                },
1178            ],
1179            span: span(0),
1180        }));
1181        assert_eq!(prog.len(), 1);
1182    }
1183
1184    #[test]
1185    fn reexported_unary_and_bin_ops_are_usable_in_typed_ast() {
1186        // UnaryOp and BinOp are reused from ast.rs (no type info to add to a
1187        // bare operator). this test pins the fact that they are reachable from
1188        // typed_ast call sites without a duplicate definition.
1189        let _u: UnaryOp = UnaryOp::Not;
1190        let _b: BinOp = BinOp::Add;
1191        // a typed Unary node uses the ast::UnaryOp value directly.
1192        let neg = TypedExpr::Unary {
1193            op: UnaryOp::Neg,
1194            operand: Box::new(TypedExpr::Int {
1195                value: 5,
1196                ty: QalaType::I64,
1197                span: span(1),
1198            }),
1199            ty: QalaType::I64,
1200            span: span(0),
1201        };
1202        assert_eq!(neg.ty(), &QalaType::I64);
1203    }
1204
1205    #[test]
1206    fn pattern_is_reused_from_ast_in_typed_match_arms() {
1207        // patterns carry no resolved type in v1 -- the scrutinee's TypedExpr
1208        // .ty() is the relevant fact during exhaustiveness checking. so a
1209        // TypedMatchArm holds the original ast::Pattern unchanged.
1210        let arm = TypedMatchArm {
1211            pattern: Pattern::Variant {
1212                name: "Circle".to_string(),
1213                sub: vec![Pattern::Binding {
1214                    name: "r".to_string(),
1215                    span: span(1),
1216                }],
1217                span: span(0),
1218            },
1219            guard: Some(TypedExpr::Binary {
1220                op: BinOp::Gt,
1221                lhs: Box::new(TypedExpr::Ident {
1222                    name: "r".to_string(),
1223                    ty: QalaType::F64,
1224                    span: span(2),
1225                }),
1226                rhs: Box::new(TypedExpr::Float {
1227                    value: 0.0,
1228                    ty: QalaType::F64,
1229                    span: span(3),
1230                }),
1231                ty: QalaType::Bool,
1232                span: span(2),
1233            }),
1234            body: TypedMatchArmBody::Expr(Box::new(TypedExpr::Float {
1235                value: 1.0,
1236                ty: QalaType::F64,
1237                span: span(4),
1238            })),
1239            span: span(0),
1240        };
1241        // the pattern compares equal to itself (it is the ast::Pattern's
1242        // derived PartialEq, unchanged here).
1243        assert_eq!(
1244            arm.pattern,
1245            Pattern::Variant {
1246                name: "Circle".to_string(),
1247                sub: vec![Pattern::Binding {
1248                    name: "r".to_string(),
1249                    span: span(1)
1250                }],
1251                span: span(0),
1252            }
1253        );
1254        assert!(arm.guard.is_some());
1255    }
1256
1257    #[test]
1258    fn faithful_nodes_pipeline_match_orelse_interpolation_exist() {
1259        // the typed AST is faithful, same as the untyped AST: Pipeline,
1260        // Match, OrElse, and Interpolation are real variants, not desugared.
1261        // build one of each and confirm .ty() / .span() work.
1262        let pipe = TypedExpr::Pipeline {
1263            lhs: Box::new(TypedExpr::Int {
1264                value: 1,
1265                ty: QalaType::I64,
1266                span: span(1),
1267            }),
1268            call: Box::new(TypedExpr::Ident {
1269                name: "double".to_string(),
1270                ty: QalaType::Function {
1271                    params: vec![QalaType::I64],
1272                    returns: Box::new(QalaType::I64),
1273                },
1274                span: span(3),
1275            }),
1276            ty: QalaType::I64,
1277            span: span(0),
1278        };
1279        assert_eq!(pipe.ty(), &QalaType::I64);
1280        assert_eq!(pipe.span(), span(0));
1281
1282        let m = TypedExpr::Match {
1283            scrutinee: Box::new(TypedExpr::Bool {
1284                value: true,
1285                ty: QalaType::Bool,
1286                span: span(5),
1287            }),
1288            arms: vec![TypedMatchArm {
1289                pattern: Pattern::Bool {
1290                    value: true,
1291                    span: span(6),
1292                },
1293                guard: None,
1294                body: TypedMatchArmBody::Expr(Box::new(TypedExpr::Int {
1295                    value: 1,
1296                    ty: QalaType::I64,
1297                    span: span(7),
1298                })),
1299                span: span(6),
1300            }],
1301            ty: QalaType::I64,
1302            span: span(4),
1303        };
1304        assert_eq!(m.ty(), &QalaType::I64);
1305
1306        let or = TypedExpr::OrElse {
1307            expr: Box::new(TypedExpr::Ident {
1308                name: "x".to_string(),
1309                ty: QalaType::Option(Box::new(QalaType::I64)),
1310                span: span(9),
1311            }),
1312            fallback: Box::new(TypedExpr::Int {
1313                value: 0,
1314                ty: QalaType::I64,
1315                span: span(10),
1316            }),
1317            ty: QalaType::I64,
1318            span: span(8),
1319        };
1320        assert_eq!(or.ty(), &QalaType::I64);
1321
1322        let interp = TypedExpr::Interpolation {
1323            parts: vec![
1324                TypedInterpPart::Literal("hello, ".to_string()),
1325                TypedInterpPart::Expr(TypedExpr::Ident {
1326                    name: "name".to_string(),
1327                    ty: QalaType::Str,
1328                    span: span(12),
1329                }),
1330            ],
1331            ty: QalaType::Str,
1332            span: span(11),
1333        };
1334        assert_eq!(interp.ty(), &QalaType::Str);
1335    }
1336}