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