Skip to main content

aver/ir/
calls.rs

1use crate::ast::{Expr, FnBody, Stmt};
2
3/// Shared semantic lowering helpers that sit between resolved AST and concrete backends.
4///
5/// This is intentionally Aver-specific, not Rust- or VM-specific. Backends can
6/// provide a tiny context adapter and ask the shared layer what a call-shaped
7/// expression means semantically before choosing their own target encoding.
8pub trait CallLowerCtx {
9    /// Whether this identifier names a local runtime value and therefore must
10    /// remain a dynamic call target rather than a direct function/namespace path.
11    fn is_local_value(&self, name: &str) -> bool;
12
13    /// Whether this name denotes a user-defined type in the current compilation context.
14    fn is_user_type(&self, name: &str) -> bool;
15
16    /// Resolve a dotted path to `(module_prefix, local_suffix)` when it refers to
17    /// a user module path rather than a builtin namespace.
18    fn resolve_module_call<'a>(&self, dotted: &'a str) -> Option<(&'a str, &'a str)>;
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum WrapperKind {
23    ResultOk,
24    ResultErr,
25    OptionSome,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum CallPlan {
30    /// Runtime value call: callee is not a statically known Aver function/builtin/ctor path.
31    Dynamic,
32    /// Source-level function name, either bare (`fib`) or fully-qualified (`Data.Fib.fib`).
33    Function(String),
34    /// Namespace builtin/service call like `List.len`, `Console.print`, `SelfHostRuntime.*`.
35    Builtin(String),
36    /// Wrapper constructor lowering such as `Result.Ok`, `Result.Err`, `Option.Some`.
37    Wrapper(WrapperKind),
38    /// Constant constructor value `Option.None`.
39    NoneValue,
40    /// User-defined variant constructor such as `Shape.Circle` or `Domain.Shape.Circle`.
41    TypeConstructor {
42        qualified_type_name: String,
43        variant_name: String,
44    },
45}
46
47pub type SemanticCallee = CallPlan;
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum ForwardArg {
51    Local(String),
52    Slot(u16),
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ForwardCallPlan {
57    pub target: CallPlan,
58    pub args: Vec<ForwardArg>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub enum TailCallPlan {
63    SelfCall,
64    KnownFunction(String),
65    Unknown(String),
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum SemanticConstructor {
70    Wrapper(WrapperKind),
71    NoneValue,
72    TypeConstructor {
73        qualified_type_name: String,
74        variant_name: String,
75    },
76    Unknown(String),
77}
78
79pub fn expr_to_dotted_name(expr: &Expr) -> Option<String> {
80    match expr {
81        Expr::Ident(name) => Some(name.clone()),
82        Expr::Attr(obj, field) => {
83            let head = expr_to_dotted_name(obj)?;
84            Some(format!("{head}.{field}"))
85        }
86        _ => None,
87    }
88}
89
90pub fn is_builtin_namespace(name: &str) -> bool {
91    matches!(
92        name,
93        "Args"
94            | "Bool"
95            | "Byte"
96            | "Char"
97            | "Console"
98            | "Disk"
99            | "Env"
100            | "Float"
101            | "Http"
102            | "HttpServer"
103            | "Int"
104            | "List"
105            | "Map"
106            | "Option"
107            | "Random"
108            | "Result"
109            | "SelfHostRuntime"
110            | "String"
111            | "Tcp"
112            | "Terminal"
113            | "Time"
114            | "Vector"
115    )
116}
117
118pub fn classify_call_plan(expr: &Expr, ctx: &impl CallLowerCtx) -> CallPlan {
119    match expr {
120        Expr::Resolved(_) => CallPlan::Dynamic,
121        Expr::Ident(name) => match ctx.is_local_value(name) {
122            true => CallPlan::Dynamic,
123            false => classify_named_callee(name, None, ctx),
124        },
125        Expr::Attr(_, _) => {
126            let Some(dotted) = expr_to_dotted_name(expr) else {
127                return CallPlan::Dynamic;
128            };
129            match dotted.chars().next().is_some_and(|c| c.is_uppercase()) {
130                false => CallPlan::Dynamic,
131                true => {
132                    let module_split = ctx.resolve_module_call(&dotted);
133                    match module_split {
134                        Some((prefix, suffix)) => {
135                            classify_named_callee(&dotted, Some((prefix, suffix)), ctx)
136                        }
137                        None => classify_named_callee(&dotted, None, ctx),
138                    }
139                }
140            }
141        }
142        _ => CallPlan::Dynamic,
143    }
144}
145
146pub fn classify_callee(expr: &Expr, ctx: &impl CallLowerCtx) -> SemanticCallee {
147    classify_call_plan(expr, ctx)
148}
149
150pub fn classify_forward_call_plan(expr: &Expr, ctx: &impl CallLowerCtx) -> Option<ForwardCallPlan> {
151    let Expr::FnCall(fn_expr, args) = expr else {
152        return None;
153    };
154    classify_forward_call_parts(fn_expr, args, ctx)
155}
156
157pub fn classify_forward_call_parts(
158    fn_expr: &Expr,
159    args: &[Expr],
160    ctx: &impl CallLowerCtx,
161) -> Option<ForwardCallPlan> {
162    let target = classify_call_plan(fn_expr, ctx);
163    if matches!(target, CallPlan::Dynamic)
164        || matches!(target, CallPlan::NoneValue)
165        || matches!(target, CallPlan::Wrapper(_) if args.len() != 1)
166    {
167        return None;
168    }
169
170    let args = args
171        .iter()
172        .map(|arg| classify_forward_arg(arg, ctx))
173        .collect::<Option<Vec<_>>>()?;
174
175    Some(ForwardCallPlan { target, args })
176}
177
178pub fn classify_forward_fn_body(body: &FnBody, ctx: &impl CallLowerCtx) -> Option<ForwardCallPlan> {
179    let [Stmt::Expr(expr)] = body.stmts() else {
180        return None;
181    };
182    classify_forward_call_plan(expr, ctx)
183}
184
185pub fn classify_tail_call_plan(
186    target: &str,
187    current_fn: &str,
188    ctx: &impl CallLowerCtx,
189) -> TailCallPlan {
190    if target == current_fn {
191        return TailCallPlan::SelfCall;
192    }
193
194    let module_split = if target.chars().next().is_some_and(|c| c.is_uppercase()) {
195        ctx.resolve_module_call(target)
196    } else {
197        None
198    };
199
200    match classify_named_callee(target, module_split, ctx) {
201        CallPlan::Function(name) => TailCallPlan::KnownFunction(name),
202        _ => TailCallPlan::Unknown(target.to_string()),
203    }
204}
205
206pub fn classify_constructor_name(name: &str, ctx: &impl CallLowerCtx) -> SemanticConstructor {
207    match name {
208        "None" | "Option.None" => return SemanticConstructor::NoneValue,
209        "Ok" | "Result.Ok" => return SemanticConstructor::Wrapper(WrapperKind::ResultOk),
210        "Err" | "Result.Err" => return SemanticConstructor::Wrapper(WrapperKind::ResultErr),
211        "Some" | "Option.Some" => return SemanticConstructor::Wrapper(WrapperKind::OptionSome),
212        _ => {}
213    }
214
215    let module_split = if name.chars().next().is_some_and(|c| c.is_uppercase()) {
216        ctx.resolve_module_call(name)
217    } else {
218        None
219    };
220
221    let bare = match module_split {
222        Some((_, suffix)) => suffix,
223        None => name,
224    };
225
226    if let Some((type_name, variant_name)) = bare.rsplit_once('.')
227        && ctx.is_user_type(type_name)
228    {
229        let qualified_type_name = match module_split {
230            Some((prefix, _)) => format!("{prefix}.{type_name}"),
231            None => type_name.to_string(),
232        };
233        return SemanticConstructor::TypeConstructor {
234            qualified_type_name,
235            variant_name: variant_name.to_string(),
236        };
237    }
238
239    SemanticConstructor::Unknown(name.to_string())
240}
241
242fn classify_forward_arg(expr: &Expr, ctx: &impl CallLowerCtx) -> Option<ForwardArg> {
243    match expr {
244        Expr::Resolved(slot) => Some(ForwardArg::Slot(*slot)),
245        Expr::Ident(name) if ctx.is_local_value(name) => Some(ForwardArg::Local(name.clone())),
246        _ => None,
247    }
248}
249
250fn classify_named_callee(
251    full_name: &str,
252    module_split: Option<(&str, &str)>,
253    ctx: &impl CallLowerCtx,
254) -> CallPlan {
255    let bare = match module_split {
256        Some((_, suffix)) => suffix,
257        None => full_name,
258    };
259
260    if module_split.is_none() {
261        match bare {
262            "Option.None" => return CallPlan::NoneValue,
263            "Result.Ok" => return CallPlan::Wrapper(WrapperKind::ResultOk),
264            "Result.Err" => return CallPlan::Wrapper(WrapperKind::ResultErr),
265            "Option.Some" => return CallPlan::Wrapper(WrapperKind::OptionSome),
266            _ => {}
267        }
268
269        if let Some((ns, _)) = bare.split_once('.')
270            && is_builtin_namespace(ns)
271        {
272            return CallPlan::Builtin(bare.to_string());
273        }
274    }
275
276    if let Some((type_name, variant_name)) = bare.rsplit_once('.')
277        && ctx.is_user_type(type_name)
278    {
279        let qualified_type_name = match module_split {
280            Some((prefix, _)) => format!("{prefix}.{type_name}"),
281            None => type_name.to_string(),
282        };
283        return CallPlan::TypeConstructor {
284            qualified_type_name,
285            variant_name: variant_name.to_string(),
286        };
287    }
288
289    CallPlan::Function(full_name.to_string())
290}