Skip to main content

kcl_lib/execution/
exec_ast.rs

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