1use crate::ast::{Expr, Literal, Spanned};
2
3use super::{CallLowerCtx, CallPlan, classify_call_plan, expr_to_dotted_name};
4
5#[derive(Debug, Clone, PartialEq)]
10pub enum LeafOp<'a> {
11 FieldAccess {
12 object: &'a Spanned<Expr>,
13 field_name: &'a str,
14 },
15 MapGet {
16 map: &'a Spanned<Expr>,
17 key: &'a Spanned<Expr>,
18 },
19 MapSet {
20 map: &'a Spanned<Expr>,
21 key: &'a Spanned<Expr>,
22 value: &'a Spanned<Expr>,
23 },
24 VectorNew {
25 size: &'a Spanned<Expr>,
26 fill: &'a Spanned<Expr>,
27 },
28 VectorSetOrDefaultSameVector {
29 vector: &'a Spanned<Expr>,
30 index: &'a Spanned<Expr>,
31 value: &'a Spanned<Expr>,
32 },
33 VectorGetOrDefaultLiteral {
34 vector: &'a Spanned<Expr>,
35 index: &'a Spanned<Expr>,
36 default_literal: &'a Literal,
37 },
38 IntModOrDefaultLiteral {
40 a: &'a Spanned<Expr>,
41 b: &'a Spanned<Expr>,
42 default_literal: &'a Literal,
43 },
44 IntDivOrDefaultLiteral {
46 a: &'a Spanned<Expr>,
47 b: &'a Spanned<Expr>,
48 default_literal: &'a Literal,
49 },
50 ListIndexGet {
52 list: &'a Spanned<Expr>,
53 index: &'a Spanned<Expr>,
54 },
55 NoneValue,
57 VariantConstructor {
59 qualified_type_name: String,
60 variant_name: String,
61 },
62 StaticRef(String),
64}
65
66pub fn classify_leaf_op<'a>(expr: &'a Expr, ctx: &impl CallLowerCtx) -> Option<LeafOp<'a>> {
67 match expr {
68 Expr::Attr(object, field_name) => classify_field_access(expr, object, field_name, ctx),
69 Expr::FnCall(fn_expr, args) => classify_leaf_call(&fn_expr.node, args, ctx),
70 _ => None,
71 }
72}
73
74fn classify_field_access<'a>(
75 full_expr: &'a Expr,
76 object: &'a Spanned<Expr>,
77 field_name: &'a str,
78 ctx: &impl CallLowerCtx,
79) -> Option<LeafOp<'a>> {
80 if !expr_to_dotted_name(full_expr)
81 .is_some_and(|dotted| dotted.chars().next().is_some_and(|c| c.is_uppercase()))
82 {
83 return Some(LeafOp::FieldAccess { object, field_name });
84 }
85
86 match classify_call_plan(full_expr, ctx) {
88 CallPlan::NoneValue => Some(LeafOp::NoneValue),
89 CallPlan::TypeConstructor {
90 qualified_type_name,
91 variant_name,
92 } => Some(LeafOp::VariantConstructor {
93 qualified_type_name,
94 variant_name,
95 }),
96 CallPlan::Builtin(name) => Some(LeafOp::StaticRef(name)),
97 CallPlan::Function(name) => Some(LeafOp::StaticRef(name)),
98 CallPlan::Wrapper(_) => Some(LeafOp::StaticRef(
99 expr_to_dotted_name(full_expr).unwrap_or_default(),
100 )),
101 CallPlan::Dynamic => None,
102 }
103}
104
105fn classify_leaf_call<'a>(
106 fn_expr: &'a Expr,
107 args: &'a [Spanned<Expr>],
108 ctx: &impl CallLowerCtx,
109) -> Option<LeafOp<'a>> {
110 match classify_call_plan(fn_expr, ctx) {
111 CallPlan::Builtin(name) => match name.as_str() {
112 "Map.get" if args.len() == 2 => Some(LeafOp::MapGet {
113 map: &args[0],
114 key: &args[1],
115 }),
116 "Map.set" if args.len() == 3 => Some(LeafOp::MapSet {
117 map: &args[0],
118 key: &args[1],
119 value: &args[2],
120 }),
121 "Vector.new" if args.len() == 2 => Some(LeafOp::VectorNew {
122 size: &args[0],
123 fill: &args[1],
124 }),
125 "Vector.get" if args.len() == 2 => classify_list_index_get(&args[0], &args[1], ctx),
126 "Option.withDefault" if args.len() == 2 => {
127 classify_vector_set_or_default(&args[0], &args[1], ctx)
128 .or_else(|| classify_vector_get_or_default(&args[0], &args[1], ctx))
129 }
130 "Result.withDefault" if args.len() == 2 => {
131 classify_int_mod_or_div_or_default(&args[0], &args[1], ctx)
132 }
133 _ => None,
134 },
135 _ => None,
136 }
137}
138
139fn classify_vector_set_or_default<'a>(
140 option_expr: &'a Spanned<Expr>,
141 default_expr: &'a Spanned<Expr>,
142 ctx: &impl CallLowerCtx,
143) -> Option<LeafOp<'a>> {
144 let Expr::FnCall(inner_callee, inner_args) = &option_expr.node else {
145 return None;
146 };
147 if inner_args.len() != 3 {
148 return None;
149 }
150
151 match classify_call_plan(&inner_callee.node, ctx) {
152 CallPlan::Builtin(name) if name == "Vector.set" && default_expr == &inner_args[0] => {
153 Some(LeafOp::VectorSetOrDefaultSameVector {
154 vector: &inner_args[0],
155 index: &inner_args[1],
156 value: &inner_args[2],
157 })
158 }
159 _ => None,
160 }
161}
162
163fn classify_vector_get_or_default<'a>(
164 option_expr: &'a Spanned<Expr>,
165 default_expr: &'a Spanned<Expr>,
166 ctx: &impl CallLowerCtx,
167) -> Option<LeafOp<'a>> {
168 let default_literal = match &default_expr.node {
169 Expr::Literal(lit) => lit,
170 _ => return None,
171 };
172
173 let Expr::FnCall(inner_callee, inner_args) = &option_expr.node else {
174 return None;
175 };
176 if inner_args.len() != 2 {
177 return None;
178 }
179
180 match classify_call_plan(&inner_callee.node, ctx) {
181 CallPlan::Builtin(name) if name == "Vector.get" => {
182 Some(LeafOp::VectorGetOrDefaultLiteral {
183 vector: &inner_args[0],
184 index: &inner_args[1],
185 default_literal,
186 })
187 }
188 _ => None,
189 }
190}
191
192fn classify_list_index_get<'a>(
193 vector_expr: &'a Spanned<Expr>,
194 index: &'a Spanned<Expr>,
195 ctx: &impl CallLowerCtx,
196) -> Option<LeafOp<'a>> {
197 let Expr::FnCall(inner_callee, inner_args) = &vector_expr.node else {
199 return None;
200 };
201 if inner_args.len() != 1 {
202 return None;
203 }
204 match classify_call_plan(&inner_callee.node, ctx) {
205 CallPlan::Builtin(name) if name == "Vector.fromList" => Some(LeafOp::ListIndexGet {
206 list: &inner_args[0],
207 index,
208 }),
209 _ => None,
210 }
211}
212
213fn classify_int_mod_or_div_or_default<'a>(
214 result_expr: &'a Spanned<Expr>,
215 default_expr: &'a Spanned<Expr>,
216 ctx: &impl CallLowerCtx,
217) -> Option<LeafOp<'a>> {
218 let default_literal = match &default_expr.node {
219 Expr::Literal(lit) => lit,
220 _ => return None,
221 };
222
223 let Expr::FnCall(inner_callee, inner_args) = &result_expr.node else {
224 return None;
225 };
226 if inner_args.len() != 2 {
227 return None;
228 }
229
230 match classify_call_plan(&inner_callee.node, ctx) {
231 CallPlan::Builtin(name) if name == "Int.mod" => Some(LeafOp::IntModOrDefaultLiteral {
232 a: &inner_args[0],
233 b: &inner_args[1],
234 default_literal,
235 }),
236 CallPlan::Builtin(name) if name == "Int.div" => Some(LeafOp::IntDivOrDefaultLiteral {
237 a: &inner_args[0],
238 b: &inner_args[1],
239 default_literal,
240 }),
241 _ => None,
242 }
243}