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