Skip to main content

kcl_lib/execution/
fn_call.rs

1use async_recursion::async_recursion;
2use indexmap::IndexMap;
3
4use crate::CompilationIssue;
5use crate::NodePath;
6use crate::SourceRange;
7use crate::errors::KclError;
8use crate::errors::KclErrorDetails;
9use crate::execution::BodyType;
10use crate::execution::ExecState;
11use crate::execution::ExecutorContext;
12use crate::execution::Geometry;
13use crate::execution::KclValue;
14use crate::execution::KclValueControlFlow;
15use crate::execution::Metadata;
16use crate::execution::Solid;
17use crate::execution::StatementKind;
18use crate::execution::TagEngineInfo;
19use crate::execution::TagIdentifier;
20use crate::execution::annotations;
21use crate::execution::cad_op::Group;
22use crate::execution::cad_op::OpArg;
23use crate::execution::cad_op::OpKclValue;
24use crate::execution::cad_op::Operation;
25use crate::execution::control_continue;
26use crate::execution::kcl_value::FunctionBody;
27use crate::execution::kcl_value::FunctionSource;
28use crate::execution::kcl_value::NamedParam;
29use crate::execution::memory;
30use crate::execution::types::RuntimeType;
31use crate::parsing::ast::types::CallExpressionKw;
32use crate::parsing::ast::types::Node;
33use crate::parsing::ast::types::Type;
34
35#[derive(Debug, Clone)]
36pub struct Args<Status: ArgsStatus = Desugared> {
37    /// Name of the function these args are being passed into.
38    pub fn_name: Option<String>,
39    /// Unlabeled keyword args. Currently only the first formal arg can be unlabeled.
40    /// If the argument was a local variable, then the first element of the tuple is its name
41    /// which may be used to treat this arg as a labelled arg.
42    pub unlabeled: Vec<(Option<String>, Arg)>,
43    /// Labeled args.
44    pub labeled: IndexMap<String, Arg>,
45    pub source_range: SourceRange,
46    pub node_path: Option<NodePath>,
47    pub ctx: ExecutorContext,
48    /// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
49    /// Otherwise it's None.
50    pub pipe_value: Option<Arg>,
51    _status: std::marker::PhantomData<Status>,
52}
53
54pub trait ArgsStatus: std::fmt::Debug + Clone {}
55
56#[derive(Debug, Clone)]
57pub struct Sugary;
58impl ArgsStatus for Sugary {}
59
60// Invariants guaranteed by the `Desugared` status:
61// - There is either 0 or 1 unlabeled arguments
62// - Any lableled args are in the labeled map, and not the unlabeled Vec.
63// - The arguments match the type signature of the function exactly
64// - pipe_value.is_none()
65#[derive(Debug, Clone)]
66pub struct Desugared;
67impl ArgsStatus for Desugared {}
68
69impl Args<Sugary> {
70    /// Collect the given keyword arguments.
71    pub fn new(
72        labeled: IndexMap<String, Arg>,
73        unlabeled: Vec<(Option<String>, Arg)>,
74        source_range: SourceRange,
75        node_path: Option<NodePath>,
76        exec_state: &mut ExecState,
77        ctx: ExecutorContext,
78        fn_name: Option<String>,
79    ) -> Args<Sugary> {
80        Args {
81            fn_name,
82            labeled,
83            unlabeled,
84            source_range,
85            node_path,
86            ctx,
87            pipe_value: exec_state.pipe_value().map(|v| Arg::new(v.clone(), source_range)),
88            _status: std::marker::PhantomData,
89        }
90    }
91}
92
93impl<Status: ArgsStatus> Args<Status> {
94    /// How many arguments are there?
95    pub fn len(&self) -> usize {
96        self.labeled.len() + self.unlabeled.len()
97    }
98
99    /// Are there no arguments?
100    pub fn is_empty(&self) -> bool {
101        self.labeled.is_empty() && self.unlabeled.is_empty()
102    }
103}
104
105impl Args<Desugared> {
106    pub fn new_no_args(
107        source_range: SourceRange,
108        node_path: Option<NodePath>,
109        ctx: ExecutorContext,
110        fn_name: Option<String>,
111    ) -> Args {
112        Args {
113            fn_name,
114            unlabeled: Default::default(),
115            labeled: Default::default(),
116            source_range,
117            node_path,
118            ctx,
119            pipe_value: None,
120            _status: std::marker::PhantomData,
121        }
122    }
123
124    /// Get the unlabeled keyword argument. If not set, returns None.
125    pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
126        self.unlabeled.first().map(|(_, a)| a)
127    }
128}
129
130#[derive(Debug, Clone)]
131pub struct Arg {
132    /// The evaluated argument.
133    pub value: KclValue,
134    /// The source range of the unevaluated argument.
135    pub source_range: SourceRange,
136}
137
138impl Arg {
139    pub fn new(value: KclValue, source_range: SourceRange) -> Self {
140        Self { value, source_range }
141    }
142
143    pub fn synthetic(value: KclValue) -> Self {
144        Self {
145            value,
146            source_range: SourceRange::synthetic(),
147        }
148    }
149
150    pub fn source_ranges(&self) -> Vec<SourceRange> {
151        vec![self.source_range]
152    }
153}
154
155impl Node<CallExpressionKw> {
156    #[async_recursion]
157    pub(super) async fn execute(
158        &self,
159        exec_state: &mut ExecState,
160        ctx: &ExecutorContext,
161    ) -> Result<KclValueControlFlow, KclError> {
162        let fn_name = &self.callee;
163        let callsite: SourceRange = self.into();
164
165        // Clone the function so that we can use a mutable reference to
166        // exec_state.
167        let func: KclValue = fn_name.get_result(exec_state, ctx).await?.clone();
168
169        let Some(fn_src) = func.as_function() else {
170            return Err(KclError::new_semantic(KclErrorDetails::new(
171                "cannot call this because it isn't a function".to_string(),
172                vec![callsite],
173            )));
174        };
175
176        // Build a hashmap from argument labels to the final evaluated values.
177        let mut fn_args = IndexMap::with_capacity(self.arguments.len());
178        let mut unlabeled = Vec::new();
179
180        // Evaluate the unlabeled first param, if any exists.
181        if let Some(ref arg_expr) = self.unlabeled {
182            let source_range = SourceRange::from(arg_expr.clone());
183            let metadata = Metadata { source_range };
184            let value_cf = ctx
185                .execute_expr(arg_expr, exec_state, &metadata, &[], StatementKind::Expression)
186                .await?;
187            let value = control_continue!(value_cf);
188
189            let label = arg_expr.ident_name().map(str::to_owned);
190
191            unlabeled.push((label, Arg::new(value, source_range)))
192        }
193
194        for arg_expr in &self.arguments {
195            let source_range = SourceRange::from(arg_expr.arg.clone());
196            let metadata = Metadata { source_range };
197            let value_cf = ctx
198                .execute_expr(&arg_expr.arg, exec_state, &metadata, &[], StatementKind::Expression)
199                .await?;
200            let value = control_continue!(value_cf);
201            let arg = Arg::new(value, source_range);
202            match &arg_expr.label {
203                Some(l) => {
204                    fn_args.insert(l.name.clone(), arg);
205                }
206                None => {
207                    unlabeled.push((arg_expr.arg.ident_name().map(str::to_owned), arg));
208                }
209            }
210        }
211
212        let args = Args::new(
213            fn_args,
214            unlabeled,
215            callsite,
216            self.node_path.clone(),
217            exec_state,
218            ctx.clone(),
219            Some(fn_name.name.name.clone()),
220        );
221
222        let return_value = fn_src
223            .call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
224            .await
225            .map_err(|e| {
226                // Add the call expression to the source ranges.
227                //
228                // TODO: Use the name that the function was defined
229                // with, not the identifier it was used with.
230                e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
231            })?;
232
233        let result = return_value.ok_or_else(move || {
234            let mut source_ranges: Vec<SourceRange> = vec![callsite];
235            // We want to send the source range of the original function.
236            if let KclValue::Function { meta, .. } = func {
237                source_ranges = meta.iter().map(|m| m.source_range).collect();
238            };
239            KclError::new_undefined_value(
240                KclErrorDetails::new(
241                    format!("Result of user-defined function {fn_name} is undefined"),
242                    source_ranges,
243                ),
244                None,
245            )
246        })?;
247
248        Ok(result)
249    }
250}
251
252impl FunctionSource {
253    pub(crate) async fn call_kw(
254        &self,
255        fn_name: Option<String>,
256        exec_state: &mut ExecState,
257        ctx: &ExecutorContext,
258        args: Args<Sugary>,
259        callsite: SourceRange,
260    ) -> Result<Option<KclValueControlFlow>, KclError> {
261        exec_state.inc_call_stack_size(callsite)?;
262
263        let result = self.inner_call_kw(fn_name, exec_state, ctx, args, callsite).await;
264
265        exec_state.dec_call_stack_size(callsite)?;
266        result
267    }
268
269    async fn inner_call_kw(
270        &self,
271        fn_name: Option<String>,
272        exec_state: &mut ExecState,
273        ctx: &ExecutorContext,
274        args: Args<Sugary>,
275        callsite: SourceRange,
276    ) -> Result<Option<KclValueControlFlow>, KclError> {
277        if self.deprecated {
278            exec_state.warn(
279                CompilationIssue::err(
280                    callsite,
281                    format!(
282                        "{} is deprecated, see the docs for a recommended replacement",
283                        match &fn_name {
284                            Some(n) => format!("`{n}`"),
285                            None => "This function".to_owned(),
286                        }
287                    ),
288                ),
289                annotations::WARN_DEPRECATED,
290            );
291        }
292        if self.experimental {
293            exec_state.warn_experimental(
294                &match &fn_name {
295                    Some(n) => format!("`{n}`"),
296                    None => "This function".to_owned(),
297                },
298                callsite,
299            );
300        }
301
302        let args = type_check_params_kw(fn_name.as_deref(), self, args, exec_state)?;
303
304        // Warn if experimental arguments are used after desugaring.
305        for (label, arg) in &args.labeled {
306            if let Some(param) = self.named_args.get(label.as_str())
307                && param.experimental
308            {
309                exec_state.warn_experimental(
310                    &match &fn_name {
311                        Some(f) => format!("`{f}({label})`"),
312                        None => label.to_owned(),
313                    },
314                    arg.source_range,
315                );
316            }
317        }
318
319        // Don't early return until the stack frame is popped!
320        self.body.prep_mem(exec_state);
321
322        // Some function calls might get added to the feature tree.
323        // We do this by adding an "operation".
324
325        // Don't add operations if the KCL code being executed is
326        // just the KCL stdlib calling other KCL stdlib,
327        // because the stdlib internals aren't relevant to users,
328        // that would just be pointless noise.
329        //
330        // Do add operations if the KCL being executed is
331        // user-defined, or the calling code is user-defined,
332        // because that's relevant to the user.
333        let would_trace_stdlib_internals = exec_state.mod_local.inside_stdlib && self.is_std;
334        // self.include_in_feature_tree is set by the KCL annotation `@(feature_tree = true)`.
335        let should_track_operation = !would_trace_stdlib_internals && self.include_in_feature_tree;
336        let op = if should_track_operation {
337            let op_labeled_args = args
338                .labeled
339                .iter()
340                .map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
341                .collect();
342
343            // If you're calling a stdlib function, track that call as an operation.
344            if self.is_std {
345                Some(Operation::StdLibCall {
346                    name: fn_name.clone().unwrap_or_else(|| "unknown function".to_owned()),
347                    unlabeled_arg: args
348                        .unlabeled_kw_arg_unconverted()
349                        .map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
350                    labeled_args: op_labeled_args,
351                    node_path: NodePath::placeholder(),
352                    source_range: callsite,
353                    stdlib_entry_source_range: exec_state.mod_local.stdlib_entry_source_range,
354                    is_error: false,
355                })
356            } else {
357                // Otherwise, you're calling a user-defined function, track that call as an operation.
358                exec_state.push_op(Operation::GroupBegin {
359                    group: Group::FunctionCall {
360                        name: fn_name.clone(),
361                        function_source_range: self.ast.as_source_range(),
362                        unlabeled_arg: args
363                            .unlabeled_kw_arg_unconverted()
364                            .map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
365                        labeled_args: op_labeled_args,
366                    },
367                    node_path: NodePath::placeholder(),
368                    source_range: callsite,
369                });
370
371                None
372            }
373        } else {
374            None
375        };
376
377        let is_calling_into_stdlib = match &self.body {
378            FunctionBody::Rust(_) => true,
379            FunctionBody::Kcl(_) => self.is_std,
380        };
381        let is_crossing_into_stdlib = is_calling_into_stdlib && !exec_state.mod_local.inside_stdlib;
382        let is_crossing_out_of_stdlib = !is_calling_into_stdlib && exec_state.mod_local.inside_stdlib;
383        let stdlib_entry_source_range = if is_crossing_into_stdlib {
384            // When we're calling into the stdlib, for example calling hole(),
385            // track the location so that any further stdlib calls like
386            // subtract() can point to the hole() call. The frontend needs this.
387            Some(callsite)
388        } else if is_crossing_out_of_stdlib {
389            // When map() calls a user-defined function, and it calls extrude()
390            // for example, we want it to point the the extrude() call, not
391            // the map() call.
392            None
393        } else {
394            // When we're not crossing the stdlib boundary, keep the previous
395            // value.
396            exec_state.mod_local.stdlib_entry_source_range
397        };
398
399        let prev_inside_stdlib = std::mem::replace(&mut exec_state.mod_local.inside_stdlib, is_calling_into_stdlib);
400        let prev_stdlib_entry_source_range = std::mem::replace(
401            &mut exec_state.mod_local.stdlib_entry_source_range,
402            stdlib_entry_source_range,
403        );
404        // Do not early return via ? or something until we've
405        // - put this `prev_inside_stdlib` value back.
406        // - called the pop_env.
407        let result = match &self.body {
408            FunctionBody::Rust(f) => f(exec_state, args).await.map(Some),
409            FunctionBody::Kcl(_) => {
410                if let Err(e) = assign_args_to_params_kw(self, args, exec_state) {
411                    exec_state.mod_local.inside_stdlib = prev_inside_stdlib;
412                    exec_state.mut_stack().pop_env();
413                    return Err(e);
414                }
415
416                ctx.exec_block(&self.ast.body, exec_state, BodyType::Block)
417                    .await
418                    .map(|cf| {
419                        if let Some(cf) = cf
420                            && cf.is_some_return()
421                        {
422                            return Some(cf);
423                        }
424                        // Ignore the block's value and extract the return value
425                        // from memory.
426                        exec_state
427                            .stack()
428                            .get(memory::RETURN_NAME, self.ast.as_source_range())
429                            .ok()
430                            .cloned()
431                            .map(KclValue::continue_)
432                    })
433            }
434        };
435        exec_state.mod_local.inside_stdlib = prev_inside_stdlib;
436        exec_state.mod_local.stdlib_entry_source_range = prev_stdlib_entry_source_range;
437        exec_state.mut_stack().pop_env();
438
439        if should_track_operation {
440            if let Some(mut op) = op {
441                op.set_std_lib_call_is_error(result.is_err());
442                // Track call operation.  We do this after the call
443                // since things like patternTransform may call user code
444                // before running, and we will likely want to use the
445                // return value. The call takes ownership of the args,
446                // so we need to build the op before the call.
447                exec_state.push_op(op);
448            } else if !is_calling_into_stdlib {
449                exec_state.push_op(Operation::GroupEnd);
450            }
451        }
452
453        let mut result = match result {
454            Ok(Some(value)) => {
455                if value.is_some_return() {
456                    return Ok(Some(value));
457                } else {
458                    Ok(Some(value.into_value()))
459                }
460            }
461            Ok(None) => Ok(None),
462            Err(e) => Err(e),
463        };
464
465        if self.is_std
466            && let Ok(Some(result)) = &mut result
467        {
468            update_memory_for_tags_of_geometry(result, exec_state)?;
469        }
470
471        coerce_result_type(result, self, exec_state).map(|r| r.map(KclValue::continue_))
472    }
473}
474
475impl FunctionBody {
476    fn prep_mem(&self, exec_state: &mut ExecState) {
477        match self {
478            FunctionBody::Rust(_) => exec_state.mut_stack().push_new_root_env(true),
479            FunctionBody::Kcl(memory) => exec_state.mut_stack().push_new_env_for_call(*memory),
480        }
481    }
482}
483
484fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
485    // If the return result is a sketch or solid, we want to update the
486    // memory for the tags of the group.
487    // TODO: This could probably be done in a better way, but as of now this was my only idea
488    // and it works.
489    match result {
490        KclValue::Sketch { value } => {
491            for (name, tag) in value.tags.iter() {
492                if exec_state.stack().cur_frame_contains(name) {
493                    exec_state.mut_stack().update(name, |v, _| {
494                        v.as_mut_tag().unwrap().merge_info(tag);
495                    });
496                } else {
497                    exec_state
498                        .mut_stack()
499                        .add(
500                            name.to_owned(),
501                            KclValue::TagIdentifier(Box::new(tag.clone())),
502                            SourceRange::default(),
503                        )
504                        .unwrap();
505                }
506            }
507        }
508        KclValue::Solid { value } => {
509            let surfaces = value.value.clone();
510            if value.sketch_mut().is_none() {
511                // If the solid isn't based on a sketch, then it doesn't have a tag container,
512                // so there's nothing to do here.
513                return Ok(());
514            };
515            // Now that we know there's work to do (because there's a tag container),
516            // run some clones.
517            let solid_copies: Vec<Box<Solid>> = surfaces.iter().map(|_| value.clone()).collect();
518            // Get the tag container. We expect it to always succeed because we already checked
519            // for a tag container above.
520            let Some(sketch) = value.sketch_mut() else {
521                return Ok(());
522            };
523            for (v, mut solid_copy) in surfaces.iter().zip(solid_copies) {
524                if let Some(sketch) = solid_copy.sketch_mut() {
525                    sketch.tags.clear(); // Avoid recursive tags.
526                }
527                if let Some(tag) = v.get_tag() {
528                    // Get the past tag and update it.
529                    let tag_id = if let Some(t) = sketch.tags.get(&tag.name) {
530                        let mut t = t.clone();
531                        let Some(info) = t.get_cur_info() else {
532                            return Err(KclError::new_internal(KclErrorDetails::new(
533                                format!("Tag {} does not have path info", tag.name),
534                                vec![tag.into()],
535                            )));
536                        };
537
538                        let mut info = info.clone();
539                        info.id = v.get_id();
540                        info.surface = Some(v.clone());
541                        info.geometry = Geometry::Solid(*solid_copy);
542                        t.info.push((exec_state.stack().current_epoch(), info));
543                        t
544                    } else {
545                        // It's probably a fillet or a chamfer.
546                        // Initialize it.
547                        TagIdentifier {
548                            value: tag.name.clone(),
549                            info: vec![(
550                                exec_state.stack().current_epoch(),
551                                TagEngineInfo {
552                                    id: v.get_id(),
553                                    surface: Some(v.clone()),
554                                    path: None,
555                                    geometry: Geometry::Solid(*solid_copy),
556                                },
557                            )],
558                            meta: vec![Metadata {
559                                source_range: tag.clone().into(),
560                            }],
561                        }
562                    };
563
564                    // update the sketch tags.
565                    sketch.merge_tags(Some(&tag_id).into_iter());
566
567                    if exec_state.stack().cur_frame_contains(&tag.name) {
568                        exec_state.mut_stack().update(&tag.name, |v, _| {
569                            v.as_mut_tag().unwrap().merge_info(&tag_id);
570                        });
571                    } else {
572                        exec_state
573                            .mut_stack()
574                            .add(
575                                tag.name.clone(),
576                                KclValue::TagIdentifier(Box::new(tag_id)),
577                                SourceRange::default(),
578                            )
579                            .unwrap();
580                    }
581                }
582            }
583
584            // Find the stale sketch in memory and update it.
585            if let Some(sketch) = value.sketch() {
586                if sketch.tags.is_empty() {
587                    return Ok(());
588                }
589                let sketch_tags: Vec<_> = sketch.tags.values().cloned().collect();
590                let sketches_to_update: Vec<_> = exec_state
591                    .stack()
592                    .find_keys_in_current_env(|v| match v {
593                        KclValue::Sketch { value: sk } => sk.original_id == sketch.original_id,
594                        _ => false,
595                    })
596                    .cloned()
597                    .collect();
598
599                for k in sketches_to_update {
600                    exec_state.mut_stack().update(&k, |v, _| {
601                        let sketch = v.as_mut_sketch().unwrap();
602                        sketch.merge_tags(sketch_tags.iter());
603                    });
604                }
605            }
606        }
607        KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
608            for v in value {
609                update_memory_for_tags_of_geometry(v, exec_state)?;
610            }
611        }
612        _ => {}
613    }
614    Ok(())
615}
616
617fn type_err_str(expected: &Type, found: &KclValue, source_range: &SourceRange, exec_state: &mut ExecState) -> String {
618    fn strip_backticks(s: &str) -> &str {
619        let mut result = s;
620        if s.starts_with('`') {
621            result = &result[1..]
622        }
623        if s.ends_with('`') {
624            result = &result[..result.len() - 1]
625        }
626        result
627    }
628
629    let expected_human = expected.human_friendly_type();
630    let expected_ty = expected.to_string();
631    let expected_str =
632        if expected_human == expected_ty || expected_human == format!("a value with type `{expected_ty}`") {
633            format!("a value with type `{expected_ty}`")
634        } else {
635            format!("{expected_human} (`{expected_ty}`)")
636        };
637    let found_human = found.human_friendly_type();
638    let found_ty = found.principal_type_string();
639    let found_str = if found_human == found_ty || found_human == format!("a {}", strip_backticks(&found_ty)) {
640        format!("a value with type {found_ty}")
641    } else {
642        format!("{found_human} (with type {found_ty})")
643    };
644
645    let mut result = format!("{expected_str}, but found {found_str}.");
646
647    if found.is_unknown_number() {
648        exec_state.clear_units_warnings(source_range);
649        result.push_str("\nThe found value is a number but has incomplete units information. You can probably fix this error by specifying the units using type ascription, e.g., `len: mm` or `(a * b): deg`.");
650    }
651
652    result
653}
654
655fn type_check_params_kw(
656    fn_name: Option<&str>,
657    fn_def: &FunctionSource,
658    mut args: Args<Sugary>,
659    exec_state: &mut ExecState,
660) -> Result<Args<Desugared>, KclError> {
661    let fn_name = fn_name.or(args.fn_name.as_deref());
662    let mut result = Args::new_no_args(
663        args.source_range,
664        args.node_path.clone(),
665        args.ctx,
666        fn_name.map(|f| f.to_string()).or_else(|| args.fn_name.clone()),
667    );
668
669    // If it's possible the input arg was meant to be labelled and we probably don't want to use
670    // it as the input arg, then treat it as labelled.
671    if let Some((Some(label), _)) = args.unlabeled.first()
672        && args.unlabeled.len() == 1
673        && (fn_def.input_arg.is_none() || args.pipe_value.is_some())
674        && fn_def.named_args.iter().any(|p| p.0 == label)
675        && !args.labeled.contains_key(label)
676    {
677        let Some((label, arg)) = args.unlabeled.pop() else {
678            let message = "Expected unlabeled arg to be present".to_owned();
679            debug_assert!(false, "{}", &message);
680            return Err(KclError::new_internal(KclErrorDetails::new(
681                message,
682                vec![args.source_range],
683            )));
684        };
685        args.labeled.insert(label.unwrap(), arg);
686    }
687
688    // Apply the `a == a: a` shorthand by desugaring unlabeled args into labeled ones.
689    let (labeled_unlabeled, unlabeled_unlabeled) = args.unlabeled.into_iter().partition(|(l, _)| {
690        if let Some(l) = l
691            && fn_def.named_args.contains_key(l)
692            && !args.labeled.contains_key(l)
693        {
694            true
695        } else {
696            false
697        }
698    });
699    args.unlabeled = unlabeled_unlabeled;
700    for (l, arg) in labeled_unlabeled {
701        let previous = args.labeled.insert(l.unwrap(), arg);
702        debug_assert!(previous.is_none());
703    }
704
705    if let Some((name, ty)) = &fn_def.input_arg {
706        // Expecting an input arg
707
708        if args.unlabeled.is_empty() {
709            // No args provided
710
711            if let Some(pipe) = args.pipe_value {
712                // But there is a pipeline
713                result.unlabeled = vec![(None, pipe)];
714            } else if let Some(arg) = args.labeled.swap_remove(name) {
715                // Mistakenly labelled
716                exec_state.err(CompilationIssue::err(
717                    arg.source_range,
718                    format!(
719                        "{} expects an unlabeled first argument (`@{name}`), but it is labelled in the call. You might try removing the `{name} = `",
720                        fn_name
721                            .map(|n| format!("The function `{n}`"))
722                            .unwrap_or_else(|| "This function".to_owned()),
723                    ),
724                ));
725                result.unlabeled = vec![(Some(name.clone()), arg)];
726            } else {
727                // Just missing
728                return Err(KclError::new_argument(KclErrorDetails::new(
729                    "This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(),
730                    fn_def.ast.as_source_ranges(),
731                )));
732            }
733        } else if args.unlabeled.len() == 1
734            && let Some(unlabeled_arg) = args.unlabeled.pop()
735        {
736            let mut arg = unlabeled_arg.1;
737            if let Some(ty) = ty {
738                // Suppress warnings about types because they should only be
739                // warned about once for the function definition.
740                let rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range, false, true)
741                    .map_err(|e| KclError::new_semantic(e.into()))?;
742                arg.value = arg.value.coerce(&rty, true, exec_state).map_err(|_| {
743                    KclError::new_argument(KclErrorDetails::new(
744                        format!(
745                            "The input argument of {} requires {}",
746                            fn_name
747                                .map(|n| format!("`{n}`"))
748                                .unwrap_or_else(|| "this function".to_owned()),
749                            type_err_str(ty, &arg.value, &arg.source_range, exec_state),
750                        ),
751                        vec![arg.source_range],
752                    ))
753                })?;
754            }
755            result.unlabeled = vec![(None, arg)]
756        } else {
757            // Multiple unlabelled args
758
759            // Try to un-spread args into an array
760            if let Some(Type::Array { len, .. }) = ty {
761                if len.satisfied(args.unlabeled.len(), false).is_none() {
762                    exec_state.err(CompilationIssue::err(
763                        args.source_range,
764                        format!(
765                            "{} expects an array input argument with {} elements",
766                            fn_name
767                                .map(|n| format!("The function `{n}`"))
768                                .unwrap_or_else(|| "This function".to_owned()),
769                            len.human_friendly_type(),
770                        ),
771                    ));
772                }
773
774                let source_range = SourceRange::merge(args.unlabeled.iter().map(|(_, a)| a.source_range));
775                exec_state.warn_experimental("array input arguments", source_range);
776                result.unlabeled = vec![(
777                    None,
778                    Arg {
779                        source_range,
780                        value: KclValue::HomArray {
781                            value: args.unlabeled.drain(..).map(|(_, a)| a.value).collect(),
782                            ty: RuntimeType::any(),
783                        },
784                    },
785                )]
786            }
787        }
788    }
789
790    // Either we didn't move the arg above, or we're not expecting one.
791    if !args.unlabeled.is_empty() {
792        // Not expecting an input arg, but found one or more
793        let actuals = args.labeled.keys();
794        let formals: Vec<_> = fn_def
795            .named_args
796            .keys()
797            .filter_map(|name| {
798                if actuals.clone().any(|a| a == name) {
799                    return None;
800                }
801
802                Some(format!("`{name}`"))
803            })
804            .collect();
805
806        let suggestion = if formals.is_empty() {
807            String::new()
808        } else {
809            format!("; suggested labels: {}", formals.join(", "))
810        };
811
812        let mut errors = args.unlabeled.iter().map(|(_, arg)| {
813            CompilationIssue::err(
814                arg.source_range,
815                format!("This argument needs a label, but it doesn't have one{suggestion}"),
816            )
817        });
818
819        let first = errors.next().unwrap();
820        errors.for_each(|e| exec_state.err(e));
821
822        return Err(KclError::new_argument(first.into()));
823    }
824
825    for (label, mut arg) in args.labeled {
826        match fn_def.named_args.get(&label) {
827            Some(NamedParam {
828                experimental: _,
829                default_value: def,
830                ty,
831            }) => {
832                // For optional args, passing None should be the same as not passing an arg.
833                if !(def.is_some() && matches!(arg.value, KclValue::KclNone { .. })) {
834                    if let Some(ty) = ty {
835                        // Suppress warnings about types because they should
836                        // only be warned about once for the function
837                        // definition.
838                        let rty = RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range, false, true)
839                            .map_err(|e| KclError::new_semantic(e.into()))?;
840                        arg.value = arg
841                                .value
842                                .coerce(
843                                    &rty,
844                                    true,
845                                    exec_state,
846                                )
847                                .map_err(|e| {
848                                    let mut message = format!(
849                                        "{label} requires {}",
850                                        type_err_str(ty, &arg.value, &arg.source_range, exec_state),
851                                    );
852                                    if let Some(ty) = e.explicit_coercion {
853                                        // TODO if we have access to the AST for the argument we could choose which example to suggest.
854                                        message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n  using a numeric suffix: `42{ty}`\n  or using type ascription: `foo(): {ty}`");
855                                    }
856                                    KclError::new_argument(KclErrorDetails::new(
857                                        message,
858                                        vec![arg.source_range],
859                                    ))
860                                })?;
861                    }
862                    result.labeled.insert(label, arg);
863                }
864            }
865            None => {
866                exec_state.err(CompilationIssue::err(
867                    arg.source_range,
868                    format!(
869                        "`{label}` is not an argument of {}",
870                        fn_name
871                            .map(|n| format!("`{n}`"))
872                            .unwrap_or_else(|| "this function".to_owned()),
873                    ),
874                ));
875            }
876        }
877    }
878
879    Ok(result)
880}
881
882fn assign_args_to_params_kw(
883    fn_def: &FunctionSource,
884    args: Args<Desugared>,
885    exec_state: &mut ExecState,
886) -> Result<(), KclError> {
887    // Add the arguments to the memory.  A new call frame should have already
888    // been created.
889    let source_ranges = fn_def.ast.as_source_ranges();
890
891    for (name, param) in fn_def.named_args.iter() {
892        let arg = args.labeled.get(name);
893        match arg {
894            Some(arg) => {
895                exec_state.mut_stack().add(
896                    name.clone(),
897                    arg.value.clone(),
898                    arg.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
899                )?;
900            }
901            None => match &param.default_value {
902                Some(default_val) => {
903                    let value = KclValue::from_default_param(default_val.clone(), exec_state);
904                    exec_state
905                        .mut_stack()
906                        .add(name.clone(), value, default_val.source_range())?;
907                }
908                None => {
909                    return Err(KclError::new_argument(KclErrorDetails::new(
910                        format!("This function requires a parameter {name}, but you haven't passed it one."),
911                        source_ranges,
912                    )));
913                }
914            },
915        }
916    }
917
918    if let Some((param_name, _)) = &fn_def.input_arg {
919        let Some(unlabeled) = args.unlabeled_kw_arg_unconverted() else {
920            debug_assert!(false, "Bad args");
921            return Err(KclError::new_internal(KclErrorDetails::new(
922                "Desugared arguments are inconsistent".to_owned(),
923                source_ranges,
924            )));
925        };
926        exec_state.mut_stack().add(
927            param_name.clone(),
928            unlabeled.value.clone(),
929            unlabeled.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
930        )?;
931    }
932
933    Ok(())
934}
935
936fn coerce_result_type(
937    result: Result<Option<KclValue>, KclError>,
938    fn_def: &FunctionSource,
939    exec_state: &mut ExecState,
940) -> Result<Option<KclValue>, KclError> {
941    if let Ok(Some(val)) = result {
942        if let Some(ret_ty) = &fn_def.return_type {
943            // Suppress warnings about types because they should only be warned
944            // about once for the function definition.
945            let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range(), false, true)
946                .map_err(|e| KclError::new_semantic(e.into()))?;
947            let val = val.coerce(&ty, true, exec_state).map_err(|_| {
948                KclError::new_type(KclErrorDetails::new(
949                    format!(
950                        "This function requires its result to be {}",
951                        type_err_str(ret_ty, &val, &(&val).into(), exec_state)
952                    ),
953                    ret_ty.as_source_ranges(),
954                ))
955            })?;
956            Ok(Some(val))
957        } else {
958            Ok(Some(val))
959        }
960    } else {
961        result
962    }
963}
964
965#[cfg(test)]
966mod test {
967    use std::sync::Arc;
968
969    use super::*;
970    use crate::execution::ContextType;
971    use crate::execution::EnvironmentRef;
972    use crate::execution::memory::Stack;
973    use crate::execution::parse_execute;
974    use crate::execution::types::NumericType;
975    use crate::parsing::ast::types::DefaultParamVal;
976    use crate::parsing::ast::types::FunctionExpression;
977    use crate::parsing::ast::types::Identifier;
978    use crate::parsing::ast::types::Parameter;
979    use crate::parsing::ast::types::Program;
980
981    #[tokio::test(flavor = "multi_thread")]
982    async fn test_assign_args_to_params() {
983        // Set up a little framework for this test.
984        fn mem(number: usize) -> KclValue {
985            KclValue::Number {
986                value: number as f64,
987                ty: NumericType::count(),
988                meta: Default::default(),
989            }
990        }
991        fn ident(s: &'static str) -> Node<Identifier> {
992            Node::no_src(Identifier {
993                name: s.to_owned(),
994                digest: None,
995            })
996        }
997        fn opt_param(s: &'static str) -> Parameter {
998            Parameter {
999                experimental: false,
1000                identifier: ident(s),
1001                param_type: None,
1002                default_value: Some(DefaultParamVal::none()),
1003                labeled: true,
1004                digest: None,
1005            }
1006        }
1007        fn req_param(s: &'static str) -> Parameter {
1008            Parameter {
1009                experimental: false,
1010                identifier: ident(s),
1011                param_type: None,
1012                default_value: None,
1013                labeled: true,
1014                digest: None,
1015            }
1016        }
1017        fn additional_program_memory(items: &[(String, KclValue)]) -> Stack {
1018            let mut program_memory = Stack::new_for_tests();
1019            for (name, item) in items {
1020                program_memory
1021                    .add(name.clone(), item.clone(), SourceRange::default())
1022                    .unwrap();
1023            }
1024            program_memory
1025        }
1026        // Declare the test cases.
1027        for (test_name, params, args, expected) in [
1028            ("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
1029            (
1030                "all params required, and all given, should be OK",
1031                vec![req_param("x")],
1032                vec![("x", mem(1))],
1033                Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
1034            ),
1035            (
1036                "all params required, none given, should error",
1037                vec![req_param("x")],
1038                vec![],
1039                Err(KclError::new_argument(KclErrorDetails::new(
1040                    "This function requires a parameter x, but you haven't passed it one.".to_owned(),
1041                    vec![SourceRange::default()],
1042                ))),
1043            ),
1044            (
1045                "all params optional, none given, should be OK",
1046                vec![opt_param("x")],
1047                vec![],
1048                Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])),
1049            ),
1050            (
1051                "mixed params, too few given",
1052                vec![req_param("x"), opt_param("y")],
1053                vec![],
1054                Err(KclError::new_argument(KclErrorDetails::new(
1055                    "This function requires a parameter x, but you haven't passed it one.".to_owned(),
1056                    vec![SourceRange::default()],
1057                ))),
1058            ),
1059            (
1060                "mixed params, minimum given, should be OK",
1061                vec![req_param("x"), opt_param("y")],
1062                vec![("x", mem(1))],
1063                Ok(additional_program_memory(&[
1064                    ("x".to_owned(), mem(1)),
1065                    ("y".to_owned(), KclValue::none()),
1066                ])),
1067            ),
1068            (
1069                "mixed params, maximum given, should be OK",
1070                vec![req_param("x"), opt_param("y")],
1071                vec![("x", mem(1)), ("y", mem(2))],
1072                Ok(additional_program_memory(&[
1073                    ("x".to_owned(), mem(1)),
1074                    ("y".to_owned(), mem(2)),
1075                ])),
1076            ),
1077        ] {
1078            // Run each test.
1079            let func_expr = Node::no_src(FunctionExpression {
1080                name: None,
1081                params,
1082                body: Program::empty(),
1083                return_type: None,
1084                digest: None,
1085            });
1086            let func_src = FunctionSource::kcl(
1087                Box::new(func_expr),
1088                EnvironmentRef::dummy(),
1089                crate::execution::kcl_value::KclFunctionSourceParams {
1090                    is_std: false,
1091                    experimental: false,
1092                    include_in_feature_tree: false,
1093                },
1094            );
1095            let labeled = args
1096                .iter()
1097                .map(|(name, value)| {
1098                    let arg = Arg::new(value.clone(), SourceRange::default());
1099                    ((*name).to_owned(), arg)
1100                })
1101                .collect::<IndexMap<_, _>>();
1102            let exec_ctxt = ExecutorContext {
1103                engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().unwrap())),
1104                fs: Arc::new(crate::fs::FileManager::new()),
1105                settings: Default::default(),
1106                context_type: ContextType::Mock,
1107            };
1108            let mut exec_state = ExecState::new(&exec_ctxt);
1109            exec_state.mod_local.stack = Stack::new_for_tests();
1110
1111            let args = Args {
1112                fn_name: Some("test".to_owned()),
1113                labeled,
1114                unlabeled: Vec::new(),
1115                source_range: SourceRange::default(),
1116                node_path: None,
1117                ctx: exec_ctxt,
1118                pipe_value: None,
1119                _status: std::marker::PhantomData,
1120            };
1121
1122            let actual = assign_args_to_params_kw(&func_src, args, &mut exec_state).map(|_| exec_state.mod_local.stack);
1123            assert_eq!(
1124                actual, expected,
1125                "failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
1126            );
1127        }
1128    }
1129
1130    #[tokio::test(flavor = "multi_thread")]
1131    async fn type_check_user_args() {
1132        let program = r#"fn makeMessage(prefix: string, suffix: string) {
1133  return prefix + suffix
1134}
1135
1136msg1 = makeMessage(prefix = "world", suffix = " hello")
1137msg2 = makeMessage(prefix = 1, suffix = 3)"#;
1138        let err = parse_execute(program).await.unwrap_err();
1139        assert_eq!(
1140            err.message(),
1141            "prefix requires a value with type `string`, but found a value with type `number`.\nThe found value is a number but has incomplete units information. You can probably fix this error by specifying the units using type ascription, e.g., `len: mm` or `(a * b): deg`."
1142        )
1143    }
1144
1145    #[tokio::test(flavor = "multi_thread")]
1146    async fn map_closure_error_mentions_fn_name() {
1147        let program = r#"
1148arr = ["hello"]
1149map(array = arr, f = fn(@item: number) { return item })
1150"#;
1151        let err = parse_execute(program).await.unwrap_err();
1152        assert!(
1153            err.message().contains("map closure"),
1154            "expected map closure errors to include the closure name, got: {}",
1155            err.message()
1156        );
1157    }
1158
1159    #[tokio::test(flavor = "multi_thread")]
1160    async fn array_input_arg() {
1161        let ast = r#"fn f(@input: [mm]) { return 1 }
1162f([1, 2, 3])
1163f(1, 2, 3)
1164"#;
1165        parse_execute(ast).await.unwrap();
1166    }
1167}