Skip to main content

cove_syntax/
ast.rs

1//! The Cove abstract syntax tree.
2//!
3//! The tree mirrors the surface language described by `docs/LANGUAGE_CARD.md`.
4//! Where the Language Card and ADR 0001 disagree, the Language Card wins.
5//! What each form below *means* — how it is typed, how it evaluates, and
6//! which errors it can produce — is stated once in
7//! `docs/LANGUAGE_REFERENCE.md`.
8
9use cove_diag::{Span, Spanned};
10
11pub type Ident = Spanned<String>;
12
13/// One `.cove` file. Every file in a directory is an implementation unit of the
14/// same module.
15#[derive(Clone, Debug)]
16pub struct SourceUnit {
17    pub uses: Vec<Use>,
18    pub items: Vec<Item>,
19    pub span: Span,
20}
21
22/// `use console.println` or `use http`.
23#[derive(Clone, Debug)]
24pub struct Use {
25    pub path: Vec<Ident>,
26    pub span: Span,
27}
28
29/// A top-level declaration.
30#[derive(Clone, Debug)]
31pub struct Item {
32    pub doc: Option<String>,
33    pub exported: bool,
34    /// `test fn name() -> Result<Unit, Error>`: a declaration the test
35    /// runner calls and nothing else does.
36    ///
37    /// `test` occupies the position `export` occupies and says the
38    /// comparable thing — who may call this — so a declaration carries at
39    /// most one of the two, and only a `fn` carries `test` at all.
40    pub is_test: bool,
41    /// `export opaque struct User { ... }`: the export publishes the type's
42    /// name and its exported methods, and nothing about how it is built.
43    ///
44    /// `opaque` narrows an `export` rather than standing in for one, so it
45    /// only ever appears together with one, and only on a struct: exporting
46    /// an enum exports its cases, because a `match` over them is what the
47    /// enum is for. See ADR 0014.
48    pub is_opaque: bool,
49    pub kind: ItemKind,
50    pub span: Span,
51}
52
53#[derive(Clone, Debug)]
54pub enum ItemKind {
55    Fn(FnDecl),
56    Struct(StructDecl),
57    Enum(EnumDecl),
58    /// `trait Display { fn describe(self) -> String }`
59    Trait(TraitDecl),
60    Impl(ImplBlock),
61    /// `export type Handler = async fn(...) -> ...`
62    TypeAlias(TypeAlias),
63}
64
65/// One type parameter of a declaration, with the traits it is bounded by.
66///
67/// A bound is checked at the call site that instantiates the parameter, and
68/// it is what makes a method call on a value of that parameter resolvable.
69#[derive(Clone, Debug)]
70pub struct GenericParam {
71    pub name: Ident,
72    /// `T: Display + Ordered` binds two traits; an unbounded `T` binds none.
73    pub bounds: Vec<Ident>,
74    pub span: Span,
75}
76
77impl GenericParam {
78    /// An unbounded parameter, which is what every generic was before traits.
79    pub fn unbounded(name: Ident) -> GenericParam {
80        let span = name.span;
81        GenericParam {
82            name,
83            bounds: Vec::new(),
84            span,
85        }
86    }
87}
88
89/// Renders a type parameter back to declaration syntax: `T` or `T: A + B`.
90impl std::fmt::Display for GenericParam {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.write_str(&self.name.node)?;
93        for (i, bound) in self.bounds.iter().enumerate() {
94            f.write_str(if i == 0 { ": " } else { " + " })?;
95            f.write_str(&bound.node)?;
96        }
97        Ok(())
98    }
99}
100
101#[derive(Clone, Debug)]
102pub struct FnDecl {
103    pub name: Ident,
104    pub is_async: bool,
105    pub generics: Vec<GenericParam>,
106    /// `self` / `var self`, present on methods only.
107    pub receiver: Option<Receiver>,
108    pub params: Vec<Param>,
109    pub return_type: Option<Type>,
110    pub body: Block,
111    pub span: Span,
112}
113
114/// The `self` parameter of a method.
115#[derive(Clone, Copy, Debug)]
116pub struct Receiver {
117    /// `var self` declares a mutating receiver.
118    pub is_var: bool,
119    pub span: Span,
120}
121
122#[derive(Clone, Debug)]
123pub struct Param {
124    /// A `var` parameter is a non-escaping inout alias, marked at both the
125    /// declaration and the call site.
126    pub is_var: bool,
127    pub name: Ident,
128    pub ty: Option<Type>,
129    /// `items: T...` is an immutable `Array<T>` inside the function.
130    pub variadic: bool,
131    pub default: Option<Expr>,
132    pub span: Span,
133}
134
135#[derive(Clone, Debug)]
136pub struct StructDecl {
137    pub name: Ident,
138    pub generics: Vec<GenericParam>,
139    pub fields: Vec<Field>,
140    pub span: Span,
141}
142
143#[derive(Clone, Debug)]
144pub struct Field {
145    pub doc: Option<String>,
146    pub name: Ident,
147    pub ty: Type,
148    pub span: Span,
149}
150
151#[derive(Clone, Debug)]
152pub struct EnumDecl {
153    pub name: Ident,
154    pub generics: Vec<GenericParam>,
155    pub cases: Vec<EnumCase>,
156    pub span: Span,
157}
158
159#[derive(Clone, Debug)]
160pub struct EnumCase {
161    pub doc: Option<String>,
162    pub name: Ident,
163    /// `InvalidPort(String)` carries positional payload types.
164    pub payload: Vec<Type>,
165    pub span: Span,
166}
167
168/// A trait: a set of method signatures a type conforms to explicitly.
169///
170/// Conformance is only ever declared by an `impl Trait for Type` block; there
171/// is no structural conformance and no blanket implementation.
172#[derive(Clone, Debug)]
173pub struct TraitDecl {
174    pub name: Ident,
175    pub methods: Vec<TraitMethod>,
176    pub span: Span,
177}
178
179/// One method signature a trait declares, with an optional default body.
180///
181/// A method with a default body is supplied by every conformance that does
182/// not override it; one without must be supplied by every conformance.
183#[derive(Clone, Debug)]
184pub struct TraitMethod {
185    pub doc: Option<String>,
186    pub name: Ident,
187    pub is_async: bool,
188    /// `self` / `var self`. A method without one is an associated function,
189    /// which has no receiver and so cannot be called through `dyn Trait`.
190    pub receiver: Option<Receiver>,
191    pub params: Vec<Param>,
192    pub return_type: Option<Type>,
193    pub default: Option<Block>,
194    pub span: Span,
195}
196
197/// `impl Type { ... }`, or `impl Trait for Type { ... }` when `trait_name` is
198/// present.
199#[derive(Clone, Debug)]
200pub struct ImplBlock {
201    /// The trait this block declares a conformance to, for `impl Trait for
202    /// Type`.
203    pub trait_name: Option<Ident>,
204    pub type_name: Ident,
205    pub generics: Vec<GenericParam>,
206    pub items: Vec<Item>,
207    pub span: Span,
208}
209
210#[derive(Clone, Debug)]
211pub struct TypeAlias {
212    pub name: Ident,
213    pub generics: Vec<GenericParam>,
214    pub ty: Type,
215    pub span: Span,
216}
217
218/// A type expression.
219#[derive(Clone, Debug)]
220pub struct Type {
221    pub kind: TypeKind,
222    pub span: Span,
223}
224
225#[derive(Clone, Debug)]
226pub enum TypeKind {
227    /// `Int`, `Array<T>`, `http.Request`, `Result<T, E>`.
228    Named { path: Vec<Ident>, args: Vec<Type> },
229    /// `async fn(request: http.Request) -> Result<http.Response, Error>`
230    Fn {
231        is_async: bool,
232        params: Vec<Param>,
233        return_type: Option<Box<Type>>,
234    },
235    /// `dyn Display`: a value of any type that conforms to the trait,
236    /// carrying its implementation with it.
237    Dyn(Ident),
238    /// `()`
239    Unit,
240}
241
242/// Renders a type back to the Cove source form it would be written in, so
243/// tooling such as `cove outline` can show a typed interface without the
244/// user writing it twice.
245impl std::fmt::Display for Type {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        match &self.kind {
248            TypeKind::Unit => write!(f, "()"),
249            TypeKind::Dyn(name) => write!(f, "dyn {}", name.node),
250            TypeKind::Named { path, args } => {
251                let path = path
252                    .iter()
253                    .map(|segment| segment.node.as_str())
254                    .collect::<Vec<_>>()
255                    .join(".");
256                write!(f, "{path}")?;
257                if !args.is_empty() {
258                    let args = args
259                        .iter()
260                        .map(|arg| arg.to_string())
261                        .collect::<Vec<_>>()
262                        .join(", ");
263                    write!(f, "<{args}>")?;
264                }
265                Ok(())
266            }
267            TypeKind::Fn {
268                is_async,
269                params,
270                return_type,
271            } => {
272                if *is_async {
273                    write!(f, "async ")?;
274                }
275                let params = params
276                    .iter()
277                    .map(|param| param.to_string())
278                    .collect::<Vec<_>>()
279                    .join(", ");
280                write!(f, "fn({params})")?;
281                if let Some(return_type) = return_type {
282                    write!(f, " -> {return_type}")?;
283                }
284                Ok(())
285            }
286        }
287    }
288}
289
290/// Renders a parameter back to declaration syntax: `[var ]name: Type[...]`.
291/// A parameter with no type (a lambda parameter) prints just its name, and a
292/// function-type parameter with no name (the parser's convention for a bare
293/// type in a `fn(...)` type) prints just its type.
294impl std::fmt::Display for Param {
295    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296        let Some(ty) = &self.ty else {
297            return write!(f, "{}", self.name.node);
298        };
299        if self.is_var {
300            write!(f, "var ")?;
301        }
302        if !self.name.node.is_empty() {
303            write!(f, "{}: ", self.name.node)?;
304        }
305        write!(f, "{ty}")?;
306        if self.variadic {
307            write!(f, "...")?;
308        }
309        // A default is part of the signature: adding one is a compatible
310        // change, and removing one is not, so a rendering that dropped it
311        // could not tell the two apart.
312        if let Some(default) = &self.default {
313            write!(f, " = {}", crate::format::format_expr(default))?;
314        }
315        Ok(())
316    }
317}
318
319#[derive(Clone, Debug)]
320pub struct Block {
321    pub statements: Vec<Stmt>,
322    /// The last expression in a block is its value.
323    pub tail: Option<Box<Expr>>,
324    pub span: Span,
325}
326
327#[derive(Clone, Debug)]
328pub struct Stmt {
329    pub kind: StmtKind,
330    pub span: Span,
331}
332
333#[derive(Clone, Debug)]
334pub enum StmtKind {
335    /// `let name: T = expr` / `var name = expr`
336    Let {
337        is_var: bool,
338        name: Ident,
339        ty: Option<Type>,
340        value: Expr,
341    },
342    Expr(Expr),
343    /// A nested declaration, such as a local `fn`.
344    Item(Box<Item>),
345}
346
347/// Identifies one expression within the file it was parsed from.
348///
349/// The id is assigned by [`number_unit`](crate::number::number_unit) once the
350/// file has been parsed, not by the parser itself: an expression the parser
351/// builds carries [`ExprId::UNSET`] until that pass has run over the whole
352/// unit. [`parse_file`](crate::parse_file) runs it, so a tree a caller
353/// receives never holds an unset id.
354///
355/// An id is unique within one [`SourceUnit`] and means nothing outside it.
356/// Every file numbers from zero, so an id is only ever readable alongside the
357/// file it came from, and comparing ids across files compares nothing.
358///
359/// It exists so that a pass can record what it worked out about an expression
360/// in a side table indexed by the id — a `Vec` as long as the file has
361/// expressions — instead of a map keyed by a hash of the expression. A hash
362/// cannot tell two identical subexpressions apart, and an address does not
363/// survive the tree being moved or cloned; a dense index does both, and is a
364/// load rather than a lookup.
365#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
366pub struct ExprId(pub u32);
367
368impl ExprId {
369    /// The id an expression has before `number_unit` has run.
370    ///
371    /// Every expression the parser builds starts here and none keeps it, so
372    /// this value marks a tree that was built by hand or never numbered
373    /// rather than one position within a file.
374    pub const UNSET: ExprId = ExprId(u32::MAX);
375}
376
377#[derive(Clone, Debug)]
378pub struct Expr {
379    /// Unique within the file this expression was parsed from. See
380    /// [`ExprId`].
381    pub id: ExprId,
382    pub kind: ExprKind,
383    pub span: Span,
384}
385
386/// One argument at a call site.
387#[derive(Clone, Debug)]
388pub struct Arg {
389    /// Static argument labels are parameter names and part of the API contract.
390    pub label: Option<Ident>,
391    /// `fill(var output)` marks an inout alias at the call site.
392    pub is_var: bool,
393    /// `...array` spreads into a variadic parameter.
394    pub spread: bool,
395    pub value: Expr,
396    pub span: Span,
397}
398
399#[derive(Clone, Debug)]
400pub enum ExprKind {
401    Int(i64),
402    Float(f64),
403    Bool(bool),
404    Duration(i64),
405    /// A string literal with its interpolated expressions already parsed.
406    Str(Vec<StrPart>),
407    /// `()`
408    Unit,
409    /// A bare name.
410    Ident(String),
411    /// `[1, 2]` produces an immutable `Array`.
412    ArrayLit(Vec<Expr>),
413    /// `console.println`, `LogLevel.Debug`, `self.status`
414    Field {
415        base: Box<Expr>,
416        name: Ident,
417    },
418    /// `f(a, b: c)`; also struct initialization via synthesized labeled calls.
419    Call {
420        callee: Box<Expr>,
421        generics: Vec<Type>,
422        args: Vec<Arg>,
423        /// `clock.timeout(500ms) { ... }` and `tasks.spawn { ... }`.
424        trailing: Option<Box<Expr>>,
425    },
426    Unary {
427        op: UnaryOp,
428        operand: Box<Expr>,
429    },
430    Binary {
431        op: BinaryOp,
432        lhs: Box<Expr>,
433        rhs: Box<Expr>,
434    },
435    /// `place = value`, `place += value`
436    Assign {
437        op: Option<BinaryOp>,
438        target: Box<Expr>,
439        value: Box<Expr>,
440    },
441    /// `expr?` returns the error from the current function.
442    Try(Box<Expr>),
443    Await(Box<Expr>),
444    Block(Block),
445    If {
446        condition: Box<Expr>,
447        then_branch: Block,
448        else_branch: Option<Box<Expr>>,
449    },
450    /// `match` must cover every enum case.
451    Match {
452        scrutinee: Box<Expr>,
453        arms: Vec<MatchArm>,
454    },
455    /// A loop is an expression. It evaluates to `Unit`, because it can
456    /// reach its end without breaking and there is nothing at that end to
457    /// produce but `Unit`.
458    For {
459        binding: Ident,
460        iterable: Box<Expr>,
461        body: Block,
462    },
463    While {
464        condition: Box<Expr>,
465        body: Block,
466    },
467    Return(Option<Box<Expr>>),
468    /// `break` / `break expr`. Exits the nearest enclosing loop, which is
469    /// `Unit` however it leaves: `expr` is evaluated for its effects and its
470    /// value discarded. Resolve rejects a `break` outside a loop.
471    Break(Option<Box<Expr>>),
472    /// `continue`. Skips to the next iteration of the nearest enclosing loop.
473    /// Resolve rejects this outside a loop.
474    Continue,
475    /// `fn(x) { ... }` / `async fn(x) { ... }`
476    Lambda {
477        is_async: bool,
478        params: Vec<Param>,
479        body: Block,
480    },
481    /// `scope tasks { ... }`
482    Scope {
483        name: Ident,
484        body: Block,
485    },
486    /// `0..<attempts` and `0..n`
487    Range {
488        start: Box<Expr>,
489        end: Box<Expr>,
490        inclusive_end: bool,
491    },
492}
493
494/// A resolved piece of a string literal.
495#[derive(Clone, Debug)]
496pub enum StrPart {
497    Text(String),
498    Interpolation(Expr),
499}
500
501#[derive(Clone, Copy, Debug, PartialEq, Eq)]
502pub enum UnaryOp {
503    Not,
504    Neg,
505}
506
507#[derive(Clone, Copy, Debug, PartialEq, Eq)]
508pub enum BinaryOp {
509    Add,
510    Sub,
511    Mul,
512    Div,
513    Rem,
514    Eq,
515    Ne,
516    Lt,
517    Le,
518    Gt,
519    Ge,
520    /// `a is b`: shared-storage identity, for the handful of types that have
521    /// one. Same precedence as `==`; see the Language Card.
522    Is,
523    And,
524    Or,
525}
526
527#[derive(Clone, Debug)]
528pub struct MatchArm {
529    pub pattern: Pattern,
530    pub body: Expr,
531    pub span: Span,
532}
533
534#[derive(Clone, Debug)]
535pub struct Pattern {
536    pub kind: PatternKind,
537    pub span: Span,
538}
539
540#[derive(Clone, Debug)]
541pub enum PatternKind {
542    /// `_`
543    Wildcard,
544    /// `other` — binds the scrutinee.
545    Binding(String),
546    /// `"debug"`, `1`, `true`
547    Literal(Expr),
548    /// `Ok(value)`, `LogLevel.Debug`, `ConfigError.InvalidPort(raw)`
549    Variant {
550        path: Vec<Ident>,
551        payload: Vec<Pattern>,
552    },
553}
554
555#[cfg(test)]
556mod tests {
557    use super::*;
558
559    fn span() -> Span {
560        Span::new(cove_diag::FileId(0), 0, 0)
561    }
562
563    fn ident(name: &str) -> Ident {
564        Spanned::new(name.to_string(), span())
565    }
566
567    fn named(path: &[&str], args: Vec<Type>) -> Type {
568        Type {
569            kind: TypeKind::Named {
570                path: path.iter().map(|s| ident(s)).collect(),
571                args,
572            },
573            span: span(),
574        }
575    }
576
577    fn unit() -> Type {
578        Type {
579            kind: TypeKind::Unit,
580            span: span(),
581        }
582    }
583
584    fn param(name: &str, ty: Option<Type>) -> Param {
585        Param {
586            is_var: false,
587            name: ident(name),
588            ty,
589            variadic: false,
590            default: None,
591            span: span(),
592        }
593    }
594
595    #[test]
596    fn displays_a_plain_named_type() {
597        assert_eq!(named(&["Int"], vec![]).to_string(), "Int");
598    }
599
600    #[test]
601    fn displays_a_named_type_with_one_argument() {
602        assert_eq!(
603            named(&["Array"], vec![named(&["String"], vec![])]).to_string(),
604            "Array<String>"
605        );
606    }
607
608    #[test]
609    fn displays_a_dotted_path() {
610        assert_eq!(
611            named(&["http", "Request"], vec![]).to_string(),
612            "http.Request"
613        );
614    }
615
616    #[test]
617    fn displays_a_named_type_with_two_arguments() {
618        assert_eq!(
619            named(
620                &["Result"],
621                vec![named(&["Unit"], vec![]), named(&["Error"], vec![])]
622            )
623            .to_string(),
624            "Result<Unit, Error>"
625        );
626    }
627
628    #[test]
629    fn displays_nested_generics() {
630        let ty = named(
631            &["Map"],
632            vec![
633                named(&["String"], vec![]),
634                named(&["Array"], vec![named(&["EventHandler"], vec![])]),
635            ],
636        );
637        assert_eq!(ty.to_string(), "Map<String, Array<EventHandler>>");
638    }
639
640    #[test]
641    fn displays_unit() {
642        assert_eq!(unit().to_string(), "()");
643    }
644
645    #[test]
646    fn displays_a_fn_type_with_named_params_and_return_type() {
647        let ty = Type {
648            kind: TypeKind::Fn {
649                is_async: false,
650                params: vec![
651                    param("name", Some(named(&["Type"], vec![]))),
652                    param("other", Some(named(&["Type"], vec![]))),
653                ],
654                return_type: Some(Box::new(named(&["Ret"], vec![]))),
655            },
656            span: span(),
657        };
658        assert_eq!(ty.to_string(), "fn(name: Type, other: Type) -> Ret");
659    }
660
661    #[test]
662    fn displays_an_async_fn_type() {
663        let ty = Type {
664            kind: TypeKind::Fn {
665                is_async: true,
666                params: vec![],
667                return_type: None,
668            },
669            span: span(),
670        };
671        assert_eq!(ty.to_string(), "async fn()");
672    }
673
674    #[test]
675    fn displays_a_fn_type_with_no_return_type() {
676        let ty = Type {
677            kind: TypeKind::Fn {
678                is_async: false,
679                params: vec![param("x", Some(named(&["Int"], vec![])))],
680                return_type: None,
681            },
682            span: span(),
683        };
684        assert_eq!(ty.to_string(), "fn(x: Int)");
685    }
686
687    #[test]
688    fn displays_an_unnamed_fn_type_param() {
689        let ty = Type {
690            kind: TypeKind::Fn {
691                is_async: false,
692                params: vec![param("", Some(named(&["String"], vec![])))],
693                return_type: None,
694            },
695            span: span(),
696        };
697        assert_eq!(ty.to_string(), "fn(String)");
698    }
699
700    #[test]
701    fn displays_a_var_and_variadic_fn_type_param() {
702        let mut p = param("items", Some(named(&["Int"], vec![])));
703        p.is_var = true;
704        p.variadic = true;
705        let ty = Type {
706            kind: TypeKind::Fn {
707                is_async: false,
708                params: vec![p],
709                return_type: None,
710            },
711            span: span(),
712        };
713        assert_eq!(ty.to_string(), "fn(var items: Int...)");
714    }
715
716    #[test]
717    fn displays_a_lambda_param_with_no_type() {
718        assert_eq!(param("x", None).to_string(), "x");
719    }
720}
721
722#[cfg(test)]
723mod param_tests {
724    use super::*;
725    use cove_diag::SourceMap;
726
727    fn parse_one(source: &str) -> SourceUnit {
728        let mut sources = SourceMap::new();
729        let file = sources.add("test.cove", source.to_string());
730        crate::parse_file(&sources, file).expect("source parses")
731    }
732
733    fn signature(source: &str) -> String {
734        let unit = parse_one(source);
735        let ItemKind::Fn(decl) = &unit.items[0].kind else {
736            panic!("expected a function");
737        };
738        decl.params
739            .iter()
740            .map(ToString::to_string)
741            .collect::<Vec<_>>()
742            .join(", ")
743    }
744
745    #[test]
746    fn a_parameter_renders_its_default() {
747        assert_eq!(
748            signature("export fn f(name: String = \"world\") {\n}\n"),
749            "name: String = \"world\""
750        );
751        assert_eq!(
752            signature("export fn f(count: Int = 1) {\n}\n"),
753            "count: Int = 1"
754        );
755    }
756
757    #[test]
758    fn a_parameter_without_a_default_renders_without_one() {
759        assert_eq!(
760            signature("export fn f(name: String) {\n}\n"),
761            "name: String"
762        );
763    }
764
765    #[test]
766    fn var_and_variadic_survive_alongside_a_default() {
767        assert_eq!(
768            signature("export fn f(var out: Int) {\n}\n"),
769            "var out: Int"
770        );
771        assert_eq!(
772            signature("export fn f(items: Int...) {\n}\n"),
773            "items: Int..."
774        );
775    }
776}