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