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}