kcl_lib/execution/
exec_ast.rs

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