Skip to main content

kcl_lib/execution/
exec_ast.rs

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