compose_eval/expression/
call.rs

1use crate::vm::ErrorMode;
2use crate::{Eval, Evaluated, Machine};
3use compose_library::diag::{At, SourceResult, Spanned, Trace, TracePoint, bail};
4use compose_library::{Arg, Args, Func, NativeScope, Type, UnboundItem, Value};
5use compose_syntax::ast::AstNode;
6use compose_syntax::{Label, Span, ast};
7use ecow::{EcoString, EcoVec, eco_format};
8use extension_traits::extension;
9
10impl Eval for ast::FuncCall<'_> {
11    fn eval(self, vm: &mut Machine) -> SourceResult<Evaluated> {
12        let callee_span = self.callee().span();
13
14        let (callee, args) = self.eval_callee_and_args(vm)?;
15
16        let func = callee.value.cast::<Func>().at(callee_span)?;
17
18        func.call(vm, args).map(Evaluated::mutable).trace(
19            || TracePoint::Call(func.name().map(EcoString::from)),
20            self.span(),
21        )
22    }
23}
24
25#[extension(trait FuncCallExt)]
26impl ast::FuncCall<'_> {
27    fn eval_callee_and_args(&self, vm: &mut Machine) -> SourceResult<(Evaluated, Args)> {
28        if let ast::Expr::FieldAccess(field_access) = self.callee() {
29            self.eval_method_call(&field_access, vm)
30        } else {
31            self.eval_regular_call(vm)
32        }
33    }
34
35    fn eval_method_call(
36        &self,
37        field_access: &ast::FieldAccess,
38        vm: &mut Machine,
39    ) -> SourceResult<(Evaluated, Args)> {
40        let target_expr = field_access.target();
41        let field = field_access.field();
42
43        let target = target_expr.eval(vm)?;
44        let mut args = self.args().eval(vm)?;
45
46        let callee_binding = {
47            let res = target.value.ty().scope().try_get(&field);
48
49            match res {
50                Ok(binding) => binding,
51                Err(err) => {
52                    let value_scope = Value::scope();
53                    match value_scope.try_get(&field) {
54                        Ok(binding) => binding,
55                        Err(mut err2) => {
56                            err2.possible_misspellings.extend(err.possible_misspellings);
57                            return Err(err2.with_item(UnboundItem::FieldOrMethod(Some(
58                                target.value.ty().name().into(),
59                            ))))
60                            .at(field.span());
61                        }
62                    }
63                }
64            }
65        };
66        let callee = callee_binding
67            .read_checked(target_expr.span(), vm.sink_mut())
68            .clone();
69
70        let target_ty = target.value.ty();
71        if let Value::Func(func) = &callee {
72            if func.requires_mut_self() && !target.mutable {
73                let target_text = target_expr.to_untyped().to_text();
74                bail!(field.span(), "cannot call method `{}` on an immutable {}", field.as_str(), target_ty.name();
75                    label_message: "cannot call `{}` on `{target_text}` because it is not mutable", field.as_str();
76                    label: Label::secondary(target.origin.unwrap_or(Span::detached()), eco_format!("help: try making `{target_text}` mutable with `mut"));
77                    note: "only mutable variables can call methods that modify their contents";
78                    hint: "if mutation is not intended, you can use `.clone()` to create a new copy";
79                )
80            }
81
82            if func.is_associated_function() {
83                return err_call_associated_function_as_method(
84                    &target_ty,
85                    field.as_str(),
86                    field.span(),
87                );
88            }
89        }
90
91        args.insert(0, target_expr.span(), target.value);
92
93        Ok((Evaluated::new(callee, target.mutable), args))
94    }
95
96    fn eval_regular_call(&self, vm: &mut Machine) -> SourceResult<(Evaluated, Args)> {
97        let callee = self.callee().eval(vm)?;
98        let args = self.args().eval(vm)?.spanned(self.span());
99        Ok((callee, args))
100    }
101}
102
103fn err_call_associated_function_as_method<T>(
104    target_ty: &Type,
105    field: &str,
106    span: Span,
107) -> SourceResult<T> {
108    bail!(
109        span,
110        "cannot call associated function `{}::{}` as a method", target_ty.name(), field;
111        label_message: "not a method on `{}`", target_ty.name();
112        note: "`{}` is an associated function of `{}`, not a method", field, target_ty.name();
113        hint: "use path syntax instead: `{}::{}(args)`", target_ty.name(), field;
114    )
115}
116
117#[extension(trait EvalArgs)]
118impl ast::Args<'_> {
119    fn eval(self, vm: &mut Machine) -> SourceResult<Args> {
120        let mut items = EcoVec::with_capacity(self.items().count());
121
122        // If we are within a let binding, function args cannot refer to the let binding,
123        // So there is no need to defer these errors
124        vm.with_closure_capture_errors_mode(ErrorMode::Immediate, |vm| {
125            for arg in self.items() {
126                let span = arg.span();
127                match arg {
128                    ast::Arg::Pos(expr) => items.push(Arg {
129                        span,
130                        name: None,
131                        value: Spanned::new(expr.eval(vm)?.value, expr.span()),
132                    }),
133                    ast::Arg::Named(named) => items.push(Arg {
134                        span,
135                        name: Some(named.name().get().into()),
136                        value: Spanned::new(named.expr().eval(vm)?.value, named.expr().span()),
137                    }),
138                }
139            }
140
141            Ok(())
142        })?;
143
144        // Do not assign a span here, we want to assign the span at the callsite (the whole call)
145        Ok(Args {
146            span: Span::detached(),
147            items,
148        })
149    }
150}