Skip to main content

aver/ir/
leaf.rs

1use crate::ast::{Expr, Literal, Spanned};
2
3use super::{CallLowerCtx, CallPlan, classify_call_plan, expr_to_dotted_name};
4
5/// Small expression-shaped leaf operations shared across backends.
6///
7/// These are still semantic plans, not backend instructions: they recognize
8/// common Aver expression shapes whose meaning is clearer than the raw AST.
9#[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    /// Fused `Result.withDefault(Int.mod(a, b), literal)` → skip Result allocation.
39    IntModOrDefaultLiteral {
40        a: &'a Spanned<Expr>,
41        b: &'a Spanned<Expr>,
42        default_literal: &'a Literal,
43    },
44    /// Fused `Result.withDefault(Int.div(a, b), literal)` → skip Result allocation.
45    IntDivOrDefaultLiteral {
46        a: &'a Spanned<Expr>,
47        b: &'a Spanned<Expr>,
48        default_literal: &'a Literal,
49    },
50    /// Fused `Vector.get(Vector.fromList(list), index)` → skip AverVector allocation.
51    ListIndexGet {
52        list: &'a Spanned<Expr>,
53        index: &'a Spanned<Expr>,
54    },
55    /// `Option.None` in non-call position.
56    NoneValue,
57    /// Nullary variant constructor: `Shape.Circle`, `Domain.Types.TaskStatus.Blocked`.
58    VariantConstructor {
59        qualified_type_name: String,
60        variant_name: String,
61    },
62    /// Static module/builtin path not in call position: `Fibonacci.fib`, `List.len`.
63    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    // Uppercase dotted path: reuse classify_call_plan to determine semantics.
87    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    // Match Vector.get(Vector.fromList(list), index)
198    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}