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