Skip to main content

kcl_lib/execution/
exec_ast.rs

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