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