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