Skip to main content

kcl_lib/execution/
exec_ast.rs

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