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