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                // Optional constraints have a lower priority.
1726                sketch_block_state
1727                    .solver_optional_constraints
1728                    .iter()
1729                    .cloned()
1730                    .map(|c| ezpz::ConstraintRequest::new(c, 1)),
1731            )
1732            .collect::<Vec<_>>();
1733        let initial_guesses = sketch_block_state
1734            .sketch_vars
1735            .iter()
1736            .map(|v| {
1737                let Some(sketch_var) = v.as_sketch_var() else {
1738                    return Err(internal_err("Expected sketch variable", self));
1739                };
1740                let constraint_id = sketch_var.id.to_constraint_id(range)?;
1741                // Normalize units.
1742                let number_value = KclValue::Number {
1743                    value: sketch_var.initial_value,
1744                    ty: sketch_var.ty,
1745                    meta: sketch_var.meta.clone(),
1746                };
1747                let initial_guess_value = normalize_to_solver_distance_unit(
1748                    &number_value,
1749                    v.into(),
1750                    exec_state,
1751                    "sketch variable initial value",
1752                )?;
1753                let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1754                    n.n
1755                } else {
1756                    let message = format!(
1757                        "Expected number after coercion, but found {}",
1758                        initial_guess_value.human_friendly_type()
1759                    );
1760                    debug_assert!(false, "{}", &message);
1761                    return Err(internal_err(message, self));
1762                };
1763                Ok((constraint_id, initial_guess))
1764            })
1765            .collect::<Result<Vec<_>, KclError>>()?;
1766        // Solve constraints.
1767        let config = ezpz::Config::default()
1768            .with_max_iterations(50)
1769            .with_convergence_tolerance(SOLVER_CONVERGENCE_TOLERANCE);
1770        let solve_result = if exec_state.mod_local.freedom_analysis {
1771            ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
1772                let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
1773                (outcome.outcome, Some(freedom_analysis))
1774            })
1775        } else {
1776            ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1777        };
1778        // Build a combined list of all constraints (regular + optional) for conflict detection
1779        let num_required_constraints = sketch_block_state.solver_constraints.len();
1780        let all_constraints: Vec<ezpz::Constraint> = sketch_block_state
1781            .solver_constraints
1782            .iter()
1783            .cloned()
1784            .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
1785            .collect();
1786
1787        let (solve_outcome, solve_analysis) = match solve_result {
1788            Ok((solved, freedom)) => {
1789                let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
1790                (outcome, freedom)
1791            }
1792            Err(failure) => {
1793                match &failure.error {
1794                    NonLinearSystemError::FaerMatrix { .. }
1795                    | NonLinearSystemError::Faer { .. }
1796                    | NonLinearSystemError::FaerSolve { .. }
1797                    | NonLinearSystemError::FaerSvd(..)
1798                    | NonLinearSystemError::DidNotConverge => {
1799                        // Constraint solver failed to find a solution. Build a
1800                        // solution that is the initial guesses.
1801                        exec_state.warn(
1802                            CompilationIssue::err(range, "Constraint solver failed to find a solution".to_owned()),
1803                            annotations::WARN_SOLVER,
1804                        );
1805                        let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1806                        (
1807                            Solved {
1808                                final_values,
1809                                iterations: Default::default(),
1810                                warnings: failure.warnings,
1811                                priority_solved: Default::default(),
1812                                variables_in_conflicts: Default::default(),
1813                            },
1814                            None,
1815                        )
1816                    }
1817                    NonLinearSystemError::EmptySystemNotAllowed
1818                    | NonLinearSystemError::WrongNumberGuesses { .. }
1819                    | NonLinearSystemError::MissingGuess { .. }
1820                    | NonLinearSystemError::NotFound(..) => {
1821                        // These indicate something's gone wrong in KCL or ezpz,
1822                        // it's not a user error. We should investigate this.
1823                        #[cfg(target_arch = "wasm32")]
1824                        web_sys::console::error_1(
1825                            &format!("Internal error from constraint solver: {}", &failure.error).into(),
1826                        );
1827                        return Err(internal_err(
1828                            format!("Internal error from constraint solver: {}", &failure.error),
1829                            self,
1830                        ));
1831                    }
1832                    _ => {
1833                        // Catch all error case so that it's not a breaking change to publish new errors.
1834                        return Err(internal_err(
1835                            format!("Error from constraint solver: {}", &failure.error),
1836                            self,
1837                        ));
1838                    }
1839                }
1840            }
1841        };
1842        // Propagate warnings.
1843        for warning in &solve_outcome.warnings {
1844            let message = if let Some(index) = warning.about_constraint.as_ref() {
1845                format!("{}; constraint index {}", &warning.content, index)
1846            } else {
1847                format!("{}", &warning.content)
1848            };
1849            exec_state.warn(CompilationIssue::err(range, message), annotations::WARN_SOLVER);
1850        }
1851        // Substitute solutions back into sketch variables.
1852        let sketch_engine_id = exec_state.next_uuid();
1853        let solution_ty = solver_numeric_type(exec_state);
1854        let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1855        for unsolved_segment in &sketch_block_state.needed_by_engine {
1856            solved_segments.push(substitute_sketch_var_in_segment(
1857                unsolved_segment.clone(),
1858                &sketch_surface,
1859                sketch_engine_id,
1860                None,
1861                &solve_outcome,
1862                solver_numeric_type(exec_state),
1863                solve_analysis.as_ref(),
1864            )?);
1865        }
1866        // Store variable solutions so that the sketch refactoring API can
1867        // write them back to the source. When editing a sketch block, we
1868        // exit early so that the sketch block that we're editing is always
1869        // the last one. Therefore, we should overwrite any previous
1870        // solutions.
1871        exec_state.mod_local.artifacts.var_solutions =
1872            sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
1873
1874        // Create scene objects after unknowns are solved.
1875        let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1876
1877        // Build the sketch and send everything to the engine.
1878        let sketch = create_segments_in_engine(
1879            &sketch_surface,
1880            sketch_engine_id,
1881            &mut solved_segments,
1882            &sketch_block_state.segment_tags,
1883            ctx,
1884            exec_state,
1885            range,
1886        )
1887        .await?;
1888
1889        // We now have enough information to fill in the path.
1890        if let Some(sketch_artifact_id) = sketch.as_ref().map(|s| s.artifact_id) {
1891            if let Some(Artifact::SketchBlock(sketch_block_artifact)) =
1892                exec_state.artifact_mut(sketch_block_artifact_id)
1893            {
1894                sketch_block_artifact.path_id = Some(sketch_artifact_id);
1895            } else {
1896                let message = "Sketch block artifact not found, so path couldn't be linked to it".to_owned();
1897                debug_assert!(false, "{message}");
1898                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1899            }
1900        }
1901
1902        // Substitute solutions back into sketch variables. This time, collect
1903        // all the variables in the sketch block. The set of variables may have
1904        // overlap with the objects sent to the engine, but it isn't necessarily
1905        // the same.
1906        let variables = substitute_sketch_vars(
1907            variables,
1908            &sketch_surface,
1909            sketch_engine_id,
1910            sketch.as_ref(),
1911            &solve_outcome,
1912            solution_ty,
1913            solve_analysis.as_ref(),
1914        )?;
1915
1916        let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1917        for scene_object in scene_objects {
1918            segment_object_ids.push(scene_object.id);
1919            // Fill in placeholder scene objects.
1920            exec_state.set_scene_object(scene_object);
1921        }
1922        // Update the sketch scene object with the segments.
1923        let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
1924            let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1925            debug_assert!(false, "{}", &message);
1926            return Err(internal_err(message, range));
1927        };
1928        let ObjectKind::Sketch(front_sketch) = &mut sketch_object.kind else {
1929            let message = format!(
1930                "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1931                sketch_id, sketch_object
1932            );
1933            debug_assert!(
1934                false,
1935                "{}; scene_objects={:#?}",
1936                &message, &exec_state.mod_local.artifacts.scene_objects
1937            );
1938            return Err(internal_err(message, range));
1939        };
1940        front_sketch.segments.extend(segment_object_ids);
1941        // Update the sketch scene object with constraints.
1942        front_sketch
1943            .constraints
1944            .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1945
1946        // Push sketch solve operation
1947        exec_state.push_op(Operation::SketchSolve {
1948            sketch_id,
1949            node_path: NodePath::placeholder(),
1950            source_range: range,
1951        });
1952
1953        // Warn if the sketch has conflicting constraints. Skip this when
1954        // freedom analysis didn't run (e.g., during dragging), because the
1955        // freedom values on points are stale defaults in that case.
1956        if exec_state.mod_local.freedom_analysis {
1957            let status = {
1958                let scene_objects = &exec_state.mod_local.artifacts.scene_objects;
1959                scene_objects
1960                    .get(sketch_id.0)
1961                    .and_then(|obj| sketch_constraint_status_for_sketch(scene_objects, obj))
1962            };
1963            if let Some(status) = status
1964                && status.status == ConstraintKind::OverConstrained
1965            {
1966                let description = if status.conflict_count == 1 {
1967                    "segment has"
1968                } else {
1969                    "segments have"
1970                };
1971                let message = format!(
1972                    "Sketch is over-constrained: {} {description} conflicting constraints",
1973                    status.conflict_count,
1974                );
1975                exec_state.warn(
1976                    CompilationIssue::err(range, message),
1977                    annotations::WARN_OVER_CONSTRAINED_SKETCH,
1978                );
1979            }
1980        }
1981
1982        let properties = self.sketch_properties(sketch, variables);
1983        let metadata = Metadata {
1984            source_range: SourceRange::from(self),
1985        };
1986        let return_value = KclValue::Object {
1987            value: properties,
1988            constrainable: Default::default(),
1989            meta: vec![metadata],
1990        };
1991        Ok(if self.is_being_edited {
1992            // When the sketch block is being edited, we exit the program
1993            // immediately.
1994            return_value.exit()
1995        } else {
1996            return_value.continue_()
1997        })
1998    }
1999
2000    /// Executes the arguments of the sketch block and returns the sketch ID and
2001    /// surface. The surface is the `on` argument, which is basically a Plane or
2002    /// Face.
2003    ///
2004    /// In sketch mode, the execution cache is used to look up the sketch
2005    /// surface.
2006    ///
2007    /// The sketch ID is generated in either case so that it's stable. But only
2008    /// a placeholder scene object is created for it.
2009    async fn exec_arguments(
2010        &self,
2011        exec_state: &mut ExecState,
2012        ctx: &ExecutorContext,
2013    ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2014        let range = SourceRange::from(self);
2015
2016        if !exec_state.sketch_mode() {
2017            // Evaluate arguments.
2018            //
2019            // Sketch mode only executes the sketch block body. Arguments must
2020            // be evaluated in engine execution so that things like Planes and
2021            // Faces can be created in the engine.
2022            let mut labeled = IndexMap::new();
2023            for labeled_arg in &self.arguments {
2024                let source_range = SourceRange::from(labeled_arg.arg.clone());
2025                let metadata = Metadata { source_range };
2026                let value_cf = ctx
2027                    .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
2028                    .await?;
2029                let value = early_return!(value_cf);
2030                let arg = Arg::new(value, source_range);
2031                match &labeled_arg.label {
2032                    Some(label) => {
2033                        labeled.insert(label.name.clone(), arg);
2034                    }
2035                    None => {
2036                        let name = labeled_arg.arg.ident_name();
2037                        if let Some(name) = name {
2038                            labeled.insert(name.to_owned(), arg);
2039                        } else {
2040                            return Err(KclError::new_semantic(KclErrorDetails::new(
2041                                "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
2042                                vec![SourceRange::from(&labeled_arg.arg)],
2043                            ))
2044                            .into());
2045                        }
2046                    }
2047                }
2048            }
2049            let mut args = Args::new_no_args(
2050                range,
2051                self.node_path.clone(),
2052                ctx.clone(),
2053                Some("sketch block".to_owned()),
2054            );
2055            args.labeled = labeled;
2056
2057            let arg_on_value: KclValue =
2058                args.get_kw_arg(SKETCH_BLOCK_PARAM_ON, &RuntimeType::sketch_or_surface(), exec_state)?;
2059
2060            let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2061                let message =
2062                    "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2063                debug_assert!(false, "{message}");
2064                return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2065            };
2066            let mut sketch_surface = arg_on.into_sketch_surface();
2067
2068            // Ensure that the plane has an ObjectId. Always create an Object so
2069            // that we're consistent with IDs.
2070            match &mut sketch_surface {
2071                SketchSurface::Plane(plane) => {
2072                    // Ensure that it's been created in the engine.
2073                    ensure_sketch_plane_in_engine(plane, exec_state, ctx, range, self.node_path.clone()).await?;
2074                }
2075                SketchSurface::Face(_) => {
2076                    // All faces should already be created in the engine.
2077                }
2078            }
2079
2080            // Generate an ID for the sketch block. This must be done after
2081            // arguments so that we get the same result when the arguments are
2082            // cached. This must be done before the sketch block body so that no
2083            // matter how many IDs are generated due to objects in the body, the
2084            // sketch ID is always stable.
2085            let sketch_id = exec_state.next_object_id();
2086            exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2087            let on_cache_name = sketch_on_cache_name(sketch_id);
2088            // Store in memory so that it's cached.
2089            exec_state.mut_stack().add(on_cache_name, arg_on_value, range)?;
2090
2091            Ok((sketch_id, sketch_surface))
2092        } else {
2093            // In sketch mode, we can't re-evaluate arguments. Instead, look
2094            // them up from cache.
2095
2096            // Generate an ID for the sketch block. This must be done before the
2097            // sketch block body so that no matter how many IDs are generated
2098            // due to objects in the body, the sketch ID is always stable.
2099            let sketch_id = exec_state.next_object_id();
2100            exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2101            let on_cache_name = sketch_on_cache_name(sketch_id);
2102            let arg_on_value = exec_state.stack().get(&on_cache_name, range)?.clone();
2103
2104            let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2105                let message =
2106                    "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2107                debug_assert!(false, "{message}");
2108                return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2109            };
2110            let mut sketch_surface = arg_on.into_sketch_surface();
2111
2112            // Ensure that the plane has an ObjectId. Always create an Object so
2113            // that we're consistent with IDs.
2114            if sketch_surface.object_id().is_none() {
2115                // Look up the last object. Since this is where we would have
2116                // created it in real execution, it will be the last object.
2117                let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
2118                    return Err(internal_err(
2119                        "In sketch mode, the `on` plane argument must refer to an existing plane object.",
2120                        range,
2121                    )
2122                    .into());
2123                };
2124                sketch_surface.set_object_id(last_object.id);
2125            }
2126
2127            Ok((sketch_id, sketch_surface))
2128        }
2129    }
2130
2131    async fn load_sketch2_into_current_scope(
2132        &self,
2133        exec_state: &mut ExecState,
2134        ctx: &ExecutorContext,
2135        source_range: SourceRange,
2136    ) -> Result<(), KclError> {
2137        let path = vec!["std".to_owned(), "solver".to_owned()];
2138        let resolved_path = ModulePath::from_std_import_path(&path)?;
2139        let module_id = ctx
2140            .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
2141            .await?;
2142        let (env_ref, exports) = ctx.exec_module_for_items(module_id, exec_state, source_range).await?;
2143
2144        for name in exports {
2145            let value = exec_state
2146                .stack()
2147                .memory
2148                .get_from(&name, env_ref, source_range, 0)?
2149                .clone();
2150            exec_state.mut_stack().add(name, value, source_range)?;
2151        }
2152        Ok(())
2153    }
2154
2155    /// Augment the variables in the sketch block with properties that should be
2156    /// accessible on the returned sketch object. This includes metadata like
2157    /// the sketch so that the engine ID and surface can be accessed.
2158    pub(crate) fn sketch_properties(
2159        &self,
2160        sketch: Option<Sketch>,
2161        variables: HashMap<String, KclValue>,
2162    ) -> HashMap<String, KclValue> {
2163        let Some(sketch) = sketch else {
2164            // The sketch block did not produce a Sketch, so we cannot provide
2165            // it.
2166            return variables;
2167        };
2168
2169        let mut properties = variables;
2170
2171        let sketch_value = KclValue::Sketch {
2172            value: Box::new(sketch),
2173        };
2174        let mut meta_map = HashMap::with_capacity(1);
2175        meta_map.insert(SKETCH_OBJECT_META_SKETCH.to_owned(), sketch_value);
2176        let meta_value = KclValue::Object {
2177            value: meta_map,
2178            constrainable: false,
2179            meta: vec![Metadata {
2180                source_range: SourceRange::from(self),
2181            }],
2182        };
2183
2184        properties.insert(SKETCH_OBJECT_META.to_owned(), meta_value);
2185
2186        properties
2187    }
2188}
2189
2190impl SketchBlock {
2191    fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
2192        exec_state.mut_stack().push_new_env_for_call(parent);
2193    }
2194}
2195
2196impl Node<SketchVar> {
2197    pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2198        let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
2199            return Err(KclError::new_semantic(KclErrorDetails::new(
2200                "Cannot use a sketch variable outside of a sketch block".to_owned(),
2201                vec![SourceRange::from(self)],
2202            )));
2203        };
2204        let id = sketch_block_state.next_sketch_var_id();
2205        let sketch_var = if let Some(initial) = &self.initial {
2206            KclValue::from_sketch_var_literal(initial, id, exec_state)
2207        } else {
2208            let metadata = Metadata {
2209                source_range: SourceRange::from(self),
2210            };
2211
2212            KclValue::SketchVar {
2213                value: Box::new(super::SketchVar {
2214                    id,
2215                    initial_value: 0.0,
2216                    ty: NumericType::default(),
2217                    meta: vec![metadata],
2218                }),
2219            }
2220        };
2221
2222        let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2223            return Err(KclError::new_semantic(KclErrorDetails::new(
2224                "Cannot use a sketch variable outside of a sketch block".to_owned(),
2225                vec![SourceRange::from(self)],
2226            )));
2227        };
2228        sketch_block_state.sketch_vars.push(sketch_var.clone());
2229
2230        Ok(sketch_var)
2231    }
2232}
2233
2234fn apply_ascription(
2235    value: &KclValue,
2236    ty: &Node<Type>,
2237    exec_state: &mut ExecState,
2238    source_range: SourceRange,
2239) -> Result<KclValue, KclError> {
2240    let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false, false)
2241        .map_err(|e| KclError::new_semantic(e.into()))?;
2242
2243    if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
2244        exec_state.clear_units_warnings(&source_range);
2245    }
2246
2247    value.coerce(&ty, false, exec_state).map_err(|_| {
2248        let suggestion = if ty == RuntimeType::length() {
2249            ", you might try coercing to a fully specified numeric type such as `mm`"
2250        } else if ty == RuntimeType::angle() {
2251            ", you might try coercing to a fully specified numeric type such as `deg`"
2252        } else {
2253            ""
2254        };
2255        let ty_str = if let Some(ty) = value.principal_type() {
2256            format!("(with type `{ty}`) ")
2257        } else {
2258            String::new()
2259        };
2260        KclError::new_semantic(KclErrorDetails::new(
2261            format!(
2262                "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
2263                value.human_friendly_type()
2264            ),
2265            vec![source_range],
2266        ))
2267    })
2268}
2269
2270impl BinaryPart {
2271    #[async_recursion]
2272    pub(super) async fn get_result(
2273        &self,
2274        exec_state: &mut ExecState,
2275        ctx: &ExecutorContext,
2276    ) -> Result<KclValueControlFlow, KclError> {
2277        match self {
2278            BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
2279            BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned().map(KclValue::continue_),
2280            BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
2281            BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
2282            BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
2283            BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
2284            BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
2285            BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
2286            BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
2287            BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
2288            BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
2289            BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
2290        }
2291    }
2292}
2293
2294impl Node<Name> {
2295    pub(super) async fn get_result<'a>(
2296        &self,
2297        exec_state: &'a mut ExecState,
2298        ctx: &ExecutorContext,
2299    ) -> Result<&'a KclValue, KclError> {
2300        let being_declared = exec_state.mod_local.being_declared.clone();
2301        self.get_result_inner(exec_state, ctx)
2302            .await
2303            .map_err(|e| var_in_own_ref_err(e, &being_declared))
2304    }
2305
2306    async fn get_result_inner<'a>(
2307        &self,
2308        exec_state: &'a mut ExecState,
2309        ctx: &ExecutorContext,
2310    ) -> Result<&'a KclValue, KclError> {
2311        if self.abs_path {
2312            return Err(KclError::new_semantic(KclErrorDetails::new(
2313                "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
2314                self.as_source_ranges(),
2315            )));
2316        }
2317
2318        let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
2319
2320        if self.path.is_empty() {
2321            let item_value = exec_state.stack().get(&self.name.name, self.into());
2322            if item_value.is_ok() {
2323                return item_value;
2324            }
2325            return exec_state.stack().get(&mod_name, self.into());
2326        }
2327
2328        let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
2329        for p in &self.path {
2330            let value = match mem_spec {
2331                Some((env, exports)) => {
2332                    if !exports.contains(&p.name) {
2333                        return Err(KclError::new_semantic(KclErrorDetails::new(
2334                            format!("Item {} not found in module's exported items", p.name),
2335                            p.as_source_ranges(),
2336                        )));
2337                    }
2338
2339                    exec_state
2340                        .stack()
2341                        .memory
2342                        .get_from(&p.name, env, p.as_source_range(), 0)?
2343                }
2344                None => exec_state
2345                    .stack()
2346                    .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
2347            };
2348
2349            let KclValue::Module { value: module_id, .. } = value else {
2350                return Err(KclError::new_semantic(KclErrorDetails::new(
2351                    format!(
2352                        "Identifier in path must refer to a module, found {}",
2353                        value.human_friendly_type()
2354                    ),
2355                    p.as_source_ranges(),
2356                )));
2357            };
2358
2359            mem_spec = Some(
2360                ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
2361                    .await?,
2362            );
2363        }
2364
2365        let (env, exports) = mem_spec.unwrap();
2366
2367        let item_exported = exports.contains(&self.name.name);
2368        let item_value = exec_state
2369            .stack()
2370            .memory
2371            .get_from(&self.name.name, env, self.name.as_source_range(), 0);
2372
2373        // Item is defined and exported.
2374        if item_exported && item_value.is_ok() {
2375            return item_value;
2376        }
2377
2378        let mod_exported = exports.contains(&mod_name);
2379        let mod_value = exec_state
2380            .stack()
2381            .memory
2382            .get_from(&mod_name, env, self.name.as_source_range(), 0);
2383
2384        // Module is defined and exported.
2385        if mod_exported && mod_value.is_ok() {
2386            return mod_value;
2387        }
2388
2389        // Neither item or module is defined.
2390        if item_value.is_err() && mod_value.is_err() {
2391            return item_value;
2392        }
2393
2394        // Either item or module is defined, but not exported.
2395        debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
2396        Err(KclError::new_semantic(KclErrorDetails::new(
2397            format!("Item {} not found in module's exported items", self.name.name),
2398            self.name.as_source_ranges(),
2399        )))
2400    }
2401}
2402
2403impl Node<MemberExpression> {
2404    async fn get_result(
2405        &self,
2406        exec_state: &mut ExecState,
2407        ctx: &ExecutorContext,
2408    ) -> Result<KclValueControlFlow, KclError> {
2409        let meta = Metadata {
2410            source_range: SourceRange::from(self),
2411        };
2412        // TODO: The order of execution is wrong. We should execute the object
2413        // *before* the property.
2414        let property = Property::try_from(
2415            self.computed,
2416            self.property.clone(),
2417            exec_state,
2418            self.into(),
2419            ctx,
2420            &meta,
2421            &[],
2422            StatementKind::Expression,
2423        )
2424        .await?;
2425        let object_cf = ctx
2426            .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
2427            .await?;
2428        let object = control_continue!(object_cf);
2429
2430        // Check the property and object match -- e.g. ints for arrays, strs for objects.
2431        match (object, property, self.computed) {
2432            (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
2433                "at" => match &segment.repr {
2434                    SegmentRepr::Unsolved { segment } => {
2435                        match &segment.kind {
2436                            UnsolvedSegmentKind::Point { position, .. } => {
2437                                // TODO: assert that types of all elements are the same.
2438                                Ok(KclValue::HomArray {
2439                                    value: vec![
2440                                        KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
2441                                        KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
2442                                    ],
2443                                    ty: RuntimeType::any(),
2444                                }
2445                                .continue_())
2446                            }
2447                            _ => Err(KclError::new_undefined_value(
2448                                KclErrorDetails::new(
2449                                    format!("Property '{property}' not found in segment"),
2450                                    vec![self.clone().into()],
2451                                ),
2452                                None,
2453                            )),
2454                        }
2455                    }
2456                    SegmentRepr::Solved { segment } => {
2457                        match &segment.kind {
2458                            SegmentKind::Point { position, .. } => {
2459                                // TODO: assert that types of all elements are the same.
2460                                Ok(KclValue::array_from_point2d(
2461                                    [position[0].n, position[1].n],
2462                                    position[0].ty,
2463                                    segment.meta.clone(),
2464                                )
2465                                .continue_())
2466                            }
2467                            _ => Err(KclError::new_undefined_value(
2468                                KclErrorDetails::new(
2469                                    format!("Property '{property}' not found in segment"),
2470                                    vec![self.clone().into()],
2471                                ),
2472                                None,
2473                            )),
2474                        }
2475                    }
2476                },
2477                "start" => match &segment.repr {
2478                    SegmentRepr::Unsolved { segment } => match &segment.kind {
2479                        UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2480                            KclErrorDetails::new(
2481                                format!("Property '{property}' not found in point segment"),
2482                                vec![self.clone().into()],
2483                            ),
2484                            None,
2485                        )),
2486                        UnsolvedSegmentKind::Line {
2487                            start,
2488                            ctor,
2489                            start_object_id,
2490                            ..
2491                        } => Ok(KclValue::Segment {
2492                            value: Box::new(AbstractSegment {
2493                                repr: SegmentRepr::Unsolved {
2494                                    segment: Box::new(UnsolvedSegment {
2495                                        id: segment.id,
2496                                        object_id: *start_object_id,
2497                                        kind: UnsolvedSegmentKind::Point {
2498                                            position: start.clone(),
2499                                            ctor: Box::new(PointCtor {
2500                                                position: ctor.start.clone(),
2501                                            }),
2502                                        },
2503                                        tag: segment.tag.clone(),
2504                                        node_path: segment.node_path.clone(),
2505                                        meta: segment.meta.clone(),
2506                                    }),
2507                                },
2508                                meta: segment.meta.clone(),
2509                            }),
2510                        }
2511                        .continue_()),
2512                        UnsolvedSegmentKind::Arc {
2513                            start,
2514                            ctor,
2515                            start_object_id,
2516                            ..
2517                        } => Ok(KclValue::Segment {
2518                            value: Box::new(AbstractSegment {
2519                                repr: SegmentRepr::Unsolved {
2520                                    segment: Box::new(UnsolvedSegment {
2521                                        id: segment.id,
2522                                        object_id: *start_object_id,
2523                                        kind: UnsolvedSegmentKind::Point {
2524                                            position: start.clone(),
2525                                            ctor: Box::new(PointCtor {
2526                                                position: ctor.start.clone(),
2527                                            }),
2528                                        },
2529                                        tag: segment.tag.clone(),
2530                                        node_path: segment.node_path.clone(),
2531                                        meta: segment.meta.clone(),
2532                                    }),
2533                                },
2534                                meta: segment.meta.clone(),
2535                            }),
2536                        }
2537                        .continue_()),
2538                        UnsolvedSegmentKind::Circle {
2539                            start,
2540                            ctor,
2541                            start_object_id,
2542                            ..
2543                        } => Ok(KclValue::Segment {
2544                            value: Box::new(AbstractSegment {
2545                                repr: SegmentRepr::Unsolved {
2546                                    segment: Box::new(UnsolvedSegment {
2547                                        id: segment.id,
2548                                        object_id: *start_object_id,
2549                                        kind: UnsolvedSegmentKind::Point {
2550                                            position: start.clone(),
2551                                            ctor: Box::new(PointCtor {
2552                                                position: ctor.start.clone(),
2553                                            }),
2554                                        },
2555                                        tag: segment.tag.clone(),
2556                                        node_path: segment.node_path.clone(),
2557                                        meta: segment.meta.clone(),
2558                                    }),
2559                                },
2560                                meta: segment.meta.clone(),
2561                            }),
2562                        }
2563                        .continue_()),
2564                    },
2565                    SegmentRepr::Solved { segment } => match &segment.kind {
2566                        SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2567                            KclErrorDetails::new(
2568                                format!("Property '{property}' not found in point segment"),
2569                                vec![self.clone().into()],
2570                            ),
2571                            None,
2572                        )),
2573                        SegmentKind::Line {
2574                            start,
2575                            ctor,
2576                            start_object_id,
2577                            start_freedom,
2578                            ..
2579                        } => Ok(KclValue::Segment {
2580                            value: Box::new(AbstractSegment {
2581                                repr: SegmentRepr::Solved {
2582                                    segment: Box::new(Segment {
2583                                        id: segment.id,
2584                                        object_id: *start_object_id,
2585                                        kind: SegmentKind::Point {
2586                                            position: start.clone(),
2587                                            ctor: Box::new(PointCtor {
2588                                                position: ctor.start.clone(),
2589                                            }),
2590                                            freedom: *start_freedom,
2591                                        },
2592                                        surface: segment.surface.clone(),
2593                                        sketch_id: segment.sketch_id,
2594                                        sketch: segment.sketch.clone(),
2595                                        tag: segment.tag.clone(),
2596                                        node_path: segment.node_path.clone(),
2597                                        meta: segment.meta.clone(),
2598                                    }),
2599                                },
2600                                meta: segment.meta.clone(),
2601                            }),
2602                        }
2603                        .continue_()),
2604                        SegmentKind::Arc {
2605                            start,
2606                            ctor,
2607                            start_object_id,
2608                            start_freedom,
2609                            ..
2610                        } => Ok(KclValue::Segment {
2611                            value: Box::new(AbstractSegment {
2612                                repr: SegmentRepr::Solved {
2613                                    segment: Box::new(Segment {
2614                                        id: segment.id,
2615                                        object_id: *start_object_id,
2616                                        kind: SegmentKind::Point {
2617                                            position: start.clone(),
2618                                            ctor: Box::new(PointCtor {
2619                                                position: ctor.start.clone(),
2620                                            }),
2621                                            freedom: *start_freedom,
2622                                        },
2623                                        surface: segment.surface.clone(),
2624                                        sketch_id: segment.sketch_id,
2625                                        sketch: segment.sketch.clone(),
2626                                        tag: segment.tag.clone(),
2627                                        node_path: segment.node_path.clone(),
2628                                        meta: segment.meta.clone(),
2629                                    }),
2630                                },
2631                                meta: segment.meta.clone(),
2632                            }),
2633                        }
2634                        .continue_()),
2635                        SegmentKind::Circle {
2636                            start,
2637                            ctor,
2638                            start_object_id,
2639                            start_freedom,
2640                            ..
2641                        } => Ok(KclValue::Segment {
2642                            value: Box::new(AbstractSegment {
2643                                repr: SegmentRepr::Solved {
2644                                    segment: Box::new(Segment {
2645                                        id: segment.id,
2646                                        object_id: *start_object_id,
2647                                        kind: SegmentKind::Point {
2648                                            position: start.clone(),
2649                                            ctor: Box::new(PointCtor {
2650                                                position: ctor.start.clone(),
2651                                            }),
2652                                            freedom: *start_freedom,
2653                                        },
2654                                        surface: segment.surface.clone(),
2655                                        sketch_id: segment.sketch_id,
2656                                        sketch: segment.sketch.clone(),
2657                                        tag: segment.tag.clone(),
2658                                        node_path: segment.node_path.clone(),
2659                                        meta: segment.meta.clone(),
2660                                    }),
2661                                },
2662                                meta: segment.meta.clone(),
2663                            }),
2664                        }
2665                        .continue_()),
2666                    },
2667                },
2668                "end" => match &segment.repr {
2669                    SegmentRepr::Unsolved { segment } => match &segment.kind {
2670                        UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2671                            KclErrorDetails::new(
2672                                format!("Property '{property}' not found in point segment"),
2673                                vec![self.clone().into()],
2674                            ),
2675                            None,
2676                        )),
2677                        UnsolvedSegmentKind::Line {
2678                            end,
2679                            ctor,
2680                            end_object_id,
2681                            ..
2682                        } => Ok(KclValue::Segment {
2683                            value: Box::new(AbstractSegment {
2684                                repr: SegmentRepr::Unsolved {
2685                                    segment: Box::new(UnsolvedSegment {
2686                                        id: segment.id,
2687                                        object_id: *end_object_id,
2688                                        kind: UnsolvedSegmentKind::Point {
2689                                            position: end.clone(),
2690                                            ctor: Box::new(PointCtor {
2691                                                position: ctor.end.clone(),
2692                                            }),
2693                                        },
2694                                        tag: segment.tag.clone(),
2695                                        node_path: segment.node_path.clone(),
2696                                        meta: segment.meta.clone(),
2697                                    }),
2698                                },
2699                                meta: segment.meta.clone(),
2700                            }),
2701                        }
2702                        .continue_()),
2703                        UnsolvedSegmentKind::Arc {
2704                            end,
2705                            ctor,
2706                            end_object_id,
2707                            ..
2708                        } => Ok(KclValue::Segment {
2709                            value: Box::new(AbstractSegment {
2710                                repr: SegmentRepr::Unsolved {
2711                                    segment: Box::new(UnsolvedSegment {
2712                                        id: segment.id,
2713                                        object_id: *end_object_id,
2714                                        kind: UnsolvedSegmentKind::Point {
2715                                            position: end.clone(),
2716                                            ctor: Box::new(PointCtor {
2717                                                position: ctor.end.clone(),
2718                                            }),
2719                                        },
2720                                        tag: segment.tag.clone(),
2721                                        node_path: segment.node_path.clone(),
2722                                        meta: segment.meta.clone(),
2723                                    }),
2724                                },
2725                                meta: segment.meta.clone(),
2726                            }),
2727                        }
2728                        .continue_()),
2729                        UnsolvedSegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2730                            KclErrorDetails::new(
2731                                format!("Property '{property}' not found in segment"),
2732                                vec![self.into()],
2733                            ),
2734                            None,
2735                        )),
2736                    },
2737                    SegmentRepr::Solved { segment } => match &segment.kind {
2738                        SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2739                            KclErrorDetails::new(
2740                                format!("Property '{property}' not found in point segment"),
2741                                vec![self.clone().into()],
2742                            ),
2743                            None,
2744                        )),
2745                        SegmentKind::Line {
2746                            end,
2747                            ctor,
2748                            end_object_id,
2749                            end_freedom,
2750                            ..
2751                        } => Ok(KclValue::Segment {
2752                            value: Box::new(AbstractSegment {
2753                                repr: SegmentRepr::Solved {
2754                                    segment: Box::new(Segment {
2755                                        id: segment.id,
2756                                        object_id: *end_object_id,
2757                                        kind: SegmentKind::Point {
2758                                            position: end.clone(),
2759                                            ctor: Box::new(PointCtor {
2760                                                position: ctor.end.clone(),
2761                                            }),
2762                                            freedom: *end_freedom,
2763                                        },
2764                                        surface: segment.surface.clone(),
2765                                        sketch_id: segment.sketch_id,
2766                                        sketch: segment.sketch.clone(),
2767                                        tag: segment.tag.clone(),
2768                                        node_path: segment.node_path.clone(),
2769                                        meta: segment.meta.clone(),
2770                                    }),
2771                                },
2772                                meta: segment.meta.clone(),
2773                            }),
2774                        }
2775                        .continue_()),
2776                        SegmentKind::Arc {
2777                            end,
2778                            ctor,
2779                            end_object_id,
2780                            end_freedom,
2781                            ..
2782                        } => Ok(KclValue::Segment {
2783                            value: Box::new(AbstractSegment {
2784                                repr: SegmentRepr::Solved {
2785                                    segment: Box::new(Segment {
2786                                        id: segment.id,
2787                                        object_id: *end_object_id,
2788                                        kind: SegmentKind::Point {
2789                                            position: end.clone(),
2790                                            ctor: Box::new(PointCtor {
2791                                                position: ctor.end.clone(),
2792                                            }),
2793                                            freedom: *end_freedom,
2794                                        },
2795                                        surface: segment.surface.clone(),
2796                                        sketch_id: segment.sketch_id,
2797                                        sketch: segment.sketch.clone(),
2798                                        tag: segment.tag.clone(),
2799                                        node_path: segment.node_path.clone(),
2800                                        meta: segment.meta.clone(),
2801                                    }),
2802                                },
2803                                meta: segment.meta.clone(),
2804                            }),
2805                        }
2806                        .continue_()),
2807                        SegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2808                            KclErrorDetails::new(
2809                                format!("Property '{property}' not found in segment"),
2810                                vec![self.into()],
2811                            ),
2812                            None,
2813                        )),
2814                    },
2815                },
2816                "center" => match &segment.repr {
2817                    SegmentRepr::Unsolved { segment } => match &segment.kind {
2818                        UnsolvedSegmentKind::Arc {
2819                            center,
2820                            ctor,
2821                            center_object_id,
2822                            ..
2823                        } => Ok(KclValue::Segment {
2824                            value: Box::new(AbstractSegment {
2825                                repr: SegmentRepr::Unsolved {
2826                                    segment: Box::new(UnsolvedSegment {
2827                                        id: segment.id,
2828                                        object_id: *center_object_id,
2829                                        kind: UnsolvedSegmentKind::Point {
2830                                            position: center.clone(),
2831                                            ctor: Box::new(PointCtor {
2832                                                position: ctor.center.clone(),
2833                                            }),
2834                                        },
2835                                        tag: segment.tag.clone(),
2836                                        node_path: segment.node_path.clone(),
2837                                        meta: segment.meta.clone(),
2838                                    }),
2839                                },
2840                                meta: segment.meta.clone(),
2841                            }),
2842                        }
2843                        .continue_()),
2844                        UnsolvedSegmentKind::Circle {
2845                            center,
2846                            ctor,
2847                            center_object_id,
2848                            ..
2849                        } => Ok(KclValue::Segment {
2850                            value: Box::new(AbstractSegment {
2851                                repr: SegmentRepr::Unsolved {
2852                                    segment: Box::new(UnsolvedSegment {
2853                                        id: segment.id,
2854                                        object_id: *center_object_id,
2855                                        kind: UnsolvedSegmentKind::Point {
2856                                            position: center.clone(),
2857                                            ctor: Box::new(PointCtor {
2858                                                position: ctor.center.clone(),
2859                                            }),
2860                                        },
2861                                        tag: segment.tag.clone(),
2862                                        node_path: segment.node_path.clone(),
2863                                        meta: segment.meta.clone(),
2864                                    }),
2865                                },
2866                                meta: segment.meta.clone(),
2867                            }),
2868                        }
2869                        .continue_()),
2870                        _ => Err(KclError::new_undefined_value(
2871                            KclErrorDetails::new(
2872                                format!("Property '{property}' not found in segment"),
2873                                vec![self.clone().into()],
2874                            ),
2875                            None,
2876                        )),
2877                    },
2878                    SegmentRepr::Solved { segment } => match &segment.kind {
2879                        SegmentKind::Arc {
2880                            center,
2881                            ctor,
2882                            center_object_id,
2883                            center_freedom,
2884                            ..
2885                        } => Ok(KclValue::Segment {
2886                            value: Box::new(AbstractSegment {
2887                                repr: SegmentRepr::Solved {
2888                                    segment: Box::new(Segment {
2889                                        id: segment.id,
2890                                        object_id: *center_object_id,
2891                                        kind: SegmentKind::Point {
2892                                            position: center.clone(),
2893                                            ctor: Box::new(PointCtor {
2894                                                position: ctor.center.clone(),
2895                                            }),
2896                                            freedom: *center_freedom,
2897                                        },
2898                                        surface: segment.surface.clone(),
2899                                        sketch_id: segment.sketch_id,
2900                                        sketch: segment.sketch.clone(),
2901                                        tag: segment.tag.clone(),
2902                                        node_path: segment.node_path.clone(),
2903                                        meta: segment.meta.clone(),
2904                                    }),
2905                                },
2906                                meta: segment.meta.clone(),
2907                            }),
2908                        }
2909                        .continue_()),
2910                        SegmentKind::Circle {
2911                            center,
2912                            ctor,
2913                            center_object_id,
2914                            center_freedom,
2915                            ..
2916                        } => Ok(KclValue::Segment {
2917                            value: Box::new(AbstractSegment {
2918                                repr: SegmentRepr::Solved {
2919                                    segment: Box::new(Segment {
2920                                        id: segment.id,
2921                                        object_id: *center_object_id,
2922                                        kind: SegmentKind::Point {
2923                                            position: center.clone(),
2924                                            ctor: Box::new(PointCtor {
2925                                                position: ctor.center.clone(),
2926                                            }),
2927                                            freedom: *center_freedom,
2928                                        },
2929                                        surface: segment.surface.clone(),
2930                                        sketch_id: segment.sketch_id,
2931                                        sketch: segment.sketch.clone(),
2932                                        tag: segment.tag.clone(),
2933                                        node_path: segment.node_path.clone(),
2934                                        meta: segment.meta.clone(),
2935                                    }),
2936                                },
2937                                meta: segment.meta.clone(),
2938                            }),
2939                        }
2940                        .continue_()),
2941                        _ => Err(KclError::new_undefined_value(
2942                            KclErrorDetails::new(
2943                                format!("Property '{property}' not found in segment"),
2944                                vec![self.clone().into()],
2945                            ),
2946                            None,
2947                        )),
2948                    },
2949                },
2950                other => Err(KclError::new_undefined_value(
2951                    KclErrorDetails::new(
2952                        format!("Property '{other}' not found in segment"),
2953                        vec![self.clone().into()],
2954                    ),
2955                    None,
2956                )),
2957            },
2958            (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
2959                "zAxis" => {
2960                    let (p, u) = plane.info.z_axis.as_3_dims();
2961                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2962                }
2963                "yAxis" => {
2964                    let (p, u) = plane.info.y_axis.as_3_dims();
2965                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2966                }
2967                "xAxis" => {
2968                    let (p, u) = plane.info.x_axis.as_3_dims();
2969                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2970                }
2971                "origin" => {
2972                    let (p, u) = plane.info.origin.as_3_dims();
2973                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2974                }
2975                other => Err(KclError::new_undefined_value(
2976                    KclErrorDetails::new(
2977                        format!("Property '{other}' not found in plane"),
2978                        vec![self.clone().into()],
2979                    ),
2980                    None,
2981                )),
2982            },
2983            (KclValue::Object { value: map, .. }, Property::String(property), false) => {
2984                if let Some(value) = map.get(&property) {
2985                    Ok(value.to_owned().continue_())
2986                } else {
2987                    Err(KclError::new_undefined_value(
2988                        KclErrorDetails::new(
2989                            format!("Property '{property}' not found in object"),
2990                            vec![self.clone().into()],
2991                        ),
2992                        None,
2993                    ))
2994                }
2995            }
2996            (KclValue::Object { .. }, Property::String(property), true) => {
2997                Err(KclError::new_semantic(KclErrorDetails::new(
2998                    format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
2999                    vec![self.clone().into()],
3000                )))
3001            }
3002            (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
3003                if i == 0
3004                    && let Some(value) = map.get("x")
3005                {
3006                    return Ok(value.to_owned().continue_());
3007                }
3008                if i == 1
3009                    && let Some(value) = map.get("y")
3010                {
3011                    return Ok(value.to_owned().continue_());
3012                }
3013                if i == 2
3014                    && let Some(value) = map.get("z")
3015                {
3016                    return Ok(value.to_owned().continue_());
3017                }
3018                let t = p.type_name();
3019                let article = article_for(t);
3020                Err(KclError::new_semantic(KclErrorDetails::new(
3021                    format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
3022                    vec![self.clone().into()],
3023                )))
3024            }
3025            (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
3026                let value_of_arr = arr.get(index);
3027                if let Some(value) = value_of_arr {
3028                    Ok(value.to_owned().continue_())
3029                } else {
3030                    Err(KclError::new_undefined_value(
3031                        KclErrorDetails::new(
3032                            format!("The array doesn't have any item at index {index}"),
3033                            vec![self.clone().into()],
3034                        ),
3035                        None,
3036                    ))
3037                }
3038            }
3039            // Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
3040            // This is kind of a silly property, but it's possible it occurs in generic code or something.
3041            (obj, Property::UInt(0), _) => Ok(obj.continue_()),
3042            (KclValue::HomArray { .. }, p, _) => {
3043                let t = p.type_name();
3044                let article = article_for(t);
3045                Err(KclError::new_semantic(KclErrorDetails::new(
3046                    format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
3047                    vec![self.clone().into()],
3048                )))
3049            }
3050            (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
3051                let Some(sketch) = value.sketch() else {
3052                    return Err(KclError::new_semantic(KclErrorDetails::new(
3053                        "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
3054                        vec![self.clone().into()],
3055                    )));
3056                };
3057                Ok(KclValue::Sketch {
3058                    value: Box::new(sketch.clone()),
3059                }
3060                .continue_())
3061            }
3062            (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
3063                // This is a common mistake.
3064                Err(KclError::new_semantic(KclErrorDetails::new(
3065                    format!(
3066                        "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
3067                        geometry.human_friendly_type()
3068                    ),
3069                    vec![self.clone().into()],
3070                )))
3071            }
3072            (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
3073                meta: vec![Metadata {
3074                    source_range: SourceRange::from(self.clone()),
3075                }],
3076                value: sk
3077                    .tags
3078                    .iter()
3079                    .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
3080                    .collect(),
3081                constrainable: false,
3082            }
3083            .continue_()),
3084            (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
3085                Err(KclError::new_semantic(KclErrorDetails::new(
3086                    format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
3087                    vec![self.clone().into()],
3088                )))
3089            }
3090            (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
3091                format!(
3092                    "Only objects can have members accessed with dot notation, but you're trying to access {}",
3093                    being_indexed.human_friendly_type()
3094                ),
3095                vec![self.clone().into()],
3096            ))),
3097            (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
3098                format!(
3099                    "Only arrays can be indexed, but you're trying to index {}",
3100                    being_indexed.human_friendly_type()
3101                ),
3102                vec![self.clone().into()],
3103            ))),
3104        }
3105    }
3106}
3107
3108impl Node<BinaryExpression> {
3109    pub(super) async fn get_result(
3110        &self,
3111        exec_state: &mut ExecState,
3112        ctx: &ExecutorContext,
3113    ) -> Result<KclValueControlFlow, KclError> {
3114        enum State {
3115            EvaluateLeft(Node<BinaryExpression>),
3116            FromLeft {
3117                node: Node<BinaryExpression>,
3118            },
3119            EvaluateRight {
3120                node: Node<BinaryExpression>,
3121                left: KclValue,
3122            },
3123            FromRight {
3124                node: Node<BinaryExpression>,
3125                left: KclValue,
3126            },
3127        }
3128
3129        let mut stack = vec![State::EvaluateLeft(self.clone())];
3130        let mut last_result: Option<KclValue> = None;
3131
3132        while let Some(state) = stack.pop() {
3133            match state {
3134                State::EvaluateLeft(node) => {
3135                    let left_part = node.left.clone();
3136                    match left_part {
3137                        BinaryPart::BinaryExpression(child) => {
3138                            stack.push(State::FromLeft { node });
3139                            stack.push(State::EvaluateLeft(*child));
3140                        }
3141                        part => {
3142                            let left_value = part.get_result(exec_state, ctx).await?;
3143                            let left_value = control_continue!(left_value);
3144                            stack.push(State::EvaluateRight { node, left: left_value });
3145                        }
3146                    }
3147                }
3148                State::FromLeft { node } => {
3149                    let Some(left_value) = last_result.take() else {
3150                        return Err(Self::missing_result_error(&node));
3151                    };
3152                    stack.push(State::EvaluateRight { node, left: left_value });
3153                }
3154                State::EvaluateRight { node, left } => {
3155                    let right_part = node.right.clone();
3156                    match right_part {
3157                        BinaryPart::BinaryExpression(child) => {
3158                            stack.push(State::FromRight { node, left });
3159                            stack.push(State::EvaluateLeft(*child));
3160                        }
3161                        part => {
3162                            let right_value = part.get_result(exec_state, ctx).await?;
3163                            let right_value = control_continue!(right_value);
3164                            let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
3165                            last_result = Some(result);
3166                        }
3167                    }
3168                }
3169                State::FromRight { node, left } => {
3170                    let Some(right_value) = last_result.take() else {
3171                        return Err(Self::missing_result_error(&node));
3172                    };
3173                    let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
3174                    last_result = Some(result);
3175                }
3176            }
3177        }
3178
3179        last_result
3180            .map(KclValue::continue_)
3181            .ok_or_else(|| Self::missing_result_error(self))
3182    }
3183
3184    async fn apply_operator(
3185        &self,
3186        exec_state: &mut ExecState,
3187        ctx: &ExecutorContext,
3188        left_value: KclValue,
3189        right_value: KclValue,
3190    ) -> Result<KclValue, KclError> {
3191        let mut meta = left_value.metadata();
3192        meta.extend(right_value.metadata());
3193
3194        // First check if we are doing string concatenation.
3195        if self.operator == BinaryOperator::Add
3196            && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
3197                (&left_value, &right_value)
3198        {
3199            return Ok(KclValue::String {
3200                value: format!("{left}{right}"),
3201                meta,
3202            });
3203        }
3204
3205        // Then check if we have solids.
3206        if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
3207            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
3208                let args = Args::new_no_args(
3209                    self.into(),
3210                    self.node_path.clone(),
3211                    ctx.clone(),
3212                    Some("union".to_owned()),
3213                );
3214                let result = crate::std::csg::inner_union(
3215                    vec![*left.clone(), *right.clone()],
3216                    Default::default(),
3217                    crate::std::csg::CsgAlgorithm::Latest,
3218                    exec_state,
3219                    args,
3220                )
3221                .await?;
3222                return Ok(result.into());
3223            }
3224        } else if self.operator == BinaryOperator::Sub {
3225            // Check if we have solids.
3226            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
3227                let args = Args::new_no_args(
3228                    self.into(),
3229                    self.node_path.clone(),
3230                    ctx.clone(),
3231                    Some("subtract".to_owned()),
3232                );
3233                let result = crate::std::csg::inner_subtract(
3234                    vec![*left.clone()],
3235                    vec![*right.clone()],
3236                    Default::default(),
3237                    crate::std::csg::CsgAlgorithm::Latest,
3238                    exec_state,
3239                    args,
3240                )
3241                .await?;
3242                return Ok(result.into());
3243            }
3244        } else if self.operator == BinaryOperator::And
3245            && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
3246        {
3247            // Check if we have solids.
3248            let args = Args::new_no_args(
3249                self.into(),
3250                self.node_path.clone(),
3251                ctx.clone(),
3252                Some("intersect".to_owned()),
3253            );
3254            let result = crate::std::csg::inner_intersect(
3255                vec![*left.clone(), *right.clone()],
3256                Default::default(),
3257                crate::std::csg::CsgAlgorithm::Latest,
3258                exec_state,
3259                args,
3260            )
3261            .await?;
3262            return Ok(result.into());
3263        }
3264
3265        // Check if we are doing logical operations on booleans.
3266        if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
3267            let KclValue::Bool { value: left_value, .. } = left_value else {
3268                return Err(KclError::new_semantic(KclErrorDetails::new(
3269                    format!(
3270                        "Cannot apply logical operator to non-boolean value: {}",
3271                        left_value.human_friendly_type()
3272                    ),
3273                    vec![self.left.clone().into()],
3274                )));
3275            };
3276            let KclValue::Bool { value: right_value, .. } = right_value else {
3277                return Err(KclError::new_semantic(KclErrorDetails::new(
3278                    format!(
3279                        "Cannot apply logical operator to non-boolean value: {}",
3280                        right_value.human_friendly_type()
3281                    ),
3282                    vec![self.right.clone().into()],
3283                )));
3284            };
3285            let raw_value = match self.operator {
3286                BinaryOperator::Or => left_value || right_value,
3287                BinaryOperator::And => left_value && right_value,
3288                _ => unreachable!(),
3289            };
3290            return Ok(KclValue::Bool { value: raw_value, meta });
3291        }
3292
3293        // Check if we're doing equivalence in sketch mode.
3294        if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
3295            match (&left_value, &right_value) {
3296                // Same sketch variables.
3297                (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
3298                    if left_value.id == right_value.id =>
3299                {
3300                    return Ok(KclValue::none());
3301                }
3302                // Different sketch variables.
3303                (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
3304                    let constraint = Constraint::ScalarEqual(
3305                        var0.id.to_constraint_id(self.as_source_range())?,
3306                        var1.id.to_constraint_id(self.as_source_range())?,
3307                    );
3308                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3309                        let message = "Being inside a sketch block should have already been checked above".to_owned();
3310                        debug_assert!(false, "{}", &message);
3311                        return Err(internal_err(message, self));
3312                    };
3313                    sketch_block_state.solver_constraints.push(constraint);
3314                    return Ok(KclValue::none());
3315                }
3316                // One sketch variable, one number.
3317                (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
3318                | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
3319                    let number_value = normalize_to_solver_distance_unit(
3320                        input_number,
3321                        input_number.into(),
3322                        exec_state,
3323                        "fixed constraint value",
3324                    )?;
3325                    let Some(n) = number_value.as_ty_f64() else {
3326                        let message = format!(
3327                            "Expected number after coercion, but found {}",
3328                            number_value.human_friendly_type()
3329                        );
3330                        debug_assert!(false, "{}", &message);
3331                        return Err(internal_err(message, self));
3332                    };
3333                    let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
3334                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3335                        let message = "Being inside a sketch block should have already been checked above".to_owned();
3336                        debug_assert!(false, "{}", &message);
3337                        return Err(internal_err(message, self));
3338                    };
3339                    sketch_block_state.solver_constraints.push(constraint);
3340                    exec_state.warn_experimental("scalar fixed constraint", self.as_source_range());
3341                    return Ok(KclValue::none());
3342                }
3343                // One sketch constraint, one number.
3344                (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
3345                | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
3346                    let number_value = match constraint.kind {
3347                        // These constraint kinds expect the RHS to be an angle.
3348                        SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
3349                            input_number,
3350                            input_number.into(),
3351                            exec_state,
3352                            "fixed constraint value",
3353                        )?,
3354                        // These constraint kinds expect the RHS to be a distance.
3355                        SketchConstraintKind::Distance { .. }
3356                        | SketchConstraintKind::PointLineDistance { .. }
3357                        | SketchConstraintKind::LineLineDistance { .. }
3358                        | SketchConstraintKind::PointCircularDistance { .. }
3359                        | SketchConstraintKind::LineCircularDistance { .. }
3360                        | SketchConstraintKind::CircularCircularDistance { .. }
3361                        | SketchConstraintKind::Radius { .. }
3362                        | SketchConstraintKind::Diameter { .. }
3363                        | SketchConstraintKind::HorizontalDistance { .. }
3364                        | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
3365                            input_number,
3366                            input_number.into(),
3367                            exec_state,
3368                            "fixed constraint value",
3369                        )?,
3370                    };
3371                    let Some(n) = number_value.as_ty_f64() else {
3372                        let message = format!(
3373                            "Expected number after coercion, but found {}",
3374                            number_value.human_friendly_type()
3375                        );
3376                        debug_assert!(false, "{}", &message);
3377                        return Err(internal_err(message, self));
3378                    };
3379                    // Recast the number side of == to get the source expression text.
3380                    let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
3381                        &self.right
3382                    } else {
3383                        &self.left
3384                    };
3385                    let source = {
3386                        use crate::unparser::ExprContext;
3387                        let mut buf = String::new();
3388                        number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
3389                        crate::frontend::sketch::ConstraintSource {
3390                            expr: buf,
3391                            is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
3392                        }
3393                    };
3394
3395                    match &constraint.kind {
3396                        SketchConstraintKind::Angle { line0, line1 } => {
3397                            let range = self.as_source_range();
3398                            // Line 0 is points A and B.
3399                            // Line 1 is points C and D.
3400                            let ax = line0.vars[0].x.to_constraint_id(range)?;
3401                            let ay = line0.vars[0].y.to_constraint_id(range)?;
3402                            let bx = line0.vars[1].x.to_constraint_id(range)?;
3403                            let by = line0.vars[1].y.to_constraint_id(range)?;
3404                            let cx = line1.vars[0].x.to_constraint_id(range)?;
3405                            let cy = line1.vars[0].y.to_constraint_id(range)?;
3406                            let dx = line1.vars[1].x.to_constraint_id(range)?;
3407                            let dy = line1.vars[1].y.to_constraint_id(range)?;
3408                            let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(
3409                                ezpz::datatypes::inputs::DatumPoint::new_xy(ax, ay),
3410                                ezpz::datatypes::inputs::DatumPoint::new_xy(bx, by),
3411                            );
3412                            let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(
3413                                ezpz::datatypes::inputs::DatumPoint::new_xy(cx, cy),
3414                                ezpz::datatypes::inputs::DatumPoint::new_xy(dx, dy),
3415                            );
3416                            let desired_angle = match n.ty {
3417                                NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Degrees))
3418                                | NumericType::Default {
3419                                    len: _,
3420                                    angle: kcmc::units::UnitAngle::Degrees,
3421                                } => ezpz::datatypes::Angle::from_degrees(n.n),
3422                                NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Radians))
3423                                | NumericType::Default {
3424                                    len: _,
3425                                    angle: kcmc::units::UnitAngle::Radians,
3426                                } => ezpz::datatypes::Angle::from_radians(n.n),
3427                                NumericType::Known(crate::exec::UnitType::Count)
3428                                | NumericType::Known(crate::exec::UnitType::GenericLength)
3429                                | NumericType::Known(crate::exec::UnitType::GenericAngle)
3430                                | NumericType::Known(crate::exec::UnitType::Length(_))
3431                                | NumericType::Unknown
3432                                | NumericType::Any => {
3433                                    let message = format!("Expected angle but found {:?}", n);
3434                                    debug_assert!(false, "{}", &message);
3435                                    return Err(internal_err(message, self));
3436                                }
3437                            };
3438                            let solver_constraint = Constraint::LinesAtAngle(
3439                                solver_line0,
3440                                solver_line1,
3441                                ezpz::datatypes::AngleKind::Other(desired_angle),
3442                            );
3443                            let constraint_id = exec_state.next_object_id();
3444                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3445                                let message =
3446                                    "Being inside a sketch block should have already been checked above".to_owned();
3447                                debug_assert!(false, "{}", &message);
3448                                return Err(internal_err(message, self));
3449                            };
3450                            sketch_block_state.solver_constraints.push(solver_constraint);
3451                            use crate::execution::Artifact;
3452                            use crate::execution::CodeRef;
3453                            use crate::execution::SketchBlockConstraint;
3454                            use crate::execution::SketchBlockConstraintType;
3455                            use crate::front::Angle;
3456                            use crate::front::SourceRef;
3457
3458                            let Some(sketch_id) = sketch_block_state.sketch_id else {
3459                                let message = "Sketch id missing for constraint artifact".to_owned();
3460                                debug_assert!(false, "{}", &message);
3461                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3462                            };
3463                            let sketch_constraint = crate::front::Constraint::Angle(Angle {
3464                                lines: vec![line0.object_id, line1.object_id],
3465                                angle: n.try_into().map_err(|_| {
3466                                    internal_err("Failed to convert angle units numeric suffix:", range)
3467                                })?,
3468                                source,
3469                            });
3470                            sketch_block_state.sketch_constraints.push(constraint_id);
3471                            let artifact_id = exec_state.next_artifact_id();
3472                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3473                                id: artifact_id,
3474                                sketch_id,
3475                                constraint_id,
3476                                constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3477                                code_ref: CodeRef::placeholder(range),
3478                            }));
3479                            exec_state.add_scene_object(
3480                                Object {
3481                                    id: constraint_id,
3482                                    kind: ObjectKind::Constraint {
3483                                        constraint: sketch_constraint,
3484                                    },
3485                                    label: Default::default(),
3486                                    comments: Default::default(),
3487                                    artifact_id,
3488                                    source: SourceRef::new(range, self.node_path.clone()),
3489                                },
3490                                range,
3491                            );
3492                        }
3493                        SketchConstraintKind::Distance { points, label_position } => {
3494                            let range = self.as_source_range();
3495                            let p0 = &points[0];
3496                            let p1 = &points[1];
3497                            let sketch_var_ty = solver_numeric_type(exec_state);
3498                            let constraint_id = exec_state.next_object_id();
3499                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3500                                let message =
3501                                    "Being inside a sketch block should have already been checked above".to_owned();
3502                                debug_assert!(false, "{}", &message);
3503                                return Err(internal_err(message, self));
3504                            };
3505                            match (p0, p1) {
3506                                (
3507                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3508                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3509                                ) => {
3510                                    let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3511                                        p0.vars.x.to_constraint_id(range)?,
3512                                        p0.vars.y.to_constraint_id(range)?,
3513                                    );
3514                                    let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3515                                        p1.vars.x.to_constraint_id(range)?,
3516                                        p1.vars.y.to_constraint_id(range)?,
3517                                    );
3518                                    sketch_block_state
3519                                        .solver_constraints
3520                                        .push(Constraint::Distance(solver_pt0, solver_pt1, n.n));
3521                                }
3522                                (
3523                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3524                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3525                                )
3526                                | (
3527                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3528                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3529                                ) => {
3530                                    let origin_x_id = sketch_block_state.next_sketch_var_id();
3531                                    sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3532                                        value: Box::new(crate::execution::SketchVar {
3533                                            id: origin_x_id,
3534                                            initial_value: 0.0,
3535                                            ty: sketch_var_ty,
3536                                            meta: vec![],
3537                                        }),
3538                                    });
3539                                    let origin_y_id = sketch_block_state.next_sketch_var_id();
3540                                    sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3541                                        value: Box::new(crate::execution::SketchVar {
3542                                            id: origin_y_id,
3543                                            initial_value: 0.0,
3544                                            ty: sketch_var_ty,
3545                                            meta: vec![],
3546                                        }),
3547                                    });
3548                                    let origin_x = origin_x_id.to_constraint_id(range)?;
3549                                    let origin_y = origin_y_id.to_constraint_id(range)?;
3550                                    sketch_block_state
3551                                        .solver_constraints
3552                                        .push(Constraint::Fixed(origin_x, 0.0));
3553                                    sketch_block_state
3554                                        .solver_constraints
3555                                        .push(Constraint::Fixed(origin_y, 0.0));
3556                                    let solver_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3557                                        point.vars.x.to_constraint_id(range)?,
3558                                        point.vars.y.to_constraint_id(range)?,
3559                                    );
3560                                    let origin_point = ezpz::datatypes::inputs::DatumPoint::new_xy(origin_x, origin_y);
3561                                    sketch_block_state.solver_constraints.push(Constraint::Distance(
3562                                        solver_point,
3563                                        origin_point,
3564                                        n.n,
3565                                    ));
3566                                }
3567                                (
3568                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3569                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3570                                ) => {
3571                                    return Err(internal_err(
3572                                        "distance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3573                                        range,
3574                                    ));
3575                                }
3576                            }
3577                            use crate::execution::Artifact;
3578                            use crate::execution::CodeRef;
3579                            use crate::execution::SketchBlockConstraint;
3580                            use crate::execution::SketchBlockConstraintType;
3581                            use crate::front::Distance;
3582                            use crate::front::SourceRef;
3583                            use crate::frontend::sketch::ConstraintSegment;
3584
3585                            let Some(sketch_id) = sketch_block_state.sketch_id else {
3586                                let message = "Sketch id missing for constraint artifact".to_owned();
3587                                debug_assert!(false, "{}", &message);
3588                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3589                            };
3590                            let sketch_constraint = crate::front::Constraint::Distance(Distance {
3591                                points: vec![
3592                                    match p0 {
3593                                        crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3594                                            ConstraintSegment::from(point.object_id)
3595                                        }
3596                                        crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3597                                            ConstraintSegment::ORIGIN
3598                                        }
3599                                    },
3600                                    match p1 {
3601                                        crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3602                                            ConstraintSegment::from(point.object_id)
3603                                        }
3604                                        crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3605                                            ConstraintSegment::ORIGIN
3606                                        }
3607                                    },
3608                                ],
3609                                distance: n.try_into().map_err(|_| {
3610                                    internal_err("Failed to convert distance units numeric suffix:", range)
3611                                })?,
3612                                label_position: label_position.clone(),
3613                                source,
3614                            });
3615                            sketch_block_state.sketch_constraints.push(constraint_id);
3616                            let artifact_id = exec_state.next_artifact_id();
3617                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3618                                id: artifact_id,
3619                                sketch_id,
3620                                constraint_id,
3621                                constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3622                                code_ref: CodeRef::placeholder(range),
3623                            }));
3624                            exec_state.add_scene_object(
3625                                Object {
3626                                    id: constraint_id,
3627                                    kind: ObjectKind::Constraint {
3628                                        constraint: sketch_constraint,
3629                                    },
3630                                    label: Default::default(),
3631                                    comments: Default::default(),
3632                                    artifact_id,
3633                                    source: SourceRef::new(range, self.node_path.clone()),
3634                                },
3635                                range,
3636                            );
3637                        }
3638                        SketchConstraintKind::PointLineDistance {
3639                            point,
3640                            line,
3641                            input_object_ids,
3642                            label_position,
3643                        } => {
3644                            let range = self.as_source_range();
3645                            let sketch_var_ty = solver_numeric_type(exec_state);
3646                            let sketch_vars = exec_state
3647                                .mod_local
3648                                .sketch_block
3649                                .as_ref()
3650                                .ok_or_else(|| {
3651                                    internal_err(
3652                                        "Being inside a sketch block should have already been checked above",
3653                                        self,
3654                                    )
3655                                })?
3656                                .sketch_vars
3657                                .clone();
3658                            let support_initial =
3659                                projected_point_on_line_initial_position(&sketch_vars, point, line, exec_state, range)?;
3660                            let solver_line = datum_line_from_constrainable(line, range)?;
3661
3662                            let constraint_id = exec_state.next_object_id();
3663                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3664                                let message =
3665                                    "Being inside a sketch block should have already been checked above".to_owned();
3666                                debug_assert!(false, "{}", &message);
3667                                return Err(internal_err(message, self));
3668                            };
3669
3670                            // Lower point-line distance by adding a hidden
3671                            // support point on the line, then constrain the
3672                            // selected point-to-support segment to be
3673                            // perpendicular and equal to the requested
3674                            // distance.
3675                            let solver_point = datum_point_from_constrainable_or_origin(
3676                                sketch_block_state,
3677                                sketch_var_ty,
3678                                point,
3679                                range,
3680                            )?;
3681                            let support_x_id = sketch_block_state.next_sketch_var_id();
3682                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3683                                value: Box::new(crate::execution::SketchVar {
3684                                    id: support_x_id,
3685                                    initial_value: support_initial[0],
3686                                    ty: sketch_var_ty,
3687                                    meta: vec![],
3688                                }),
3689                            });
3690                            let support_y_id = sketch_block_state.next_sketch_var_id();
3691                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3692                                value: Box::new(crate::execution::SketchVar {
3693                                    id: support_y_id,
3694                                    initial_value: support_initial[1],
3695                                    ty: sketch_var_ty,
3696                                    meta: vec![],
3697                                }),
3698                            });
3699                            let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3700                                support_x_id.to_constraint_id(range)?,
3701                                support_y_id.to_constraint_id(range)?,
3702                            );
3703                            let support_line =
3704                                ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
3705
3706                            sketch_block_state
3707                                .solver_constraints
3708                                .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
3709                            sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
3710                                support_line,
3711                                solver_line,
3712                                ezpz::datatypes::AngleKind::Perpendicular,
3713                            ));
3714                            sketch_block_state.solver_constraints.push(Constraint::Distance(
3715                                solver_point,
3716                                support_point,
3717                                n.n,
3718                            ));
3719
3720                            use crate::execution::Artifact;
3721                            use crate::execution::CodeRef;
3722                            use crate::execution::SketchBlockConstraint;
3723                            use crate::execution::SketchBlockConstraintType;
3724                            use crate::front::Distance;
3725                            use crate::front::SourceRef;
3726                            use crate::frontend::sketch::ConstraintSegment;
3727
3728                            let Some(sketch_id) = sketch_block_state.sketch_id else {
3729                                let message = "Sketch id missing for constraint artifact".to_owned();
3730                                debug_assert!(false, "{}", &message);
3731                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3732                            };
3733                            let sketch_constraint = crate::front::Constraint::Distance(Distance {
3734                                points: input_object_ids
3735                                    .iter()
3736                                    .copied()
3737                                    .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
3738                                    .collect(),
3739                                distance: n.try_into().map_err(|_| {
3740                                    internal_err("Failed to convert distance units numeric suffix:", range)
3741                                })?,
3742                                label_position: label_position.clone(),
3743                                source,
3744                            });
3745                            sketch_block_state.sketch_constraints.push(constraint_id);
3746                            let artifact_id = exec_state.next_artifact_id();
3747                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3748                                id: artifact_id,
3749                                sketch_id,
3750                                constraint_id,
3751                                constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3752                                code_ref: CodeRef::placeholder(range),
3753                            }));
3754                            exec_state.add_scene_object(
3755                                Object {
3756                                    id: constraint_id,
3757                                    kind: ObjectKind::Constraint {
3758                                        constraint: sketch_constraint,
3759                                    },
3760                                    label: Default::default(),
3761                                    comments: Default::default(),
3762                                    artifact_id,
3763                                    source: SourceRef::new(range, self.node_path.clone()),
3764                                },
3765                                range,
3766                            );
3767                        }
3768                        SketchConstraintKind::LineLineDistance {
3769                            line0,
3770                            line1,
3771                            input_object_ids,
3772                            label_position,
3773                        } => {
3774                            let range = self.as_source_range();
3775                            let reference_point = crate::execution::ConstrainablePoint2d {
3776                                vars: line0.vars[0].clone(),
3777                                object_id: line0.object_id,
3778                            };
3779                            let sketch_var_ty = solver_numeric_type(exec_state);
3780                            let sketch_vars = exec_state
3781                                .mod_local
3782                                .sketch_block
3783                                .as_ref()
3784                                .ok_or_else(|| {
3785                                    internal_err(
3786                                        "Being inside a sketch block should have already been checked above",
3787                                        self,
3788                                    )
3789                                })?
3790                                .sketch_vars
3791                                .clone();
3792                            let support_initial = projected_point_on_line_initial_position(
3793                                &sketch_vars,
3794                                &crate::execution::ConstrainablePoint2dOrOrigin::Point(reference_point.clone()),
3795                                line1,
3796                                exec_state,
3797                                range,
3798                            )?;
3799                            let solver_point = datum_point_from_constrainable(&reference_point, range)?;
3800                            let solver_line0 = datum_line_from_constrainable(line0, range)?;
3801                            let solver_line1 = datum_line_from_constrainable(line1, range)?;
3802
3803                            let constraint_id = exec_state.next_object_id();
3804                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3805                                let message =
3806                                    "Being inside a sketch block should have already been checked above".to_owned();
3807                                debug_assert!(false, "{}", &message);
3808                                return Err(internal_err(message, self));
3809                            };
3810
3811                            // Lower line-line distance to the point-line
3812                            // construction above by choosing one endpoint on
3813                            // line0 as the reference point, forcing the lines
3814                            // parallel, and measuring perpendicularly to
3815                            // line1.
3816                            let support_x_id = sketch_block_state.next_sketch_var_id();
3817                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3818                                value: Box::new(crate::execution::SketchVar {
3819                                    id: support_x_id,
3820                                    initial_value: support_initial[0],
3821                                    ty: sketch_var_ty,
3822                                    meta: vec![],
3823                                }),
3824                            });
3825                            let support_y_id = sketch_block_state.next_sketch_var_id();
3826                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3827                                value: Box::new(crate::execution::SketchVar {
3828                                    id: support_y_id,
3829                                    initial_value: support_initial[1],
3830                                    ty: sketch_var_ty,
3831                                    meta: vec![],
3832                                }),
3833                            });
3834                            let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3835                                support_x_id.to_constraint_id(range)?,
3836                                support_y_id.to_constraint_id(range)?,
3837                            );
3838                            let support_line =
3839                                ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
3840
3841                            sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
3842                                solver_line0,
3843                                solver_line1,
3844                                ezpz::datatypes::AngleKind::Parallel,
3845                            ));
3846                            sketch_block_state
3847                                .solver_constraints
3848                                .push(Constraint::PointLineDistance(support_point, solver_line1, 0.0));
3849                            sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
3850                                support_line,
3851                                solver_line1,
3852                                ezpz::datatypes::AngleKind::Perpendicular,
3853                            ));
3854                            sketch_block_state.solver_constraints.push(Constraint::Distance(
3855                                solver_point,
3856                                support_point,
3857                                n.n,
3858                            ));
3859
3860                            use crate::execution::Artifact;
3861                            use crate::execution::CodeRef;
3862                            use crate::execution::SketchBlockConstraint;
3863                            use crate::execution::SketchBlockConstraintType;
3864                            use crate::front::Distance;
3865                            use crate::front::SourceRef;
3866                            use crate::frontend::sketch::ConstraintSegment;
3867
3868                            let Some(sketch_id) = sketch_block_state.sketch_id else {
3869                                let message = "Sketch id missing for constraint artifact".to_owned();
3870                                debug_assert!(false, "{}", &message);
3871                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3872                            };
3873                            let sketch_constraint = crate::front::Constraint::Distance(Distance {
3874                                points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
3875                                distance: n.try_into().map_err(|_| {
3876                                    internal_err("Failed to convert distance units numeric suffix:", range)
3877                                })?,
3878                                label_position: label_position.clone(),
3879                                source,
3880                            });
3881                            sketch_block_state.sketch_constraints.push(constraint_id);
3882                            let artifact_id = exec_state.next_artifact_id();
3883                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3884                                id: artifact_id,
3885                                sketch_id,
3886                                constraint_id,
3887                                constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3888                                code_ref: CodeRef::placeholder(range),
3889                            }));
3890                            exec_state.add_scene_object(
3891                                Object {
3892                                    id: constraint_id,
3893                                    kind: ObjectKind::Constraint {
3894                                        constraint: sketch_constraint,
3895                                    },
3896                                    label: Default::default(),
3897                                    comments: Default::default(),
3898                                    artifact_id,
3899                                    source: SourceRef::new(range, self.node_path.clone()),
3900                                },
3901                                range,
3902                            );
3903                        }
3904                        SketchConstraintKind::PointCircularDistance {
3905                            point,
3906                            center,
3907                            start,
3908                            end,
3909                            input_object_ids,
3910                            label_position,
3911                        } => {
3912                            let range = self.as_source_range();
3913                            let sketch_var_ty = solver_numeric_type(exec_state);
3914                            let sketch_vars = exec_state
3915                                .mod_local
3916                                .sketch_block
3917                                .as_ref()
3918                                .ok_or_else(|| {
3919                                    internal_err(
3920                                        "Being inside a sketch block should have already been checked above",
3921                                        self,
3922                                    )
3923                                })?
3924                                .sketch_vars
3925                                .clone();
3926                            let circular =
3927                                circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
3928
3929                            let constraint_id = exec_state.next_object_id();
3930                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3931                                let message =
3932                                    "Being inside a sketch block should have already been checked above".to_owned();
3933                                debug_assert!(false, "{}", &message);
3934                                return Err(internal_err(message, self));
3935                            };
3936
3937                            // Lower point-circular distance to exterior
3938                            // circle tangency: a hidden circle centered on the
3939                            // point has radius equal to the requested distance
3940                            // and is tangent to the target arc/circle.
3941                            let target_point = datum_point_from_constrainable_or_origin(
3942                                sketch_block_state,
3943                                sketch_var_ty,
3944                                point,
3945                                range,
3946                            )?;
3947                            push_circular_distance_constraints(
3948                                sketch_block_state,
3949                                sketch_var_ty,
3950                                target_point,
3951                                circular,
3952                                n.n,
3953                                range,
3954                            )?;
3955
3956                            use crate::execution::Artifact;
3957                            use crate::execution::CodeRef;
3958                            use crate::execution::SketchBlockConstraint;
3959                            use crate::execution::SketchBlockConstraintType;
3960                            use crate::front::Distance;
3961                            use crate::front::SourceRef;
3962                            use crate::frontend::sketch::ConstraintSegment;
3963
3964                            let Some(sketch_id) = sketch_block_state.sketch_id else {
3965                                let message = "Sketch id missing for constraint artifact".to_owned();
3966                                debug_assert!(false, "{}", &message);
3967                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3968                            };
3969                            let sketch_constraint = crate::front::Constraint::Distance(Distance {
3970                                points: input_object_ids
3971                                    .iter()
3972                                    .copied()
3973                                    .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
3974                                    .collect(),
3975                                distance: n.try_into().map_err(|_| {
3976                                    internal_err("Failed to convert distance units numeric suffix:", range)
3977                                })?,
3978                                label_position: label_position.clone(),
3979                                source,
3980                            });
3981                            sketch_block_state.sketch_constraints.push(constraint_id);
3982                            let artifact_id = exec_state.next_artifact_id();
3983                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3984                                id: artifact_id,
3985                                sketch_id,
3986                                constraint_id,
3987                                constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3988                                code_ref: CodeRef::placeholder(range),
3989                            }));
3990                            exec_state.add_scene_object(
3991                                Object {
3992                                    id: constraint_id,
3993                                    kind: ObjectKind::Constraint {
3994                                        constraint: sketch_constraint,
3995                                    },
3996                                    label: Default::default(),
3997                                    comments: Default::default(),
3998                                    artifact_id,
3999                                    source: SourceRef::new(range, self.node_path.clone()),
4000                                },
4001                                range,
4002                            );
4003                        }
4004                        SketchConstraintKind::LineCircularDistance {
4005                            line,
4006                            center,
4007                            start,
4008                            end,
4009                            input_object_ids,
4010                            label_position,
4011                        } => {
4012                            let range = self.as_source_range();
4013                            let sketch_var_ty = solver_numeric_type(exec_state);
4014                            let sketch_vars = exec_state
4015                                .mod_local
4016                                .sketch_block
4017                                .as_ref()
4018                                .ok_or_else(|| {
4019                                    internal_err(
4020                                        "Being inside a sketch block should have already been checked above",
4021                                        self,
4022                                    )
4023                                })?
4024                                .sketch_vars
4025                                .clone();
4026                            let support_initial = projected_point_on_line_initial_position(
4027                                &sketch_vars,
4028                                &crate::execution::ConstrainablePoint2dOrOrigin::Point(center.clone()),
4029                                line,
4030                                exec_state,
4031                                range,
4032                            )?;
4033                            let solver_line = datum_line_from_constrainable(line, range)?;
4034                            let circular =
4035                                circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
4036
4037                            let constraint_id = exec_state.next_object_id();
4038                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4039                                let message =
4040                                    "Being inside a sketch block should have already been checked above".to_owned();
4041                                debug_assert!(false, "{}", &message);
4042                                return Err(internal_err(message, self));
4043                            };
4044
4045                            // Lower line-circular distance by first projecting
4046                            // the circular center onto the target line with a
4047                            // hidden support point. The circular distance is
4048                            // then the point-circular construction from that
4049                            // support point.
4050                            let support_x_id = sketch_block_state.next_sketch_var_id();
4051                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4052                                value: Box::new(crate::execution::SketchVar {
4053                                    id: support_x_id,
4054                                    initial_value: support_initial[0],
4055                                    ty: sketch_var_ty,
4056                                    meta: vec![],
4057                                }),
4058                            });
4059                            let support_y_id = sketch_block_state.next_sketch_var_id();
4060                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4061                                value: Box::new(crate::execution::SketchVar {
4062                                    id: support_y_id,
4063                                    initial_value: support_initial[1],
4064                                    ty: sketch_var_ty,
4065                                    meta: vec![],
4066                                }),
4067                            });
4068                            let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4069                                support_x_id.to_constraint_id(range)?,
4070                                support_y_id.to_constraint_id(range)?,
4071                            );
4072                            let support_line =
4073                                ezpz::datatypes::inputs::DatumLineSegment::new(circular.center, support_point);
4074
4075                            sketch_block_state
4076                                .solver_constraints
4077                                .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
4078                            sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4079                                support_line,
4080                                solver_line,
4081                                ezpz::datatypes::AngleKind::Perpendicular,
4082                            ));
4083                            push_circular_distance_constraints(
4084                                sketch_block_state,
4085                                sketch_var_ty,
4086                                support_point,
4087                                circular,
4088                                n.n,
4089                                range,
4090                            )?;
4091
4092                            use crate::execution::Artifact;
4093                            use crate::execution::CodeRef;
4094                            use crate::execution::SketchBlockConstraint;
4095                            use crate::execution::SketchBlockConstraintType;
4096                            use crate::front::Distance;
4097                            use crate::front::SourceRef;
4098                            use crate::frontend::sketch::ConstraintSegment;
4099
4100                            let Some(sketch_id) = sketch_block_state.sketch_id else {
4101                                let message = "Sketch id missing for constraint artifact".to_owned();
4102                                debug_assert!(false, "{}", &message);
4103                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4104                            };
4105                            let sketch_constraint = crate::front::Constraint::Distance(Distance {
4106                                points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4107                                distance: n.try_into().map_err(|_| {
4108                                    internal_err("Failed to convert distance units numeric suffix:", range)
4109                                })?,
4110                                label_position: label_position.clone(),
4111                                source,
4112                            });
4113                            sketch_block_state.sketch_constraints.push(constraint_id);
4114                            let artifact_id = exec_state.next_artifact_id();
4115                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4116                                id: artifact_id,
4117                                sketch_id,
4118                                constraint_id,
4119                                constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4120                                code_ref: CodeRef::placeholder(range),
4121                            }));
4122                            exec_state.add_scene_object(
4123                                Object {
4124                                    id: constraint_id,
4125                                    kind: ObjectKind::Constraint {
4126                                        constraint: sketch_constraint,
4127                                    },
4128                                    label: Default::default(),
4129                                    comments: Default::default(),
4130                                    artifact_id,
4131                                    source: SourceRef::new(range, self.node_path.clone()),
4132                                },
4133                                range,
4134                            );
4135                        }
4136                        SketchConstraintKind::CircularCircularDistance {
4137                            center0,
4138                            start0,
4139                            end0,
4140                            center1,
4141                            start1,
4142                            end1,
4143                            input_object_ids,
4144                            label_position,
4145                        } => {
4146                            let range = self.as_source_range();
4147                            let sketch_var_ty = solver_numeric_type(exec_state);
4148                            let sketch_vars = exec_state
4149                                .mod_local
4150                                .sketch_block
4151                                .as_ref()
4152                                .ok_or_else(|| {
4153                                    internal_err(
4154                                        "Being inside a sketch block should have already been checked above",
4155                                        self,
4156                                    )
4157                                })?
4158                                .sketch_vars
4159                                .clone();
4160                            let circular0 = circular_distance_datums(
4161                                &sketch_vars,
4162                                center0,
4163                                start0,
4164                                end0.as_ref(),
4165                                exec_state,
4166                                range,
4167                            )?;
4168                            let circular1 = circular_distance_datums(
4169                                &sketch_vars,
4170                                center1,
4171                                start1,
4172                                end1.as_ref(),
4173                                exec_state,
4174                                range,
4175                            )?;
4176                            let support_initial = circular_circular_support_initial_position(
4177                                &sketch_vars,
4178                                center0,
4179                                center1,
4180                                circular0.radius_initial_value,
4181                                n.n,
4182                                exec_state,
4183                                range,
4184                            )?;
4185
4186                            let constraint_id = exec_state.next_object_id();
4187                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4188                                let message =
4189                                    "Being inside a sketch block should have already been checked above".to_owned();
4190                                debug_assert!(false, "{}", &message);
4191                                return Err(internal_err(message, self));
4192                            };
4193
4194                            // Lower circular-circular distance with a hidden
4195                            // spacer circle of radius d/2. Constraining its
4196                            // center onto the line between target centers and
4197                            // making it exterior-tangent to both targets gives
4198                            // center distance r0 + d + r1.
4199                            let circular_target0 =
4200                                push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular0, range)?;
4201                            let circular_target1 =
4202                                push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular1, range)?;
4203
4204                            let support_x_id = sketch_block_state.next_sketch_var_id();
4205                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4206                                value: Box::new(crate::execution::SketchVar {
4207                                    id: support_x_id,
4208                                    initial_value: support_initial[0],
4209                                    ty: sketch_var_ty,
4210                                    meta: vec![],
4211                                }),
4212                            });
4213                            let support_y_id = sketch_block_state.next_sketch_var_id();
4214                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4215                                value: Box::new(crate::execution::SketchVar {
4216                                    id: support_y_id,
4217                                    initial_value: support_initial[1],
4218                                    ty: sketch_var_ty,
4219                                    meta: vec![],
4220                                }),
4221                            });
4222                            let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4223                                support_x_id.to_constraint_id(range)?,
4224                                support_y_id.to_constraint_id(range)?,
4225                            );
4226
4227                            let support_radius_id = sketch_block_state.next_sketch_var_id();
4228                            let support_radius_value = n.n / 2.0;
4229                            sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4230                                value: Box::new(crate::execution::SketchVar {
4231                                    id: support_radius_id,
4232                                    initial_value: support_radius_value,
4233                                    ty: sketch_var_ty,
4234                                    meta: vec![],
4235                                }),
4236                            });
4237                            let support_radius =
4238                                ezpz::datatypes::inputs::DatumDistance::new(support_radius_id.to_constraint_id(range)?);
4239                            let support_circle = ezpz::datatypes::inputs::DatumCircle {
4240                                center: support_point,
4241                                radius: support_radius,
4242                            };
4243                            let center_line = ezpz::datatypes::inputs::DatumLineSegment::new(
4244                                circular_target0.center,
4245                                circular_target1.center,
4246                            );
4247
4248                            sketch_block_state
4249                                .solver_constraints
4250                                .push(Constraint::Fixed(support_radius.id, support_radius_value));
4251                            sketch_block_state
4252                                .solver_constraints
4253                                .push(Constraint::PointLineDistance(support_point, center_line, 0.0));
4254                            sketch_block_state
4255                                .solver_constraints
4256                                .push(Constraint::CircleTangentToCircle(
4257                                    circular_target0,
4258                                    support_circle,
4259                                    ezpz::CircleSide::Exterior,
4260                                ));
4261                            sketch_block_state
4262                                .solver_constraints
4263                                .push(Constraint::CircleTangentToCircle(
4264                                    support_circle,
4265                                    circular_target1,
4266                                    ezpz::CircleSide::Exterior,
4267                                ));
4268
4269                            use crate::execution::Artifact;
4270                            use crate::execution::CodeRef;
4271                            use crate::execution::SketchBlockConstraint;
4272                            use crate::execution::SketchBlockConstraintType;
4273                            use crate::front::Distance;
4274                            use crate::front::SourceRef;
4275                            use crate::frontend::sketch::ConstraintSegment;
4276
4277                            let Some(sketch_id) = sketch_block_state.sketch_id else {
4278                                let message = "Sketch id missing for constraint artifact".to_owned();
4279                                debug_assert!(false, "{}", &message);
4280                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4281                            };
4282                            let sketch_constraint = crate::front::Constraint::Distance(Distance {
4283                                points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4284                                distance: n.try_into().map_err(|_| {
4285                                    internal_err("Failed to convert distance units numeric suffix:", range)
4286                                })?,
4287                                label_position: label_position.clone(),
4288                                source,
4289                            });
4290                            sketch_block_state.sketch_constraints.push(constraint_id);
4291                            let artifact_id = exec_state.next_artifact_id();
4292                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4293                                id: artifact_id,
4294                                sketch_id,
4295                                constraint_id,
4296                                constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4297                                code_ref: CodeRef::placeholder(range),
4298                            }));
4299                            exec_state.add_scene_object(
4300                                Object {
4301                                    id: constraint_id,
4302                                    kind: ObjectKind::Constraint {
4303                                        constraint: sketch_constraint,
4304                                    },
4305                                    label: Default::default(),
4306                                    comments: Default::default(),
4307                                    artifact_id,
4308                                    source: SourceRef::new(range, self.node_path.clone()),
4309                                },
4310                                range,
4311                            );
4312                        }
4313                        SketchConstraintKind::Radius { .. } | SketchConstraintKind::Diameter { .. } => {
4314                            #[derive(Clone, Copy)]
4315                            enum CircularSegmentConstraintTarget {
4316                                Arc {
4317                                    object_id: ObjectId,
4318                                    end: [crate::execution::SketchVarId; 2],
4319                                },
4320                                Circle {
4321                                    object_id: ObjectId,
4322                                },
4323                            }
4324
4325                            fn sketch_var_initial_value(
4326                                sketch_vars: &[KclValue],
4327                                id: crate::execution::SketchVarId,
4328                                exec_state: &mut ExecState,
4329                                range: SourceRange,
4330                            ) -> Result<f64, KclError> {
4331                                sketch_vars
4332                                    .get(id.0)
4333                                    .and_then(KclValue::as_sketch_var)
4334                                    .map(|sketch_var| {
4335                                        sketch_var
4336                                            .initial_value_to_solver_units(
4337                                                exec_state,
4338                                                range,
4339                                                "circle radius initial value",
4340                                            )
4341                                            .map(|value| value.n)
4342                                    })
4343                                    .transpose()?
4344                                    .ok_or_else(|| {
4345                                        internal_err(
4346                                            format!("Missing sketch variable initial value for id {}", id.0),
4347                                            range,
4348                                        )
4349                                    })
4350                            }
4351
4352                            let (points, label_position) = match &constraint.kind {
4353                                SketchConstraintKind::Radius { points, label_position } => {
4354                                    (points, label_position.clone())
4355                                }
4356                                SketchConstraintKind::Diameter { points, label_position } => {
4357                                    (points, label_position.clone())
4358                                }
4359                                _ => unreachable!(),
4360                            };
4361                            let range = self.as_source_range();
4362                            let center = &points[0];
4363                            let start = &points[1];
4364                            let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
4365                                return Err(internal_err(
4366                                    "Being inside a sketch block should have already been checked above",
4367                                    self,
4368                                ));
4369                            };
4370                            let (constraint_name, is_diameter) = match &constraint.kind {
4371                                SketchConstraintKind::Radius { .. } => ("radius", false),
4372                                SketchConstraintKind::Diameter { .. } => ("diameter", true),
4373                                _ => unreachable!(),
4374                            };
4375                            let sketch_vars = sketch_block_state.sketch_vars.clone();
4376                            let target_segment = sketch_block_state
4377                                .needed_by_engine
4378                                .iter()
4379                                .find_map(|seg| match &seg.kind {
4380                                    UnsolvedSegmentKind::Arc {
4381                                        center_object_id,
4382                                        start_object_id,
4383                                        end,
4384                                        ..
4385                                    } if *center_object_id == center.object_id
4386                                        && *start_object_id == start.object_id =>
4387                                    {
4388                                        let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
4389                                            (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
4390                                                (*end_x, *end_y)
4391                                            }
4392                                            _ => return None,
4393                                        };
4394                                        Some(CircularSegmentConstraintTarget::Arc {
4395                                            object_id: seg.object_id,
4396                                            end: [end_x_var, end_y_var],
4397                                        })
4398                                    }
4399                                    UnsolvedSegmentKind::Circle {
4400                                        center_object_id,
4401                                        start_object_id,
4402                                        ..
4403                                    } if *center_object_id == center.object_id
4404                                        && *start_object_id == start.object_id =>
4405                                    {
4406                                        Some(CircularSegmentConstraintTarget::Circle {
4407                                            object_id: seg.object_id,
4408                                        })
4409                                    }
4410                                    _ => None,
4411                                })
4412                                .ok_or_else(|| {
4413                                    internal_err(
4414                                        format!("Could not find circular segment for {} constraint", constraint_name),
4415                                        range,
4416                                    )
4417                                })?;
4418                            let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
4419                            let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4420                                center.vars.x.to_constraint_id(range)?,
4421                                center.vars.y.to_constraint_id(range)?,
4422                            );
4423                            let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4424                                start.vars.x.to_constraint_id(range)?,
4425                                start.vars.y.to_constraint_id(range)?,
4426                            );
4427                            let solver_constraint = match target_segment {
4428                                CircularSegmentConstraintTarget::Arc { end, .. } => {
4429                                    let solver_arc = ezpz::datatypes::inputs::DatumCircularArc {
4430                                        center: center_point,
4431                                        start: start_point,
4432                                        end: ezpz::datatypes::inputs::DatumPoint::new_xy(
4433                                            end[0].to_constraint_id(range)?,
4434                                            end[1].to_constraint_id(range)?,
4435                                        ),
4436                                    };
4437                                    Constraint::ArcRadius(solver_arc, radius_value)
4438                                }
4439                                CircularSegmentConstraintTarget::Circle { .. } => {
4440                                    let sketch_var_ty = solver_numeric_type(exec_state);
4441                                    let start_x =
4442                                        sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
4443                                    let start_y =
4444                                        sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
4445                                    let center_x =
4446                                        sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
4447                                    let center_y =
4448                                        sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
4449
4450                                    // Get the hypotenuse between the two points, the radius
4451                                    let radius_initial_value = libm::hypot(start_x - center_x, start_y - center_y);
4452
4453                                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4454                                        let message =
4455                                            "Being inside a sketch block should have already been checked above"
4456                                                .to_owned();
4457                                        debug_assert!(false, "{}", &message);
4458                                        return Err(internal_err(message, self));
4459                                    };
4460                                    let radius_id = sketch_block_state.next_sketch_var_id();
4461                                    sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4462                                        value: Box::new(crate::execution::SketchVar {
4463                                            id: radius_id,
4464                                            initial_value: radius_initial_value,
4465                                            ty: sketch_var_ty,
4466                                            meta: vec![],
4467                                        }),
4468                                    });
4469                                    let radius =
4470                                        ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
4471                                    let solver_circle = ezpz::datatypes::inputs::DatumCircle {
4472                                        center: center_point,
4473                                        radius,
4474                                    };
4475                                    sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
4476                                        start_point,
4477                                        center_point,
4478                                        radius,
4479                                    ));
4480                                    Constraint::CircleRadius(solver_circle, radius_value)
4481                                }
4482                            };
4483
4484                            let constraint_id = exec_state.next_object_id();
4485                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4486                                let message =
4487                                    "Being inside a sketch block should have already been checked above".to_owned();
4488                                debug_assert!(false, "{}", &message);
4489                                return Err(internal_err(message, self));
4490                            };
4491                            sketch_block_state.solver_constraints.push(solver_constraint);
4492                            use crate::execution::Artifact;
4493                            use crate::execution::CodeRef;
4494                            use crate::execution::SketchBlockConstraint;
4495                            use crate::execution::SketchBlockConstraintType;
4496                            use crate::front::SourceRef;
4497                            let segment_object_id = match target_segment {
4498                                CircularSegmentConstraintTarget::Arc { object_id, .. }
4499                                | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
4500                            };
4501
4502                            let constraint = if is_diameter {
4503                                use crate::frontend::sketch::Diameter;
4504                                crate::front::Constraint::Diameter(Diameter {
4505                                    arc: segment_object_id,
4506                                    diameter: n.try_into().map_err(|_| {
4507                                        internal_err("Failed to convert diameter units numeric suffix:", range)
4508                                    })?,
4509                                    label_position,
4510                                    source,
4511                                })
4512                            } else {
4513                                use crate::frontend::sketch::Radius;
4514                                crate::front::Constraint::Radius(Radius {
4515                                    arc: segment_object_id,
4516                                    radius: n.try_into().map_err(|_| {
4517                                        internal_err("Failed to convert radius units numeric suffix:", range)
4518                                    })?,
4519                                    label_position,
4520                                    source,
4521                                })
4522                            };
4523                            sketch_block_state.sketch_constraints.push(constraint_id);
4524                            let Some(sketch_id) = sketch_block_state.sketch_id else {
4525                                let message = "Sketch id missing for constraint artifact".to_owned();
4526                                debug_assert!(false, "{}", &message);
4527                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4528                            };
4529                            let artifact_id = exec_state.next_artifact_id();
4530                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4531                                id: artifact_id,
4532                                sketch_id,
4533                                constraint_id,
4534                                constraint_type: SketchBlockConstraintType::from(&constraint),
4535                                code_ref: CodeRef::placeholder(range),
4536                            }));
4537                            exec_state.add_scene_object(
4538                                Object {
4539                                    id: constraint_id,
4540                                    kind: ObjectKind::Constraint { constraint },
4541                                    label: Default::default(),
4542                                    comments: Default::default(),
4543                                    artifact_id,
4544                                    source: SourceRef::new(range, self.node_path.clone()),
4545                                },
4546                                range,
4547                            );
4548                        }
4549                        SketchConstraintKind::HorizontalDistance { points, label_position } => {
4550                            let range = self.as_source_range();
4551                            let p0 = &points[0];
4552                            let p1 = &points[1];
4553                            let constraint_id = exec_state.next_object_id();
4554                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4555                                let message =
4556                                    "Being inside a sketch block should have already been checked above".to_owned();
4557                                debug_assert!(false, "{}", &message);
4558                                return Err(internal_err(message, self));
4559                            };
4560                            match (p0, p1) {
4561                                (
4562                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
4563                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
4564                                ) => {
4565                                    let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4566                                        p0.vars.x.to_constraint_id(range)?,
4567                                        p0.vars.y.to_constraint_id(range)?,
4568                                    );
4569                                    let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4570                                        p1.vars.x.to_constraint_id(range)?,
4571                                        p1.vars.y.to_constraint_id(range)?,
4572                                    );
4573                                    sketch_block_state
4574                                        .solver_constraints
4575                                        .push(ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n));
4576                                }
4577                                (
4578                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4579                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4580                                ) => {
4581                                    // horizontalDistance([point, ORIGIN]) == n means 0 - point.x = n, so point.x = -n.
4582                                    sketch_block_state
4583                                        .solver_constraints
4584                                        .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, -n.n));
4585                                }
4586                                (
4587                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4588                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4589                                ) => {
4590                                    // horizontalDistance([ORIGIN, point]) == n means point.x - 0 = n, so point.x = n.
4591                                    sketch_block_state
4592                                        .solver_constraints
4593                                        .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, n.n));
4594                                }
4595                                (
4596                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4597                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4598                                ) => {
4599                                    return Err(internal_err(
4600                                        "horizontalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
4601                                        range,
4602                                    ));
4603                                }
4604                            }
4605                            use crate::execution::Artifact;
4606                            use crate::execution::CodeRef;
4607                            use crate::execution::SketchBlockConstraint;
4608                            use crate::execution::SketchBlockConstraintType;
4609                            use crate::front::Distance;
4610                            use crate::front::SourceRef;
4611                            use crate::frontend::sketch::ConstraintSegment;
4612
4613                            let constraint = crate::front::Constraint::HorizontalDistance(Distance {
4614                                points: vec![
4615                                    match p0 {
4616                                        crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4617                                            ConstraintSegment::from(point.object_id)
4618                                        }
4619                                        crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4620                                            ConstraintSegment::ORIGIN
4621                                        }
4622                                    },
4623                                    match p1 {
4624                                        crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4625                                            ConstraintSegment::from(point.object_id)
4626                                        }
4627                                        crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4628                                            ConstraintSegment::ORIGIN
4629                                        }
4630                                    },
4631                                ],
4632                                distance: n.try_into().map_err(|_| {
4633                                    internal_err("Failed to convert distance units numeric suffix:", range)
4634                                })?,
4635                                label_position: label_position.clone(),
4636                                source,
4637                            });
4638                            sketch_block_state.sketch_constraints.push(constraint_id);
4639                            let Some(sketch_id) = sketch_block_state.sketch_id else {
4640                                let message = "Sketch id missing for constraint artifact".to_owned();
4641                                debug_assert!(false, "{}", &message);
4642                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4643                            };
4644                            let artifact_id = exec_state.next_artifact_id();
4645                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4646                                id: artifact_id,
4647                                sketch_id,
4648                                constraint_id,
4649                                constraint_type: SketchBlockConstraintType::from(&constraint),
4650                                code_ref: CodeRef::placeholder(range),
4651                            }));
4652                            exec_state.add_scene_object(
4653                                Object {
4654                                    id: constraint_id,
4655                                    kind: ObjectKind::Constraint { constraint },
4656                                    label: Default::default(),
4657                                    comments: Default::default(),
4658                                    artifact_id,
4659                                    source: SourceRef::new(range, self.node_path.clone()),
4660                                },
4661                                range,
4662                            );
4663                        }
4664                        SketchConstraintKind::VerticalDistance { points, label_position } => {
4665                            let range = self.as_source_range();
4666                            let p0 = &points[0];
4667                            let p1 = &points[1];
4668                            let constraint_id = exec_state.next_object_id();
4669                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4670                                let message =
4671                                    "Being inside a sketch block should have already been checked above".to_owned();
4672                                debug_assert!(false, "{}", &message);
4673                                return Err(internal_err(message, self));
4674                            };
4675                            match (p0, p1) {
4676                                (
4677                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
4678                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
4679                                ) => {
4680                                    let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4681                                        p0.vars.x.to_constraint_id(range)?,
4682                                        p0.vars.y.to_constraint_id(range)?,
4683                                    );
4684                                    let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4685                                        p1.vars.x.to_constraint_id(range)?,
4686                                        p1.vars.y.to_constraint_id(range)?,
4687                                    );
4688                                    sketch_block_state
4689                                        .solver_constraints
4690                                        .push(ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n));
4691                                }
4692                                (
4693                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4694                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4695                                ) => {
4696                                    sketch_block_state
4697                                        .solver_constraints
4698                                        .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, -n.n));
4699                                }
4700                                (
4701                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4702                                    crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4703                                ) => {
4704                                    sketch_block_state
4705                                        .solver_constraints
4706                                        .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, n.n));
4707                                }
4708                                (
4709                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4710                                    crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4711                                ) => {
4712                                    return Err(internal_err(
4713                                        "verticalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
4714                                        range,
4715                                    ));
4716                                }
4717                            }
4718                            use crate::execution::Artifact;
4719                            use crate::execution::CodeRef;
4720                            use crate::execution::SketchBlockConstraint;
4721                            use crate::execution::SketchBlockConstraintType;
4722                            use crate::front::Distance;
4723                            use crate::front::SourceRef;
4724                            use crate::frontend::sketch::ConstraintSegment;
4725
4726                            let constraint = crate::front::Constraint::VerticalDistance(Distance {
4727                                points: vec![
4728                                    match p0 {
4729                                        crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4730                                            ConstraintSegment::from(point.object_id)
4731                                        }
4732                                        crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4733                                            ConstraintSegment::ORIGIN
4734                                        }
4735                                    },
4736                                    match p1 {
4737                                        crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
4738                                            ConstraintSegment::from(point.object_id)
4739                                        }
4740                                        crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4741                                            ConstraintSegment::ORIGIN
4742                                        }
4743                                    },
4744                                ],
4745                                distance: n.try_into().map_err(|_| {
4746                                    internal_err("Failed to convert distance units numeric suffix:", range)
4747                                })?,
4748                                label_position: label_position.clone(),
4749                                source,
4750                            });
4751                            sketch_block_state.sketch_constraints.push(constraint_id);
4752                            let Some(sketch_id) = sketch_block_state.sketch_id else {
4753                                let message = "Sketch id missing for constraint artifact".to_owned();
4754                                debug_assert!(false, "{}", &message);
4755                                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4756                            };
4757                            let artifact_id = exec_state.next_artifact_id();
4758                            exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4759                                id: artifact_id,
4760                                sketch_id,
4761                                constraint_id,
4762                                constraint_type: SketchBlockConstraintType::from(&constraint),
4763                                code_ref: CodeRef::placeholder(range),
4764                            }));
4765                            exec_state.add_scene_object(
4766                                Object {
4767                                    id: constraint_id,
4768                                    kind: ObjectKind::Constraint { constraint },
4769                                    label: Default::default(),
4770                                    comments: Default::default(),
4771                                    artifact_id,
4772                                    source: SourceRef::new(range, self.node_path.clone()),
4773                                },
4774                                range,
4775                            );
4776                        }
4777                    }
4778                    return Ok(KclValue::none());
4779                }
4780                _ => {
4781                    return Err(KclError::new_semantic(KclErrorDetails::new(
4782                        format!(
4783                            "Cannot create an equivalence constraint between values of these types: {} and {}",
4784                            left_value.human_friendly_type(),
4785                            right_value.human_friendly_type()
4786                        ),
4787                        vec![self.into()],
4788                    )));
4789                }
4790            }
4791        }
4792
4793        let left = number_as_f64(&left_value, self.left.clone().into())?;
4794        let right = number_as_f64(&right_value, self.right.clone().into())?;
4795
4796        let value = match self.operator {
4797            BinaryOperator::Add => {
4798                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
4799                self.warn_on_unknown(&ty, "Adding", exec_state);
4800                KclValue::Number { value: l + r, meta, ty }
4801            }
4802            BinaryOperator::Sub => {
4803                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
4804                self.warn_on_unknown(&ty, "Subtracting", exec_state);
4805                KclValue::Number { value: l - r, meta, ty }
4806            }
4807            BinaryOperator::Mul => {
4808                let (l, r, ty) = NumericType::combine_mul(left, right);
4809                self.warn_on_unknown(&ty, "Multiplying", exec_state);
4810                KclValue::Number { value: l * r, meta, ty }
4811            }
4812            BinaryOperator::Div => {
4813                let (l, r, ty) = NumericType::combine_div(left, right);
4814                self.warn_on_unknown(&ty, "Dividing", exec_state);
4815                KclValue::Number { value: l / r, meta, ty }
4816            }
4817            BinaryOperator::Mod => {
4818                let (l, r, ty) = NumericType::combine_mod(left, right);
4819                self.warn_on_unknown(&ty, "Modulo of", exec_state);
4820                KclValue::Number { value: l % r, meta, ty }
4821            }
4822            BinaryOperator::Pow => KclValue::Number {
4823                value: libm::pow(left.n, right.n),
4824                meta,
4825                ty: exec_state.current_default_units(),
4826            },
4827            BinaryOperator::Neq => {
4828                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
4829                self.warn_on_unknown(&ty, "Comparing", exec_state);
4830                KclValue::Bool { value: l != r, meta }
4831            }
4832            BinaryOperator::Gt => {
4833                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
4834                self.warn_on_unknown(&ty, "Comparing", exec_state);
4835                KclValue::Bool { value: l > r, meta }
4836            }
4837            BinaryOperator::Gte => {
4838                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
4839                self.warn_on_unknown(&ty, "Comparing", exec_state);
4840                KclValue::Bool { value: l >= r, meta }
4841            }
4842            BinaryOperator::Lt => {
4843                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
4844                self.warn_on_unknown(&ty, "Comparing", exec_state);
4845                KclValue::Bool { value: l < r, meta }
4846            }
4847            BinaryOperator::Lte => {
4848                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
4849                self.warn_on_unknown(&ty, "Comparing", exec_state);
4850                KclValue::Bool { value: l <= r, meta }
4851            }
4852            BinaryOperator::Eq => {
4853                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
4854                self.warn_on_unknown(&ty, "Comparing", exec_state);
4855                KclValue::Bool { value: l == r, meta }
4856            }
4857            BinaryOperator::And | BinaryOperator::Or => unreachable!(),
4858        };
4859
4860        Ok(value)
4861    }
4862
4863    fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
4864        internal_err("missing result while evaluating binary expression", node)
4865    }
4866
4867    fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
4868        if ty == &NumericType::Unknown {
4869            let sr = self.as_source_range();
4870            exec_state.clear_units_warnings(&sr);
4871            let mut err = CompilationIssue::err(
4872                sr,
4873                format!(
4874                    "{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)`."
4875                ),
4876            );
4877            err.tag = crate::errors::Tag::UnknownNumericUnits;
4878            exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
4879        }
4880    }
4881}
4882
4883impl Node<UnaryExpression> {
4884    pub(super) async fn get_result(
4885        &self,
4886        exec_state: &mut ExecState,
4887        ctx: &ExecutorContext,
4888    ) -> Result<KclValueControlFlow, KclError> {
4889        match self.operator {
4890            UnaryOperator::Not => {
4891                let value = self.argument.get_result(exec_state, ctx).await?;
4892                let value = control_continue!(value);
4893                let KclValue::Bool {
4894                    value: bool_value,
4895                    meta: _,
4896                } = value
4897                else {
4898                    return Err(KclError::new_semantic(KclErrorDetails::new(
4899                        format!(
4900                            "Cannot apply unary operator ! to non-boolean value: {}",
4901                            value.human_friendly_type()
4902                        ),
4903                        vec![self.into()],
4904                    )));
4905                };
4906                let meta = vec![Metadata {
4907                    source_range: self.into(),
4908                }];
4909                let negated = KclValue::Bool {
4910                    value: !bool_value,
4911                    meta,
4912                };
4913
4914                Ok(negated.continue_())
4915            }
4916            UnaryOperator::Neg => {
4917                let value = self.argument.get_result(exec_state, ctx).await?;
4918                let value = control_continue!(value);
4919                let err = || {
4920                    KclError::new_semantic(KclErrorDetails::new(
4921                        format!(
4922                            "You can only negate numbers, planes, or lines, but this is a {}",
4923                            value.human_friendly_type()
4924                        ),
4925                        vec![self.into()],
4926                    ))
4927                };
4928                match &value {
4929                    KclValue::Number { value, ty, .. } => {
4930                        let meta = vec![Metadata {
4931                            source_range: self.into(),
4932                        }];
4933                        Ok(KclValue::Number {
4934                            value: -value,
4935                            meta,
4936                            ty: *ty,
4937                        }
4938                        .continue_())
4939                    }
4940                    KclValue::Plane { value } => {
4941                        let mut plane = value.clone();
4942                        if plane.info.x_axis.x != 0.0 {
4943                            plane.info.x_axis.x *= -1.0;
4944                        }
4945                        if plane.info.x_axis.y != 0.0 {
4946                            plane.info.x_axis.y *= -1.0;
4947                        }
4948                        if plane.info.x_axis.z != 0.0 {
4949                            plane.info.x_axis.z *= -1.0;
4950                        }
4951
4952                        plane.id = exec_state.next_uuid();
4953                        plane.object_id = None;
4954                        Ok(KclValue::Plane { value: plane }.continue_())
4955                    }
4956                    KclValue::Object {
4957                        value: values, meta, ..
4958                    } => {
4959                        // Special-case for negating line-like objects.
4960                        let Some(direction) = values.get("direction") else {
4961                            return Err(err());
4962                        };
4963
4964                        let direction = match direction {
4965                            KclValue::Tuple { value: values, meta } => {
4966                                let values = values
4967                                    .iter()
4968                                    .map(|v| match v {
4969                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
4970                                            value: *value * -1.0,
4971                                            ty: *ty,
4972                                            meta: meta.clone(),
4973                                        }),
4974                                        _ => Err(err()),
4975                                    })
4976                                    .collect::<Result<Vec<_>, _>>()?;
4977
4978                                KclValue::Tuple {
4979                                    value: values,
4980                                    meta: meta.clone(),
4981                                }
4982                            }
4983                            KclValue::HomArray {
4984                                value: values,
4985                                ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
4986                            } => {
4987                                let values = values
4988                                    .iter()
4989                                    .map(|v| match v {
4990                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
4991                                            value: *value * -1.0,
4992                                            ty: *ty,
4993                                            meta: meta.clone(),
4994                                        }),
4995                                        _ => Err(err()),
4996                                    })
4997                                    .collect::<Result<Vec<_>, _>>()?;
4998
4999                                KclValue::HomArray {
5000                                    value: values,
5001                                    ty: ty.clone(),
5002                                }
5003                            }
5004                            _ => return Err(err()),
5005                        };
5006
5007                        let mut value = values.clone();
5008                        value.insert("direction".to_owned(), direction);
5009                        Ok(KclValue::Object {
5010                            value,
5011                            meta: meta.clone(),
5012                            constrainable: false,
5013                        }
5014                        .continue_())
5015                    }
5016                    _ => Err(err()),
5017                }
5018            }
5019            UnaryOperator::Plus => {
5020                let operand = self.argument.get_result(exec_state, ctx).await?;
5021                let operand = control_continue!(operand);
5022                match operand {
5023                    KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
5024                    _ => Err(KclError::new_semantic(KclErrorDetails::new(
5025                        format!(
5026                            "You can only apply unary + to numbers or planes, but this is a {}",
5027                            operand.human_friendly_type()
5028                        ),
5029                        vec![self.into()],
5030                    ))),
5031                }
5032            }
5033        }
5034    }
5035}
5036
5037pub(crate) async fn execute_pipe_body(
5038    exec_state: &mut ExecState,
5039    body: &[Expr],
5040    source_range: SourceRange,
5041    ctx: &ExecutorContext,
5042) -> Result<KclValueControlFlow, KclError> {
5043    let Some((first, body)) = body.split_first() else {
5044        return Err(KclError::new_semantic(KclErrorDetails::new(
5045            "Pipe expressions cannot be empty".to_owned(),
5046            vec![source_range],
5047        )));
5048    };
5049    // Evaluate the first element in the pipeline.
5050    // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
5051    // they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
5052    // of its own.
5053    let meta = Metadata {
5054        source_range: SourceRange::from(first),
5055    };
5056    let output = ctx
5057        .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
5058        .await?;
5059    let output = control_continue!(output);
5060
5061    // Now that we've evaluated the first child expression in the pipeline, following child expressions
5062    // should use the previous child expression for %.
5063    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
5064    let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
5065    // Evaluate remaining elements.
5066    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
5067    // Restore the previous pipe value.
5068    exec_state.mod_local.pipe_value = previous_pipe_value;
5069
5070    result
5071}
5072
5073/// Execute the tail of a pipe expression.  exec_state.pipe_value must be set by
5074/// the caller.
5075#[async_recursion]
5076async fn inner_execute_pipe_body(
5077    exec_state: &mut ExecState,
5078    body: &[Expr],
5079    ctx: &ExecutorContext,
5080) -> Result<KclValueControlFlow, KclError> {
5081    for expression in body {
5082        if let Expr::TagDeclarator(_) = expression {
5083            return Err(KclError::new_semantic(KclErrorDetails::new(
5084                format!("This cannot be in a PipeExpression: {expression:?}"),
5085                vec![expression.into()],
5086            )));
5087        }
5088        let metadata = Metadata {
5089            source_range: SourceRange::from(expression),
5090        };
5091        let output = ctx
5092            .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
5093            .await?;
5094        let output = control_continue!(output);
5095        exec_state.mod_local.pipe_value = Some(output);
5096    }
5097    // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
5098    let final_output = exec_state.mod_local.pipe_value.take().unwrap();
5099    Ok(final_output.continue_())
5100}
5101
5102impl Node<TagDeclarator> {
5103    pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
5104        let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
5105            value: self.name.clone(),
5106            info: Vec::new(),
5107            meta: vec![Metadata {
5108                source_range: self.into(),
5109            }],
5110        }));
5111
5112        exec_state
5113            .mut_stack()
5114            .add(self.name.clone(), memory_item, self.into())?;
5115
5116        Ok(self.into())
5117    }
5118}
5119
5120impl Node<ArrayExpression> {
5121    #[async_recursion]
5122    pub(super) async fn execute(
5123        &self,
5124        exec_state: &mut ExecState,
5125        ctx: &ExecutorContext,
5126    ) -> Result<KclValueControlFlow, KclError> {
5127        let mut results = Vec::with_capacity(self.elements.len());
5128
5129        for element in &self.elements {
5130            let metadata = Metadata::from(element);
5131            // TODO: Carry statement kind here so that we know if we're
5132            // inside a variable declaration.
5133            let value = ctx
5134                .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
5135                .await?;
5136            let value = control_continue!(value);
5137
5138            results.push(value);
5139        }
5140
5141        Ok(KclValue::HomArray {
5142            value: results,
5143            ty: RuntimeType::Primitive(PrimitiveType::Any),
5144        }
5145        .continue_())
5146    }
5147}
5148
5149impl Node<ArrayRangeExpression> {
5150    #[async_recursion]
5151    pub(super) async fn execute(
5152        &self,
5153        exec_state: &mut ExecState,
5154        ctx: &ExecutorContext,
5155    ) -> Result<KclValueControlFlow, KclError> {
5156        let metadata = Metadata::from(&self.start_element);
5157        let start_val = ctx
5158            .execute_expr(
5159                &self.start_element,
5160                exec_state,
5161                &metadata,
5162                &[],
5163                StatementKind::Expression,
5164            )
5165            .await?;
5166        let start_val = control_continue!(start_val);
5167        let start = start_val
5168            .as_ty_f64()
5169            .ok_or(KclError::new_semantic(KclErrorDetails::new(
5170                format!(
5171                    "Expected number for range start but found {}",
5172                    start_val.human_friendly_type()
5173                ),
5174                vec![self.into()],
5175            )))?;
5176        let metadata = Metadata::from(&self.end_element);
5177        let end_val = ctx
5178            .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
5179            .await?;
5180        let end_val = control_continue!(end_val);
5181        let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
5182            format!(
5183                "Expected number for range end but found {}",
5184                end_val.human_friendly_type()
5185            ),
5186            vec![self.into()],
5187        )))?;
5188
5189        let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
5190        let Some(start) = crate::try_f64_to_i64(start) else {
5191            return Err(KclError::new_semantic(KclErrorDetails::new(
5192                format!("Range start must be an integer, but found {start}"),
5193                vec![self.into()],
5194            )));
5195        };
5196        let Some(end) = crate::try_f64_to_i64(end) else {
5197            return Err(KclError::new_semantic(KclErrorDetails::new(
5198                format!("Range end must be an integer, but found {end}"),
5199                vec![self.into()],
5200            )));
5201        };
5202
5203        if end < start {
5204            return Err(KclError::new_semantic(KclErrorDetails::new(
5205                format!("Range start is greater than range end: {start} .. {end}"),
5206                vec![self.into()],
5207            )));
5208        }
5209
5210        let range: Vec<_> = if self.end_inclusive {
5211            (start..=end).collect()
5212        } else {
5213            (start..end).collect()
5214        };
5215
5216        let meta = vec![Metadata {
5217            source_range: self.into(),
5218        }];
5219
5220        Ok(KclValue::HomArray {
5221            value: range
5222                .into_iter()
5223                .map(|num| KclValue::Number {
5224                    value: num as f64,
5225                    ty,
5226                    meta: meta.clone(),
5227                })
5228                .collect(),
5229            ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
5230        }
5231        .continue_())
5232    }
5233}
5234
5235impl Node<ObjectExpression> {
5236    #[async_recursion]
5237    pub(super) async fn execute(
5238        &self,
5239        exec_state: &mut ExecState,
5240        ctx: &ExecutorContext,
5241    ) -> Result<KclValueControlFlow, KclError> {
5242        let mut object = HashMap::with_capacity(self.properties.len());
5243        for property in &self.properties {
5244            let metadata = Metadata::from(&property.value);
5245            let result = ctx
5246                .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
5247                .await?;
5248            let result = control_continue!(result);
5249            object.insert(property.key.name.clone(), result);
5250        }
5251
5252        Ok(KclValue::Object {
5253            value: object,
5254            meta: vec![Metadata {
5255                source_range: self.into(),
5256            }],
5257            constrainable: false,
5258        }
5259        .continue_())
5260    }
5261}
5262
5263fn article_for<S: AsRef<str>>(s: S) -> &'static str {
5264    // '[' is included since it's an array.
5265    if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
5266        "an"
5267    } else {
5268        "a"
5269    }
5270}
5271
5272fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
5273    v.as_ty_f64().ok_or_else(|| {
5274        let actual_type = v.human_friendly_type();
5275        KclError::new_semantic(KclErrorDetails::new(
5276            format!("Expected a number, but found {actual_type}",),
5277            vec![source_range],
5278        ))
5279    })
5280}
5281
5282impl Node<IfExpression> {
5283    #[async_recursion]
5284    pub(super) async fn get_result(
5285        &self,
5286        exec_state: &mut ExecState,
5287        ctx: &ExecutorContext,
5288    ) -> Result<KclValueControlFlow, KclError> {
5289        // Check the `if` branch.
5290        let cond_value = ctx
5291            .execute_expr(
5292                &self.cond,
5293                exec_state,
5294                &Metadata::from(self),
5295                &[],
5296                StatementKind::Expression,
5297            )
5298            .await?;
5299        let cond_value = control_continue!(cond_value);
5300        if cond_value.get_bool()? {
5301            let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
5302            // Block must end in an expression, so this has to be Some.
5303            // Enforced by the parser.
5304            // See https://github.com/KittyCAD/modeling-app/issues/4015
5305            return Ok(block_result.unwrap());
5306        }
5307
5308        // Check any `else if` branches.
5309        for else_if in &self.else_ifs {
5310            let cond_value = ctx
5311                .execute_expr(
5312                    &else_if.cond,
5313                    exec_state,
5314                    &Metadata::from(self),
5315                    &[],
5316                    StatementKind::Expression,
5317                )
5318                .await?;
5319            let cond_value = control_continue!(cond_value);
5320            if cond_value.get_bool()? {
5321                let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
5322                // Block must end in an expression, so this has to be Some.
5323                // Enforced by the parser.
5324                // See https://github.com/KittyCAD/modeling-app/issues/4015
5325                return Ok(block_result.unwrap());
5326            }
5327        }
5328
5329        // Run the final `else` branch.
5330        ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
5331            .await
5332            .map(|expr| expr.unwrap())
5333    }
5334}
5335
5336#[derive(Debug)]
5337enum Property {
5338    UInt(usize),
5339    String(String),
5340}
5341
5342impl Property {
5343    #[allow(clippy::too_many_arguments)]
5344    async fn try_from<'a>(
5345        computed: bool,
5346        value: Expr,
5347        exec_state: &mut ExecState,
5348        sr: SourceRange,
5349        ctx: &ExecutorContext,
5350        metadata: &Metadata,
5351        annotations: &[Node<Annotation>],
5352        statement_kind: StatementKind<'a>,
5353    ) -> Result<Self, KclError> {
5354        let property_sr = vec![sr];
5355        if !computed {
5356            let Expr::Name(identifier) = value else {
5357                // Should actually be impossible because the parser would reject it.
5358                return Err(KclError::new_semantic(KclErrorDetails::new(
5359                    "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
5360                        .to_owned(),
5361                    property_sr,
5362                )));
5363            };
5364            return Ok(Property::String(identifier.to_string()));
5365        }
5366
5367        let prop_value = ctx
5368            .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
5369            .await?;
5370        let prop_value = match prop_value.control {
5371            ControlFlowKind::Continue => prop_value.into_value(),
5372            ControlFlowKind::Exit => {
5373                let message = "Early return inside array brackets is currently not supported".to_owned();
5374                debug_assert!(false, "{}", &message);
5375                return Err(internal_err(message, sr));
5376            }
5377        };
5378        match prop_value {
5379            KclValue::Number { value, ty, meta: _ } => {
5380                if !matches!(
5381                    ty,
5382                    NumericType::Unknown
5383                        | NumericType::Default { .. }
5384                        | NumericType::Known(crate::exec::UnitType::Count)
5385                ) {
5386                    return Err(KclError::new_semantic(KclErrorDetails::new(
5387                        format!(
5388                            "{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"
5389                        ),
5390                        property_sr,
5391                    )));
5392                }
5393                if let Some(x) = crate::try_f64_to_usize(value) {
5394                    Ok(Property::UInt(x))
5395                } else {
5396                    Err(KclError::new_semantic(KclErrorDetails::new(
5397                        format!("{value} is not a valid index, indices must be whole numbers >= 0"),
5398                        property_sr,
5399                    )))
5400                }
5401            }
5402            _ => Err(KclError::new_semantic(KclErrorDetails::new(
5403                "Only numbers (>= 0) can be indexes".to_owned(),
5404                vec![sr],
5405            ))),
5406        }
5407    }
5408}
5409
5410impl Property {
5411    fn type_name(&self) -> &'static str {
5412        match self {
5413            Property::UInt(_) => "number",
5414            Property::String(_) => "string",
5415        }
5416    }
5417}
5418
5419impl Node<PipeExpression> {
5420    #[async_recursion]
5421    pub(super) async fn get_result(
5422        &self,
5423        exec_state: &mut ExecState,
5424        ctx: &ExecutorContext,
5425    ) -> Result<KclValueControlFlow, KclError> {
5426        execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
5427    }
5428}
5429
5430#[cfg(test)]
5431mod test {
5432    use std::sync::Arc;
5433
5434    use tokio::io::AsyncWriteExt;
5435
5436    use super::*;
5437    use crate::ExecutorSettings;
5438    use crate::errors::Severity;
5439    use crate::exec::UnitType;
5440    use crate::execution::ContextType;
5441    use crate::execution::parse_execute;
5442
5443    #[tokio::test(flavor = "multi_thread")]
5444    async fn ascription() {
5445        let program = r#"
5446a = 42: number
5447b = a: number
5448p = {
5449  origin = { x = 0, y = 0, z = 0 },
5450  xAxis = { x = 1, y = 0, z = 0 },
5451  yAxis = { x = 0, y = 1, z = 0 },
5452  zAxis = { x = 0, y = 0, z = 1 }
5453}: Plane
5454arr1 = [42]: [number(cm)]
5455"#;
5456
5457        let result = parse_execute(program).await.unwrap();
5458        let mem = result.exec_state.stack();
5459        assert!(matches!(
5460            mem.memory
5461                .get_from("p", result.mem_env, SourceRange::default(), 0)
5462                .unwrap(),
5463            KclValue::Plane { .. }
5464        ));
5465        let arr1 = mem
5466            .memory
5467            .get_from("arr1", result.mem_env, SourceRange::default(), 0)
5468            .unwrap();
5469        if let KclValue::HomArray { value, ty } = arr1 {
5470            assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
5471            assert_eq!(
5472                *ty,
5473                RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
5474            );
5475            // Compare, ignoring meta.
5476            if let KclValue::Number { value, ty, .. } = &value[0] {
5477                // It should not convert units.
5478                assert_eq!(*value, 42.0);
5479                assert_eq!(
5480                    *ty,
5481                    NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
5482                );
5483            } else {
5484                panic!("Expected a number; found {:?}", value[0]);
5485            }
5486        } else {
5487            panic!("Expected HomArray; found {arr1:?}");
5488        }
5489
5490        let program = r#"
5491a = 42: string
5492"#;
5493        let result = parse_execute(program).await;
5494        let err = result.unwrap_err();
5495        assert!(
5496            err.to_string()
5497                .contains("could not coerce a number (with type `number`) to type `string`"),
5498            "Expected error but found {err:?}"
5499        );
5500
5501        let program = r#"
5502a = 42: Plane
5503"#;
5504        let result = parse_execute(program).await;
5505        let err = result.unwrap_err();
5506        assert!(
5507            err.to_string()
5508                .contains("could not coerce a number (with type `number`) to type `Plane`"),
5509            "Expected error but found {err:?}"
5510        );
5511
5512        let program = r#"
5513arr = [0]: [string]
5514"#;
5515        let result = parse_execute(program).await;
5516        let err = result.unwrap_err();
5517        assert!(
5518            err.to_string().contains(
5519                "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
5520            ),
5521            "Expected error but found {err:?}"
5522        );
5523
5524        let program = r#"
5525mixedArr = [0, "a"]: [number(mm)]
5526"#;
5527        let result = parse_execute(program).await;
5528        let err = result.unwrap_err();
5529        assert!(
5530            err.to_string().contains(
5531                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
5532            ),
5533            "Expected error but found {err:?}"
5534        );
5535
5536        let program = r#"
5537mixedArr = [0, "a"]: [mm]
5538"#;
5539        let result = parse_execute(program).await;
5540        let err = result.unwrap_err();
5541        assert!(
5542            err.to_string().contains(
5543                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
5544            ),
5545            "Expected error but found {err:?}"
5546        );
5547    }
5548
5549    #[tokio::test(flavor = "multi_thread")]
5550    async fn neg_plane() {
5551        let program = r#"
5552p = {
5553  origin = { x = 0, y = 0, z = 0 },
5554  xAxis = { x = 1, y = 0, z = 0 },
5555  yAxis = { x = 0, y = 1, z = 0 },
5556}: Plane
5557p2 = -p
5558"#;
5559
5560        let result = parse_execute(program).await.unwrap();
5561        let mem = result.exec_state.stack();
5562        match mem
5563            .memory
5564            .get_from("p2", result.mem_env, SourceRange::default(), 0)
5565            .unwrap()
5566        {
5567            KclValue::Plane { value } => {
5568                assert_eq!(value.info.x_axis.x, -1.0);
5569                assert_eq!(value.info.x_axis.y, 0.0);
5570                assert_eq!(value.info.x_axis.z, 0.0);
5571            }
5572            _ => unreachable!(),
5573        }
5574    }
5575
5576    #[tokio::test(flavor = "multi_thread")]
5577    async fn multiple_returns() {
5578        let program = r#"fn foo() {
5579  return 0
5580  return 42
5581}
5582
5583a = foo()
5584"#;
5585
5586        let result = parse_execute(program).await;
5587        assert!(result.unwrap_err().to_string().contains("return"));
5588    }
5589
5590    #[tokio::test(flavor = "multi_thread")]
5591    async fn load_all_modules() {
5592        // program a.kcl
5593        let program_a_kcl = r#"
5594export a = 1
5595"#;
5596        // program b.kcl
5597        let program_b_kcl = r#"
5598import a from 'a.kcl'
5599
5600export b = a + 1
5601"#;
5602        // program c.kcl
5603        let program_c_kcl = r#"
5604import a from 'a.kcl'
5605
5606export c = a + 2
5607"#;
5608
5609        // program main.kcl
5610        let main_kcl = r#"
5611import b from 'b.kcl'
5612import c from 'c.kcl'
5613
5614d = b + c
5615"#;
5616
5617        let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
5618            .parse_errs_as_err()
5619            .unwrap();
5620
5621        let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
5622
5623        tokio::fs::File::create(tmpdir.path().join("main.kcl"))
5624            .await
5625            .unwrap()
5626            .write_all(main_kcl.as_bytes())
5627            .await
5628            .unwrap();
5629
5630        tokio::fs::File::create(tmpdir.path().join("a.kcl"))
5631            .await
5632            .unwrap()
5633            .write_all(program_a_kcl.as_bytes())
5634            .await
5635            .unwrap();
5636
5637        tokio::fs::File::create(tmpdir.path().join("b.kcl"))
5638            .await
5639            .unwrap()
5640            .write_all(program_b_kcl.as_bytes())
5641            .await
5642            .unwrap();
5643
5644        tokio::fs::File::create(tmpdir.path().join("c.kcl"))
5645            .await
5646            .unwrap()
5647            .write_all(program_c_kcl.as_bytes())
5648            .await
5649            .unwrap();
5650
5651        let exec_ctxt = ExecutorContext {
5652            engine: Arc::new(Box::new(
5653                crate::engine::conn_mock::EngineConnection::new()
5654                    .map_err(|err| {
5655                        internal_err(
5656                            format!("Failed to create mock engine connection: {err}"),
5657                            SourceRange::default(),
5658                        )
5659                    })
5660                    .unwrap(),
5661            )),
5662            engine_batch: crate::engine::EngineBatchContext::default(),
5663            fs: Arc::new(crate::fs::FileManager::new()),
5664            settings: ExecutorSettings {
5665                project_directory: Some(crate::TypedPath(tmpdir.path().into())),
5666                ..Default::default()
5667            },
5668            context_type: ContextType::Mock,
5669        };
5670        let mut exec_state = ExecState::new(&exec_ctxt);
5671
5672        exec_ctxt
5673            .run(
5674                &crate::Program {
5675                    ast: main.clone(),
5676                    original_file_contents: "".to_owned(),
5677                },
5678                &mut exec_state,
5679            )
5680            .await
5681            .unwrap();
5682    }
5683
5684    #[tokio::test(flavor = "multi_thread")]
5685    async fn user_coercion() {
5686        let program = r#"fn foo(x: Axis2d) {
5687  return 0
5688}
5689
5690foo(x = { direction = [0, 0], origin = [0, 0]})
5691"#;
5692
5693        parse_execute(program).await.unwrap();
5694
5695        let program = r#"fn foo(x: Axis3d) {
5696  return 0
5697}
5698
5699foo(x = { direction = [0, 0], origin = [0, 0]})
5700"#;
5701
5702        parse_execute(program).await.unwrap_err();
5703    }
5704
5705    #[tokio::test(flavor = "multi_thread")]
5706    async fn coerce_return() {
5707        let program = r#"fn foo(): number(mm) {
5708  return 42
5709}
5710
5711a = foo()
5712"#;
5713
5714        parse_execute(program).await.unwrap();
5715
5716        let program = r#"fn foo(): mm {
5717  return 42
5718}
5719
5720a = foo()
5721"#;
5722
5723        parse_execute(program).await.unwrap();
5724
5725        let program = r#"fn foo(): number(mm) {
5726  return { bar: 42 }
5727}
5728
5729a = foo()
5730"#;
5731
5732        parse_execute(program).await.unwrap_err();
5733
5734        let program = r#"fn foo(): mm {
5735  return { bar: 42 }
5736}
5737
5738a = foo()
5739"#;
5740
5741        parse_execute(program).await.unwrap_err();
5742    }
5743
5744    #[tokio::test(flavor = "multi_thread")]
5745    async fn test_sensible_error_when_missing_equals_in_kwarg() {
5746        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)"]
5747            .into_iter()
5748            .enumerate()
5749        {
5750            let program = format!(
5751                "fn foo() {{ return 0 }}
5752z = 0
5753fn f(x, y, z) {{ return 0 }}
5754{call}"
5755            );
5756            let err = parse_execute(&program).await.unwrap_err();
5757            let msg = err.message();
5758            assert!(
5759                msg.contains("This argument needs a label, but it doesn't have one"),
5760                "failed test {i}: {msg}"
5761            );
5762            assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
5763            if i == 0 {
5764                assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
5765            }
5766        }
5767    }
5768
5769    #[tokio::test(flavor = "multi_thread")]
5770    async fn default_param_for_unlabeled() {
5771        // Tests that the input param for myExtrude is taken from the pipeline value and same-name
5772        // keyword args.
5773        let ast = r#"fn myExtrude(@sk, length) {
5774  return extrude(sk, length)
5775}
5776sketch001 = startSketchOn(XY)
5777  |> circle(center = [0, 0], radius = 93.75)
5778  |> myExtrude(length = 40)
5779"#;
5780
5781        parse_execute(ast).await.unwrap();
5782    }
5783
5784    #[tokio::test(flavor = "multi_thread")]
5785    async fn dont_use_unlabelled_as_input() {
5786        // `length` should be used as the `length` argument to extrude, not the unlabelled input
5787        let ast = r#"length = 10
5788startSketchOn(XY)
5789  |> circle(center = [0, 0], radius = 93.75)
5790  |> extrude(length)
5791"#;
5792
5793        parse_execute(ast).await.unwrap();
5794    }
5795
5796    #[tokio::test(flavor = "multi_thread")]
5797    async fn ascription_in_binop() {
5798        let ast = r#"foo = tan(0): number(rad) - 4deg"#;
5799        parse_execute(ast).await.unwrap();
5800
5801        let ast = r#"foo = tan(0): rad - 4deg"#;
5802        parse_execute(ast).await.unwrap();
5803    }
5804
5805    #[tokio::test(flavor = "multi_thread")]
5806    async fn neg_sqrt() {
5807        let ast = r#"bad = sqrt(-2)"#;
5808
5809        let e = parse_execute(ast).await.unwrap_err();
5810        // Make sure we get a useful error message and not an engine error.
5811        assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
5812    }
5813
5814    #[tokio::test(flavor = "multi_thread")]
5815    async fn non_array_fns() {
5816        let ast = r#"push(1, item = 2)
5817pop(1)
5818map(1, f = fn(@x) { return x + 1 })
5819reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
5820
5821        parse_execute(ast).await.unwrap();
5822    }
5823
5824    #[tokio::test(flavor = "multi_thread")]
5825    async fn non_array_indexing() {
5826        let good = r#"a = 42
5827good = a[0]
5828"#;
5829        let result = parse_execute(good).await.unwrap();
5830        let mem = result.exec_state.stack();
5831        let num = mem
5832            .memory
5833            .get_from("good", result.mem_env, SourceRange::default(), 0)
5834            .unwrap()
5835            .as_ty_f64()
5836            .unwrap();
5837        assert_eq!(num.n, 42.0);
5838
5839        let bad = r#"a = 42
5840bad = a[1]
5841"#;
5842
5843        parse_execute(bad).await.unwrap_err();
5844    }
5845
5846    #[tokio::test(flavor = "multi_thread")]
5847    async fn coerce_unknown_to_length() {
5848        let ast = r#"x = 2mm * 2mm
5849y = x: number(Length)"#;
5850        let e = parse_execute(ast).await.unwrap_err();
5851        assert!(
5852            e.message().contains("could not coerce"),
5853            "Error message: '{}'",
5854            e.message()
5855        );
5856
5857        let ast = r#"x = 2mm
5858y = x: number(Length)"#;
5859        let result = parse_execute(ast).await.unwrap();
5860        let mem = result.exec_state.stack();
5861        let num = mem
5862            .memory
5863            .get_from("y", result.mem_env, SourceRange::default(), 0)
5864            .unwrap()
5865            .as_ty_f64()
5866            .unwrap();
5867        assert_eq!(num.n, 2.0);
5868        assert_eq!(num.ty, NumericType::mm());
5869    }
5870
5871    #[tokio::test(flavor = "multi_thread")]
5872    async fn one_warning_unknown() {
5873        let ast = r#"
5874// Should warn once
5875a = PI * 2
5876// Should warn once
5877b = (PI * 2) / 3
5878// Should not warn
5879c = ((PI * 2) / 3): number(deg)
5880"#;
5881
5882        let result = parse_execute(ast).await.unwrap();
5883        assert_eq!(result.exec_state.issues().len(), 2);
5884    }
5885
5886    #[tokio::test(flavor = "multi_thread")]
5887    async fn non_count_indexing() {
5888        let ast = r#"x = [0, 0]
5889y = x[1mm]
5890"#;
5891        parse_execute(ast).await.unwrap_err();
5892
5893        let ast = r#"x = [0, 0]
5894y = 1deg
5895z = x[y]
5896"#;
5897        parse_execute(ast).await.unwrap_err();
5898
5899        let ast = r#"x = [0, 0]
5900y = x[0mm + 1]
5901"#;
5902        parse_execute(ast).await.unwrap_err();
5903    }
5904
5905    #[tokio::test(flavor = "multi_thread")]
5906    async fn getting_property_of_plane() {
5907        let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
5908        parse_execute(&ast).await.unwrap();
5909    }
5910
5911    #[tokio::test(flavor = "multi_thread")]
5912    async fn no_artifacts_from_within_hole_call() {
5913        // Test that executing stdlib KCL, like the `hole` function
5914        // (which is actually implemented in KCL not Rust)
5915        // does not generate artifacts from within the stdlib code,
5916        // only from the user code.
5917        let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
5918        let out = parse_execute(&ast).await.unwrap();
5919
5920        // Get all the operations that occurred.
5921        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
5922
5923        // There should be 5, for sketching the cube and applying the hole.
5924        // If the stdlib internal calls are being tracked, that's a bug,
5925        // and the actual number of operations will be something like 35.
5926        let expected = 5;
5927        assert_eq!(
5928            actual_operations.len(),
5929            expected,
5930            "expected {expected} operations, received {}:\n{actual_operations:#?}",
5931            actual_operations.len(),
5932        );
5933    }
5934
5935    #[tokio::test(flavor = "multi_thread")]
5936    async fn feature_tree_annotation_on_user_defined_kcl() {
5937        // The call to foo() should not generate an operation,
5938        // because its 'feature_tree' attribute has been set to false.
5939        let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
5940        let out = parse_execute(&ast).await.unwrap();
5941
5942        // Get all the operations that occurred.
5943        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
5944
5945        let expected = 0;
5946        assert_eq!(
5947            actual_operations.len(),
5948            expected,
5949            "expected {expected} operations, received {}:\n{actual_operations:#?}",
5950            actual_operations.len(),
5951        );
5952    }
5953
5954    #[tokio::test(flavor = "multi_thread")]
5955    async fn no_feature_tree_annotation_on_user_defined_kcl() {
5956        // The call to foo() should generate an operation,
5957        // because @(feature_tree) defaults to true.
5958        let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
5959        let out = parse_execute(&ast).await.unwrap();
5960
5961        // Get all the operations that occurred.
5962        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
5963
5964        let expected = 2;
5965        assert_eq!(
5966            actual_operations.len(),
5967            expected,
5968            "expected {expected} operations, received {}:\n{actual_operations:#?}",
5969            actual_operations.len(),
5970        );
5971        assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
5972        assert!(matches!(actual_operations[1], Operation::GroupEnd));
5973    }
5974
5975    #[tokio::test(flavor = "multi_thread")]
5976    async fn custom_warning() {
5977        let warn = r#"
5978a = PI * 2
5979"#;
5980        let result = parse_execute(warn).await.unwrap();
5981        assert_eq!(result.exec_state.issues().len(), 1);
5982        assert_eq!(result.exec_state.issues()[0].severity, Severity::Warning);
5983
5984        let allow = r#"
5985@warnings(allow = unknownUnits)
5986a = PI * 2
5987"#;
5988        let result = parse_execute(allow).await.unwrap();
5989        assert_eq!(result.exec_state.issues().len(), 0);
5990
5991        let deny = r#"
5992@warnings(deny = [unknownUnits])
5993a = PI * 2
5994"#;
5995        let result = parse_execute(deny).await.unwrap();
5996        assert_eq!(result.exec_state.issues().len(), 1);
5997        assert_eq!(result.exec_state.issues()[0].severity, Severity::Error);
5998    }
5999
6000    #[tokio::test(flavor = "multi_thread")]
6001    async fn sketch_block_unqualified_functions_use_sketch2() {
6002        let ast = r#"
6003s = sketch(on = XY) {
6004  line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
6005  line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
6006  coincident([line1.end, line2.start])
6007}
6008"#;
6009        let result = parse_execute(ast).await.unwrap();
6010        let mem = result.exec_state.stack();
6011        let sketch_value = mem
6012            .memory
6013            .get_from("s", result.mem_env, SourceRange::default(), 0)
6014            .unwrap();
6015
6016        let KclValue::Object { value, .. } = sketch_value else {
6017            panic!("Expected sketch block to return an object, got {sketch_value:?}");
6018        };
6019
6020        assert!(value.contains_key("line1"));
6021        assert!(value.contains_key("line2"));
6022        // Ensure sketch2 aliases used during execution are not returned as
6023        // sketch block fields.
6024        assert!(!value.contains_key("line"));
6025        assert!(!value.contains_key("coincident"));
6026    }
6027
6028    #[tokio::test(flavor = "multi_thread")]
6029    async fn solver_module_is_not_available_outside_sketch_blocks() {
6030        let err = parse_execute("a = solver::ORIGIN").await.unwrap_err();
6031        assert!(err.message().contains("solver"), "Error message: '{}'", err.message());
6032
6033        let err = parse_execute(
6034            r#"@settings(experimentalFeatures = allow)
6035
6036import "std::solver""#,
6037        )
6038        .await
6039        .unwrap_err();
6040        assert!(
6041            err.message().contains("only available inside sketch blocks"),
6042            "Error message: '{}'",
6043            err.message()
6044        );
6045    }
6046
6047    #[tokio::test(flavor = "multi_thread")]
6048    async fn cannot_solid_extrude_an_open_profile() {
6049        // This should fail during mock execution, because KCL should catch
6050        // that the profile is not closed.
6051        let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
6052        let program = crate::Program::parse_no_errs(&code).expect("should parse");
6053        let exec_ctxt = ExecutorContext::new_mock(None).await;
6054        let mut exec_state = ExecState::new(&exec_ctxt);
6055
6056        let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
6057        assert!(matches!(err, KclError::Semantic { .. }));
6058        exec_ctxt.close().await;
6059    }
6060}