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}