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 ListIndexGet {
46 list: &'a Spanned<Expr>,
47 index: &'a Spanned<Expr>,
48 },
49 NoneValue,
51 VariantConstructor {
53 qualified_type_name: String,
54 variant_name: String,
55 },
56 StaticRef(String),
58}
59
60pub fn classify_leaf_op<'a>(expr: &'a Expr, ctx: &impl CallLowerCtx) -> Option<LeafOp<'a>> {
61 match expr {
62 Expr::Attr(object, field_name) => classify_field_access(expr, object, field_name, ctx),
63 Expr::FnCall(fn_expr, args) => classify_leaf_call(&fn_expr.node, args, ctx),
64 _ => None,
65 }
66}
67
68fn classify_field_access<'a>(
69 full_expr: &'a Expr,
70 object: &'a Spanned<Expr>,
71 field_name: &'a str,
72 ctx: &impl CallLowerCtx,
73) -> Option<LeafOp<'a>> {
74 if !expr_to_dotted_name(full_expr)
75 .is_some_and(|dotted| dotted.chars().next().is_some_and(|c| c.is_uppercase()))
76 {
77 return Some(LeafOp::FieldAccess { object, field_name });
78 }
79
80 match classify_call_plan(full_expr, ctx) {
82 CallPlan::NoneValue => Some(LeafOp::NoneValue),
83 CallPlan::TypeConstructor {
84 qualified_type_name,
85 variant_name,
86 } => Some(LeafOp::VariantConstructor {
87 qualified_type_name,
88 variant_name,
89 }),
90 CallPlan::Builtin(name) => Some(LeafOp::StaticRef(name)),
91 CallPlan::Function(name) => Some(LeafOp::StaticRef(name)),
92 CallPlan::Wrapper(_) => Some(LeafOp::StaticRef(
93 expr_to_dotted_name(full_expr).unwrap_or_default(),
94 )),
95 CallPlan::Dynamic => None,
96 }
97}
98
99fn classify_leaf_call<'a>(
100 fn_expr: &'a Expr,
101 args: &'a [Spanned<Expr>],
102 ctx: &impl CallLowerCtx,
103) -> Option<LeafOp<'a>> {
104 match classify_call_plan(fn_expr, ctx) {
105 CallPlan::Builtin(name) => match name.as_str() {
106 "Map.get" if args.len() == 2 => Some(LeafOp::MapGet {
107 map: &args[0],
108 key: &args[1],
109 }),
110 "Map.set" if args.len() == 3 => Some(LeafOp::MapSet {
111 map: &args[0],
112 key: &args[1],
113 value: &args[2],
114 }),
115 "Vector.new" if args.len() == 2 => Some(LeafOp::VectorNew {
116 size: &args[0],
117 fill: &args[1],
118 }),
119 "Vector.get" if args.len() == 2 => classify_list_index_get(&args[0], &args[1], ctx),
120 "Option.withDefault" if args.len() == 2 => {
121 classify_vector_set_or_default(&args[0], &args[1], ctx)
122 .or_else(|| classify_vector_get_or_default(&args[0], &args[1], ctx))
123 }
124 "Result.withDefault" if args.len() == 2 => {
125 classify_int_mod_or_default(&args[0], &args[1], ctx)
126 }
127 _ => None,
128 },
129 _ => None,
130 }
131}
132
133fn classify_vector_set_or_default<'a>(
134 option_expr: &'a Spanned<Expr>,
135 default_expr: &'a Spanned<Expr>,
136 ctx: &impl CallLowerCtx,
137) -> Option<LeafOp<'a>> {
138 let Expr::FnCall(inner_callee, inner_args) = &option_expr.node else {
139 return None;
140 };
141 if inner_args.len() != 3 {
142 return None;
143 }
144
145 match classify_call_plan(&inner_callee.node, ctx) {
146 CallPlan::Builtin(name) if name == "Vector.set" && default_expr == &inner_args[0] => {
147 Some(LeafOp::VectorSetOrDefaultSameVector {
148 vector: &inner_args[0],
149 index: &inner_args[1],
150 value: &inner_args[2],
151 })
152 }
153 _ => None,
154 }
155}
156
157fn classify_vector_get_or_default<'a>(
158 option_expr: &'a Spanned<Expr>,
159 default_expr: &'a Spanned<Expr>,
160 ctx: &impl CallLowerCtx,
161) -> Option<LeafOp<'a>> {
162 let default_literal = match &default_expr.node {
163 Expr::Literal(lit) => lit,
164 _ => return None,
165 };
166
167 let Expr::FnCall(inner_callee, inner_args) = &option_expr.node else {
168 return None;
169 };
170 if inner_args.len() != 2 {
171 return None;
172 }
173
174 match classify_call_plan(&inner_callee.node, ctx) {
175 CallPlan::Builtin(name) if name == "Vector.get" => {
176 Some(LeafOp::VectorGetOrDefaultLiteral {
177 vector: &inner_args[0],
178 index: &inner_args[1],
179 default_literal,
180 })
181 }
182 _ => None,
183 }
184}
185
186fn classify_list_index_get<'a>(
187 vector_expr: &'a Spanned<Expr>,
188 index: &'a Spanned<Expr>,
189 ctx: &impl CallLowerCtx,
190) -> Option<LeafOp<'a>> {
191 let Expr::FnCall(inner_callee, inner_args) = &vector_expr.node else {
193 return None;
194 };
195 if inner_args.len() != 1 {
196 return None;
197 }
198 match classify_call_plan(&inner_callee.node, ctx) {
199 CallPlan::Builtin(name) if name == "Vector.fromList" => Some(LeafOp::ListIndexGet {
200 list: &inner_args[0],
201 index,
202 }),
203 _ => None,
204 }
205}
206
207fn classify_int_mod_or_default<'a>(
208 result_expr: &'a Spanned<Expr>,
209 default_expr: &'a Spanned<Expr>,
210 ctx: &impl CallLowerCtx,
211) -> Option<LeafOp<'a>> {
212 let default_literal = match &default_expr.node {
213 Expr::Literal(lit) => lit,
214 _ => return None,
215 };
216
217 let Expr::FnCall(inner_callee, inner_args) = &result_expr.node else {
218 return None;
219 };
220 if inner_args.len() != 2 {
221 return None;
222 }
223
224 match classify_call_plan(&inner_callee.node, ctx) {
225 CallPlan::Builtin(name) if name == "Int.mod" => Some(LeafOp::IntModOrDefaultLiteral {
226 a: &inner_args[0],
227 b: &inner_args[1],
228 default_literal,
229 }),
230 _ => None,
231 }
232}