kcl_lib/execution/
exec_ast.rs

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