Skip to main content

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