kcl_lib/execution/
exec_ast.rs

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