1use async_recursion::async_recursion;
2use indexmap::IndexMap;
3
4use super::{types::ArrayLen, EnvironmentRef};
5use crate::{
6 docs::StdLibFn,
7 errors::{KclError, KclErrorDetails},
8 execution::{
9 cad_op::{Group, OpArg, OpKclValue, Operation},
10 kcl_value::FunctionSource,
11 memory,
12 types::RuntimeType,
13 BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, 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_fn() 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 exec_state,
610 )
611 .map_err(|e| {
612 let mut message = format!(
613 "{label} requires a value with type `{}`, but found {}",
614 ty,
615 arg.value.human_friendly_type(),
616 );
617 if let Some(ty) = e.explicit_coercion {
618 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})`");
620 }
621 KclError::Semantic(KclErrorDetails::new(
622 message,
623 vec![arg.source_range],
624 ))
625 })?;
626 }
627 }
628 None => {
629 exec_state.err(CompilationError::err(
630 arg.source_range,
631 format!(
632 "`{label}` is not an argument of {}",
633 fn_name
634 .map(|n| format!("`{}`", n))
635 .unwrap_or_else(|| "this function".to_owned()),
636 ),
637 ));
638 }
639 }
640 }
641
642 if !args.errors.is_empty() {
643 let actuals = args.labeled.keys();
644 let formals: Vec<_> = fn_def
645 .named_args
646 .keys()
647 .filter_map(|name| {
648 if actuals.clone().any(|a| a == name) {
649 return None;
650 }
651
652 Some(format!("`{name}`"))
653 })
654 .collect();
655
656 let suggestion = if formals.is_empty() {
657 String::new()
658 } else {
659 format!("; suggested labels: {}", formals.join(", "))
660 };
661
662 let mut errors = args.errors.iter().map(|e| {
663 CompilationError::err(
664 e.source_range,
665 format!("This argument needs a label, but it doesn't have one{suggestion}"),
666 )
667 });
668
669 let first = errors.next().unwrap();
670 errors.for_each(|e| exec_state.err(e));
671
672 return Err(KclError::Semantic(first.into()));
673 }
674
675 if let Some(arg) = &mut args.unlabeled {
676 if let Some((_, Some(ty))) = &fn_def.input_arg {
677 arg.1.value = arg
678 .1
679 .value
680 .coerce(
681 &RuntimeType::from_parsed(ty.clone(), exec_state, arg.1.source_range)
682 .map_err(|e| KclError::Semantic(e.into()))?,
683 exec_state,
684 )
685 .map_err(|_| {
686 KclError::Semantic(KclErrorDetails::new(
687 format!(
688 "The input argument of {} requires a value with type `{}`, but found {}",
689 fn_name
690 .map(|n| format!("`{}`", n))
691 .unwrap_or_else(|| "this function".to_owned()),
692 ty,
693 arg.1.value.human_friendly_type()
694 ),
695 vec![arg.1.source_range],
696 ))
697 })?;
698 }
699 } else if let Some((name, _)) = &fn_def.input_arg {
700 if let Some(arg) = args.labeled.get(name) {
701 exec_state.err(CompilationError::err(
702 arg.source_range,
703 format!(
704 "{} expects an unlabeled first parameter (`@{name}`), but it is labelled in the call",
705 fn_name
706 .map(|n| format!("The function `{}`", n))
707 .unwrap_or_else(|| "This function".to_owned()),
708 ),
709 ));
710 }
711 }
712
713 Ok(())
714}
715
716fn assign_args_to_params_kw(
717 fn_def: &FunctionDefinition<'_>,
718 args: Args,
719 exec_state: &mut ExecState,
720) -> Result<(), KclError> {
721 let source_ranges = fn_def.as_source_range().into_iter().collect();
724
725 for (name, (default, _)) in fn_def.named_args.iter() {
726 let arg = args.kw_args.labeled.get(name);
727 match arg {
728 Some(arg) => {
729 exec_state.mut_stack().add(
730 name.clone(),
731 arg.value.clone(),
732 arg.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
733 )?;
734 }
735 None => match default {
736 Some(ref default_val) => {
737 let value = KclValue::from_default_param(default_val.clone(), exec_state);
738 exec_state
739 .mut_stack()
740 .add(name.clone(), value, default_val.source_range())?;
741 }
742 None => {
743 return Err(KclError::Semantic(KclErrorDetails::new(
744 format!(
745 "This function requires a parameter {}, but you haven't passed it one.",
746 name
747 ),
748 source_ranges,
749 )));
750 }
751 },
752 }
753 }
754
755 if let Some((param_name, _)) = &fn_def.input_arg {
756 let unlabelled = args.unlabeled_kw_arg_unconverted();
757
758 let Some(unlabeled) = unlabelled else {
759 return Err(if args.kw_args.labeled.contains_key(param_name) {
760 KclError::Semantic(KclErrorDetails::new(
761 format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
762 source_ranges,
763 ))
764 } else {
765 KclError::Semantic(KclErrorDetails::new(
766 "This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(),
767 source_ranges,
768 ))
769 });
770 };
771 exec_state.mut_stack().add(
772 param_name.clone(),
773 unlabeled.value.clone(),
774 unlabeled.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
775 )?;
776 }
777
778 Ok(())
779}
780
781fn coerce_result_type(
782 result: Result<Option<KclValue>, KclError>,
783 fn_def: &FunctionDefinition<'_>,
784 exec_state: &mut ExecState,
785) -> Result<Option<KclValue>, KclError> {
786 if let Ok(Some(val)) = result {
787 if let Some(ret_ty) = &fn_def.return_type {
788 let mut ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
789 .map_err(|e| KclError::Semantic(e.into()))?;
790 if let RuntimeType::Array(inner, ArrayLen::NonEmpty) = &ty {
798 ty = RuntimeType::Union(vec![(**inner).clone(), ty]);
799 }
800 let val = val.coerce(&ty, exec_state).map_err(|_| {
801 KclError::Semantic(KclErrorDetails::new(
802 format!(
803 "This function requires its result to be of type `{}`, but found {}",
804 ty.human_friendly_type(),
805 val.human_friendly_type(),
806 ),
807 ret_ty.as_source_ranges(),
808 ))
809 })?;
810 Ok(Some(val))
811 } else {
812 Ok(Some(val))
813 }
814 } else {
815 result
816 }
817}
818
819#[cfg(test)]
820mod test {
821 use std::sync::Arc;
822
823 use super::*;
824 use crate::{
825 execution::{memory::Stack, parse_execute, types::NumericType, ContextType},
826 parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
827 };
828
829 #[tokio::test(flavor = "multi_thread")]
830 async fn test_assign_args_to_params() {
831 fn mem(number: usize) -> KclValue {
833 KclValue::Number {
834 value: number as f64,
835 ty: NumericType::count(),
836 meta: Default::default(),
837 }
838 }
839 fn ident(s: &'static str) -> Node<Identifier> {
840 Node::no_src(Identifier {
841 name: s.to_owned(),
842 digest: None,
843 })
844 }
845 fn opt_param(s: &'static str) -> Parameter {
846 Parameter {
847 identifier: ident(s),
848 type_: None,
849 default_value: Some(DefaultParamVal::none()),
850 labeled: true,
851 digest: None,
852 }
853 }
854 fn req_param(s: &'static str) -> Parameter {
855 Parameter {
856 identifier: ident(s),
857 type_: None,
858 default_value: None,
859 labeled: true,
860 digest: None,
861 }
862 }
863 fn additional_program_memory(items: &[(String, KclValue)]) -> Stack {
864 let mut program_memory = Stack::new_for_tests();
865 for (name, item) in items {
866 program_memory
867 .add(name.clone(), item.clone(), SourceRange::default())
868 .unwrap();
869 }
870 program_memory
871 }
872 for (test_name, params, args, expected) in [
874 ("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
875 (
876 "all params required, and all given, should be OK",
877 vec![req_param("x")],
878 vec![("x", mem(1))],
879 Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
880 ),
881 (
882 "all params required, none given, should error",
883 vec![req_param("x")],
884 vec![],
885 Err(KclError::Semantic(KclErrorDetails::new(
886 "This function requires a parameter x, but you haven't passed it one.".to_owned(),
887 vec![SourceRange::default()],
888 ))),
889 ),
890 (
891 "all params optional, none given, should be OK",
892 vec![opt_param("x")],
893 vec![],
894 Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])),
895 ),
896 (
897 "mixed params, too few given",
898 vec![req_param("x"), opt_param("y")],
899 vec![],
900 Err(KclError::Semantic(KclErrorDetails::new(
901 "This function requires a parameter x, but you haven't passed it one.".to_owned(),
902 vec![SourceRange::default()],
903 ))),
904 ),
905 (
906 "mixed params, minimum given, should be OK",
907 vec![req_param("x"), opt_param("y")],
908 vec![("x", mem(1))],
909 Ok(additional_program_memory(&[
910 ("x".to_owned(), mem(1)),
911 ("y".to_owned(), KclValue::none()),
912 ])),
913 ),
914 (
915 "mixed params, maximum given, should be OK",
916 vec![req_param("x"), opt_param("y")],
917 vec![("x", mem(1)), ("y", mem(2))],
918 Ok(additional_program_memory(&[
919 ("x".to_owned(), mem(1)),
920 ("y".to_owned(), mem(2)),
921 ])),
922 ),
923 ] {
924 let func_expr = Node::no_src(FunctionExpression {
926 params,
927 body: Program::empty(),
928 return_type: None,
929 digest: None,
930 });
931 let func_src = FunctionSource::User {
932 ast: Box::new(func_expr),
933 settings: Default::default(),
934 memory: EnvironmentRef::dummy(),
935 };
936 let labeled = args
937 .iter()
938 .map(|(name, value)| {
939 let arg = Arg::new(value.clone(), SourceRange::default());
940 ((*name).to_owned(), arg)
941 })
942 .collect::<IndexMap<_, _>>();
943 let exec_ctxt = ExecutorContext {
944 engine: Arc::new(Box::new(
945 crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
946 )),
947 fs: Arc::new(crate::fs::FileManager::new()),
948 stdlib: Arc::new(crate::std::StdLib::new()),
949 settings: Default::default(),
950 context_type: ContextType::Mock,
951 };
952 let mut exec_state = ExecState::new(&exec_ctxt);
953 exec_state.mod_local.stack = Stack::new_for_tests();
954
955 let args = Args::new_kw(
956 KwArgs {
957 unlabeled: None,
958 labeled,
959 errors: Vec::new(),
960 },
961 SourceRange::default(),
962 exec_ctxt,
963 None,
964 );
965 let actual = assign_args_to_params_kw(&(&func_src).into(), args, &mut exec_state)
966 .map(|_| exec_state.mod_local.stack);
967 assert_eq!(
968 actual, expected,
969 "failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
970 );
971 }
972 }
973
974 #[tokio::test(flavor = "multi_thread")]
975 async fn type_check_user_args() {
976 let program = r#"fn makeMessage(prefix: string, suffix: string) {
977 return prefix + suffix
978}
979
980msg1 = makeMessage(prefix = "world", suffix = " hello")
981msg2 = makeMessage(prefix = 1, suffix = 3)"#;
982 let err = parse_execute(program).await.unwrap_err();
983 assert_eq!(
984 err.message(),
985 "prefix requires a value with type `string`, but found number(default units)"
986 )
987 }
988}