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