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