Skip to main content

kcl_lib/execution/
exec_ast.rs

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