kcl_lib/execution/
exec_ast.rs

1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use indexmap::IndexMap;
5
6#[cfg(feature = "artifact-graph")]
7use crate::execution::cad_op::{Group, OpArg, OpKclValue, Operation};
8use crate::{
9    errors::{KclError, KclErrorDetails},
10    execution::{
11        annotations,
12        kcl_value::{FunctionSource, TypeDef},
13        memory,
14        state::ModuleState,
15        types::{NumericType, PrimitiveType, RuntimeType},
16        BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo,
17        TagIdentifier,
18    },
19    fmt,
20    modules::{ModuleId, ModulePath, ModuleRepr},
21    parsing::ast::types::{
22        Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
23        BinaryPart, BodyItem, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
24        ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef,
25        ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
26    },
27    source_range::SourceRange,
28    std::{
29        args::{Arg, Args, KwArgs, TyF64},
30        FunctionKind,
31    },
32    CompilationError,
33};
34
35enum StatementKind<'a> {
36    Declaration { name: &'a str },
37    Expression,
38}
39
40impl<'a> StatementKind<'a> {
41    fn expect_name(&self) -> &'a str {
42        match self {
43            StatementKind::Declaration { name } => name,
44            StatementKind::Expression => unreachable!(),
45        }
46    }
47}
48
49impl ExecutorContext {
50    /// Returns true if importing the prelude should be skipped.
51    async fn handle_annotations(
52        &self,
53        annotations: impl Iterator<Item = &Node<Annotation>>,
54        body_type: BodyType,
55        exec_state: &mut ExecState,
56    ) -> Result<bool, KclError> {
57        let mut no_prelude = false;
58        for annotation in annotations {
59            if annotation.name() == Some(annotations::SETTINGS) {
60                if matches!(body_type, BodyType::Root) {
61                    if exec_state.mod_local.settings.update_from_annotation(annotation)? {
62                        exec_state.mod_local.explicit_length_units = true;
63                    }
64                } else {
65                    exec_state.err(CompilationError::err(
66                        annotation.as_source_range(),
67                        "Settings can only be modified at the top level scope of a file",
68                    ));
69                }
70            } else if annotation.name() == Some(annotations::NO_PRELUDE) {
71                if matches!(body_type, BodyType::Root) {
72                    no_prelude = true;
73                } else {
74                    exec_state.err(CompilationError::err(
75                        annotation.as_source_range(),
76                        "The standard library can only be skipped at the top level scope of a file",
77                    ));
78                }
79            } else {
80                exec_state.warn(CompilationError::err(
81                    annotation.as_source_range(),
82                    "Unknown annotation",
83                ));
84            }
85        }
86        Ok(no_prelude)
87    }
88
89    pub(super) async fn exec_module_body(
90        &self,
91        program: &Node<Program>,
92        exec_state: &mut ExecState,
93        preserve_mem: bool,
94        module_id: ModuleId,
95        path: &ModulePath,
96    ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
97        crate::log::log(format!("enter module {path} {}", exec_state.stack()));
98
99        let mut local_state = ModuleState::new(path.std_path(), exec_state.stack().memory.clone(), Some(module_id));
100        if !preserve_mem {
101            std::mem::swap(&mut exec_state.mod_local, &mut local_state);
102        }
103
104        let no_prelude = self
105            .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
106            .await?;
107
108        if !preserve_mem {
109            exec_state.mut_stack().push_new_root_env(!no_prelude);
110        }
111
112        let result = self
113            .exec_block(program, exec_state, crate::execution::BodyType::Root)
114            .await;
115
116        let env_ref = if preserve_mem {
117            exec_state.mut_stack().pop_and_preserve_env()
118        } else {
119            exec_state.mut_stack().pop_env()
120        };
121        if !preserve_mem {
122            std::mem::swap(&mut exec_state.mod_local, &mut local_state);
123        }
124
125        crate::log::log(format!("leave {path}"));
126
127        result.map(|result| (result, env_ref, local_state.module_exports))
128    }
129
130    /// Execute an AST's program.
131    #[async_recursion]
132    pub(super) async fn exec_block<'a>(
133        &'a self,
134        program: NodeRef<'a, Program>,
135        exec_state: &mut ExecState,
136        body_type: BodyType,
137    ) -> Result<Option<KclValue>, KclError> {
138        let mut last_expr = None;
139        // Iterate over the body of the program.
140        for statement in &program.body {
141            match statement {
142                BodyItem::ImportStatement(import_stmt) => {
143                    if !matches!(body_type, BodyType::Root) {
144                        return Err(KclError::Semantic(KclErrorDetails {
145                            message: "Imports are only supported at the top-level of a file.".to_owned(),
146                            source_ranges: vec![import_stmt.into()],
147                        }));
148                    }
149
150                    let source_range = SourceRange::from(import_stmt);
151                    let attrs = &import_stmt.outer_attrs;
152                    let module_id = self
153                        .open_module(&import_stmt.path, attrs, exec_state, source_range)
154                        .await?;
155
156                    match &import_stmt.selector {
157                        ImportSelector::List { items } => {
158                            let (env_ref, module_exports) =
159                                self.exec_module_for_items(module_id, exec_state, source_range).await?;
160                            for import_item in items {
161                                // Extract the item from the module.
162                                let mem = &exec_state.stack().memory;
163                                let mut value = mem
164                                    .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
165                                    .cloned();
166                                let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
167                                let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
168
169                                if value.is_err() && ty.is_err() {
170                                    return Err(KclError::UndefinedValue(KclErrorDetails {
171                                        message: format!("{} is not defined in module", import_item.name.name),
172                                        source_ranges: vec![SourceRange::from(&import_item.name)],
173                                    }));
174                                }
175
176                                // Check that the item is allowed to be imported (in at least one namespace).
177                                if value.is_ok() && !module_exports.contains(&import_item.name.name) {
178                                    value = Err(KclError::Semantic(KclErrorDetails {
179                                        message: format!(
180                                            "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
181                                            import_item.name.name
182                                        ),
183                                        source_ranges: vec![SourceRange::from(&import_item.name)],
184                                    }));
185                                }
186
187                                if ty.is_ok() && !module_exports.contains(&ty_name) {
188                                    ty = Err(KclError::Semantic(KclErrorDetails {
189                                        message: String::new(),
190                                        source_ranges: vec![],
191                                    }));
192                                }
193
194                                if value.is_err() && ty.is_err() {
195                                    return value.map(Option::Some);
196                                }
197
198                                // Add the item to the current module.
199                                if let Ok(value) = value {
200                                    exec_state.mut_stack().add(
201                                        import_item.identifier().to_owned(),
202                                        value,
203                                        SourceRange::from(&import_item.name),
204                                    )?;
205
206                                    if let ItemVisibility::Export = import_stmt.visibility {
207                                        exec_state
208                                            .mod_local
209                                            .module_exports
210                                            .push(import_item.identifier().to_owned());
211                                    }
212                                }
213
214                                if let Ok(ty) = ty {
215                                    let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
216                                    // Add the item to the current module.
217                                    exec_state.mut_stack().add(
218                                        ty_name.clone(),
219                                        ty,
220                                        SourceRange::from(&import_item.name),
221                                    )?;
222
223                                    if let ItemVisibility::Export = import_stmt.visibility {
224                                        exec_state.mod_local.module_exports.push(ty_name);
225                                    }
226                                }
227                            }
228                        }
229                        ImportSelector::Glob(_) => {
230                            let (env_ref, module_exports) =
231                                self.exec_module_for_items(module_id, exec_state, source_range).await?;
232                            for name in module_exports.iter() {
233                                let item = exec_state
234                                    .stack()
235                                    .memory
236                                    .get_from(name, env_ref, source_range, 0)
237                                    .map_err(|_err| {
238                                        KclError::Internal(KclErrorDetails {
239                                            message: format!("{} is not defined in module (but was exported?)", name),
240                                            source_ranges: vec![source_range],
241                                        })
242                                    })?
243                                    .clone();
244                                exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
245
246                                if let ItemVisibility::Export = import_stmt.visibility {
247                                    exec_state.mod_local.module_exports.push(name.clone());
248                                }
249                            }
250                        }
251                        ImportSelector::None { .. } => {
252                            let name = import_stmt.module_name().unwrap();
253                            let item = KclValue::Module {
254                                value: module_id,
255                                meta: vec![source_range.into()],
256                            };
257                            exec_state.mut_stack().add(name, item, source_range)?;
258                        }
259                    }
260                    last_expr = None;
261                }
262                BodyItem::ExpressionStatement(expression_statement) => {
263                    let metadata = Metadata::from(expression_statement);
264                    last_expr = Some(
265                        self.execute_expr(
266                            &expression_statement.expression,
267                            exec_state,
268                            &metadata,
269                            &[],
270                            StatementKind::Expression,
271                        )
272                        .await?,
273                    );
274                }
275                BodyItem::VariableDeclaration(variable_declaration) => {
276                    let var_name = variable_declaration.declaration.id.name.to_string();
277                    let source_range = SourceRange::from(&variable_declaration.declaration.init);
278                    let metadata = Metadata { source_range };
279
280                    let annotations = &variable_declaration.outer_attrs;
281
282                    let value = self
283                        .execute_expr(
284                            &variable_declaration.declaration.init,
285                            exec_state,
286                            &metadata,
287                            annotations,
288                            StatementKind::Declaration { name: &var_name },
289                        )
290                        .await?;
291                    exec_state
292                        .mut_stack()
293                        .add(var_name.clone(), value.clone(), source_range)?;
294
295                    // Track exports.
296                    if let ItemVisibility::Export = variable_declaration.visibility {
297                        exec_state.mod_local.module_exports.push(var_name);
298                    }
299                    // Variable declaration can be the return value of a module.
300                    last_expr = matches!(body_type, BodyType::Root).then_some(value);
301                }
302                BodyItem::TypeDeclaration(ty) => {
303                    let metadata = Metadata::from(&**ty);
304                    let impl_kind = annotations::get_impl(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
305                    match impl_kind {
306                        annotations::Impl::Rust => {
307                            let std_path = match &exec_state.mod_local.std_path {
308                                Some(p) => p,
309                                None => {
310                                    return Err(KclError::Semantic(KclErrorDetails {
311                                        message: "User-defined types are not yet supported.".to_owned(),
312                                        source_ranges: vec![metadata.source_range],
313                                    }));
314                                }
315                            };
316                            let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
317                            let value = KclValue::Type {
318                                value: TypeDef::RustRepr(t, props),
319                                meta: vec![metadata],
320                            };
321                            let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
322                            exec_state
323                                .mut_stack()
324                                .add(name_in_mem.clone(), value, metadata.source_range)
325                                .map_err(|_| {
326                                    KclError::Semantic(KclErrorDetails {
327                                        message: format!("Redefinition of type {}.", ty.name.name),
328                                        source_ranges: vec![metadata.source_range],
329                                    })
330                                })?;
331
332                            if let ItemVisibility::Export = ty.visibility {
333                                exec_state.mod_local.module_exports.push(name_in_mem);
334                            }
335                        }
336                        // Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
337                        annotations::Impl::Primitive => {}
338                        annotations::Impl::Kcl => match &ty.alias {
339                            Some(alias) => {
340                                let value = KclValue::Type {
341                                    value: TypeDef::Alias(
342                                        RuntimeType::from_parsed(
343                                            alias.inner.clone(),
344                                            exec_state,
345                                            metadata.source_range,
346                                        )
347                                        .map_err(|e| KclError::Semantic(e.into()))?,
348                                    ),
349                                    meta: vec![metadata],
350                                };
351                                let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
352                                exec_state
353                                    .mut_stack()
354                                    .add(name_in_mem.clone(), value, metadata.source_range)
355                                    .map_err(|_| {
356                                        KclError::Semantic(KclErrorDetails {
357                                            message: format!("Redefinition of type {}.", ty.name.name),
358                                            source_ranges: vec![metadata.source_range],
359                                        })
360                                    })?;
361
362                                if let ItemVisibility::Export = ty.visibility {
363                                    exec_state.mod_local.module_exports.push(name_in_mem);
364                                }
365                            }
366                            None => {
367                                return Err(KclError::Semantic(KclErrorDetails {
368                                    message: "User-defined types are not yet supported.".to_owned(),
369                                    source_ranges: vec![metadata.source_range],
370                                }))
371                            }
372                        },
373                    }
374
375                    last_expr = None;
376                }
377                BodyItem::ReturnStatement(return_statement) => {
378                    let metadata = Metadata::from(return_statement);
379
380                    if matches!(body_type, BodyType::Root) {
381                        return Err(KclError::Semantic(KclErrorDetails {
382                            message: "Cannot return from outside a function.".to_owned(),
383                            source_ranges: vec![metadata.source_range],
384                        }));
385                    }
386
387                    let value = self
388                        .execute_expr(
389                            &return_statement.argument,
390                            exec_state,
391                            &metadata,
392                            &[],
393                            StatementKind::Expression,
394                        )
395                        .await?;
396                    exec_state
397                        .mut_stack()
398                        .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
399                        .map_err(|_| {
400                            KclError::Semantic(KclErrorDetails {
401                                message: "Multiple returns from a single function.".to_owned(),
402                                source_ranges: vec![metadata.source_range],
403                            })
404                        })?;
405                    last_expr = None;
406                }
407            }
408        }
409
410        if matches!(body_type, BodyType::Root) {
411            // Flush the batch queue.
412            self.engine
413                .flush_batch(
414                    // True here tells the engine to flush all the end commands as well like fillets
415                    // and chamfers where the engine would otherwise eat the ID of the segments.
416                    true,
417                    SourceRange::new(program.end, program.end, program.module_id),
418                )
419                .await?;
420        }
421
422        Ok(last_expr)
423    }
424
425    pub async fn open_module(
426        &self,
427        path: &ImportPath,
428        attrs: &[Node<Annotation>],
429        exec_state: &mut ExecState,
430        source_range: SourceRange,
431    ) -> Result<ModuleId, KclError> {
432        let resolved_path = ModulePath::from_import_path(path, &self.settings.project_directory);
433
434        match path {
435            ImportPath::Kcl { .. } => {
436                exec_state.global.mod_loader.cycle_check(&resolved_path, source_range)?;
437
438                if let Some(id) = exec_state.id_for_module(&resolved_path) {
439                    return Ok(id);
440                }
441
442                let id = exec_state.next_module_id();
443                // Add file path string to global state even if it fails to import
444                exec_state.add_path_to_source_id(resolved_path.clone(), id);
445                let source = resolved_path.source(&self.fs, source_range).await?;
446                exec_state.add_id_to_source(id, source.clone());
447                // TODO handle parsing errors properly
448                let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
449                exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
450
451                Ok(id)
452            }
453            ImportPath::Foreign { .. } => {
454                if let Some(id) = exec_state.id_for_module(&resolved_path) {
455                    return Ok(id);
456                }
457
458                let id = exec_state.next_module_id();
459                let path = resolved_path.expect_path();
460                // Add file path string to global state even if it fails to import
461                exec_state.add_path_to_source_id(resolved_path.clone(), id);
462                let format = super::import::format_from_annotations(attrs, path, source_range)?;
463                let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
464                exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom, None));
465                Ok(id)
466            }
467            ImportPath::Std { .. } => {
468                if let Some(id) = exec_state.id_for_module(&resolved_path) {
469                    return Ok(id);
470                }
471
472                let id = exec_state.next_module_id();
473                // Add file path string to global state even if it fails to import
474                exec_state.add_path_to_source_id(resolved_path.clone(), id);
475                let source = resolved_path.source(&self.fs, source_range).await?;
476                exec_state.add_id_to_source(id, source.clone());
477                let parsed = crate::parsing::parse_str(&source.source, id)
478                    .parse_errs_as_err()
479                    .unwrap();
480                exec_state.add_module(id, resolved_path, ModuleRepr::Kcl(parsed, None));
481                Ok(id)
482            }
483        }
484    }
485
486    pub(super) async fn exec_module_for_items(
487        &self,
488        module_id: ModuleId,
489        exec_state: &mut ExecState,
490        source_range: SourceRange,
491    ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
492        let path = exec_state.global.module_infos[&module_id].path.clone();
493        let mut repr = exec_state.global.module_infos[&module_id].take_repr();
494        // DON'T EARLY RETURN! We need to restore the module repr
495
496        let result = match &mut repr {
497            ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
498            ModuleRepr::Kcl(_, Some((_, env_ref, items))) => Ok((*env_ref, items.clone())),
499            ModuleRepr::Kcl(program, cache) => self
500                .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
501                .await
502                .map(|(val, er, items)| {
503                    *cache = Some((val, er, items.clone()));
504                    (er, items)
505                }),
506            ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails {
507                message: "Cannot import items from foreign modules".to_owned(),
508                source_ranges: vec![geom.source_range],
509            })),
510            ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
511        };
512
513        exec_state.global.module_infos[&module_id].restore_repr(repr);
514        result
515    }
516
517    async fn exec_module_for_result(
518        &self,
519        module_id: ModuleId,
520        exec_state: &mut ExecState,
521        source_range: SourceRange,
522    ) -> Result<Option<KclValue>, KclError> {
523        let path = exec_state.global.module_infos[&module_id].path.clone();
524        let mut repr = exec_state.global.module_infos[&module_id].take_repr();
525        // DON'T EARLY RETURN! We need to restore the module repr
526
527        let result = match &mut repr {
528            ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
529            ModuleRepr::Kcl(_, Some((val, _, _))) => Ok(val.clone()),
530            ModuleRepr::Kcl(program, cached_items) => {
531                let result = self
532                    .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
533                    .await;
534                match result {
535                    Ok((val, env, items)) => {
536                        *cached_items = Some((val.clone(), env, items));
537                        Ok(val)
538                    }
539                    Err(e) => Err(e),
540                }
541            }
542            ModuleRepr::Foreign(_, Some(imported)) => Ok(Some(imported.clone())),
543            ModuleRepr::Foreign(geom, cached) => {
544                let result = super::import::send_to_engine(geom.clone(), self)
545                    .await
546                    .map(|geom| Some(KclValue::ImportedGeometry(geom)));
547
548                match result {
549                    Ok(val) => {
550                        *cached = val.clone();
551                        Ok(val)
552                    }
553                    Err(e) => Err(e),
554                }
555            }
556            ModuleRepr::Dummy => unreachable!(),
557        };
558
559        exec_state.global.module_infos[&module_id].restore_repr(repr);
560
561        result
562    }
563
564    pub async fn exec_module_from_ast(
565        &self,
566        program: &Node<Program>,
567        module_id: ModuleId,
568        path: &ModulePath,
569        exec_state: &mut ExecState,
570        source_range: SourceRange,
571        preserve_mem: bool,
572    ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
573        exec_state.global.mod_loader.enter_module(path);
574        let result = self
575            .exec_module_body(program, exec_state, preserve_mem, module_id, path)
576            .await;
577        exec_state.global.mod_loader.leave_module(path);
578
579        result.map_err(|err| {
580            if let KclError::ImportCycle(_) = err {
581                // It was an import cycle.  Keep the original message.
582                err.override_source_ranges(vec![source_range])
583            } else {
584                // TODO would be great to have line/column for the underlying error here
585                KclError::Semantic(KclErrorDetails {
586                    message: format!(
587                        "Error loading imported file ({path}). Open it to view more details.\n  {}",
588                        err.message()
589                    ),
590                    source_ranges: vec![source_range],
591                })
592            }
593        })
594    }
595
596    #[async_recursion]
597    async fn execute_expr<'a: 'async_recursion>(
598        &self,
599        init: &Expr,
600        exec_state: &mut ExecState,
601        metadata: &Metadata,
602        annotations: &[Node<Annotation>],
603        statement_kind: StatementKind<'a>,
604    ) -> Result<KclValue, KclError> {
605        let item = match init {
606            Expr::None(none) => KclValue::from(none),
607            Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
608            Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
609            Expr::Name(name) => {
610                let value = name.get_result(exec_state, self).await?.clone();
611                if let KclValue::Module { value: module_id, meta } = value {
612                    self.exec_module_for_result(
613                        module_id,
614                        exec_state,
615                        metadata.source_range
616                        ).await?
617                        .unwrap_or_else(|| {
618                            exec_state.warn(CompilationError::err(
619                                metadata.source_range,
620                                "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
621                            ));
622
623                            let mut new_meta = vec![metadata.to_owned()];
624                            new_meta.extend(meta);
625                            KclValue::KclNone {
626                                value: Default::default(),
627                                meta: new_meta,
628                            }
629                        })
630                } else {
631                    value
632                }
633            }
634            Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
635            Expr::FunctionExpression(function_expression) => {
636                let rust_impl = annotations::get_impl(annotations, metadata.source_range)?
637                    .map(|s| s == annotations::Impl::Rust)
638                    .unwrap_or(false);
639
640                if rust_impl {
641                    if let Some(std_path) = &exec_state.mod_local.std_path {
642                        let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
643                        KclValue::Function {
644                            value: FunctionSource::Std {
645                                func,
646                                props,
647                                ast: function_expression.clone(),
648                            },
649                            meta: vec![metadata.to_owned()],
650                        }
651                    } else {
652                        return Err(KclError::Semantic(KclErrorDetails {
653                            message: "Rust implementation of functions is restricted to the standard library"
654                                .to_owned(),
655                            source_ranges: vec![metadata.source_range],
656                        }));
657                    }
658                } else {
659                    // Snapshotting memory here is crucial for semantics so that we close
660                    // over variables. Variables defined lexically later shouldn't
661                    // be available to the function body.
662                    KclValue::Function {
663                        value: FunctionSource::User {
664                            ast: function_expression.clone(),
665                            settings: exec_state.mod_local.settings.clone(),
666                            memory: exec_state.mut_stack().snapshot(),
667                        },
668                        meta: vec![metadata.to_owned()],
669                    }
670                }
671            }
672            Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
673            Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
674            Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
675                StatementKind::Declaration { name } => {
676                    let message = format!(
677                        "you cannot declare variable {name} as %, because % can only be used in function calls"
678                    );
679
680                    return Err(KclError::Semantic(KclErrorDetails {
681                        message,
682                        source_ranges: vec![pipe_substitution.into()],
683                    }));
684                }
685                StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
686                    Some(x) => x,
687                    None => {
688                        return Err(KclError::Semantic(KclErrorDetails {
689                            message: "cannot use % outside a pipe expression".to_owned(),
690                            source_ranges: vec![pipe_substitution.into()],
691                        }));
692                    }
693                },
694            },
695            Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
696            Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
697            Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
698            Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
699            Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
700            Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
701            Expr::LabelledExpression(expr) => {
702                let result = self
703                    .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
704                    .await?;
705                exec_state
706                    .mut_stack()
707                    .add(expr.label.name.clone(), result.clone(), init.into())?;
708                // TODO this lets us use the label as a variable name, but not as a tag in most cases
709                result
710            }
711            Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
712        };
713        Ok(item)
714    }
715}
716
717impl Node<AscribedExpression> {
718    #[async_recursion]
719    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
720        let metadata = Metadata {
721            source_range: SourceRange::from(self),
722        };
723        let result = ctx
724            .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
725            .await?;
726        apply_ascription(&result, &self.ty, exec_state, self.into())
727    }
728}
729
730fn apply_ascription(
731    value: &KclValue,
732    ty: &Node<Type>,
733    exec_state: &mut ExecState,
734    source_range: SourceRange,
735) -> Result<KclValue, KclError> {
736    let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into())
737        .map_err(|e| KclError::Semantic(e.into()))?;
738
739    if let KclValue::Number { value, meta, .. } = value {
740        // If the number has unknown units but the user is explicitly specifying them, treat the value as having had it's units erased,
741        // rather than forcing the user to explicitly erase them.
742        KclValue::Number {
743            ty: NumericType::Any,
744            value: *value,
745            meta: meta.clone(),
746        }
747        .coerce(&ty, exec_state)
748    } else {
749        value.coerce(&ty, exec_state)
750    }
751    .map_err(|_| {
752        KclError::Semantic(KclErrorDetails {
753            message: format!("could not coerce {} value to type {}", value.human_friendly_type(), ty),
754            source_ranges: vec![source_range],
755        })
756    })
757}
758
759impl BinaryPart {
760    #[async_recursion]
761    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
762        match self {
763            BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state)),
764            BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned(),
765            BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
766            BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
767            BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
768            BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
769            BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
770            BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
771        }
772    }
773}
774
775impl Node<Name> {
776    async fn get_result<'a>(
777        &self,
778        exec_state: &'a mut ExecState,
779        ctx: &ExecutorContext,
780    ) -> Result<&'a KclValue, KclError> {
781        if self.abs_path {
782            return Err(KclError::Semantic(KclErrorDetails {
783                message: "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
784                source_ranges: self.as_source_ranges(),
785            }));
786        }
787
788        if self.path.is_empty() {
789            return exec_state.stack().get(&self.name.name, self.into());
790        }
791
792        let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
793        for p in &self.path {
794            let value = match mem_spec {
795                Some((env, exports)) => {
796                    if !exports.contains(&p.name) {
797                        return Err(KclError::Semantic(KclErrorDetails {
798                            message: format!("Item {} not found in module's exported items", p.name),
799                            source_ranges: p.as_source_ranges(),
800                        }));
801                    }
802
803                    exec_state
804                        .stack()
805                        .memory
806                        .get_from(&p.name, env, p.as_source_range(), 0)?
807                }
808                None => exec_state.stack().get(&p.name, self.into())?,
809            };
810
811            let KclValue::Module { value: module_id, .. } = value else {
812                return Err(KclError::Semantic(KclErrorDetails {
813                    message: format!(
814                        "Identifier in path must refer to a module, found {}",
815                        value.human_friendly_type()
816                    ),
817                    source_ranges: p.as_source_ranges(),
818                }));
819            };
820
821            mem_spec = Some(
822                ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
823                    .await?,
824            );
825        }
826
827        let (env, exports) = mem_spec.unwrap();
828        if !exports.contains(&self.name.name) {
829            return Err(KclError::Semantic(KclErrorDetails {
830                message: format!("Item {} not found in module's exported items", self.name.name),
831                source_ranges: self.name.as_source_ranges(),
832            }));
833        }
834
835        exec_state
836            .stack()
837            .memory
838            .get_from(&self.name.name, env, self.name.as_source_range(), 0)
839    }
840}
841
842impl Node<MemberExpression> {
843    fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
844        let property = Property::try_from(self.computed, self.property.clone(), exec_state, self.into())?;
845        let object = match &self.object {
846            // TODO: Don't use recursion here, use a loop.
847            MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
848            MemberObject::Identifier(identifier) => {
849                let value = exec_state.stack().get(&identifier.name, identifier.into())?;
850                value.clone()
851            }
852        };
853
854        // Check the property and object match -- e.g. ints for arrays, strs for objects.
855        match (object, property, self.computed) {
856            (KclValue::Object { value: map, meta: _ }, Property::String(property), false) => {
857                if let Some(value) = map.get(&property) {
858                    Ok(value.to_owned())
859                } else {
860                    Err(KclError::UndefinedValue(KclErrorDetails {
861                        message: format!("Property '{property}' not found in object"),
862                        source_ranges: vec![self.clone().into()],
863                    }))
864                }
865            }
866            (KclValue::Object { .. }, Property::String(property), true) => Err(KclError::Semantic(KclErrorDetails {
867                message: format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
868                source_ranges: vec![self.clone().into()],
869            })),
870            (KclValue::Object { .. }, p, _) => {
871                let t = p.type_name();
872                let article = article_for(t);
873                Err(KclError::Semantic(KclErrorDetails {
874                    message: format!(
875                        "Only strings can be used as the property of an object, but you're using {article} {t}",
876                    ),
877                    source_ranges: vec![self.clone().into()],
878                }))
879            }
880            (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
881                let value_of_arr = arr.get(index);
882                if let Some(value) = value_of_arr {
883                    Ok(value.to_owned())
884                } else {
885                    Err(KclError::UndefinedValue(KclErrorDetails {
886                        message: format!("The array doesn't have any item at index {index}"),
887                        source_ranges: vec![self.clone().into()],
888                    }))
889                }
890            }
891            (KclValue::HomArray { .. }, p, _) => {
892                let t = p.type_name();
893                let article = article_for(t);
894                Err(KclError::Semantic(KclErrorDetails {
895                    message: format!(
896                        "Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",
897                    ),
898                    source_ranges: vec![self.clone().into()],
899                }))
900            }
901            (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
902                value: Box::new(value.sketch),
903            }),
904            (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
905                meta: vec![Metadata {
906                    source_range: SourceRange::from(self.clone()),
907                }],
908                value: sk
909                    .tags
910                    .iter()
911                    .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
912                    .collect(),
913            }),
914            (being_indexed, _, _) => {
915                let t = being_indexed.human_friendly_type();
916                let article = article_for(&t);
917                Err(KclError::Semantic(KclErrorDetails {
918                    message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
919                    source_ranges: vec![self.clone().into()],
920                }))
921            }
922        }
923    }
924}
925
926impl Node<BinaryExpression> {
927    #[async_recursion]
928    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
929        let left_value = self.left.get_result(exec_state, ctx).await?;
930        let right_value = self.right.get_result(exec_state, ctx).await?;
931        let mut meta = left_value.metadata();
932        meta.extend(right_value.metadata());
933
934        // First check if we are doing string concatenation.
935        if self.operator == BinaryOperator::Add {
936            if let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
937                (&left_value, &right_value)
938            {
939                return Ok(KclValue::String {
940                    value: format!("{}{}", left, right),
941                    meta,
942                });
943            }
944        }
945
946        // Then check if we have solids.
947        if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
948            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
949                let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
950                let result = crate::std::csg::inner_union(
951                    vec![*left.clone(), *right.clone()],
952                    Default::default(),
953                    exec_state,
954                    args,
955                )
956                .await?;
957                return Ok(result.into());
958            }
959        } else if self.operator == BinaryOperator::Sub {
960            // Check if we have solids.
961            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
962                let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
963                let result = crate::std::csg::inner_subtract(
964                    vec![*left.clone()],
965                    vec![*right.clone()],
966                    Default::default(),
967                    exec_state,
968                    args,
969                )
970                .await?;
971                return Ok(result.into());
972            }
973        } else if self.operator == BinaryOperator::And {
974            // Check if we have solids.
975            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
976                let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
977                let result = crate::std::csg::inner_intersect(
978                    vec![*left.clone(), *right.clone()],
979                    Default::default(),
980                    exec_state,
981                    args,
982                )
983                .await?;
984                return Ok(result.into());
985            }
986        }
987
988        // Check if we are doing logical operations on booleans.
989        if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
990            let KclValue::Bool {
991                value: left_value,
992                meta: _,
993            } = left_value
994            else {
995                return Err(KclError::Semantic(KclErrorDetails {
996                    message: format!(
997                        "Cannot apply logical operator to non-boolean value: {}",
998                        left_value.human_friendly_type()
999                    ),
1000                    source_ranges: vec![self.left.clone().into()],
1001                }));
1002            };
1003            let KclValue::Bool {
1004                value: right_value,
1005                meta: _,
1006            } = right_value
1007            else {
1008                return Err(KclError::Semantic(KclErrorDetails {
1009                    message: format!(
1010                        "Cannot apply logical operator to non-boolean value: {}",
1011                        right_value.human_friendly_type()
1012                    ),
1013                    source_ranges: vec![self.right.clone().into()],
1014                }));
1015            };
1016            let raw_value = match self.operator {
1017                BinaryOperator::Or => left_value || right_value,
1018                BinaryOperator::And => left_value && right_value,
1019                _ => unreachable!(),
1020            };
1021            return Ok(KclValue::Bool { value: raw_value, meta });
1022        }
1023
1024        let left = number_as_f64(&left_value, self.left.clone().into())?;
1025        let right = number_as_f64(&right_value, self.right.clone().into())?;
1026
1027        let value = match self.operator {
1028            BinaryOperator::Add => {
1029                let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1030                self.warn_on_unknown(&ty, "Adding", exec_state);
1031                KclValue::Number { value: l + r, meta, ty }
1032            }
1033            BinaryOperator::Sub => {
1034                let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1035                self.warn_on_unknown(&ty, "Subtracting", exec_state);
1036                KclValue::Number { value: l - r, meta, ty }
1037            }
1038            BinaryOperator::Mul => {
1039                let (l, r, ty) = NumericType::combine_mul(left, right);
1040                self.warn_on_unknown(&ty, "Multiplying", exec_state);
1041                KclValue::Number { value: l * r, meta, ty }
1042            }
1043            BinaryOperator::Div => {
1044                let (l, r, ty) = NumericType::combine_div(left, right);
1045                self.warn_on_unknown(&ty, "Dividing", exec_state);
1046                KclValue::Number { value: l / r, meta, ty }
1047            }
1048            BinaryOperator::Mod => {
1049                let (l, r, ty) = NumericType::combine_div(left, right);
1050                self.warn_on_unknown(&ty, "Modulo of", exec_state);
1051                KclValue::Number { value: l % r, meta, ty }
1052            }
1053            BinaryOperator::Pow => KclValue::Number {
1054                value: left.n.powf(right.n),
1055                meta,
1056                ty: exec_state.current_default_units(),
1057            },
1058            BinaryOperator::Neq => {
1059                let (l, r, ty) = NumericType::combine_eq(left, right);
1060                self.warn_on_unknown(&ty, "Comparing", exec_state);
1061                KclValue::Bool { value: l != r, meta }
1062            }
1063            BinaryOperator::Gt => {
1064                let (l, r, ty) = NumericType::combine_eq(left, right);
1065                self.warn_on_unknown(&ty, "Comparing", exec_state);
1066                KclValue::Bool { value: l > r, meta }
1067            }
1068            BinaryOperator::Gte => {
1069                let (l, r, ty) = NumericType::combine_eq(left, right);
1070                self.warn_on_unknown(&ty, "Comparing", exec_state);
1071                KclValue::Bool { value: l >= r, meta }
1072            }
1073            BinaryOperator::Lt => {
1074                let (l, r, ty) = NumericType::combine_eq(left, right);
1075                self.warn_on_unknown(&ty, "Comparing", exec_state);
1076                KclValue::Bool { value: l < r, meta }
1077            }
1078            BinaryOperator::Lte => {
1079                let (l, r, ty) = NumericType::combine_eq(left, right);
1080                self.warn_on_unknown(&ty, "Comparing", exec_state);
1081                KclValue::Bool { value: l <= r, meta }
1082            }
1083            BinaryOperator::Eq => {
1084                let (l, r, ty) = NumericType::combine_eq(left, right);
1085                self.warn_on_unknown(&ty, "Comparing", exec_state);
1086                KclValue::Bool { value: l == r, meta }
1087            }
1088            BinaryOperator::And | BinaryOperator::Or => unreachable!(),
1089        };
1090
1091        Ok(value)
1092    }
1093
1094    fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
1095        if ty == &NumericType::Unknown {
1096            // TODO suggest how to fix this
1097            exec_state.warn(CompilationError::err(
1098                self.as_source_range(),
1099                format!("{} numbers which have unknown or incompatible units.", verb),
1100            ));
1101        }
1102    }
1103}
1104
1105impl Node<UnaryExpression> {
1106    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1107        if self.operator == UnaryOperator::Not {
1108            let value = self.argument.get_result(exec_state, ctx).await?;
1109            let KclValue::Bool {
1110                value: bool_value,
1111                meta: _,
1112            } = value
1113            else {
1114                return Err(KclError::Semantic(KclErrorDetails {
1115                    message: format!(
1116                        "Cannot apply unary operator ! to non-boolean value: {}",
1117                        value.human_friendly_type()
1118                    ),
1119                    source_ranges: vec![self.into()],
1120                }));
1121            };
1122            let meta = vec![Metadata {
1123                source_range: self.into(),
1124            }];
1125            let negated = KclValue::Bool {
1126                value: !bool_value,
1127                meta,
1128            };
1129
1130            return Ok(negated);
1131        }
1132
1133        let value = &self.argument.get_result(exec_state, ctx).await?;
1134        let err = || {
1135            KclError::Semantic(KclErrorDetails {
1136                message: format!(
1137                    "You can only negate numbers, planes, or lines, but this is a {}",
1138                    value.human_friendly_type()
1139                ),
1140                source_ranges: vec![self.into()],
1141            })
1142        };
1143        match value {
1144            KclValue::Number { value, ty, .. } => {
1145                let meta = vec![Metadata {
1146                    source_range: self.into(),
1147                }];
1148                Ok(KclValue::Number {
1149                    value: -value,
1150                    meta,
1151                    ty: ty.clone(),
1152                })
1153            }
1154            KclValue::Plane { value } => {
1155                let mut plane = value.clone();
1156                if plane.info.x_axis.x != 0.0 {
1157                    plane.info.x_axis.x *= -1.0;
1158                }
1159                if plane.info.x_axis.y != 0.0 {
1160                    plane.info.x_axis.y *= -1.0;
1161                }
1162                if plane.info.x_axis.z != 0.0 {
1163                    plane.info.x_axis.z *= -1.0;
1164                }
1165
1166                plane.value = PlaneType::Uninit;
1167                plane.id = exec_state.next_uuid();
1168                Ok(KclValue::Plane { value: plane })
1169            }
1170            KclValue::Object { value: values, meta } => {
1171                // Special-case for negating line-like objects.
1172                let Some(direction) = values.get("direction") else {
1173                    return Err(err());
1174                };
1175
1176                let direction = match direction {
1177                    KclValue::Tuple { value: values, meta } => {
1178                        let values = values
1179                            .iter()
1180                            .map(|v| match v {
1181                                KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1182                                    value: *value * -1.0,
1183                                    ty: ty.clone(),
1184                                    meta: meta.clone(),
1185                                }),
1186                                _ => Err(err()),
1187                            })
1188                            .collect::<Result<Vec<_>, _>>()?;
1189
1190                        KclValue::Tuple {
1191                            value: values,
1192                            meta: meta.clone(),
1193                        }
1194                    }
1195                    KclValue::HomArray {
1196                        value: values,
1197                        ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
1198                    } => {
1199                        let values = values
1200                            .iter()
1201                            .map(|v| match v {
1202                                KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1203                                    value: *value * -1.0,
1204                                    ty: ty.clone(),
1205                                    meta: meta.clone(),
1206                                }),
1207                                _ => Err(err()),
1208                            })
1209                            .collect::<Result<Vec<_>, _>>()?;
1210
1211                        KclValue::HomArray {
1212                            value: values,
1213                            ty: ty.clone(),
1214                        }
1215                    }
1216                    _ => return Err(err()),
1217                };
1218
1219                let mut value = values.clone();
1220                value.insert("direction".to_owned(), direction);
1221                Ok(KclValue::Object {
1222                    value,
1223                    meta: meta.clone(),
1224                })
1225            }
1226            _ => Err(err()),
1227        }
1228    }
1229}
1230
1231pub(crate) async fn execute_pipe_body(
1232    exec_state: &mut ExecState,
1233    body: &[Expr],
1234    source_range: SourceRange,
1235    ctx: &ExecutorContext,
1236) -> Result<KclValue, KclError> {
1237    let Some((first, body)) = body.split_first() else {
1238        return Err(KclError::Semantic(KclErrorDetails {
1239            message: "Pipe expressions cannot be empty".to_owned(),
1240            source_ranges: vec![source_range],
1241        }));
1242    };
1243    // Evaluate the first element in the pipeline.
1244    // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
1245    // they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
1246    // of its own.
1247    let meta = Metadata {
1248        source_range: SourceRange::from(first),
1249    };
1250    let output = ctx
1251        .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
1252        .await?;
1253
1254    // Now that we've evaluated the first child expression in the pipeline, following child expressions
1255    // should use the previous child expression for %.
1256    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
1257    let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
1258    // Evaluate remaining elements.
1259    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
1260    // Restore the previous pipe value.
1261    exec_state.mod_local.pipe_value = previous_pipe_value;
1262
1263    result
1264}
1265
1266/// Execute the tail of a pipe expression.  exec_state.pipe_value must be set by
1267/// the caller.
1268#[async_recursion]
1269async fn inner_execute_pipe_body(
1270    exec_state: &mut ExecState,
1271    body: &[Expr],
1272    ctx: &ExecutorContext,
1273) -> Result<KclValue, KclError> {
1274    for expression in body {
1275        if let Expr::TagDeclarator(_) = expression {
1276            return Err(KclError::Semantic(KclErrorDetails {
1277                message: format!("This cannot be in a PipeExpression: {:?}", expression),
1278                source_ranges: vec![expression.into()],
1279            }));
1280        }
1281        let metadata = Metadata {
1282            source_range: SourceRange::from(expression),
1283        };
1284        let output = ctx
1285            .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
1286            .await?;
1287        exec_state.mod_local.pipe_value = Some(output);
1288    }
1289    // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
1290    let final_output = exec_state.mod_local.pipe_value.take().unwrap();
1291    Ok(final_output)
1292}
1293
1294impl Node<CallExpressionKw> {
1295    #[async_recursion]
1296    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1297        let fn_name = &self.callee;
1298        let callsite: SourceRange = self.into();
1299
1300        // Build a hashmap from argument labels to the final evaluated values.
1301        let mut fn_args = IndexMap::with_capacity(self.arguments.len());
1302        let mut errors = Vec::new();
1303        for arg_expr in &self.arguments {
1304            let source_range = SourceRange::from(arg_expr.arg.clone());
1305            let metadata = Metadata { source_range };
1306            let value = ctx
1307                .execute_expr(&arg_expr.arg, exec_state, &metadata, &[], StatementKind::Expression)
1308                .await?;
1309            let arg = Arg::new(value, source_range);
1310            match &arg_expr.label {
1311                Some(l) => {
1312                    fn_args.insert(l.name.clone(), arg);
1313                }
1314                None => {
1315                    if let Some(id) = arg_expr.arg.ident_name() {
1316                        fn_args.insert(id.to_owned(), arg);
1317                    } else {
1318                        errors.push(arg);
1319                    }
1320                }
1321            }
1322        }
1323
1324        // Evaluate the unlabeled first param, if any exists.
1325        let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
1326            let source_range = SourceRange::from(arg_expr.clone());
1327            let metadata = Metadata { source_range };
1328            let value = ctx
1329                .execute_expr(arg_expr, exec_state, &metadata, &[], StatementKind::Expression)
1330                .await?;
1331
1332            let label = arg_expr.ident_name().map(str::to_owned);
1333
1334            Some((label, Arg::new(value, source_range)))
1335        } else {
1336            None
1337        };
1338
1339        let mut args = Args::new_kw(
1340            KwArgs {
1341                unlabeled,
1342                labeled: fn_args,
1343                errors,
1344            },
1345            self.into(),
1346            ctx.clone(),
1347            exec_state.pipe_value().map(|v| Arg::new(v.clone(), callsite)),
1348        );
1349        match ctx.stdlib.get_either(fn_name) {
1350            FunctionKind::Core(func) => {
1351                if func.deprecated() {
1352                    exec_state.warn(CompilationError::err(
1353                        self.callee.as_source_range(),
1354                        format!("`{fn_name}` is deprecated, see the docs for a recommended replacement"),
1355                    ));
1356                }
1357
1358                let formals = func.args(false);
1359
1360                // If it's possible the input arg was meant to be labelled and we probably don't want to use
1361                // it as the input arg, then treat it as labelled.
1362                if let Some((Some(label), _)) = &args.kw_args.unlabeled {
1363                    if (formals.iter().all(|a| a.label_required) || exec_state.pipe_value().is_some())
1364                        && formals.iter().any(|a| &a.name == label && a.label_required)
1365                        && !args.kw_args.labeled.contains_key(label)
1366                    {
1367                        let (label, arg) = args.kw_args.unlabeled.take().unwrap();
1368                        args.kw_args.labeled.insert(label.unwrap(), arg);
1369                    }
1370                }
1371
1372                #[cfg(feature = "artifact-graph")]
1373                let op = if func.feature_tree_operation() {
1374                    let op_labeled_args = args
1375                        .kw_args
1376                        .labeled
1377                        .iter()
1378                        .map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
1379                        .collect();
1380                    Some(Operation::StdLibCall {
1381                        std_lib_fn: (&func).into(),
1382                        unlabeled_arg: args
1383                            .unlabeled_kw_arg_unconverted()
1384                            .map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
1385                        labeled_args: op_labeled_args,
1386                        source_range: callsite,
1387                        is_error: false,
1388                    })
1389                } else {
1390                    None
1391                };
1392
1393                for (label, arg) in &args.kw_args.labeled {
1394                    match formals.iter().find(|p| &p.name == label) {
1395                        Some(p) => {
1396                            if !p.label_required {
1397                                exec_state.err(CompilationError::err(
1398                                    arg.source_range,
1399                                    format!(
1400                                        "The function `{fn_name}` expects an unlabeled first parameter (`{label}`), but it is labelled in the call"
1401                                    ),
1402                                ));
1403                            }
1404                        }
1405                        None => {
1406                            exec_state.err(CompilationError::err(
1407                                arg.source_range,
1408                                format!("`{label}` is not an argument of `{fn_name}`"),
1409                            ));
1410                        }
1411                    }
1412                }
1413
1414                // Attempt to call the function.
1415                let mut return_value = {
1416                    // Don't early-return in this block.
1417                    exec_state.mut_stack().push_new_env_for_rust_call();
1418                    let result = func.std_lib_fn()(exec_state, args).await;
1419                    exec_state.mut_stack().pop_env();
1420
1421                    #[cfg(feature = "artifact-graph")]
1422                    if let Some(mut op) = op {
1423                        op.set_std_lib_call_is_error(result.is_err());
1424                        // Track call operation.  We do this after the call
1425                        // since things like patternTransform may call user code
1426                        // before running, and we will likely want to use the
1427                        // return value. The call takes ownership of the args,
1428                        // so we need to build the op before the call.
1429                        exec_state.global.operations.push(op);
1430                    }
1431
1432                    result
1433                }?;
1434
1435                update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
1436
1437                Ok(return_value)
1438            }
1439            FunctionKind::UserDefined => {
1440                // Clone the function so that we can use a mutable reference to
1441                // exec_state.
1442                let func = fn_name.get_result(exec_state, ctx).await?.clone();
1443
1444                let Some(fn_src) = func.as_fn() else {
1445                    return Err(KclError::Semantic(KclErrorDetails {
1446                        message: "cannot call this because it isn't a function".to_string(),
1447                        source_ranges: vec![callsite],
1448                    }));
1449                };
1450
1451                let return_value = fn_src
1452                    .call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
1453                    .await
1454                    .map_err(|e| {
1455                        // Add the call expression to the source ranges.
1456                        // TODO currently ignored by the frontend
1457                        e.add_source_ranges(vec![callsite])
1458                    })?;
1459
1460                let result = return_value.ok_or_else(move || {
1461                    let mut source_ranges: Vec<SourceRange> = vec![callsite];
1462                    // We want to send the source range of the original function.
1463                    if let KclValue::Function { meta, .. } = func {
1464                        source_ranges = meta.iter().map(|m| m.source_range).collect();
1465                    };
1466                    KclError::UndefinedValue(KclErrorDetails {
1467                        message: format!("Result of user-defined function {} is undefined", fn_name),
1468                        source_ranges,
1469                    })
1470                })?;
1471
1472                Ok(result)
1473            }
1474        }
1475    }
1476}
1477
1478fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
1479    // If the return result is a sketch or solid, we want to update the
1480    // memory for the tags of the group.
1481    // TODO: This could probably be done in a better way, but as of now this was my only idea
1482    // and it works.
1483    match result {
1484        KclValue::Sketch { value } => {
1485            for (name, tag) in value.tags.iter() {
1486                if exec_state.stack().cur_frame_contains(name) {
1487                    exec_state.mut_stack().update(name, |v, _| {
1488                        v.as_mut_tag().unwrap().merge_info(tag);
1489                    });
1490                } else {
1491                    exec_state
1492                        .mut_stack()
1493                        .add(
1494                            name.to_owned(),
1495                            KclValue::TagIdentifier(Box::new(tag.clone())),
1496                            SourceRange::default(),
1497                        )
1498                        .unwrap();
1499                }
1500            }
1501        }
1502        KclValue::Solid { ref mut value } => {
1503            for v in &value.value {
1504                if let Some(tag) = v.get_tag() {
1505                    // Get the past tag and update it.
1506                    let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
1507                        let mut t = t.clone();
1508                        let Some(info) = t.get_cur_info() else {
1509                            return Err(KclError::Internal(KclErrorDetails {
1510                                message: format!("Tag {} does not have path info", tag.name),
1511                                source_ranges: vec![tag.into()],
1512                            }));
1513                        };
1514
1515                        let mut info = info.clone();
1516                        info.surface = Some(v.clone());
1517                        info.sketch = value.id;
1518                        t.info.push((exec_state.stack().current_epoch(), info));
1519                        t
1520                    } else {
1521                        // It's probably a fillet or a chamfer.
1522                        // Initialize it.
1523                        TagIdentifier {
1524                            value: tag.name.clone(),
1525                            info: vec![(
1526                                exec_state.stack().current_epoch(),
1527                                TagEngineInfo {
1528                                    id: v.get_id(),
1529                                    surface: Some(v.clone()),
1530                                    path: None,
1531                                    sketch: value.id,
1532                                },
1533                            )],
1534                            meta: vec![Metadata {
1535                                source_range: tag.clone().into(),
1536                            }],
1537                        }
1538                    };
1539
1540                    // update the sketch tags.
1541                    value.sketch.merge_tags(Some(&tag_id).into_iter());
1542
1543                    if exec_state.stack().cur_frame_contains(&tag.name) {
1544                        exec_state.mut_stack().update(&tag.name, |v, _| {
1545                            v.as_mut_tag().unwrap().merge_info(&tag_id);
1546                        });
1547                    } else {
1548                        exec_state
1549                            .mut_stack()
1550                            .add(
1551                                tag.name.clone(),
1552                                KclValue::TagIdentifier(Box::new(tag_id)),
1553                                SourceRange::default(),
1554                            )
1555                            .unwrap();
1556                    }
1557                }
1558            }
1559
1560            // Find the stale sketch in memory and update it.
1561            if !value.sketch.tags.is_empty() {
1562                let sketches_to_update: Vec<_> = exec_state
1563                    .stack()
1564                    .find_keys_in_current_env(|v| match v {
1565                        KclValue::Sketch { value: sk } => sk.original_id == value.sketch.original_id,
1566                        _ => false,
1567                    })
1568                    .cloned()
1569                    .collect();
1570
1571                for k in sketches_to_update {
1572                    exec_state.mut_stack().update(&k, |v, _| {
1573                        let sketch = v.as_mut_sketch().unwrap();
1574                        sketch.merge_tags(value.sketch.tags.values());
1575                    });
1576                }
1577            }
1578        }
1579        KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
1580            for v in value {
1581                update_memory_for_tags_of_geometry(v, exec_state)?;
1582            }
1583        }
1584        _ => {}
1585    }
1586    Ok(())
1587}
1588
1589impl Node<TagDeclarator> {
1590    pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
1591        let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
1592            value: self.name.clone(),
1593            info: Vec::new(),
1594            meta: vec![Metadata {
1595                source_range: self.into(),
1596            }],
1597        }));
1598
1599        exec_state
1600            .mut_stack()
1601            .add(self.name.clone(), memory_item.clone(), self.into())?;
1602
1603        Ok(self.into())
1604    }
1605}
1606
1607impl Node<ArrayExpression> {
1608    #[async_recursion]
1609    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1610        let mut results = Vec::with_capacity(self.elements.len());
1611
1612        for element in &self.elements {
1613            let metadata = Metadata::from(element);
1614            // TODO: Carry statement kind here so that we know if we're
1615            // inside a variable declaration.
1616            let value = ctx
1617                .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
1618                .await?;
1619
1620            results.push(value);
1621        }
1622
1623        Ok(KclValue::HomArray {
1624            value: results,
1625            ty: RuntimeType::Primitive(PrimitiveType::Any),
1626        })
1627    }
1628}
1629
1630impl Node<ArrayRangeExpression> {
1631    #[async_recursion]
1632    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1633        let metadata = Metadata::from(&self.start_element);
1634        let start_val = ctx
1635            .execute_expr(
1636                &self.start_element,
1637                exec_state,
1638                &metadata,
1639                &[],
1640                StatementKind::Expression,
1641            )
1642            .await?;
1643        let (start, start_ty) = start_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
1644            source_ranges: vec![self.into()],
1645            message: format!("Expected int but found {}", start_val.human_friendly_type()),
1646        }))?;
1647        let metadata = Metadata::from(&self.end_element);
1648        let end_val = ctx
1649            .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
1650            .await?;
1651        let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
1652            source_ranges: vec![self.into()],
1653            message: format!("Expected int but found {}", end_val.human_friendly_type()),
1654        }))?;
1655
1656        if start_ty != end_ty {
1657            let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
1658            let start = fmt::human_display_number(start.n, start.ty);
1659            let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
1660            let end = fmt::human_display_number(end.n, end.ty);
1661            return Err(KclError::Semantic(KclErrorDetails {
1662                source_ranges: vec![self.into()],
1663                message: format!("Range start and end must be of the same type, but found {start} and {end}"),
1664            }));
1665        }
1666
1667        if end < start {
1668            return Err(KclError::Semantic(KclErrorDetails {
1669                source_ranges: vec![self.into()],
1670                message: format!("Range start is greater than range end: {start} .. {end}"),
1671            }));
1672        }
1673
1674        let range: Vec<_> = if self.end_inclusive {
1675            (start..=end).collect()
1676        } else {
1677            (start..end).collect()
1678        };
1679
1680        let meta = vec![Metadata {
1681            source_range: self.into(),
1682        }];
1683
1684        Ok(KclValue::HomArray {
1685            value: range
1686                .into_iter()
1687                .map(|num| KclValue::Number {
1688                    value: num as f64,
1689                    ty: start_ty.clone(),
1690                    meta: meta.clone(),
1691                })
1692                .collect(),
1693            ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)),
1694        })
1695    }
1696}
1697
1698impl Node<ObjectExpression> {
1699    #[async_recursion]
1700    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1701        let mut object = HashMap::with_capacity(self.properties.len());
1702        for property in &self.properties {
1703            let metadata = Metadata::from(&property.value);
1704            let result = ctx
1705                .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
1706                .await?;
1707
1708            object.insert(property.key.name.clone(), result);
1709        }
1710
1711        Ok(KclValue::Object {
1712            value: object,
1713            meta: vec![Metadata {
1714                source_range: self.into(),
1715            }],
1716        })
1717    }
1718}
1719
1720fn article_for<S: AsRef<str>>(s: S) -> &'static str {
1721    // '[' is included since it's an array.
1722    if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
1723        "an"
1724    } else {
1725        "a"
1726    }
1727}
1728
1729fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
1730    v.as_ty_f64().ok_or_else(|| {
1731        let actual_type = v.human_friendly_type();
1732        KclError::Semantic(KclErrorDetails {
1733            source_ranges: vec![source_range],
1734            message: format!("Expected a number, but found {actual_type}",),
1735        })
1736    })
1737}
1738
1739impl Node<IfExpression> {
1740    #[async_recursion]
1741    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1742        // Check the `if` branch.
1743        let cond = ctx
1744            .execute_expr(
1745                &self.cond,
1746                exec_state,
1747                &Metadata::from(self),
1748                &[],
1749                StatementKind::Expression,
1750            )
1751            .await?
1752            .get_bool()?;
1753        if cond {
1754            let block_result = ctx.exec_block(&self.then_val, exec_state, BodyType::Block).await?;
1755            // Block must end in an expression, so this has to be Some.
1756            // Enforced by the parser.
1757            // See https://github.com/KittyCAD/modeling-app/issues/4015
1758            return Ok(block_result.unwrap());
1759        }
1760
1761        // Check any `else if` branches.
1762        for else_if in &self.else_ifs {
1763            let cond = ctx
1764                .execute_expr(
1765                    &else_if.cond,
1766                    exec_state,
1767                    &Metadata::from(self),
1768                    &[],
1769                    StatementKind::Expression,
1770                )
1771                .await?
1772                .get_bool()?;
1773            if cond {
1774                let block_result = ctx.exec_block(&else_if.then_val, exec_state, BodyType::Block).await?;
1775                // Block must end in an expression, so this has to be Some.
1776                // Enforced by the parser.
1777                // See https://github.com/KittyCAD/modeling-app/issues/4015
1778                return Ok(block_result.unwrap());
1779            }
1780        }
1781
1782        // Run the final `else` branch.
1783        ctx.exec_block(&self.final_else, exec_state, BodyType::Block)
1784            .await
1785            .map(|expr| expr.unwrap())
1786    }
1787}
1788
1789#[derive(Debug)]
1790enum Property {
1791    UInt(usize),
1792    String(String),
1793}
1794
1795impl Property {
1796    fn try_from(
1797        computed: bool,
1798        value: LiteralIdentifier,
1799        exec_state: &ExecState,
1800        sr: SourceRange,
1801    ) -> Result<Self, KclError> {
1802        let property_sr = vec![sr];
1803        let property_src: SourceRange = value.clone().into();
1804        match value {
1805            LiteralIdentifier::Identifier(identifier) => {
1806                let name = &identifier.name;
1807                if !computed {
1808                    // This is dot syntax. Treat the property as a literal.
1809                    Ok(Property::String(name.to_string()))
1810                } else {
1811                    // This is bracket syntax. Actually evaluate memory to
1812                    // compute the property.
1813                    let prop = exec_state.stack().get(name, property_src)?;
1814                    jvalue_to_prop(prop, property_sr, name)
1815                }
1816            }
1817            LiteralIdentifier::Literal(literal) => {
1818                let value = literal.value.clone();
1819                match value {
1820                    LiteralValue::Number { value, .. } => {
1821                        if let Some(x) = crate::try_f64_to_usize(value) {
1822                            Ok(Property::UInt(x))
1823                        } else {
1824                            Err(KclError::Semantic(KclErrorDetails {
1825                                source_ranges: property_sr,
1826                                message: format!("{value} is not a valid index, indices must be whole numbers >= 0"),
1827                            }))
1828                        }
1829                    }
1830                    _ => Err(KclError::Semantic(KclErrorDetails {
1831                        source_ranges: vec![sr],
1832                        message: "Only numbers (>= 0) can be indexes".to_owned(),
1833                    })),
1834                }
1835            }
1836        }
1837    }
1838}
1839
1840fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> {
1841    let make_err = |message: String| {
1842        Err::<Property, _>(KclError::Semantic(KclErrorDetails {
1843            source_ranges: property_sr,
1844            message,
1845        }))
1846    };
1847    match value {
1848        KclValue::Number{value: num, .. } => {
1849            let num = *num;
1850            if num < 0.0 {
1851                return make_err(format!("'{num}' is negative, so you can't index an array with it"))
1852            }
1853            let nearest_int = crate::try_f64_to_usize(num);
1854            if let Some(nearest_int) = nearest_int {
1855                Ok(Property::UInt(nearest_int))
1856            } else {
1857                make_err(format!("'{num}' is not an integer, so you can't index an array with it"))
1858            }
1859        }
1860        KclValue::String{value: x, meta:_} => Ok(Property::String(x.to_owned())),
1861        _ => {
1862            make_err(format!("{name} is not a valid property/index, you can only use a string to get the property of an object, or an int (>= 0) to get an item in an array"))
1863        }
1864    }
1865}
1866
1867impl Property {
1868    fn type_name(&self) -> &'static str {
1869        match self {
1870            Property::UInt(_) => "number",
1871            Property::String(_) => "string",
1872        }
1873    }
1874}
1875
1876impl Node<PipeExpression> {
1877    #[async_recursion]
1878    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1879        execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
1880    }
1881}
1882
1883fn type_check_params_kw(
1884    fn_name: Option<&str>,
1885    function_expression: NodeRef<'_, FunctionExpression>,
1886    args: &mut KwArgs,
1887    exec_state: &mut ExecState,
1888) -> Result<(), KclError> {
1889    // If it's possible the input arg was meant to be labelled and we probably don't want to use
1890    // it as the input arg, then treat it as labelled.
1891    if let Some((Some(label), _)) = &args.unlabeled {
1892        if (function_expression.params.iter().all(|p| p.labeled) || exec_state.pipe_value().is_some())
1893            && function_expression
1894                .params
1895                .iter()
1896                .any(|p| &p.identifier.name == label && p.labeled)
1897            && !args.labeled.contains_key(label)
1898        {
1899            let (label, arg) = args.unlabeled.take().unwrap();
1900            args.labeled.insert(label.unwrap(), arg);
1901        }
1902    }
1903
1904    for (label, arg) in &mut args.labeled {
1905        match function_expression.params.iter().find(|p| &p.identifier.name == label) {
1906            Some(p) => {
1907                if !p.labeled {
1908                    exec_state.err(CompilationError::err(
1909                        arg.source_range,
1910                        format!(
1911                            "{} expects an unlabeled first parameter (`{label}`), but it is labelled in the call",
1912                            fn_name
1913                                .map(|n| format!("The function `{}`", n))
1914                                .unwrap_or_else(|| "This function".to_owned()),
1915                        ),
1916                    ));
1917                }
1918
1919                if let Some(ty) = &p.type_ {
1920                    arg.value = arg
1921                        .value
1922                        .coerce(
1923                            &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
1924                            exec_state,
1925                        )
1926                        .map_err(|e| {
1927                            let mut message = format!(
1928                                "{label} requires a value with type `{}`, but found {}",
1929                                ty.inner,
1930                                arg.value.human_friendly_type(),
1931                            );
1932                            if let Some(ty) = e.explicit_coercion {
1933                                // TODO if we have access to the AST for the argument we could choose which example to suggest.
1934                                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})`");
1935                            }
1936                            KclError::Semantic(KclErrorDetails {
1937                                message,
1938                                source_ranges: vec![arg.source_range],
1939                            })
1940                        })?;
1941                }
1942            }
1943            None => {
1944                exec_state.err(CompilationError::err(
1945                    arg.source_range,
1946                    format!(
1947                        "`{label}` is not an argument of {}",
1948                        fn_name
1949                            .map(|n| format!("`{}`", n))
1950                            .unwrap_or_else(|| "this function".to_owned()),
1951                    ),
1952                ));
1953            }
1954        }
1955    }
1956
1957    if !args.errors.is_empty() {
1958        let actuals = args.labeled.keys();
1959        let formals: Vec<_> = function_expression
1960            .params
1961            .iter()
1962            .filter_map(|p| {
1963                if !p.labeled {
1964                    return None;
1965                }
1966
1967                let name = &p.identifier.name;
1968                if actuals.clone().any(|a| a == name) {
1969                    return None;
1970                }
1971
1972                Some(format!("`{name}`"))
1973            })
1974            .collect();
1975
1976        let suggestion = if formals.is_empty() {
1977            String::new()
1978        } else {
1979            format!("; suggested labels: {}", formals.join(", "))
1980        };
1981
1982        let mut errors = args.errors.iter().map(|e| {
1983            CompilationError::err(
1984                e.source_range,
1985                format!("This argument needs a label, but it doesn't have one{suggestion}"),
1986            )
1987        });
1988
1989        let first = errors.next().unwrap();
1990        errors.for_each(|e| exec_state.err(e));
1991
1992        return Err(KclError::Semantic(first.into()));
1993    }
1994
1995    if let Some(arg) = &mut args.unlabeled {
1996        if let Some(p) = function_expression.params.iter().find(|p| !p.labeled) {
1997            if let Some(ty) = &p.type_ {
1998                arg.1.value = arg
1999                    .1
2000                    .value
2001                    .coerce(
2002                        &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
2003                            .map_err(|e| KclError::Semantic(e.into()))?,
2004                        exec_state,
2005                    )
2006                    .map_err(|_| {
2007                        KclError::Semantic(KclErrorDetails {
2008                            message: format!(
2009                                "The input argument of {} requires a value with type `{}`, but found {}",
2010                                fn_name
2011                                    .map(|n| format!("`{}`", n))
2012                                    .unwrap_or_else(|| "this function".to_owned()),
2013                                ty.inner,
2014                                arg.1.value.human_friendly_type()
2015                            ),
2016                            source_ranges: vec![arg.1.source_range],
2017                        })
2018                    })?;
2019            }
2020        }
2021    }
2022
2023    Ok(())
2024}
2025
2026fn assign_args_to_params_kw(
2027    fn_name: Option<&str>,
2028    function_expression: NodeRef<'_, FunctionExpression>,
2029    mut args: Args,
2030    exec_state: &mut ExecState,
2031) -> Result<(), KclError> {
2032    type_check_params_kw(fn_name, function_expression, &mut args.kw_args, exec_state)?;
2033
2034    // Add the arguments to the memory.  A new call frame should have already
2035    // been created.
2036    let source_ranges = vec![function_expression.into()];
2037
2038    for param in function_expression.params.iter() {
2039        if param.labeled {
2040            let arg = args.kw_args.labeled.get(&param.identifier.name);
2041            let arg_val = match arg {
2042                Some(arg) => arg.value.clone(),
2043                None => match param.default_value {
2044                    Some(ref default_val) => KclValue::from_default_param(default_val.clone(), exec_state),
2045                    None => {
2046                        return Err(KclError::Semantic(KclErrorDetails {
2047                            source_ranges,
2048                            message: format!(
2049                                "This function requires a parameter {}, but you haven't passed it one.",
2050                                param.identifier.name
2051                            ),
2052                        }));
2053                    }
2054                },
2055            };
2056            exec_state
2057                .mut_stack()
2058                .add(param.identifier.name.clone(), arg_val, (&param.identifier).into())?;
2059        } else {
2060            let unlabelled = args.unlabeled_kw_arg_unconverted();
2061
2062            let Some(unlabeled) = unlabelled else {
2063                let param_name = &param.identifier.name;
2064                return Err(if args.kw_args.labeled.contains_key(param_name) {
2065                    KclError::Semantic(KclErrorDetails {
2066                        source_ranges,
2067                        message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
2068                    })
2069                } else {
2070                    KclError::Semantic(KclErrorDetails {
2071                        source_ranges,
2072                        message: "This function expects an unlabeled first parameter, but you haven't passed it one."
2073                            .to_owned(),
2074                    })
2075                });
2076            };
2077            exec_state.mut_stack().add(
2078                param.identifier.name.clone(),
2079                unlabeled.value.clone(),
2080                (&param.identifier).into(),
2081            )?;
2082        }
2083    }
2084
2085    Ok(())
2086}
2087
2088fn coerce_result_type(
2089    result: Result<Option<KclValue>, KclError>,
2090    function_expression: NodeRef<'_, FunctionExpression>,
2091    exec_state: &mut ExecState,
2092) -> Result<Option<KclValue>, KclError> {
2093    if let Ok(Some(val)) = result {
2094        if let Some(ret_ty) = &function_expression.return_type {
2095            let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
2096                .map_err(|e| KclError::Semantic(e.into()))?;
2097            let val = val.coerce(&ty, exec_state).map_err(|_| {
2098                KclError::Semantic(KclErrorDetails {
2099                    message: format!(
2100                        "This function requires its result to be of type `{}`, but found {}",
2101                        ty.human_friendly_type(),
2102                        val.human_friendly_type(),
2103                    ),
2104                    source_ranges: ret_ty.as_source_ranges(),
2105                })
2106            })?;
2107            Ok(Some(val))
2108        } else {
2109            Ok(Some(val))
2110        }
2111    } else {
2112        result
2113    }
2114}
2115
2116async fn call_user_defined_function_kw(
2117    fn_name: Option<&str>,
2118    args: Args,
2119    memory: EnvironmentRef,
2120    function_expression: NodeRef<'_, FunctionExpression>,
2121    exec_state: &mut ExecState,
2122    ctx: &ExecutorContext,
2123) -> Result<Option<KclValue>, KclError> {
2124    // Create a new environment to execute the function body in so that local
2125    // variables shadow variables in the parent scope.  The new environment's
2126    // parent should be the environment of the closure.
2127    exec_state.mut_stack().push_new_env_for_call(memory);
2128    if let Err(e) = assign_args_to_params_kw(fn_name, function_expression, args, exec_state) {
2129        exec_state.mut_stack().pop_env();
2130        return Err(e);
2131    }
2132
2133    // Execute the function body using the memory we just created.
2134    let result = ctx
2135        .exec_block(&function_expression.body, exec_state, BodyType::Block)
2136        .await;
2137    let mut result = result.map(|_| {
2138        exec_state
2139            .stack()
2140            .get(memory::RETURN_NAME, function_expression.as_source_range())
2141            .ok()
2142            .cloned()
2143    });
2144
2145    result = coerce_result_type(result, function_expression, exec_state);
2146
2147    // Restore the previous memory.
2148    exec_state.mut_stack().pop_env();
2149
2150    result
2151}
2152
2153impl FunctionSource {
2154    pub async fn call_kw(
2155        &self,
2156        fn_name: Option<String>,
2157        exec_state: &mut ExecState,
2158        ctx: &ExecutorContext,
2159        mut args: Args,
2160        callsite: SourceRange,
2161    ) -> Result<Option<KclValue>, KclError> {
2162        match self {
2163            FunctionSource::Std { func, ast, props } => {
2164                if props.deprecated {
2165                    exec_state.warn(CompilationError::err(
2166                        callsite,
2167                        format!(
2168                            "`{}` is deprecated, see the docs for a recommended replacement",
2169                            props.name
2170                        ),
2171                    ));
2172                }
2173
2174                type_check_params_kw(Some(&props.name), ast, &mut args.kw_args, exec_state)?;
2175
2176                if let Some(arg) = &mut args.kw_args.unlabeled {
2177                    if let Some(p) = ast.params.iter().find(|p| !p.labeled) {
2178                        if let Some(ty) = &p.type_ {
2179                            arg.1.value = arg
2180                                .1
2181                                .value
2182                                .coerce(
2183                                    &RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
2184                                        .map_err(|e| KclError::Semantic(e.into()))?,
2185                                    exec_state,
2186                                )
2187                                .map_err(|_| {
2188                                    KclError::Semantic(KclErrorDetails {
2189                                        message: format!(
2190                                            "The input argument of {} requires a value with type `{}`, but found {}",
2191                                            props.name,
2192                                            ty.inner,
2193                                            arg.1.value.human_friendly_type(),
2194                                        ),
2195                                        source_ranges: vec![callsite],
2196                                    })
2197                                })?;
2198                        }
2199                    }
2200                }
2201
2202                #[cfg(feature = "artifact-graph")]
2203                let op = if props.include_in_feature_tree {
2204                    let op_labeled_args = args
2205                        .kw_args
2206                        .labeled
2207                        .iter()
2208                        .map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
2209                        .collect();
2210                    Some(Operation::KclStdLibCall {
2211                        name: fn_name.unwrap_or_default(),
2212                        unlabeled_arg: args
2213                            .unlabeled_kw_arg_unconverted()
2214                            .map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
2215                        labeled_args: op_labeled_args,
2216                        source_range: callsite,
2217                        is_error: false,
2218                    })
2219                } else {
2220                    None
2221                };
2222
2223                // Attempt to call the function.
2224                exec_state.mut_stack().push_new_env_for_rust_call();
2225                let mut result = {
2226                    // Don't early-return in this block.
2227                    let result = func(exec_state, args).await;
2228                    exec_state.mut_stack().pop_env();
2229
2230                    #[cfg(feature = "artifact-graph")]
2231                    if let Some(mut op) = op {
2232                        op.set_std_lib_call_is_error(result.is_err());
2233                        // Track call operation.  We do this after the call
2234                        // since things like patternTransform may call user code
2235                        // before running, and we will likely want to use the
2236                        // return value. The call takes ownership of the args,
2237                        // so we need to build the op before the call.
2238                        exec_state.global.operations.push(op);
2239                    }
2240                    result
2241                }?;
2242
2243                update_memory_for_tags_of_geometry(&mut result, exec_state)?;
2244
2245                Ok(Some(result))
2246            }
2247            FunctionSource::User { ast, memory, .. } => {
2248                // Track call operation.
2249                #[cfg(feature = "artifact-graph")]
2250                {
2251                    let op_labeled_args = args
2252                        .kw_args
2253                        .labeled
2254                        .iter()
2255                        .map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
2256                        .collect();
2257                    exec_state.global.operations.push(Operation::GroupBegin {
2258                        group: Group::FunctionCall {
2259                            name: fn_name.clone(),
2260                            function_source_range: ast.as_source_range(),
2261                            unlabeled_arg: args
2262                                .kw_args
2263                                .unlabeled
2264                                .as_ref()
2265                                .map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
2266                            labeled_args: op_labeled_args,
2267                        },
2268                        source_range: callsite,
2269                    });
2270                }
2271
2272                let result =
2273                    call_user_defined_function_kw(fn_name.as_deref(), args, *memory, ast, exec_state, ctx).await;
2274
2275                // Track return operation.
2276                #[cfg(feature = "artifact-graph")]
2277                exec_state.global.operations.push(Operation::GroupEnd);
2278
2279                result
2280            }
2281            FunctionSource::None => unreachable!(),
2282        }
2283    }
2284}
2285
2286#[cfg(test)]
2287mod test {
2288    use std::sync::Arc;
2289
2290    use tokio::io::AsyncWriteExt;
2291
2292    use super::*;
2293    use crate::{
2294        exec::UnitType,
2295        execution::{memory::Stack, parse_execute, ContextType},
2296        parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
2297        ExecutorSettings, UnitLen,
2298    };
2299
2300    #[tokio::test(flavor = "multi_thread")]
2301    async fn test_assign_args_to_params() {
2302        // Set up a little framework for this test.
2303        fn mem(number: usize) -> KclValue {
2304            KclValue::Number {
2305                value: number as f64,
2306                ty: NumericType::count(),
2307                meta: Default::default(),
2308            }
2309        }
2310        fn ident(s: &'static str) -> Node<Identifier> {
2311            Node::no_src(Identifier {
2312                name: s.to_owned(),
2313                digest: None,
2314            })
2315        }
2316        fn opt_param(s: &'static str) -> Parameter {
2317            Parameter {
2318                identifier: ident(s),
2319                type_: None,
2320                default_value: Some(DefaultParamVal::none()),
2321                labeled: true,
2322                digest: None,
2323            }
2324        }
2325        fn req_param(s: &'static str) -> Parameter {
2326            Parameter {
2327                identifier: ident(s),
2328                type_: None,
2329                default_value: None,
2330                labeled: true,
2331                digest: None,
2332            }
2333        }
2334        fn additional_program_memory(items: &[(String, KclValue)]) -> Stack {
2335            let mut program_memory = Stack::new_for_tests();
2336            for (name, item) in items {
2337                program_memory
2338                    .add(name.clone(), item.clone(), SourceRange::default())
2339                    .unwrap();
2340            }
2341            program_memory
2342        }
2343        // Declare the test cases.
2344        for (test_name, params, args, expected) in [
2345            ("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
2346            (
2347                "all params required, and all given, should be OK",
2348                vec![req_param("x")],
2349                vec![("x", mem(1))],
2350                Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
2351            ),
2352            (
2353                "all params required, none given, should error",
2354                vec![req_param("x")],
2355                vec![],
2356                Err(KclError::Semantic(KclErrorDetails {
2357                    source_ranges: vec![SourceRange::default()],
2358                    message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
2359                })),
2360            ),
2361            (
2362                "all params optional, none given, should be OK",
2363                vec![opt_param("x")],
2364                vec![],
2365                Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])),
2366            ),
2367            (
2368                "mixed params, too few given",
2369                vec![req_param("x"), opt_param("y")],
2370                vec![],
2371                Err(KclError::Semantic(KclErrorDetails {
2372                    source_ranges: vec![SourceRange::default()],
2373                    message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
2374                })),
2375            ),
2376            (
2377                "mixed params, minimum given, should be OK",
2378                vec![req_param("x"), opt_param("y")],
2379                vec![("x", mem(1))],
2380                Ok(additional_program_memory(&[
2381                    ("x".to_owned(), mem(1)),
2382                    ("y".to_owned(), KclValue::none()),
2383                ])),
2384            ),
2385            (
2386                "mixed params, maximum given, should be OK",
2387                vec![req_param("x"), opt_param("y")],
2388                vec![("x", mem(1)), ("y", mem(2))],
2389                Ok(additional_program_memory(&[
2390                    ("x".to_owned(), mem(1)),
2391                    ("y".to_owned(), mem(2)),
2392                ])),
2393            ),
2394        ] {
2395            // Run each test.
2396            let func_expr = &Node::no_src(FunctionExpression {
2397                params,
2398                body: Program::empty(),
2399                return_type: None,
2400                digest: None,
2401            });
2402            let labeled = args
2403                .iter()
2404                .map(|(name, value)| {
2405                    let arg = Arg::new(value.clone(), SourceRange::default());
2406                    ((*name).to_owned(), arg)
2407                })
2408                .collect::<IndexMap<_, _>>();
2409            let exec_ctxt = ExecutorContext {
2410                engine: Arc::new(Box::new(
2411                    crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
2412                )),
2413                fs: Arc::new(crate::fs::FileManager::new()),
2414                stdlib: Arc::new(crate::std::StdLib::new()),
2415                settings: Default::default(),
2416                context_type: ContextType::Mock,
2417            };
2418            let mut exec_state = ExecState::new(&exec_ctxt);
2419            exec_state.mod_local.stack = Stack::new_for_tests();
2420
2421            let args = Args::new_kw(
2422                KwArgs {
2423                    unlabeled: None,
2424                    labeled,
2425                    errors: Vec::new(),
2426                },
2427                SourceRange::default(),
2428                exec_ctxt,
2429                None,
2430            );
2431            let actual =
2432                assign_args_to_params_kw(None, func_expr, args, &mut exec_state).map(|_| exec_state.mod_local.stack);
2433            assert_eq!(
2434                actual, expected,
2435                "failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
2436            );
2437        }
2438    }
2439
2440    #[tokio::test(flavor = "multi_thread")]
2441    async fn ascription() {
2442        let program = r#"
2443a = 42: number
2444b = a: number
2445p = {
2446  origin = { x = 0, y = 0, z = 0 },
2447  xAxis = { x = 1, y = 0, z = 0 },
2448  yAxis = { x = 0, y = 1, z = 0 },
2449  zAxis = { x = 0, y = 0, z = 1 }
2450}: Plane
2451arr1 = [42]: [number(cm)]
2452"#;
2453
2454        let result = parse_execute(program).await.unwrap();
2455        let mem = result.exec_state.stack();
2456        assert!(matches!(
2457            mem.memory
2458                .get_from("p", result.mem_env, SourceRange::default(), 0)
2459                .unwrap(),
2460            KclValue::Plane { .. }
2461        ));
2462        let arr1 = mem
2463            .memory
2464            .get_from("arr1", result.mem_env, SourceRange::default(), 0)
2465            .unwrap();
2466        if let KclValue::HomArray { value, ty } = arr1 {
2467            assert_eq!(value.len(), 1, "Expected Vec with specific length: found {:?}", value);
2468            assert_eq!(*ty, RuntimeType::known_length(UnitLen::Cm));
2469            // Compare, ignoring meta.
2470            if let KclValue::Number { value, ty, .. } = &value[0] {
2471                // Converted from mm to cm.
2472                assert_eq!(*value, 4.2);
2473                assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLen::Cm)));
2474            } else {
2475                panic!("Expected a number; found {:?}", value[0]);
2476            }
2477        } else {
2478            panic!("Expected HomArray; found {arr1:?}");
2479        }
2480
2481        let program = r#"
2482a = 42: string
2483"#;
2484        let result = parse_execute(program).await;
2485        let err = result.unwrap_err();
2486        assert!(
2487            err.to_string()
2488                .contains("could not coerce number(default units) value to type string"),
2489            "Expected error but found {err:?}"
2490        );
2491
2492        let program = r#"
2493a = 42: Plane
2494"#;
2495        let result = parse_execute(program).await;
2496        let err = result.unwrap_err();
2497        assert!(
2498            err.to_string()
2499                .contains("could not coerce number(default units) value to type Plane"),
2500            "Expected error but found {err:?}"
2501        );
2502
2503        let program = r#"
2504arr = [0]: [string]
2505"#;
2506        let result = parse_execute(program).await;
2507        let err = result.unwrap_err();
2508        assert!(
2509            err.to_string()
2510                .contains("could not coerce [any; 1] value to type [string]"),
2511            "Expected error but found {err:?}"
2512        );
2513
2514        let program = r#"
2515mixedArr = [0, "a"]: [number(mm)]
2516"#;
2517        let result = parse_execute(program).await;
2518        let err = result.unwrap_err();
2519        assert!(
2520            err.to_string()
2521                .contains("could not coerce [any; 2] value to type [number(mm)]"),
2522            "Expected error but found {err:?}"
2523        );
2524    }
2525
2526    #[tokio::test(flavor = "multi_thread")]
2527    async fn neg_plane() {
2528        let program = r#"
2529p = {
2530  origin = { x = 0, y = 0, z = 0 },
2531  xAxis = { x = 1, y = 0, z = 0 },
2532  yAxis = { x = 0, y = 1, z = 0 },
2533}: Plane
2534p2 = -p
2535"#;
2536
2537        let result = parse_execute(program).await.unwrap();
2538        let mem = result.exec_state.stack();
2539        match mem
2540            .memory
2541            .get_from("p2", result.mem_env, SourceRange::default(), 0)
2542            .unwrap()
2543        {
2544            KclValue::Plane { value } => {
2545                assert_eq!(value.info.x_axis.x, -1.0);
2546                assert_eq!(value.info.x_axis.y, 0.0);
2547                assert_eq!(value.info.x_axis.z, 0.0);
2548            }
2549            _ => unreachable!(),
2550        }
2551    }
2552
2553    #[tokio::test(flavor = "multi_thread")]
2554    async fn multiple_returns() {
2555        let program = r#"fn foo() {
2556  return 0
2557  return 42
2558}
2559
2560a = foo()
2561"#;
2562
2563        let result = parse_execute(program).await;
2564        assert!(result.unwrap_err().to_string().contains("return"));
2565    }
2566
2567    #[tokio::test(flavor = "multi_thread")]
2568    async fn load_all_modules() {
2569        // program a.kcl
2570        let program_a_kcl = r#"
2571export a = 1
2572"#;
2573        // program b.kcl
2574        let program_b_kcl = r#"
2575import a from 'a.kcl'
2576
2577export b = a + 1
2578"#;
2579        // program c.kcl
2580        let program_c_kcl = r#"
2581import a from 'a.kcl'
2582
2583export c = a + 2
2584"#;
2585
2586        // program main.kcl
2587        let main_kcl = r#"
2588import b from 'b.kcl'
2589import c from 'c.kcl'
2590
2591d = b + c
2592"#;
2593
2594        let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
2595            .parse_errs_as_err()
2596            .unwrap();
2597
2598        let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
2599
2600        tokio::fs::File::create(tmpdir.path().join("main.kcl"))
2601            .await
2602            .unwrap()
2603            .write_all(main_kcl.as_bytes())
2604            .await
2605            .unwrap();
2606
2607        tokio::fs::File::create(tmpdir.path().join("a.kcl"))
2608            .await
2609            .unwrap()
2610            .write_all(program_a_kcl.as_bytes())
2611            .await
2612            .unwrap();
2613
2614        tokio::fs::File::create(tmpdir.path().join("b.kcl"))
2615            .await
2616            .unwrap()
2617            .write_all(program_b_kcl.as_bytes())
2618            .await
2619            .unwrap();
2620
2621        tokio::fs::File::create(tmpdir.path().join("c.kcl"))
2622            .await
2623            .unwrap()
2624            .write_all(program_c_kcl.as_bytes())
2625            .await
2626            .unwrap();
2627
2628        let exec_ctxt = ExecutorContext {
2629            engine: Arc::new(Box::new(
2630                crate::engine::conn_mock::EngineConnection::new()
2631                    .await
2632                    .map_err(|err| {
2633                        KclError::Internal(crate::errors::KclErrorDetails {
2634                            message: format!("Failed to create mock engine connection: {}", err),
2635                            source_ranges: vec![SourceRange::default()],
2636                        })
2637                    })
2638                    .unwrap(),
2639            )),
2640            fs: Arc::new(crate::fs::FileManager::new()),
2641            settings: ExecutorSettings {
2642                project_directory: Some(crate::TypedPath(tmpdir.path().into())),
2643                ..Default::default()
2644            },
2645            stdlib: Arc::new(crate::std::StdLib::new()),
2646            context_type: ContextType::Mock,
2647        };
2648        let mut exec_state = ExecState::new(&exec_ctxt);
2649
2650        exec_ctxt
2651            .run(
2652                &crate::Program {
2653                    ast: main.clone(),
2654                    original_file_contents: "".to_owned(),
2655                },
2656                &mut exec_state,
2657            )
2658            .await
2659            .unwrap();
2660    }
2661
2662    #[tokio::test(flavor = "multi_thread")]
2663    async fn user_coercion() {
2664        let program = r#"fn foo(x: Axis2d) {
2665  return 0
2666}
2667
2668foo(x = { direction = [0, 0], origin = [0, 0]})
2669"#;
2670
2671        parse_execute(program).await.unwrap();
2672
2673        let program = r#"fn foo(x: Axis3d) {
2674  return 0
2675}
2676
2677foo(x = { direction = [0, 0], origin = [0, 0]})
2678"#;
2679
2680        parse_execute(program).await.unwrap_err();
2681    }
2682
2683    #[tokio::test(flavor = "multi_thread")]
2684    async fn coerce_return() {
2685        let program = r#"fn foo(): number(mm) {
2686  return 42
2687}
2688
2689a = foo()
2690"#;
2691
2692        parse_execute(program).await.unwrap();
2693
2694        let program = r#"fn foo(): number(mm) {
2695  return { bar: 42 }
2696}
2697
2698a = foo()
2699"#;
2700
2701        parse_execute(program).await.unwrap_err();
2702    }
2703
2704    #[tokio::test(flavor = "multi_thread")]
2705    async fn test_sensible_error_when_missing_equals_in_kwarg() {
2706        for (i, call) in ["f(x=1,3,0)", "f(x=1,3,z)", "f(x=1,0,z=1)", "f(x=1, 3 + 4, z)"]
2707            .into_iter()
2708            .enumerate()
2709        {
2710            let program = format!(
2711                "fn foo() {{ return 0 }}
2712z = 0
2713fn f(x, y, z) {{ return 0 }}
2714{call}"
2715            );
2716            let err = parse_execute(&program).await.unwrap_err();
2717            let msg = err.message();
2718            assert!(
2719                msg.contains("This argument needs a label, but it doesn't have one"),
2720                "failed test {i}: {msg}"
2721            );
2722            assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
2723            if i == 0 {
2724                assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
2725            }
2726        }
2727    }
2728
2729    #[tokio::test(flavor = "multi_thread")]
2730    async fn default_param_for_unlabeled() {
2731        // Tests that the input param for myExtrude is taken from the pipeline value and same-name
2732        // keyword args.
2733        let ast = r#"fn myExtrude(@sk, length) {
2734  return extrude(sk, length)
2735}
2736sketch001 = startSketchOn(XY)
2737  |> circle(center = [0, 0], radius = 93.75)
2738  |> myExtrude(length = 40)
2739"#;
2740
2741        parse_execute(ast).await.unwrap();
2742    }
2743
2744    #[tokio::test(flavor = "multi_thread")]
2745    async fn dont_use_unlabelled_as_input() {
2746        // `length` should be used as the `length` argument to extrude, not the unlabelled input
2747        let ast = r#"length = 10
2748startSketchOn(XY)
2749  |> circle(center = [0, 0], radius = 93.75)
2750  |> extrude(length)
2751"#;
2752
2753        parse_execute(ast).await.unwrap();
2754    }
2755
2756    #[tokio::test(flavor = "multi_thread")]
2757    async fn ascription_in_binop() {
2758        let ast = r#"foo = tan(0): number(rad) - 4deg"#;
2759        parse_execute(ast).await.unwrap();
2760    }
2761
2762    #[tokio::test(flavor = "multi_thread")]
2763    async fn neg_sqrt() {
2764        let ast = r#"bad = sqrt(-2)"#;
2765
2766        let e = parse_execute(ast).await.unwrap_err();
2767        // Make sure we get a useful error message and not an engine error.
2768        assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
2769    }
2770}