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 `Vector.get(Vector.fromList(list), index)` → skip AverVector allocation.
45    ListIndexGet {
46        list: &'a Spanned<Expr>,
47        index: &'a Spanned<Expr>,
48    },
49    /// `Option.None` in non-call position.
50    NoneValue,
51    /// Nullary variant constructor: `Shape.Circle`, `Domain.Types.TaskStatus.Blocked`.
52    VariantConstructor {
53        qualified_type_name: String,
54        variant_name: String,
55    },
56    /// Static module/builtin path not in call position: `Fibonacci.fib`, `List.len`.
57    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    // Uppercase dotted path: reuse classify_call_plan to determine semantics.
81    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    // Match Vector.get(Vector.fromList(list), index)
192    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}