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