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