Skip to main content

kcl_lib/execution/
exec_ast.rs

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