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