Skip to main content

aver/ir/
calls.rs

1use crate::ast::{Expr, FnBody, Spanned, 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}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ForwardCallPlan {
56    pub target: CallPlan,
57    pub args: Vec<ForwardArg>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum TailCallPlan {
62    SelfCall,
63    KnownFunction(String),
64    Unknown(String),
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum SemanticConstructor {
69    Wrapper(WrapperKind),
70    NoneValue,
71    TypeConstructor {
72        qualified_type_name: String,
73        variant_name: String,
74    },
75    Unknown(String),
76}
77
78pub fn expr_to_dotted_name(expr: &Expr) -> Option<String> {
79    match expr {
80        Expr::Ident(name) | Expr::Resolved { name, .. } => Some(name.clone()),
81        Expr::Attr(obj, field) => {
82            let head = expr_to_dotted_name(&obj.node)?;
83            Some(format!("{head}.{field}"))
84        }
85        _ => None,
86    }
87}
88
89pub fn is_builtin_namespace(name: &str) -> bool {
90    matches!(
91        name,
92        "Args"
93            | "Bool"
94            | "Byte"
95            | "Char"
96            | "Console"
97            | "Disk"
98            | "Env"
99            | "Float"
100            | "Http"
101            | "HttpServer"
102            | "Int"
103            | "List"
104            | "Map"
105            | "Option"
106            | "Random"
107            | "Result"
108            | "SelfHostRuntime"
109            | "String"
110            | "Tcp"
111            | "Terminal"
112            | "Time"
113            | "Vector"
114    )
115}
116
117pub fn classify_call_plan(expr: &Expr, ctx: &impl CallLowerCtx) -> CallPlan {
118    match expr {
119        Expr::Resolved { .. } => CallPlan::Dynamic,
120        Expr::Ident(name) => match ctx.is_local_value(name) {
121            true => CallPlan::Dynamic,
122            false => classify_named_callee(name, None, ctx),
123        },
124        Expr::Attr(_, _) => {
125            let Some(dotted) = expr_to_dotted_name(expr) else {
126                return CallPlan::Dynamic;
127            };
128            match dotted.chars().next().is_some_and(|c| c.is_uppercase()) {
129                false => CallPlan::Dynamic,
130                true => {
131                    let module_split = ctx.resolve_module_call(&dotted);
132                    match module_split {
133                        Some((prefix, suffix)) => {
134                            classify_named_callee(&dotted, Some((prefix, suffix)), ctx)
135                        }
136                        None => classify_named_callee(&dotted, None, ctx),
137                    }
138                }
139            }
140        }
141        _ => CallPlan::Dynamic,
142    }
143}
144
145pub fn classify_callee(expr: &Expr, ctx: &impl CallLowerCtx) -> SemanticCallee {
146    classify_call_plan(expr, ctx)
147}
148
149pub fn classify_forward_call_plan(expr: &Expr, ctx: &impl CallLowerCtx) -> Option<ForwardCallPlan> {
150    let Expr::FnCall(fn_expr, args) = expr else {
151        return None;
152    };
153    classify_forward_call_parts(&fn_expr.node, args, ctx)
154}
155
156pub fn classify_forward_call_parts(
157    fn_expr: &Expr,
158    args: &[Spanned<Expr>],
159    ctx: &impl CallLowerCtx,
160) -> Option<ForwardCallPlan> {
161    let target = classify_call_plan(fn_expr, ctx);
162    if matches!(target, CallPlan::Dynamic)
163        || matches!(target, CallPlan::NoneValue)
164        || matches!(target, CallPlan::Wrapper(_) if args.len() != 1)
165    {
166        return None;
167    }
168
169    let args = args
170        .iter()
171        .map(|arg| classify_forward_arg(&arg.node, ctx))
172        .collect::<Option<Vec<_>>>()?;
173
174    Some(ForwardCallPlan { target, args })
175}
176
177pub fn classify_forward_fn_body(body: &FnBody, ctx: &impl CallLowerCtx) -> Option<ForwardCallPlan> {
178    let [Stmt::Expr(expr)] = body.stmts() else {
179        return None;
180    };
181    classify_forward_call_plan(&expr.node, ctx)
182}
183
184pub fn classify_tail_call_plan(
185    target: &str,
186    current_fn: &str,
187    ctx: &impl CallLowerCtx,
188) -> TailCallPlan {
189    if target == current_fn {
190        return TailCallPlan::SelfCall;
191    }
192
193    let module_split = if target.chars().next().is_some_and(|c| c.is_uppercase()) {
194        ctx.resolve_module_call(target)
195    } else {
196        None
197    };
198
199    match classify_named_callee(target, module_split, ctx) {
200        CallPlan::Function(name) => TailCallPlan::KnownFunction(name),
201        _ => TailCallPlan::Unknown(target.to_string()),
202    }
203}
204
205pub fn classify_constructor_name(name: &str, ctx: &impl CallLowerCtx) -> SemanticConstructor {
206    match name {
207        "None" | "Option.None" => return SemanticConstructor::NoneValue,
208        "Ok" | "Result.Ok" => return SemanticConstructor::Wrapper(WrapperKind::ResultOk),
209        "Err" | "Result.Err" => return SemanticConstructor::Wrapper(WrapperKind::ResultErr),
210        "Some" | "Option.Some" => return SemanticConstructor::Wrapper(WrapperKind::OptionSome),
211        _ => {}
212    }
213
214    let module_split = if name.chars().next().is_some_and(|c| c.is_uppercase()) {
215        ctx.resolve_module_call(name)
216    } else {
217        None
218    };
219
220    let bare = match module_split {
221        Some((_, suffix)) => suffix,
222        None => name,
223    };
224
225    if let Some((type_name, variant_name)) = bare.rsplit_once('.')
226        && ctx.is_user_type(type_name)
227    {
228        let qualified_type_name = match module_split {
229            Some((prefix, _)) => format!("{prefix}.{type_name}"),
230            None => type_name.to_string(),
231        };
232        return SemanticConstructor::TypeConstructor {
233            qualified_type_name,
234            variant_name: variant_name.to_string(),
235        };
236    }
237
238    SemanticConstructor::Unknown(name.to_string())
239}
240
241fn classify_forward_arg(expr: &Expr, ctx: &impl CallLowerCtx) -> Option<ForwardArg> {
242    match expr {
243        Expr::Resolved { name, .. } => Some(ForwardArg::Local(name.clone())),
244        Expr::Ident(name) if ctx.is_local_value(name) => Some(ForwardArg::Local(name.clone())),
245        _ => None,
246    }
247}
248
249fn classify_named_callee(
250    full_name: &str,
251    module_split: Option<(&str, &str)>,
252    ctx: &impl CallLowerCtx,
253) -> CallPlan {
254    let bare = match module_split {
255        Some((_, suffix)) => suffix,
256        None => full_name,
257    };
258
259    if module_split.is_none() {
260        match bare {
261            "Option.None" => return CallPlan::NoneValue,
262            "Result.Ok" => return CallPlan::Wrapper(WrapperKind::ResultOk),
263            "Result.Err" => return CallPlan::Wrapper(WrapperKind::ResultErr),
264            "Option.Some" => return CallPlan::Wrapper(WrapperKind::OptionSome),
265            _ => {}
266        }
267
268        if let Some((ns, _)) = bare.split_once('.')
269            && is_builtin_namespace(ns)
270        {
271            return CallPlan::Builtin(bare.to_string());
272        }
273    }
274
275    if let Some((type_name, variant_name)) = bare.rsplit_once('.')
276        && ctx.is_user_type(type_name)
277    {
278        let qualified_type_name = match module_split {
279            Some((prefix, _)) => format!("{prefix}.{type_name}"),
280            None => type_name.to_string(),
281        };
282        return CallPlan::TypeConstructor {
283            qualified_type_name,
284            variant_name: variant_name.to_string(),
285        };
286    }
287
288    CallPlan::Function(full_name.to_string())
289}