1use async_recursion::async_recursion;
2use indexmap::IndexMap;
3
4use crate::{
5 docs::StdLibFn,
6 errors::{KclError, KclErrorDetails},
7 execution::{
8 cad_op::{Group, OpArg, OpKclValue, Operation},
9 kcl_value::FunctionSource,
10 memory,
11 types::RuntimeType,
12 BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo,
13 TagIdentifier,
14 },
15 parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
16 source_range::SourceRange,
17 std::StdFn,
18 CompilationError,
19};
20
21#[derive(Debug, Clone)]
22pub struct Args {
23 pub args: Vec<Arg>,
25 pub kw_args: KwArgs,
27 pub source_range: SourceRange,
28 pub ctx: ExecutorContext,
29 pub pipe_value: Option<Arg>,
32}
33
34impl Args {
35 pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
36 Self {
37 args,
38 kw_args: Default::default(),
39 source_range,
40 ctx,
41 pipe_value,
42 }
43 }
44
45 pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
47 Self {
48 args: Default::default(),
49 kw_args,
50 source_range,
51 ctx,
52 pipe_value,
53 }
54 }
55
56 pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
58 self.kw_args
59 .unlabeled
60 .as_ref()
61 .map(|(_, a)| a)
62 .or(self.args.first())
63 .or(self.pipe_value.as_ref())
64 }
65}
66
67#[derive(Debug, Clone)]
68pub struct Arg {
69 pub value: KclValue,
71 pub source_range: SourceRange,
73}
74
75impl Arg {
76 pub fn new(value: KclValue, source_range: SourceRange) -> Self {
77 Self { value, source_range }
78 }
79
80 pub fn synthetic(value: KclValue) -> Self {
81 Self {
82 value,
83 source_range: SourceRange::synthetic(),
84 }
85 }
86
87 pub fn source_ranges(&self) -> Vec<SourceRange> {
88 vec![self.source_range]
89 }
90}
91
92#[derive(Debug, Clone, Default)]
93pub struct KwArgs {
94 pub unlabeled: Option<(Option<String>, Arg)>,
98 pub labeled: IndexMap<String, Arg>,
100 pub errors: Vec<Arg>,
101}
102
103impl KwArgs {
104 pub fn len(&self) -> usize {
106 self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
107 }
108 pub fn is_empty(&self) -> bool {
110 self.labeled.len() == 0 && self.unlabeled.is_none()
111 }
112}
113
114struct FunctionDefinition<'a> {
115 input_arg: Option<(String, Option<Type>)>,
116 named_args: IndexMap<String, (Option<DefaultParamVal>, Option<Type>)>,
117 return_type: Option<Node<Type>>,
118 deprecated: bool,
119 include_in_feature_tree: bool,
120 is_std: bool,
121 body: FunctionBody<'a>,
122}
123
124#[derive(Debug)]
125enum FunctionBody<'a> {
126 Rust(StdFn),
127 Kcl(&'a Node<Program>, EnvironmentRef),
128}
129
130impl<'a> From<&'a FunctionSource> for FunctionDefinition<'a> {
131 fn from(value: &'a FunctionSource) -> Self {
132 #[allow(clippy::type_complexity)]
133 fn args_from_ast(
134 ast: &FunctionExpression,
135 ) -> (
136 Option<(String, Option<Type>)>,
137 IndexMap<String, (Option<DefaultParamVal>, Option<Type>)>,
138 ) {
139 let mut input_arg = None;
140 let mut named_args = IndexMap::new();
141 for p in &ast.params {
142 if !p.labeled {
143 input_arg = Some((p.identifier.name.clone(), p.type_.as_ref().map(|t| t.inner.clone())));
144 continue;
145 }
146
147 named_args.insert(
148 p.identifier.name.clone(),
149 (p.default_value.clone(), p.type_.as_ref().map(|t| t.inner.clone())),
150 );
151 }
152
153 (input_arg, named_args)
154 }
155
156 match value {
157 FunctionSource::Std { func, ast, props } => {
158 let (input_arg, named_args) = args_from_ast(ast);
159 FunctionDefinition {
160 input_arg,
161 named_args,
162 return_type: ast.return_type.clone(),
163 deprecated: props.deprecated,
164 include_in_feature_tree: props.include_in_feature_tree,
165 is_std: true,
166 body: FunctionBody::Rust(*func),
167 }
168 }
169 FunctionSource::User { ast, memory, .. } => {
170 let (input_arg, named_args) = args_from_ast(ast);
171 FunctionDefinition {
172 input_arg,
173 named_args,
174 return_type: ast.return_type.clone(),
175 deprecated: false,
176 include_in_feature_tree: true,
177 is_std: false,
179 body: FunctionBody::Kcl(&ast.body, *memory),
180 }
181 }
182 FunctionSource::None => unreachable!(),
183 }
184 }
185}
186
187impl From<&dyn StdLibFn> for FunctionDefinition<'static> {
188 fn from(value: &dyn StdLibFn) -> Self {
189 let mut input_arg = None;
190 let mut named_args = IndexMap::new();
191 for a in value.args(false) {
192 if !a.label_required {
193 input_arg = Some((a.name.clone(), None));
194 continue;
195 }
196
197 named_args.insert(
198 a.name.clone(),
199 (
200 if a.required {
201 None
202 } else {
203 Some(DefaultParamVal::none())
204 },
205 None,
206 ),
207 );
208 }
209 FunctionDefinition {
210 input_arg,
211 named_args,
212 return_type: None,
213 deprecated: value.deprecated(),
214 include_in_feature_tree: value.feature_tree_operation(),
215 is_std: true,
216 body: FunctionBody::Rust(value.std_lib_fn()),
217 }
218 }
219}
220
221impl Node<CallExpressionKw> {
222 #[async_recursion]
223 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
224 let fn_name = &self.callee;
225 let callsite: SourceRange = self.into();
226
227 let mut fn_args = IndexMap::with_capacity(self.arguments.len());
229 let mut errors = Vec::new();
230 for arg_expr in &self.arguments {
231 let source_range = SourceRange::from(arg_expr.arg.clone());
232 let metadata = Metadata { source_range };
233 let value = ctx
234 .execute_expr(&arg_expr.arg, exec_state, &metadata, &[], StatementKind::Expression)
235 .await?;
236 let arg = Arg::new(value, source_range);
237 match &arg_expr.label {
238 Some(l) => {
239 fn_args.insert(l.name.clone(), arg);
240 }
241 None => {
242 if let Some(id) = arg_expr.arg.ident_name() {
243 fn_args.insert(id.to_owned(), arg);
244 } else {
245 errors.push(arg);
246 }
247 }
248 }
249 }
250
251 let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
253 let source_range = SourceRange::from(arg_expr.clone());
254 let metadata = Metadata { source_range };
255 let value = ctx
256 .execute_expr(arg_expr, exec_state, &metadata, &[], StatementKind::Expression)
257 .await?;
258
259 let label = arg_expr.ident_name().map(str::to_owned);
260
261 Some((label, Arg::new(value, source_range)))
262 } else {
263 None
264 };
265
266 let args = Args::new_kw(
267 KwArgs {
268 unlabeled,
269 labeled: fn_args,
270 errors,
271 },
272 self.into(),
273 ctx.clone(),
274 exec_state.pipe_value().map(|v| Arg::new(v.clone(), callsite)),
275 );
276
277 match ctx.stdlib.get_rust_function(fn_name) {
278 Some(func) => {
279 let def: FunctionDefinition = (&*func).into();
280 def.call_kw(Some(func.name()), exec_state, ctx, args, callsite)
282 .await
283 .map(Option::unwrap)
284 .map_err(|e| {
285 e.set_last_backtrace_fn_name(Some(func.name()))
290 })
291 }
292 None => {
293 let func = fn_name.get_result(exec_state, ctx).await?.clone();
296
297 let Some(fn_src) = func.as_function() else {
298 return Err(KclError::Semantic(KclErrorDetails::new(
299 "cannot call this because it isn't a function".to_string(),
300 vec![callsite],
301 )));
302 };
303
304 let return_value = fn_src
305 .call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
306 .await
307 .map_err(|e| {
308 e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
313 })?;
314
315 let result = return_value.ok_or_else(move || {
316 let mut source_ranges: Vec<SourceRange> = vec![callsite];
317 if let KclValue::Function { meta, .. } = func {
319 source_ranges = meta.iter().map(|m| m.source_range).collect();
320 };
321 KclError::UndefinedValue(KclErrorDetails::new(
322 format!("Result of user-defined function {} is undefined", fn_name),
323 source_ranges,
324 ))
325 })?;
326
327 Ok(result)
328 }
329 }
330 }
331}
332
333impl FunctionDefinition<'_> {
334 pub async fn call_kw(
335 &self,
336 fn_name: Option<String>,
337 exec_state: &mut ExecState,
338 ctx: &ExecutorContext,
339 mut args: Args,
340 callsite: SourceRange,
341 ) -> Result<Option<KclValue>, KclError> {
342 if self.deprecated {
343 exec_state.warn(CompilationError::err(
344 callsite,
345 format!(
346 "{} is deprecated, see the docs for a recommended replacement",
347 match &fn_name {
348 Some(n) => format!("`{n}`"),
349 None => "This function".to_owned(),
350 }
351 ),
352 ));
353 }
354
355 type_check_params_kw(fn_name.as_deref(), self, &mut args.kw_args, exec_state)?;
356
357 self.body.prep_mem(exec_state);
359
360 let op = if self.include_in_feature_tree {
361 let op_labeled_args = args
362 .kw_args
363 .labeled
364 .iter()
365 .map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
366 .collect();
367
368 if self.is_std {
369 Some(Operation::StdLibCall {
370 name: fn_name.clone().unwrap_or_else(|| "unknown function".to_owned()),
371 unlabeled_arg: args
372 .unlabeled_kw_arg_unconverted()
373 .map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
374 labeled_args: op_labeled_args,
375 source_range: callsite,
376 is_error: false,
377 })
378 } else {
379 exec_state.push_op(Operation::GroupBegin {
380 group: Group::FunctionCall {
381 name: fn_name.clone(),
382 function_source_range: self.as_source_range().unwrap(),
383 unlabeled_arg: args
384 .kw_args
385 .unlabeled
386 .as_ref()
387 .map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
388 labeled_args: op_labeled_args,
389 },
390 source_range: callsite,
391 });
392
393 None
394 }
395 } else {
396 None
397 };
398
399 let mut result = match &self.body {
400 FunctionBody::Rust(f) => f(exec_state, args).await.map(Some),
401 FunctionBody::Kcl(f, _) => {
402 if let Err(e) = assign_args_to_params_kw(self, args, exec_state) {
403 exec_state.mut_stack().pop_env();
404 return Err(e);
405 }
406
407 ctx.exec_block(f, exec_state, BodyType::Block).await.map(|_| {
408 exec_state
409 .stack()
410 .get(memory::RETURN_NAME, f.as_source_range())
411 .ok()
412 .cloned()
413 })
414 }
415 };
416
417 exec_state.mut_stack().pop_env();
418
419 if let Some(mut op) = op {
420 op.set_std_lib_call_is_error(result.is_err());
421 exec_state.push_op(op);
427 } else if !self.is_std {
428 exec_state.push_op(Operation::GroupEnd);
429 }
430
431 if self.is_std {
432 if let Ok(Some(result)) = &mut result {
433 update_memory_for_tags_of_geometry(result, exec_state)?;
434 }
435 }
436
437 coerce_result_type(result, self, exec_state)
438 }
439
440 fn as_source_range(&self) -> Option<SourceRange> {
442 match &self.body {
443 FunctionBody::Rust(_) => None,
444 FunctionBody::Kcl(p, _) => Some(p.as_source_range()),
445 }
446 }
447}
448
449impl FunctionBody<'_> {
450 fn prep_mem(&self, exec_state: &mut ExecState) {
451 match self {
452 FunctionBody::Rust(_) => exec_state.mut_stack().push_new_env_for_rust_call(),
453 FunctionBody::Kcl(_, memory) => exec_state.mut_stack().push_new_env_for_call(*memory),
454 }
455 }
456}
457
458impl FunctionSource {
459 pub async fn call_kw(
460 &self,
461 fn_name: Option<String>,
462 exec_state: &mut ExecState,
463 ctx: &ExecutorContext,
464 args: Args,
465 callsite: SourceRange,
466 ) -> Result<Option<KclValue>, KclError> {
467 let def: FunctionDefinition = self.into();
468 def.call_kw(fn_name, exec_state, ctx, args, callsite).await
469 }
470}
471
472fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
473 match result {
478 KclValue::Sketch { value } => {
479 for (name, tag) in value.tags.iter() {
480 if exec_state.stack().cur_frame_contains(name) {
481 exec_state.mut_stack().update(name, |v, _| {
482 v.as_mut_tag().unwrap().merge_info(tag);
483 });
484 } else {
485 exec_state
486 .mut_stack()
487 .add(
488 name.to_owned(),
489 KclValue::TagIdentifier(Box::new(tag.clone())),
490 SourceRange::default(),
491 )
492 .unwrap();
493 }
494 }
495 }
496 KclValue::Solid { ref mut value } => {
497 for v in &value.value {
498 if let Some(tag) = v.get_tag() {
499 let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
501 let mut t = t.clone();
502 let Some(info) = t.get_cur_info() else {
503 return Err(KclError::Internal(KclErrorDetails::new(
504 format!("Tag {} does not have path info", tag.name),
505 vec![tag.into()],
506 )));
507 };
508
509 let mut info = info.clone();
510 info.surface = Some(v.clone());
511 info.sketch = value.id;
512 t.info.push((exec_state.stack().current_epoch(), info));
513 t
514 } else {
515 TagIdentifier {
518 value: tag.name.clone(),
519 info: vec![(
520 exec_state.stack().current_epoch(),
521 TagEngineInfo {
522 id: v.get_id(),
523 surface: Some(v.clone()),
524 path: None,
525 sketch: value.id,
526 },
527 )],
528 meta: vec![Metadata {
529 source_range: tag.clone().into(),
530 }],
531 }
532 };
533
534 value.sketch.merge_tags(Some(&tag_id).into_iter());
536
537 if exec_state.stack().cur_frame_contains(&tag.name) {
538 exec_state.mut_stack().update(&tag.name, |v, _| {
539 v.as_mut_tag().unwrap().merge_info(&tag_id);
540 });
541 } else {
542 exec_state
543 .mut_stack()
544 .add(
545 tag.name.clone(),
546 KclValue::TagIdentifier(Box::new(tag_id)),
547 SourceRange::default(),
548 )
549 .unwrap();
550 }
551 }
552 }
553
554 if !value.sketch.tags.is_empty() {
556 let sketches_to_update: Vec<_> = exec_state
557 .stack()
558 .find_keys_in_current_env(|v| match v {
559 KclValue::Sketch { value: sk } => sk.original_id == value.sketch.original_id,
560 _ => false,
561 })
562 .cloned()
563 .collect();
564
565 for k in sketches_to_update {
566 exec_state.mut_stack().update(&k, |v, _| {
567 let sketch = v.as_mut_sketch().unwrap();
568 sketch.merge_tags(value.sketch.tags.values());
569 });
570 }
571 }
572 }
573 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
574 for v in value {
575 update_memory_for_tags_of_geometry(v, exec_state)?;
576 }
577 }
578 _ => {}
579 }
580 Ok(())
581}
582
583fn type_check_params_kw(
584 fn_name: Option<&str>,
585 fn_def: &FunctionDefinition<'_>,
586 args: &mut KwArgs,
587 exec_state: &mut ExecState,
588) -> Result<(), KclError> {
589 if let Some((Some(label), _)) = &args.unlabeled {
592 if (fn_def.input_arg.is_none() || exec_state.pipe_value().is_some())
593 && fn_def.named_args.iter().any(|p| p.0 == label)
594 && !args.labeled.contains_key(label)
595 {
596 let (label, arg) = args.unlabeled.take().unwrap();
597 args.labeled.insert(label.unwrap(), arg);
598 }
599 }
600
601 for (label, arg) in &mut args.labeled {
602 match fn_def.named_args.get(label) {
603 Some((_, ty)) => {
604 if let Some(ty) = ty {
605 arg.value = arg
606 .value
607 .coerce(
608 &RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
609 true,
610 exec_state,
611 )
612 .map_err(|e| {
613 let mut message = format!(
614 "{label} requires a value with type `{}`, but found {}",
615 ty,
616 arg.value.human_friendly_type(),
617 );
618 if let Some(ty) = e.explicit_coercion {
619 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(): number({ty})`");
621 }
622 KclError::Semantic(KclErrorDetails::new(
623 message,
624 vec![arg.source_range],
625 ))
626 })?;
627 }
628 }
629 None => {
630 exec_state.err(CompilationError::err(
631 arg.source_range,
632 format!(
633 "`{label}` is not an argument of {}",
634 fn_name
635 .map(|n| format!("`{}`", n))
636 .unwrap_or_else(|| "this function".to_owned()),
637 ),
638 ));
639 }
640 }
641 }
642
643 if !args.errors.is_empty() {
644 let actuals = args.labeled.keys();
645 let formals: Vec<_> = fn_def
646 .named_args
647 .keys()
648 .filter_map(|name| {
649 if actuals.clone().any(|a| a == name) {
650 return None;
651 }
652
653 Some(format!("`{name}`"))
654 })
655 .collect();
656
657 let suggestion = if formals.is_empty() {
658 String::new()
659 } else {
660 format!("; suggested labels: {}", formals.join(", "))
661 };
662
663 let mut errors = args.errors.iter().map(|e| {
664 CompilationError::err(
665 e.source_range,
666 format!("This argument needs a label, but it doesn't have one{suggestion}"),
667 )
668 });
669
670 let first = errors.next().unwrap();
671 errors.for_each(|e| exec_state.err(e));
672
673 return Err(KclError::Semantic(first.into()));
674 }
675
676 if let Some(arg) = &mut args.unlabeled {
677 if let Some((_, Some(ty))) = &fn_def.input_arg {
678 arg.1.value = arg
679 .1
680 .value
681 .coerce(
682 &RuntimeType::from_parsed(ty.clone(), exec_state, arg.1.source_range)
683 .map_err(|e| KclError::Semantic(e.into()))?,
684 true,
685 exec_state,
686 )
687 .map_err(|_| {
688 KclError::Semantic(KclErrorDetails::new(
689 format!(
690 "The input argument of {} requires a value with type `{}`, but found {}",
691 fn_name
692 .map(|n| format!("`{}`", n))
693 .unwrap_or_else(|| "this function".to_owned()),
694 ty,
695 arg.1.value.human_friendly_type()
696 ),
697 vec![arg.1.source_range],
698 ))
699 })?;
700 }
701 } else if let Some((name, _)) = &fn_def.input_arg {
702 if let Some(arg) = args.labeled.get(name) {
703 exec_state.err(CompilationError::err(
704 arg.source_range,
705 format!(
706 "{} expects an unlabeled first parameter (`@{name}`), but it is labelled in the call",
707 fn_name
708 .map(|n| format!("The function `{}`", n))
709 .unwrap_or_else(|| "This function".to_owned()),
710 ),
711 ));
712 }
713 }
714
715 Ok(())
716}
717
718fn assign_args_to_params_kw(
719 fn_def: &FunctionDefinition<'_>,
720 args: Args,
721 exec_state: &mut ExecState,
722) -> Result<(), KclError> {
723 let source_ranges = fn_def.as_source_range().into_iter().collect();
726
727 for (name, (default, _)) in fn_def.named_args.iter() {
728 let arg = args.kw_args.labeled.get(name);
729 match arg {
730 Some(arg) => {
731 exec_state.mut_stack().add(
732 name.clone(),
733 arg.value.clone(),
734 arg.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
735 )?;
736 }
737 None => match default {
738 Some(ref default_val) => {
739 let value = KclValue::from_default_param(default_val.clone(), exec_state);
740 exec_state
741 .mut_stack()
742 .add(name.clone(), value, default_val.source_range())?;
743 }
744 None => {
745 return Err(KclError::Semantic(KclErrorDetails::new(
746 format!(
747 "This function requires a parameter {}, but you haven't passed it one.",
748 name
749 ),
750 source_ranges,
751 )));
752 }
753 },
754 }
755 }
756
757 if let Some((param_name, _)) = &fn_def.input_arg {
758 let unlabelled = args.unlabeled_kw_arg_unconverted();
759
760 let Some(unlabeled) = unlabelled else {
761 return Err(if args.kw_args.labeled.contains_key(param_name) {
762 KclError::Semantic(KclErrorDetails::new(
763 format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
764 source_ranges,
765 ))
766 } else {
767 KclError::Semantic(KclErrorDetails::new(
768 "This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(),
769 source_ranges,
770 ))
771 });
772 };
773 exec_state.mut_stack().add(
774 param_name.clone(),
775 unlabeled.value.clone(),
776 unlabeled.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
777 )?;
778 }
779
780 Ok(())
781}
782
783fn coerce_result_type(
784 result: Result<Option<KclValue>, KclError>,
785 fn_def: &FunctionDefinition<'_>,
786 exec_state: &mut ExecState,
787) -> Result<Option<KclValue>, KclError> {
788 if let Ok(Some(val)) = result {
789 if let Some(ret_ty) = &fn_def.return_type {
790 let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
791 .map_err(|e| KclError::Semantic(e.into()))?;
792 let val = val.coerce(&ty, true, exec_state).map_err(|_| {
793 KclError::Semantic(KclErrorDetails::new(
794 format!(
795 "This function requires its result to be of type `{}`, but found {}",
796 ty.human_friendly_type(),
797 val.human_friendly_type(),
798 ),
799 ret_ty.as_source_ranges(),
800 ))
801 })?;
802 Ok(Some(val))
803 } else {
804 Ok(Some(val))
805 }
806 } else {
807 result
808 }
809}
810
811#[cfg(test)]
812mod test {
813 use std::sync::Arc;
814
815 use super::*;
816 use crate::{
817 execution::{memory::Stack, parse_execute, types::NumericType, ContextType},
818 parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
819 };
820
821 #[tokio::test(flavor = "multi_thread")]
822 async fn test_assign_args_to_params() {
823 fn mem(number: usize) -> KclValue {
825 KclValue::Number {
826 value: number as f64,
827 ty: NumericType::count(),
828 meta: Default::default(),
829 }
830 }
831 fn ident(s: &'static str) -> Node<Identifier> {
832 Node::no_src(Identifier {
833 name: s.to_owned(),
834 digest: None,
835 })
836 }
837 fn opt_param(s: &'static str) -> Parameter {
838 Parameter {
839 identifier: ident(s),
840 type_: None,
841 default_value: Some(DefaultParamVal::none()),
842 labeled: true,
843 digest: None,
844 }
845 }
846 fn req_param(s: &'static str) -> Parameter {
847 Parameter {
848 identifier: ident(s),
849 type_: None,
850 default_value: None,
851 labeled: true,
852 digest: None,
853 }
854 }
855 fn additional_program_memory(items: &[(String, KclValue)]) -> Stack {
856 let mut program_memory = Stack::new_for_tests();
857 for (name, item) in items {
858 program_memory
859 .add(name.clone(), item.clone(), SourceRange::default())
860 .unwrap();
861 }
862 program_memory
863 }
864 for (test_name, params, args, expected) in [
866 ("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
867 (
868 "all params required, and all given, should be OK",
869 vec![req_param("x")],
870 vec![("x", mem(1))],
871 Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
872 ),
873 (
874 "all params required, none given, should error",
875 vec![req_param("x")],
876 vec![],
877 Err(KclError::Semantic(KclErrorDetails::new(
878 "This function requires a parameter x, but you haven't passed it one.".to_owned(),
879 vec![SourceRange::default()],
880 ))),
881 ),
882 (
883 "all params optional, none given, should be OK",
884 vec![opt_param("x")],
885 vec![],
886 Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])),
887 ),
888 (
889 "mixed params, too few given",
890 vec![req_param("x"), opt_param("y")],
891 vec![],
892 Err(KclError::Semantic(KclErrorDetails::new(
893 "This function requires a parameter x, but you haven't passed it one.".to_owned(),
894 vec![SourceRange::default()],
895 ))),
896 ),
897 (
898 "mixed params, minimum given, should be OK",
899 vec![req_param("x"), opt_param("y")],
900 vec![("x", mem(1))],
901 Ok(additional_program_memory(&[
902 ("x".to_owned(), mem(1)),
903 ("y".to_owned(), KclValue::none()),
904 ])),
905 ),
906 (
907 "mixed params, maximum given, should be OK",
908 vec![req_param("x"), opt_param("y")],
909 vec![("x", mem(1)), ("y", mem(2))],
910 Ok(additional_program_memory(&[
911 ("x".to_owned(), mem(1)),
912 ("y".to_owned(), mem(2)),
913 ])),
914 ),
915 ] {
916 let func_expr = Node::no_src(FunctionExpression {
918 params,
919 body: Program::empty(),
920 return_type: None,
921 digest: None,
922 });
923 let func_src = FunctionSource::User {
924 ast: Box::new(func_expr),
925 settings: Default::default(),
926 memory: EnvironmentRef::dummy(),
927 };
928 let labeled = args
929 .iter()
930 .map(|(name, value)| {
931 let arg = Arg::new(value.clone(), SourceRange::default());
932 ((*name).to_owned(), arg)
933 })
934 .collect::<IndexMap<_, _>>();
935 let exec_ctxt = ExecutorContext {
936 engine: Arc::new(Box::new(
937 crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
938 )),
939 fs: Arc::new(crate::fs::FileManager::new()),
940 stdlib: Arc::new(crate::std::StdLib::new()),
941 settings: Default::default(),
942 context_type: ContextType::Mock,
943 };
944 let mut exec_state = ExecState::new(&exec_ctxt);
945 exec_state.mod_local.stack = Stack::new_for_tests();
946
947 let args = Args::new_kw(
948 KwArgs {
949 unlabeled: None,
950 labeled,
951 errors: Vec::new(),
952 },
953 SourceRange::default(),
954 exec_ctxt,
955 None,
956 );
957 let actual = assign_args_to_params_kw(&(&func_src).into(), args, &mut exec_state)
958 .map(|_| exec_state.mod_local.stack);
959 assert_eq!(
960 actual, expected,
961 "failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
962 );
963 }
964 }
965
966 #[tokio::test(flavor = "multi_thread")]
967 async fn type_check_user_args() {
968 let program = r#"fn makeMessage(prefix: string, suffix: string) {
969 return prefix + suffix
970}
971
972msg1 = makeMessage(prefix = "world", suffix = " hello")
973msg2 = makeMessage(prefix = 1, suffix = 3)"#;
974 let err = parse_execute(program).await.unwrap_err();
975 assert_eq!(
976 err.message(),
977 "prefix requires a value with type `string`, but found number(default units)"
978 )
979 }
980}