Skip to main content

elm_ast/
expr.rs

1use crate::comment::Comment;
2use crate::ident::{Ident, ModuleName};
3use crate::literal::Literal;
4use crate::node::Spanned;
5use crate::operator::InfixDirection;
6use crate::pattern::Pattern;
7use crate::type_annotation::TypeAnnotation;
8
9/// An expression in Elm source code.
10///
11/// This covers every expression form in Elm 0.19.1.
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[derive(Clone, Debug, PartialEq)]
14pub enum Expr {
15    /// Unit expression: `()`
16    Unit,
17
18    /// A literal value: `42`, `"hello"`, `'c'`, `3.14`
19    Literal(Literal),
20
21    /// A reference to a value or constructor, possibly qualified.
22    ///
23    /// Examples:
24    /// - `foo` → `FunctionOrValue { module_name: [], name: "foo" }`
25    /// - `Just` → `FunctionOrValue { module_name: [], name: "Just" }`
26    /// - `Maybe.Just` → `FunctionOrValue { module_name: ["Maybe"], name: "Just" }`
27    FunctionOrValue {
28        module_name: ModuleName,
29        name: Ident,
30    },
31
32    /// An operator used as a prefix (in parentheses): `(+)`, `(::)`
33    PrefixOperator(Ident),
34
35    /// Operator application with resolved precedence and associativity:
36    /// `a + b` → `OperatorApplication { operator: "+", direction: Left, left, right }`
37    ///
38    /// Note: in the source AST from elm/compiler this is `Binops`, a flat list.
39    /// We use the resolved form from elm-syntax for ergonomics, but also provide
40    /// `BinOps` below for representing the raw unresolved form.
41    OperatorApplication {
42        operator: Ident,
43        direction: InfixDirection,
44        left: Box<Spanned<Expr>>,
45        right: Box<Spanned<Expr>>,
46    },
47
48    /// Raw unresolved binary operator chain, as in the source AST.
49    ///
50    /// `a + b * c` → `BinOps { operands_and_operators: [(a, +), (b, *)], final_operand: c }`
51    ///
52    /// This is the form directly from parsing, before operator precedence
53    /// resolution. Corresponds to `Binops` in `AST/Source.hs`.
54    BinOps {
55        operands_and_operators: Vec<(Spanned<Expr>, Spanned<Ident>)>,
56        final_operand: Box<Spanned<Expr>>,
57    },
58
59    /// Function application: `f x y` → `Application [f, x, y]`
60    Application(Vec<Spanned<Expr>>),
61
62    /// If-then-else expression: `if a then b else c`
63    ///
64    /// Chained if-else: `if a then b else if c then d else e`
65    /// is represented as `IfElse { branches: [IfBranch(a, b), IfBranch(c, d)], else_branch: e }`
66    IfElse {
67        branches: Vec<IfBranch>,
68        else_branch: Box<Spanned<Expr>>,
69    },
70
71    /// Negation: `-expr`
72    Negation(Box<Spanned<Expr>>),
73
74    /// Tuple expression: `( a, b )` or `( a, b, c )`
75    Tuple(Vec<Spanned<Expr>>),
76
77    /// Parenthesized expression: `( expr )`.
78    ///
79    /// `trailing_comments` captures comments that appear between the inner
80    /// expression and the closing `)`, e.g.
81    ///
82    /// ```elm
83    /// ( expr
84    ///   -- trailing
85    /// )
86    /// ```
87    Parenthesized {
88        expr: Box<Spanned<Expr>>,
89        #[cfg_attr(
90            feature = "serde",
91            serde(default, skip_serializing_if = "Vec::is_empty")
92        )]
93        trailing_comments: Vec<Spanned<Comment>>,
94    },
95
96    /// Let-in expression:
97    /// ```elm
98    /// let
99    ///     x = 1
100    ///     y = 2
101    /// in
102    ///     x + y
103    /// ```
104    ///
105    /// `trailing_comments` captures any comments that appear between the
106    /// last declaration and the `in` keyword. elm-format preserves them
107    /// as a dangling block at the end of the let body.
108    LetIn {
109        declarations: Vec<Spanned<LetDeclaration>>,
110        body: Box<Spanned<Expr>>,
111        #[cfg_attr(
112            feature = "serde",
113            serde(default, skip_serializing_if = "Vec::is_empty")
114        )]
115        trailing_comments: Vec<Spanned<Comment>>,
116    },
117
118    /// Case-of expression:
119    /// ```elm
120    /// case msg of
121    ///     Increment -> model + 1
122    ///     Decrement -> model - 1
123    /// ```
124    CaseOf {
125        expr: Box<Spanned<Expr>>,
126        branches: Vec<CaseBranch>,
127    },
128
129    /// Lambda expression: `\x y -> x + y`
130    Lambda {
131        args: Vec<Spanned<Pattern>>,
132        body: Box<Spanned<Expr>>,
133    },
134
135    /// Record expression: `{ name = "Alice", age = 30 }`
136    Record(Vec<Spanned<RecordSetter>>),
137
138    /// Record update expression: `{ model | count = model.count + 1 }`
139    RecordUpdate {
140        base: Spanned<Ident>,
141        updates: Vec<Spanned<RecordSetter>>,
142    },
143
144    /// Record field access: `model.count`
145    RecordAccess {
146        record: Box<Spanned<Expr>>,
147        field: Spanned<Ident>,
148    },
149
150    /// Record access function: `.name`
151    RecordAccessFunction(Ident),
152
153    /// List expression: `[ 1, 2, 3 ]`.
154    ///
155    /// `trailing_comments` captures any comments that appear between the
156    /// last element and the closing `]`, e.g.
157    ///
158    /// ```elm
159    /// [ a
160    /// , b
161    /// -- dangling
162    /// ]
163    /// ```
164    List {
165        elements: Vec<Spanned<Expr>>,
166        /// Inline `-- comment` that follows each element on the same source
167        /// line, e.g. `[ a -- foo\n, b -- bar\n]`. Empty, or parallel to
168        /// `elements` with `None` for elements lacking a comment.
169        #[cfg_attr(
170            feature = "serde",
171            serde(default, skip_serializing_if = "Vec::is_empty")
172        )]
173        element_inline_comments: Vec<Option<Spanned<Comment>>>,
174        #[cfg_attr(
175            feature = "serde",
176            serde(default, skip_serializing_if = "Vec::is_empty")
177        )]
178        trailing_comments: Vec<Spanned<Comment>>,
179    },
180
181    /// GLSL shader block: `[glsl| ... |]`
182    GLSLExpression(String),
183}
184
185// Manual Eq impl because Expr contains Literal which contains f64.
186impl Eq for Expr {}
187
188/// A single branch of an if-else chain: `if <condition> then <then_branch>`.
189///
190/// `trailing_comments` captures any comments that appear after `then_branch`
191/// and before the following `else` keyword. elm-format emits them as
192/// trailing comments on the branch body.
193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
194#[derive(Clone, Debug, PartialEq, Eq)]
195pub struct IfBranch {
196    pub condition: Spanned<Expr>,
197    pub then_branch: Spanned<Expr>,
198    #[cfg_attr(
199        feature = "serde",
200        serde(default, skip_serializing_if = "Vec::is_empty")
201    )]
202    pub trailing_comments: Vec<Spanned<Comment>>,
203}
204
205/// A field setter in a record expression or record update.
206///
207/// `name = expr`
208#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
209#[derive(Clone, Debug, PartialEq, Eq)]
210pub struct RecordSetter {
211    pub field: Spanned<Ident>,
212    pub value: Spanned<Expr>,
213    /// Optional trailing inline comment: `field = value -- comment`.
214    /// elm-format keeps a short comment attached at the end of the setter,
215    /// before the next `,` or `}`. Preserved only when the comment appears
216    /// on the same source line as the value.
217    #[cfg_attr(
218        feature = "serde",
219        serde(default, skip_serializing_if = "Option::is_none")
220    )]
221    pub trailing_comment: Option<Spanned<Comment>>,
222}
223
224/// A branch in a case-of expression.
225///
226/// `pattern -> expr`
227#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
228#[derive(Clone, Debug, PartialEq, Eq)]
229pub struct CaseBranch {
230    pub pattern: Spanned<Pattern>,
231    pub body: Spanned<Expr>,
232}
233
234/// A declaration within a let-in block.
235#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
236#[derive(Clone, Debug, PartialEq, Eq)]
237pub enum LetDeclaration {
238    /// A function definition within a let block.
239    ///
240    /// ```elm
241    /// let
242    ///     add x y = x + y
243    /// in
244    ///     ...
245    /// ```
246    Function(Box<Function>),
247
248    /// A destructuring within a let block.
249    ///
250    /// ```elm
251    /// let
252    ///     ( x, y ) = point
253    /// in
254    ///     ...
255    /// ```
256    Destructuring {
257        pattern: Box<Spanned<Pattern>>,
258        body: Box<Spanned<Expr>>,
259    },
260}
261
262/// A function definition (used in both top-level declarations and let blocks).
263#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
264#[derive(Clone, Debug, PartialEq, Eq)]
265pub struct Function {
266    /// Optional documentation comment.
267    pub documentation: Option<Spanned<String>>,
268
269    /// Optional type signature: `add : Int -> Int -> Int`
270    pub signature: Option<Spanned<Signature>>,
271
272    /// The function implementation.
273    pub declaration: Spanned<FunctionImplementation>,
274}
275
276/// A type signature: `name : type`.
277///
278/// `trailing_comment` captures a short inline comment that appears on the
279/// same line as the signature's final token, e.g.
280///
281/// ```elm
282/// completeBlocks :
283///     State
284///     -> Parser State --Result Parser.Problem (List Block)
285/// ```
286#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
287#[derive(Clone, Debug, PartialEq, Eq)]
288pub struct Signature {
289    pub name: Spanned<Ident>,
290    pub type_annotation: Spanned<TypeAnnotation>,
291    #[cfg_attr(
292        feature = "serde",
293        serde(default, skip_serializing_if = "Option::is_none")
294    )]
295    pub trailing_comment: Option<Spanned<Comment>>,
296}
297
298/// The implementation part of a function definition: `name args = body`
299#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
300#[derive(Clone, Debug, PartialEq, Eq)]
301pub struct FunctionImplementation {
302    pub name: Spanned<Ident>,
303    pub args: Vec<Spanned<Pattern>>,
304    pub body: Spanned<Expr>,
305}