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