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