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                    exec_state,
2813                    args,
2814                )
2815                .await?;
2816                return Ok(result.into());
2817            }
2818        } else if self.operator == BinaryOperator::Sub {
2819            // Check if we have solids.
2820            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2821                let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
2822                let result = crate::std::csg::inner_subtract(
2823                    vec![*left.clone()],
2824                    vec![*right.clone()],
2825                    Default::default(),
2826                    exec_state,
2827                    args,
2828                )
2829                .await?;
2830                return Ok(result.into());
2831            }
2832        } else if self.operator == BinaryOperator::And
2833            && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2834        {
2835            // Check if we have solids.
2836            let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
2837            let result = crate::std::csg::inner_intersect(
2838                vec![*left.clone(), *right.clone()],
2839                Default::default(),
2840                exec_state,
2841                args,
2842            )
2843            .await?;
2844            return Ok(result.into());
2845        }
2846
2847        // Check if we are doing logical operations on booleans.
2848        if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2849            let KclValue::Bool { value: left_value, .. } = left_value else {
2850                return Err(KclError::new_semantic(KclErrorDetails::new(
2851                    format!(
2852                        "Cannot apply logical operator to non-boolean value: {}",
2853                        left_value.human_friendly_type()
2854                    ),
2855                    vec![self.left.clone().into()],
2856                )));
2857            };
2858            let KclValue::Bool { value: right_value, .. } = right_value else {
2859                return Err(KclError::new_semantic(KclErrorDetails::new(
2860                    format!(
2861                        "Cannot apply logical operator to non-boolean value: {}",
2862                        right_value.human_friendly_type()
2863                    ),
2864                    vec![self.right.clone().into()],
2865                )));
2866            };
2867            let raw_value = match self.operator {
2868                BinaryOperator::Or => left_value || right_value,
2869                BinaryOperator::And => left_value && right_value,
2870                _ => unreachable!(),
2871            };
2872            return Ok(KclValue::Bool { value: raw_value, meta });
2873        }
2874
2875        // Check if we're doing equivalence in sketch mode.
2876        if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2877            match (&left_value, &right_value) {
2878                // Same sketch variables.
2879                (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2880                    if left_value.id == right_value.id =>
2881                {
2882                    return Ok(KclValue::Bool { value: true, meta });
2883                }
2884                // Different sketch variables.
2885                (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
2886                    let constraint = Constraint::ScalarEqual(
2887                        var0.id.to_constraint_id(self.as_source_range())?,
2888                        var1.id.to_constraint_id(self.as_source_range())?,
2889                    );
2890                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2891                        let message = "Being inside a sketch block should have already been checked above".to_owned();
2892                        debug_assert!(false, "{}", &message);
2893                        return Err(internal_err(message, self));
2894                    };
2895                    sketch_block_state.solver_constraints.push(constraint);
2896                    return Ok(KclValue::Bool { value: true, meta });
2897                }
2898                // One sketch variable, one number.
2899                (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2900                | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2901                    let number_value = normalize_to_solver_distance_unit(
2902                        input_number,
2903                        input_number.into(),
2904                        exec_state,
2905                        "fixed constraint value",
2906                    )?;
2907                    let Some(n) = number_value.as_ty_f64() else {
2908                        let message = format!(
2909                            "Expected number after coercion, but found {}",
2910                            number_value.human_friendly_type()
2911                        );
2912                        debug_assert!(false, "{}", &message);
2913                        return Err(internal_err(message, self));
2914                    };
2915                    let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2916                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2917                        let message = "Being inside a sketch block should have already been checked above".to_owned();
2918                        debug_assert!(false, "{}", &message);
2919                        return Err(internal_err(message, self));
2920                    };
2921                    sketch_block_state.solver_constraints.push(constraint);
2922                    return Ok(KclValue::Bool { value: true, meta });
2923                }
2924                // One sketch constraint, one number.
2925                (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
2926                | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
2927                    let number_value = match constraint.kind {
2928                        // These constraint kinds expect the RHS to be an angle.
2929                        SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
2930                            input_number,
2931                            input_number.into(),
2932                            exec_state,
2933                            "fixed constraint value",
2934                        )?,
2935                        // These constraint kinds expect the RHS to be a distance.
2936                        SketchConstraintKind::Distance { .. }
2937                        | SketchConstraintKind::Radius { .. }
2938                        | SketchConstraintKind::Diameter { .. }
2939                        | SketchConstraintKind::HorizontalDistance { .. }
2940                        | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
2941                            input_number,
2942                            input_number.into(),
2943                            exec_state,
2944                            "fixed constraint value",
2945                        )?,
2946                    };
2947                    let Some(n) = number_value.as_ty_f64() else {
2948                        let message = format!(
2949                            "Expected number after coercion, but found {}",
2950                            number_value.human_friendly_type()
2951                        );
2952                        debug_assert!(false, "{}", &message);
2953                        return Err(internal_err(message, self));
2954                    };
2955                    // Recast the number side of == to get the source expression text.
2956                    #[cfg(feature = "artifact-graph")]
2957                    let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
2958                        &self.right
2959                    } else {
2960                        &self.left
2961                    };
2962                    #[cfg(feature = "artifact-graph")]
2963                    let source = {
2964                        use crate::unparser::ExprContext;
2965                        let mut buf = String::new();
2966                        number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
2967                        crate::frontend::sketch::ConstraintSource {
2968                            expr: buf,
2969                            is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
2970                        }
2971                    };
2972
2973                    match &constraint.kind {
2974                        SketchConstraintKind::Angle { line0, line1 } => {
2975                            let range = self.as_source_range();
2976                            // Line 0 is points A and B.
2977                            // Line 1 is points C and D.
2978                            let ax = line0.vars[0].x.to_constraint_id(range)?;
2979                            let ay = line0.vars[0].y.to_constraint_id(range)?;
2980                            let bx = line0.vars[1].x.to_constraint_id(range)?;
2981                            let by = line0.vars[1].y.to_constraint_id(range)?;
2982                            let cx = line1.vars[0].x.to_constraint_id(range)?;
2983                            let cy = line1.vars[0].y.to_constraint_id(range)?;
2984                            let dx = line1.vars[1].x.to_constraint_id(range)?;
2985                            let dy = line1.vars[1].y.to_constraint_id(range)?;
2986                            let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(
2987                                ezpz::datatypes::inputs::DatumPoint::new_xy(ax, ay),
2988                                ezpz::datatypes::inputs::DatumPoint::new_xy(bx, by),
2989                            );
2990                            let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(
2991                                ezpz::datatypes::inputs::DatumPoint::new_xy(cx, cy),
2992                                ezpz::datatypes::inputs::DatumPoint::new_xy(dx, dy),
2993                            );
2994                            let desired_angle = match n.ty {
2995                                NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Degrees))
2996                                | NumericType::Default {
2997                                    len: _,
2998                                    angle: kcmc::units::UnitAngle::Degrees,
2999                                } => ezpz::datatypes::Angle::from_degrees(n.n),
3000                                NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Radians))
3001                                | NumericType::Default {
3002                                    len: _,
3003                                    angle: kcmc::units::UnitAngle::Radians,
3004                                } => ezpz::datatypes::Angle::from_radians(n.n),
3005                                NumericType::Known(crate::exec::UnitType::Count)
3006                                | NumericType::Known(crate::exec::UnitType::GenericLength)
3007                                | NumericType::Known(crate::exec::UnitType::GenericAngle)
3008                                | NumericType::Known(crate::exec::UnitType::Length(_))
3009                                | NumericType::Unknown
3010                                | NumericType::Any => {
3011                                    let message = format!("Expected angle but found {:?}", n);
3012                                    debug_assert!(false, "{}", &message);
3013                                    return Err(internal_err(message, self));
3014                                }
3015                            };
3016                            let solver_constraint = Constraint::LinesAtAngle(
3017                                solver_line0,
3018                                solver_line1,
3019                                ezpz::datatypes::AngleKind::Other(desired_angle),
3020                            );
3021                            #[cfg(feature = "artifact-graph")]
3022                            let constraint_id = exec_state.next_object_id();
3023                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3024                                let message =
3025                                    "Being inside a sketch block should have already been checked above".to_owned();
3026                                debug_assert!(false, "{}", &message);
3027                                return Err(internal_err(message, self));
3028                            };
3029                            sketch_block_state.solver_constraints.push(solver_constraint);
3030                            #[cfg(feature = "artifact-graph")]
3031                            {
3032                                use crate::execution::Artifact;
3033                                use crate::execution::CodeRef;
3034                                use crate::execution::SketchBlockConstraint;
3035                                use crate::execution::SketchBlockConstraintType;
3036                                use crate::front::Angle;
3037
3038                                let Some(sketch_id) = sketch_block_state.sketch_id else {
3039                                    let message = "Sketch id missing for constraint artifact".to_owned();
3040                                    debug_assert!(false, "{}", &message);
3041                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3042                                };
3043                                let sketch_constraint = crate::front::Constraint::Angle(Angle {
3044                                    lines: vec![line0.object_id, line1.object_id],
3045                                    angle: n.try_into().map_err(|_| {
3046                                        internal_err("Failed to convert angle units numeric suffix:", range)
3047                                    })?,
3048                                    source,
3049                                });
3050                                sketch_block_state.sketch_constraints.push(constraint_id);
3051                                let artifact_id = exec_state.next_artifact_id();
3052                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3053                                    id: artifact_id,
3054                                    sketch_id,
3055                                    constraint_id,
3056                                    constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3057                                    code_ref: CodeRef::placeholder(range),
3058                                }));
3059                                exec_state.add_scene_object(
3060                                    Object {
3061                                        id: constraint_id,
3062                                        kind: ObjectKind::Constraint {
3063                                            constraint: sketch_constraint,
3064                                        },
3065                                        label: Default::default(),
3066                                        comments: Default::default(),
3067                                        artifact_id,
3068                                        source: range.into(),
3069                                    },
3070                                    range,
3071                                );
3072                            }
3073                        }
3074                        SketchConstraintKind::Distance { points } => {
3075                            let range = self.as_source_range();
3076                            let p0 = &points[0];
3077                            let p1 = &points[1];
3078                            let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3079                                p0.vars.x.to_constraint_id(range)?,
3080                                p0.vars.y.to_constraint_id(range)?,
3081                            );
3082                            let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3083                                p1.vars.x.to_constraint_id(range)?,
3084                                p1.vars.y.to_constraint_id(range)?,
3085                            );
3086                            let solver_constraint = Constraint::Distance(solver_pt0, solver_pt1, n.n);
3087
3088                            #[cfg(feature = "artifact-graph")]
3089                            let constraint_id = exec_state.next_object_id();
3090                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3091                                let message =
3092                                    "Being inside a sketch block should have already been checked above".to_owned();
3093                                debug_assert!(false, "{}", &message);
3094                                return Err(internal_err(message, self));
3095                            };
3096                            sketch_block_state.solver_constraints.push(solver_constraint);
3097                            #[cfg(feature = "artifact-graph")]
3098                            {
3099                                use crate::execution::Artifact;
3100                                use crate::execution::CodeRef;
3101                                use crate::execution::SketchBlockConstraint;
3102                                use crate::execution::SketchBlockConstraintType;
3103                                use crate::front::Distance;
3104
3105                                let Some(sketch_id) = sketch_block_state.sketch_id else {
3106                                    let message = "Sketch id missing for constraint artifact".to_owned();
3107                                    debug_assert!(false, "{}", &message);
3108                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3109                                };
3110                                let sketch_constraint = crate::front::Constraint::Distance(Distance {
3111                                    points: vec![p0.object_id, p1.object_id],
3112                                    distance: n.try_into().map_err(|_| {
3113                                        internal_err("Failed to convert distance units numeric suffix:", range)
3114                                    })?,
3115                                    source,
3116                                });
3117                                sketch_block_state.sketch_constraints.push(constraint_id);
3118                                let artifact_id = exec_state.next_artifact_id();
3119                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3120                                    id: artifact_id,
3121                                    sketch_id,
3122                                    constraint_id,
3123                                    constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3124                                    code_ref: CodeRef::placeholder(range),
3125                                }));
3126                                exec_state.add_scene_object(
3127                                    Object {
3128                                        id: constraint_id,
3129                                        kind: ObjectKind::Constraint {
3130                                            constraint: sketch_constraint,
3131                                        },
3132                                        label: Default::default(),
3133                                        comments: Default::default(),
3134                                        artifact_id,
3135                                        source: range.into(),
3136                                    },
3137                                    range,
3138                                );
3139                            }
3140                        }
3141                        SketchConstraintKind::Radius { points } | SketchConstraintKind::Diameter { points } => {
3142                            #[derive(Clone, Copy)]
3143                            enum CircularSegmentConstraintTarget {
3144                                Arc {
3145                                    #[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
3146                                    object_id: ObjectId,
3147                                    end: [crate::execution::SketchVarId; 2],
3148                                },
3149                                Circle {
3150                                    #[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
3151                                    object_id: ObjectId,
3152                                },
3153                            }
3154
3155                            fn sketch_var_initial_value(
3156                                sketch_vars: &[KclValue],
3157                                id: crate::execution::SketchVarId,
3158                                exec_state: &mut ExecState,
3159                                range: SourceRange,
3160                            ) -> Result<f64, KclError> {
3161                                sketch_vars
3162                                    .get(id.0)
3163                                    .and_then(KclValue::as_sketch_var)
3164                                    .map(|sketch_var| {
3165                                        sketch_var
3166                                            .initial_value_to_solver_units(
3167                                                exec_state,
3168                                                range,
3169                                                "circle radius initial value",
3170                                            )
3171                                            .map(|value| value.n)
3172                                    })
3173                                    .transpose()?
3174                                    .ok_or_else(|| {
3175                                        internal_err(
3176                                            format!("Missing sketch variable initial value for id {}", id.0),
3177                                            range,
3178                                        )
3179                                    })
3180                            }
3181
3182                            let range = self.as_source_range();
3183                            let center = &points[0];
3184                            let start = &points[1];
3185                            let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
3186                                return Err(internal_err(
3187                                    "Being inside a sketch block should have already been checked above",
3188                                    self,
3189                                ));
3190                            };
3191                            let (constraint_name, is_diameter) = match &constraint.kind {
3192                                SketchConstraintKind::Radius { .. } => ("radius", false),
3193                                SketchConstraintKind::Diameter { .. } => ("diameter", true),
3194                                _ => unreachable!(),
3195                            };
3196                            let sketch_vars = sketch_block_state.sketch_vars.clone();
3197                            let target_segment = sketch_block_state
3198                                .needed_by_engine
3199                                .iter()
3200                                .find_map(|seg| match &seg.kind {
3201                                    UnsolvedSegmentKind::Arc {
3202                                        center_object_id,
3203                                        start_object_id,
3204                                        end,
3205                                        ..
3206                                    } if *center_object_id == center.object_id
3207                                        && *start_object_id == start.object_id =>
3208                                    {
3209                                        let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
3210                                            (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
3211                                                (*end_x, *end_y)
3212                                            }
3213                                            _ => return None,
3214                                        };
3215                                        Some(CircularSegmentConstraintTarget::Arc {
3216                                            object_id: seg.object_id,
3217                                            end: [end_x_var, end_y_var],
3218                                        })
3219                                    }
3220                                    UnsolvedSegmentKind::Circle {
3221                                        center_object_id,
3222                                        start_object_id,
3223                                        ..
3224                                    } if *center_object_id == center.object_id
3225                                        && *start_object_id == start.object_id =>
3226                                    {
3227                                        Some(CircularSegmentConstraintTarget::Circle {
3228                                            object_id: seg.object_id,
3229                                        })
3230                                    }
3231                                    _ => None,
3232                                })
3233                                .ok_or_else(|| {
3234                                    internal_err(
3235                                        format!("Could not find circular segment for {} constraint", constraint_name),
3236                                        range,
3237                                    )
3238                                })?;
3239                            let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
3240                            let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3241                                center.vars.x.to_constraint_id(range)?,
3242                                center.vars.y.to_constraint_id(range)?,
3243                            );
3244                            let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3245                                start.vars.x.to_constraint_id(range)?,
3246                                start.vars.y.to_constraint_id(range)?,
3247                            );
3248                            let solver_constraint = match target_segment {
3249                                CircularSegmentConstraintTarget::Arc { end, .. } => {
3250                                    let solver_arc = ezpz::datatypes::inputs::DatumCircularArc {
3251                                        center: center_point,
3252                                        start: start_point,
3253                                        end: ezpz::datatypes::inputs::DatumPoint::new_xy(
3254                                            end[0].to_constraint_id(range)?,
3255                                            end[1].to_constraint_id(range)?,
3256                                        ),
3257                                    };
3258                                    Constraint::ArcRadius(solver_arc, radius_value)
3259                                }
3260                                CircularSegmentConstraintTarget::Circle { .. } => {
3261                                    let sketch_var_ty = solver_numeric_type(exec_state);
3262                                    let start_x =
3263                                        sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
3264                                    let start_y =
3265                                        sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
3266                                    let center_x =
3267                                        sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
3268                                    let center_y =
3269                                        sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
3270
3271                                    // Get the hypotenuse between the two points, the radius
3272                                    let radius_initial_value = (start_x - center_x).hypot(start_y - center_y);
3273
3274                                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3275                                        let message =
3276                                            "Being inside a sketch block should have already been checked above"
3277                                                .to_owned();
3278                                        debug_assert!(false, "{}", &message);
3279                                        return Err(internal_err(message, self));
3280                                    };
3281                                    let radius_id = sketch_block_state.next_sketch_var_id();
3282                                    sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3283                                        value: Box::new(crate::execution::SketchVar {
3284                                            id: radius_id,
3285                                            initial_value: radius_initial_value,
3286                                            ty: sketch_var_ty,
3287                                            meta: vec![],
3288                                        }),
3289                                    });
3290                                    let radius =
3291                                        ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
3292                                    let solver_circle = ezpz::datatypes::inputs::DatumCircle {
3293                                        center: center_point,
3294                                        radius,
3295                                    };
3296                                    sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
3297                                        start_point,
3298                                        center_point,
3299                                        radius,
3300                                    ));
3301                                    Constraint::CircleRadius(solver_circle, radius_value)
3302                                }
3303                            };
3304
3305                            #[cfg(feature = "artifact-graph")]
3306                            let constraint_id = exec_state.next_object_id();
3307                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3308                                let message =
3309                                    "Being inside a sketch block should have already been checked above".to_owned();
3310                                debug_assert!(false, "{}", &message);
3311                                return Err(internal_err(message, self));
3312                            };
3313                            sketch_block_state.solver_constraints.push(solver_constraint);
3314                            #[cfg(feature = "artifact-graph")]
3315                            {
3316                                use crate::execution::Artifact;
3317                                use crate::execution::CodeRef;
3318                                use crate::execution::SketchBlockConstraint;
3319                                use crate::execution::SketchBlockConstraintType;
3320                                let segment_object_id = match target_segment {
3321                                    CircularSegmentConstraintTarget::Arc { object_id, .. }
3322                                    | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
3323                                };
3324
3325                                let constraint = if is_diameter {
3326                                    use crate::frontend::sketch::Diameter;
3327                                    crate::front::Constraint::Diameter(Diameter {
3328                                        arc: segment_object_id,
3329                                        diameter: n.try_into().map_err(|_| {
3330                                            internal_err("Failed to convert diameter units numeric suffix:", range)
3331                                        })?,
3332                                        source,
3333                                    })
3334                                } else {
3335                                    use crate::frontend::sketch::Radius;
3336                                    crate::front::Constraint::Radius(Radius {
3337                                        arc: segment_object_id,
3338                                        radius: n.try_into().map_err(|_| {
3339                                            internal_err("Failed to convert radius units numeric suffix:", range)
3340                                        })?,
3341                                        source,
3342                                    })
3343                                };
3344                                sketch_block_state.sketch_constraints.push(constraint_id);
3345                                let Some(sketch_id) = sketch_block_state.sketch_id else {
3346                                    let message = "Sketch id missing for constraint artifact".to_owned();
3347                                    debug_assert!(false, "{}", &message);
3348                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3349                                };
3350                                let artifact_id = exec_state.next_artifact_id();
3351                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3352                                    id: artifact_id,
3353                                    sketch_id,
3354                                    constraint_id,
3355                                    constraint_type: SketchBlockConstraintType::from(&constraint),
3356                                    code_ref: CodeRef::placeholder(range),
3357                                }));
3358                                exec_state.add_scene_object(
3359                                    Object {
3360                                        id: constraint_id,
3361                                        kind: ObjectKind::Constraint { constraint },
3362                                        label: Default::default(),
3363                                        comments: Default::default(),
3364                                        artifact_id,
3365                                        source: range.into(),
3366                                    },
3367                                    range,
3368                                );
3369                            }
3370                        }
3371                        SketchConstraintKind::HorizontalDistance { points } => {
3372                            let range = self.as_source_range();
3373                            let p0 = &points[0];
3374                            let p1 = &points[1];
3375                            let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3376                                p0.vars.x.to_constraint_id(range)?,
3377                                p0.vars.y.to_constraint_id(range)?,
3378                            );
3379                            let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3380                                p1.vars.x.to_constraint_id(range)?,
3381                                p1.vars.y.to_constraint_id(range)?,
3382                            );
3383                            // Horizontal distance: p1.x - p0.x = n
3384                            // Note: EZPZ's HorizontalDistance(p0, p1, d) means p0.x - p1.x = d
3385                            // So we swap the points to get p1.x - p0.x = n
3386                            let solver_constraint = ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n);
3387
3388                            #[cfg(feature = "artifact-graph")]
3389                            let constraint_id = exec_state.next_object_id();
3390                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3391                                let message =
3392                                    "Being inside a sketch block should have already been checked above".to_owned();
3393                                debug_assert!(false, "{}", &message);
3394                                return Err(internal_err(message, self));
3395                            };
3396                            sketch_block_state.solver_constraints.push(solver_constraint);
3397                            #[cfg(feature = "artifact-graph")]
3398                            {
3399                                use crate::execution::Artifact;
3400                                use crate::execution::CodeRef;
3401                                use crate::execution::SketchBlockConstraint;
3402                                use crate::execution::SketchBlockConstraintType;
3403                                use crate::front::Distance;
3404
3405                                let constraint = crate::front::Constraint::HorizontalDistance(Distance {
3406                                    points: vec![p0.object_id, p1.object_id],
3407                                    distance: n.try_into().map_err(|_| {
3408                                        internal_err("Failed to convert distance units numeric suffix:", range)
3409                                    })?,
3410                                    source,
3411                                });
3412                                sketch_block_state.sketch_constraints.push(constraint_id);
3413                                let Some(sketch_id) = sketch_block_state.sketch_id else {
3414                                    let message = "Sketch id missing for constraint artifact".to_owned();
3415                                    debug_assert!(false, "{}", &message);
3416                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3417                                };
3418                                let artifact_id = exec_state.next_artifact_id();
3419                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3420                                    id: artifact_id,
3421                                    sketch_id,
3422                                    constraint_id,
3423                                    constraint_type: SketchBlockConstraintType::from(&constraint),
3424                                    code_ref: CodeRef::placeholder(range),
3425                                }));
3426                                exec_state.add_scene_object(
3427                                    Object {
3428                                        id: constraint_id,
3429                                        kind: ObjectKind::Constraint { constraint },
3430                                        label: Default::default(),
3431                                        comments: Default::default(),
3432                                        artifact_id,
3433                                        source: range.into(),
3434                                    },
3435                                    range,
3436                                );
3437                            }
3438                        }
3439                        SketchConstraintKind::VerticalDistance { points } => {
3440                            let range = self.as_source_range();
3441                            let p0 = &points[0];
3442                            let p1 = &points[1];
3443                            let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3444                                p0.vars.x.to_constraint_id(range)?,
3445                                p0.vars.y.to_constraint_id(range)?,
3446                            );
3447                            let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3448                                p1.vars.x.to_constraint_id(range)?,
3449                                p1.vars.y.to_constraint_id(range)?,
3450                            );
3451                            // Vertical distance: p1.y - p0.y = n
3452                            // Note: EZPZ's VerticalDistance(p0, p1, d) means p0.y - p1.y = d
3453                            // So we swap the points to get p1.y - p0.y = n
3454                            let solver_constraint = ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n);
3455
3456                            #[cfg(feature = "artifact-graph")]
3457                            let constraint_id = exec_state.next_object_id();
3458                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3459                                let message =
3460                                    "Being inside a sketch block should have already been checked above".to_owned();
3461                                debug_assert!(false, "{}", &message);
3462                                return Err(internal_err(message, self));
3463                            };
3464                            sketch_block_state.solver_constraints.push(solver_constraint);
3465                            #[cfg(feature = "artifact-graph")]
3466                            {
3467                                use crate::execution::Artifact;
3468                                use crate::execution::CodeRef;
3469                                use crate::execution::SketchBlockConstraint;
3470                                use crate::execution::SketchBlockConstraintType;
3471                                use crate::front::Distance;
3472
3473                                let constraint = crate::front::Constraint::VerticalDistance(Distance {
3474                                    points: vec![p0.object_id, p1.object_id],
3475                                    distance: n.try_into().map_err(|_| {
3476                                        internal_err("Failed to convert distance units numeric suffix:", range)
3477                                    })?,
3478                                    source,
3479                                });
3480                                sketch_block_state.sketch_constraints.push(constraint_id);
3481                                let Some(sketch_id) = sketch_block_state.sketch_id else {
3482                                    let message = "Sketch id missing for constraint artifact".to_owned();
3483                                    debug_assert!(false, "{}", &message);
3484                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3485                                };
3486                                let artifact_id = exec_state.next_artifact_id();
3487                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3488                                    id: artifact_id,
3489                                    sketch_id,
3490                                    constraint_id,
3491                                    constraint_type: SketchBlockConstraintType::from(&constraint),
3492                                    code_ref: CodeRef::placeholder(range),
3493                                }));
3494                                exec_state.add_scene_object(
3495                                    Object {
3496                                        id: constraint_id,
3497                                        kind: ObjectKind::Constraint { constraint },
3498                                        label: Default::default(),
3499                                        comments: Default::default(),
3500                                        artifact_id,
3501                                        source: range.into(),
3502                                    },
3503                                    range,
3504                                );
3505                            }
3506                        }
3507                    }
3508                    return Ok(KclValue::Bool { value: true, meta });
3509                }
3510                _ => {
3511                    return Err(KclError::new_semantic(KclErrorDetails::new(
3512                        format!(
3513                            "Cannot create an equivalence constraint between values of these types: {} and {}",
3514                            left_value.human_friendly_type(),
3515                            right_value.human_friendly_type()
3516                        ),
3517                        vec![self.into()],
3518                    )));
3519                }
3520            }
3521        }
3522
3523        let left = number_as_f64(&left_value, self.left.clone().into())?;
3524        let right = number_as_f64(&right_value, self.right.clone().into())?;
3525
3526        let value = match self.operator {
3527            BinaryOperator::Add => {
3528                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3529                self.warn_on_unknown(&ty, "Adding", exec_state);
3530                KclValue::Number { value: l + r, meta, ty }
3531            }
3532            BinaryOperator::Sub => {
3533                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3534                self.warn_on_unknown(&ty, "Subtracting", exec_state);
3535                KclValue::Number { value: l - r, meta, ty }
3536            }
3537            BinaryOperator::Mul => {
3538                let (l, r, ty) = NumericType::combine_mul(left, right);
3539                self.warn_on_unknown(&ty, "Multiplying", exec_state);
3540                KclValue::Number { value: l * r, meta, ty }
3541            }
3542            BinaryOperator::Div => {
3543                let (l, r, ty) = NumericType::combine_div(left, right);
3544                self.warn_on_unknown(&ty, "Dividing", exec_state);
3545                KclValue::Number { value: l / r, meta, ty }
3546            }
3547            BinaryOperator::Mod => {
3548                let (l, r, ty) = NumericType::combine_mod(left, right);
3549                self.warn_on_unknown(&ty, "Modulo of", exec_state);
3550                KclValue::Number { value: l % r, meta, ty }
3551            }
3552            BinaryOperator::Pow => KclValue::Number {
3553                value: left.n.powf(right.n),
3554                meta,
3555                ty: exec_state.current_default_units(),
3556            },
3557            BinaryOperator::Neq => {
3558                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3559                self.warn_on_unknown(&ty, "Comparing", exec_state);
3560                KclValue::Bool { value: l != r, meta }
3561            }
3562            BinaryOperator::Gt => {
3563                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3564                self.warn_on_unknown(&ty, "Comparing", exec_state);
3565                KclValue::Bool { value: l > r, meta }
3566            }
3567            BinaryOperator::Gte => {
3568                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3569                self.warn_on_unknown(&ty, "Comparing", exec_state);
3570                KclValue::Bool { value: l >= r, meta }
3571            }
3572            BinaryOperator::Lt => {
3573                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3574                self.warn_on_unknown(&ty, "Comparing", exec_state);
3575                KclValue::Bool { value: l < r, meta }
3576            }
3577            BinaryOperator::Lte => {
3578                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3579                self.warn_on_unknown(&ty, "Comparing", exec_state);
3580                KclValue::Bool { value: l <= r, meta }
3581            }
3582            BinaryOperator::Eq => {
3583                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3584                self.warn_on_unknown(&ty, "Comparing", exec_state);
3585                KclValue::Bool { value: l == r, meta }
3586            }
3587            BinaryOperator::And | BinaryOperator::Or => unreachable!(),
3588        };
3589
3590        Ok(value)
3591    }
3592
3593    fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
3594        internal_err("missing result while evaluating binary expression", node)
3595    }
3596
3597    fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
3598        if ty == &NumericType::Unknown {
3599            let sr = self.as_source_range();
3600            exec_state.clear_units_warnings(&sr);
3601            let mut err = CompilationError::err(
3602                sr,
3603                format!(
3604                    "{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)`."
3605                ),
3606            );
3607            err.tag = crate::errors::Tag::UnknownNumericUnits;
3608            exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
3609        }
3610    }
3611}
3612
3613impl Node<UnaryExpression> {
3614    pub(super) async fn get_result(
3615        &self,
3616        exec_state: &mut ExecState,
3617        ctx: &ExecutorContext,
3618    ) -> Result<KclValueControlFlow, KclError> {
3619        match self.operator {
3620            UnaryOperator::Not => {
3621                let value = self.argument.get_result(exec_state, ctx).await?;
3622                let value = control_continue!(value);
3623                let KclValue::Bool {
3624                    value: bool_value,
3625                    meta: _,
3626                } = value
3627                else {
3628                    return Err(KclError::new_semantic(KclErrorDetails::new(
3629                        format!(
3630                            "Cannot apply unary operator ! to non-boolean value: {}",
3631                            value.human_friendly_type()
3632                        ),
3633                        vec![self.into()],
3634                    )));
3635                };
3636                let meta = vec![Metadata {
3637                    source_range: self.into(),
3638                }];
3639                let negated = KclValue::Bool {
3640                    value: !bool_value,
3641                    meta,
3642                };
3643
3644                Ok(negated.continue_())
3645            }
3646            UnaryOperator::Neg => {
3647                let value = self.argument.get_result(exec_state, ctx).await?;
3648                let value = control_continue!(value);
3649                let err = || {
3650                    KclError::new_semantic(KclErrorDetails::new(
3651                        format!(
3652                            "You can only negate numbers, planes, or lines, but this is a {}",
3653                            value.human_friendly_type()
3654                        ),
3655                        vec![self.into()],
3656                    ))
3657                };
3658                match &value {
3659                    KclValue::Number { value, ty, .. } => {
3660                        let meta = vec![Metadata {
3661                            source_range: self.into(),
3662                        }];
3663                        Ok(KclValue::Number {
3664                            value: -value,
3665                            meta,
3666                            ty: *ty,
3667                        }
3668                        .continue_())
3669                    }
3670                    KclValue::Plane { value } => {
3671                        let mut plane = value.clone();
3672                        if plane.info.x_axis.x != 0.0 {
3673                            plane.info.x_axis.x *= -1.0;
3674                        }
3675                        if plane.info.x_axis.y != 0.0 {
3676                            plane.info.x_axis.y *= -1.0;
3677                        }
3678                        if plane.info.x_axis.z != 0.0 {
3679                            plane.info.x_axis.z *= -1.0;
3680                        }
3681
3682                        plane.id = exec_state.next_uuid();
3683                        plane.object_id = None;
3684                        Ok(KclValue::Plane { value: plane }.continue_())
3685                    }
3686                    KclValue::Object {
3687                        value: values, meta, ..
3688                    } => {
3689                        // Special-case for negating line-like objects.
3690                        let Some(direction) = values.get("direction") else {
3691                            return Err(err());
3692                        };
3693
3694                        let direction = match direction {
3695                            KclValue::Tuple { value: values, meta } => {
3696                                let values = values
3697                                    .iter()
3698                                    .map(|v| match v {
3699                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3700                                            value: *value * -1.0,
3701                                            ty: *ty,
3702                                            meta: meta.clone(),
3703                                        }),
3704                                        _ => Err(err()),
3705                                    })
3706                                    .collect::<Result<Vec<_>, _>>()?;
3707
3708                                KclValue::Tuple {
3709                                    value: values,
3710                                    meta: meta.clone(),
3711                                }
3712                            }
3713                            KclValue::HomArray {
3714                                value: values,
3715                                ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
3716                            } => {
3717                                let values = values
3718                                    .iter()
3719                                    .map(|v| match v {
3720                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3721                                            value: *value * -1.0,
3722                                            ty: *ty,
3723                                            meta: meta.clone(),
3724                                        }),
3725                                        _ => Err(err()),
3726                                    })
3727                                    .collect::<Result<Vec<_>, _>>()?;
3728
3729                                KclValue::HomArray {
3730                                    value: values,
3731                                    ty: ty.clone(),
3732                                }
3733                            }
3734                            _ => return Err(err()),
3735                        };
3736
3737                        let mut value = values.clone();
3738                        value.insert("direction".to_owned(), direction);
3739                        Ok(KclValue::Object {
3740                            value,
3741                            meta: meta.clone(),
3742                            constrainable: false,
3743                        }
3744                        .continue_())
3745                    }
3746                    _ => Err(err()),
3747                }
3748            }
3749            UnaryOperator::Plus => {
3750                let operand = self.argument.get_result(exec_state, ctx).await?;
3751                let operand = control_continue!(operand);
3752                match operand {
3753                    KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
3754                    _ => Err(KclError::new_semantic(KclErrorDetails::new(
3755                        format!(
3756                            "You can only apply unary + to numbers or planes, but this is a {}",
3757                            operand.human_friendly_type()
3758                        ),
3759                        vec![self.into()],
3760                    ))),
3761                }
3762            }
3763        }
3764    }
3765}
3766
3767pub(crate) async fn execute_pipe_body(
3768    exec_state: &mut ExecState,
3769    body: &[Expr],
3770    source_range: SourceRange,
3771    ctx: &ExecutorContext,
3772) -> Result<KclValueControlFlow, KclError> {
3773    let Some((first, body)) = body.split_first() else {
3774        return Err(KclError::new_semantic(KclErrorDetails::new(
3775            "Pipe expressions cannot be empty".to_owned(),
3776            vec![source_range],
3777        )));
3778    };
3779    // Evaluate the first element in the pipeline.
3780    // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
3781    // they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
3782    // of its own.
3783    let meta = Metadata {
3784        source_range: SourceRange::from(first),
3785    };
3786    let output = ctx
3787        .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
3788        .await?;
3789    let output = control_continue!(output);
3790
3791    // Now that we've evaluated the first child expression in the pipeline, following child expressions
3792    // should use the previous child expression for %.
3793    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
3794    let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
3795    // Evaluate remaining elements.
3796    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
3797    // Restore the previous pipe value.
3798    exec_state.mod_local.pipe_value = previous_pipe_value;
3799
3800    result
3801}
3802
3803/// Execute the tail of a pipe expression.  exec_state.pipe_value must be set by
3804/// the caller.
3805#[async_recursion]
3806async fn inner_execute_pipe_body(
3807    exec_state: &mut ExecState,
3808    body: &[Expr],
3809    ctx: &ExecutorContext,
3810) -> Result<KclValueControlFlow, KclError> {
3811    for expression in body {
3812        if let Expr::TagDeclarator(_) = expression {
3813            return Err(KclError::new_semantic(KclErrorDetails::new(
3814                format!("This cannot be in a PipeExpression: {expression:?}"),
3815                vec![expression.into()],
3816            )));
3817        }
3818        let metadata = Metadata {
3819            source_range: SourceRange::from(expression),
3820        };
3821        let output = ctx
3822            .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
3823            .await?;
3824        let output = control_continue!(output);
3825        exec_state.mod_local.pipe_value = Some(output);
3826    }
3827    // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
3828    let final_output = exec_state.mod_local.pipe_value.take().unwrap();
3829    Ok(final_output.continue_())
3830}
3831
3832impl Node<TagDeclarator> {
3833    pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
3834        let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
3835            value: self.name.clone(),
3836            info: Vec::new(),
3837            meta: vec![Metadata {
3838                source_range: self.into(),
3839            }],
3840        }));
3841
3842        exec_state
3843            .mut_stack()
3844            .add(self.name.clone(), memory_item, self.into())?;
3845
3846        Ok(self.into())
3847    }
3848}
3849
3850impl Node<ArrayExpression> {
3851    #[async_recursion]
3852    pub(super) async fn execute(
3853        &self,
3854        exec_state: &mut ExecState,
3855        ctx: &ExecutorContext,
3856    ) -> Result<KclValueControlFlow, KclError> {
3857        let mut results = Vec::with_capacity(self.elements.len());
3858
3859        for element in &self.elements {
3860            let metadata = Metadata::from(element);
3861            // TODO: Carry statement kind here so that we know if we're
3862            // inside a variable declaration.
3863            let value = ctx
3864                .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
3865                .await?;
3866            let value = control_continue!(value);
3867
3868            results.push(value);
3869        }
3870
3871        Ok(KclValue::HomArray {
3872            value: results,
3873            ty: RuntimeType::Primitive(PrimitiveType::Any),
3874        }
3875        .continue_())
3876    }
3877}
3878
3879impl Node<ArrayRangeExpression> {
3880    #[async_recursion]
3881    pub(super) async fn execute(
3882        &self,
3883        exec_state: &mut ExecState,
3884        ctx: &ExecutorContext,
3885    ) -> Result<KclValueControlFlow, KclError> {
3886        let metadata = Metadata::from(&self.start_element);
3887        let start_val = ctx
3888            .execute_expr(
3889                &self.start_element,
3890                exec_state,
3891                &metadata,
3892                &[],
3893                StatementKind::Expression,
3894            )
3895            .await?;
3896        let start_val = control_continue!(start_val);
3897        let start = start_val
3898            .as_ty_f64()
3899            .ok_or(KclError::new_semantic(KclErrorDetails::new(
3900                format!(
3901                    "Expected number for range start but found {}",
3902                    start_val.human_friendly_type()
3903                ),
3904                vec![self.into()],
3905            )))?;
3906        let metadata = Metadata::from(&self.end_element);
3907        let end_val = ctx
3908            .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
3909            .await?;
3910        let end_val = control_continue!(end_val);
3911        let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
3912            format!(
3913                "Expected number for range end but found {}",
3914                end_val.human_friendly_type()
3915            ),
3916            vec![self.into()],
3917        )))?;
3918
3919        let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
3920        let Some(start) = crate::try_f64_to_i64(start) else {
3921            return Err(KclError::new_semantic(KclErrorDetails::new(
3922                format!("Range start must be an integer, but found {start}"),
3923                vec![self.into()],
3924            )));
3925        };
3926        let Some(end) = crate::try_f64_to_i64(end) else {
3927            return Err(KclError::new_semantic(KclErrorDetails::new(
3928                format!("Range end must be an integer, but found {end}"),
3929                vec![self.into()],
3930            )));
3931        };
3932
3933        if end < start {
3934            return Err(KclError::new_semantic(KclErrorDetails::new(
3935                format!("Range start is greater than range end: {start} .. {end}"),
3936                vec![self.into()],
3937            )));
3938        }
3939
3940        let range: Vec<_> = if self.end_inclusive {
3941            (start..=end).collect()
3942        } else {
3943            (start..end).collect()
3944        };
3945
3946        let meta = vec![Metadata {
3947            source_range: self.into(),
3948        }];
3949
3950        Ok(KclValue::HomArray {
3951            value: range
3952                .into_iter()
3953                .map(|num| KclValue::Number {
3954                    value: num as f64,
3955                    ty,
3956                    meta: meta.clone(),
3957                })
3958                .collect(),
3959            ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
3960        }
3961        .continue_())
3962    }
3963}
3964
3965impl Node<ObjectExpression> {
3966    #[async_recursion]
3967    pub(super) async fn execute(
3968        &self,
3969        exec_state: &mut ExecState,
3970        ctx: &ExecutorContext,
3971    ) -> Result<KclValueControlFlow, KclError> {
3972        let mut object = HashMap::with_capacity(self.properties.len());
3973        for property in &self.properties {
3974            let metadata = Metadata::from(&property.value);
3975            let result = ctx
3976                .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
3977                .await?;
3978            let result = control_continue!(result);
3979            object.insert(property.key.name.clone(), result);
3980        }
3981
3982        Ok(KclValue::Object {
3983            value: object,
3984            meta: vec![Metadata {
3985                source_range: self.into(),
3986            }],
3987            constrainable: false,
3988        }
3989        .continue_())
3990    }
3991}
3992
3993fn article_for<S: AsRef<str>>(s: S) -> &'static str {
3994    // '[' is included since it's an array.
3995    if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
3996        "an"
3997    } else {
3998        "a"
3999    }
4000}
4001
4002fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
4003    v.as_ty_f64().ok_or_else(|| {
4004        let actual_type = v.human_friendly_type();
4005        KclError::new_semantic(KclErrorDetails::new(
4006            format!("Expected a number, but found {actual_type}",),
4007            vec![source_range],
4008        ))
4009    })
4010}
4011
4012impl Node<IfExpression> {
4013    #[async_recursion]
4014    pub(super) async fn get_result(
4015        &self,
4016        exec_state: &mut ExecState,
4017        ctx: &ExecutorContext,
4018    ) -> Result<KclValueControlFlow, KclError> {
4019        // Check the `if` branch.
4020        let cond_value = ctx
4021            .execute_expr(
4022                &self.cond,
4023                exec_state,
4024                &Metadata::from(self),
4025                &[],
4026                StatementKind::Expression,
4027            )
4028            .await?;
4029        let cond_value = control_continue!(cond_value);
4030        if cond_value.get_bool()? {
4031            let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
4032            // Block must end in an expression, so this has to be Some.
4033            // Enforced by the parser.
4034            // See https://github.com/KittyCAD/modeling-app/issues/4015
4035            return Ok(block_result.unwrap());
4036        }
4037
4038        // Check any `else if` branches.
4039        for else_if in &self.else_ifs {
4040            let cond_value = ctx
4041                .execute_expr(
4042                    &else_if.cond,
4043                    exec_state,
4044                    &Metadata::from(self),
4045                    &[],
4046                    StatementKind::Expression,
4047                )
4048                .await?;
4049            let cond_value = control_continue!(cond_value);
4050            if cond_value.get_bool()? {
4051                let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
4052                // Block must end in an expression, so this has to be Some.
4053                // Enforced by the parser.
4054                // See https://github.com/KittyCAD/modeling-app/issues/4015
4055                return Ok(block_result.unwrap());
4056            }
4057        }
4058
4059        // Run the final `else` branch.
4060        ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
4061            .await
4062            .map(|expr| expr.unwrap())
4063    }
4064}
4065
4066#[derive(Debug)]
4067enum Property {
4068    UInt(usize),
4069    String(String),
4070}
4071
4072impl Property {
4073    #[allow(clippy::too_many_arguments)]
4074    async fn try_from<'a>(
4075        computed: bool,
4076        value: Expr,
4077        exec_state: &mut ExecState,
4078        sr: SourceRange,
4079        ctx: &ExecutorContext,
4080        metadata: &Metadata,
4081        annotations: &[Node<Annotation>],
4082        statement_kind: StatementKind<'a>,
4083    ) -> Result<Self, KclError> {
4084        let property_sr = vec![sr];
4085        if !computed {
4086            let Expr::Name(identifier) = value else {
4087                // Should actually be impossible because the parser would reject it.
4088                return Err(KclError::new_semantic(KclErrorDetails::new(
4089                    "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
4090                        .to_owned(),
4091                    property_sr,
4092                )));
4093            };
4094            return Ok(Property::String(identifier.to_string()));
4095        }
4096
4097        let prop_value = ctx
4098            .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
4099            .await?;
4100        let prop_value = match prop_value.control {
4101            ControlFlowKind::Continue => prop_value.into_value(),
4102            ControlFlowKind::Exit => {
4103                let message = "Early return inside array brackets is currently not supported".to_owned();
4104                debug_assert!(false, "{}", &message);
4105                return Err(internal_err(message, sr));
4106            }
4107        };
4108        match prop_value {
4109            KclValue::Number { value, ty, meta: _ } => {
4110                if !matches!(
4111                    ty,
4112                    NumericType::Unknown
4113                        | NumericType::Default { .. }
4114                        | NumericType::Known(crate::exec::UnitType::Count)
4115                ) {
4116                    return Err(KclError::new_semantic(KclErrorDetails::new(
4117                        format!(
4118                            "{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"
4119                        ),
4120                        property_sr,
4121                    )));
4122                }
4123                if let Some(x) = crate::try_f64_to_usize(value) {
4124                    Ok(Property::UInt(x))
4125                } else {
4126                    Err(KclError::new_semantic(KclErrorDetails::new(
4127                        format!("{value} is not a valid index, indices must be whole numbers >= 0"),
4128                        property_sr,
4129                    )))
4130                }
4131            }
4132            _ => Err(KclError::new_semantic(KclErrorDetails::new(
4133                "Only numbers (>= 0) can be indexes".to_owned(),
4134                vec![sr],
4135            ))),
4136        }
4137    }
4138}
4139
4140impl Property {
4141    fn type_name(&self) -> &'static str {
4142        match self {
4143            Property::UInt(_) => "number",
4144            Property::String(_) => "string",
4145        }
4146    }
4147}
4148
4149impl Node<PipeExpression> {
4150    #[async_recursion]
4151    pub(super) async fn get_result(
4152        &self,
4153        exec_state: &mut ExecState,
4154        ctx: &ExecutorContext,
4155    ) -> Result<KclValueControlFlow, KclError> {
4156        execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
4157    }
4158}
4159
4160#[cfg(test)]
4161mod test {
4162    use std::sync::Arc;
4163
4164    use tokio::io::AsyncWriteExt;
4165
4166    use super::*;
4167    use crate::ExecutorSettings;
4168    use crate::errors::Severity;
4169    use crate::exec::UnitType;
4170    use crate::execution::ContextType;
4171    use crate::execution::parse_execute;
4172
4173    #[tokio::test(flavor = "multi_thread")]
4174    async fn ascription() {
4175        let program = r#"
4176a = 42: number
4177b = a: number
4178p = {
4179  origin = { x = 0, y = 0, z = 0 },
4180  xAxis = { x = 1, y = 0, z = 0 },
4181  yAxis = { x = 0, y = 1, z = 0 },
4182  zAxis = { x = 0, y = 0, z = 1 }
4183}: Plane
4184arr1 = [42]: [number(cm)]
4185"#;
4186
4187        let result = parse_execute(program).await.unwrap();
4188        let mem = result.exec_state.stack();
4189        assert!(matches!(
4190            mem.memory
4191                .get_from("p", result.mem_env, SourceRange::default(), 0)
4192                .unwrap(),
4193            KclValue::Plane { .. }
4194        ));
4195        let arr1 = mem
4196            .memory
4197            .get_from("arr1", result.mem_env, SourceRange::default(), 0)
4198            .unwrap();
4199        if let KclValue::HomArray { value, ty } = arr1 {
4200            assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
4201            assert_eq!(
4202                *ty,
4203                RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
4204            );
4205            // Compare, ignoring meta.
4206            if let KclValue::Number { value, ty, .. } = &value[0] {
4207                // It should not convert units.
4208                assert_eq!(*value, 42.0);
4209                assert_eq!(
4210                    *ty,
4211                    NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
4212                );
4213            } else {
4214                panic!("Expected a number; found {:?}", value[0]);
4215            }
4216        } else {
4217            panic!("Expected HomArray; found {arr1:?}");
4218        }
4219
4220        let program = r#"
4221a = 42: string
4222"#;
4223        let result = parse_execute(program).await;
4224        let err = result.unwrap_err();
4225        assert!(
4226            err.to_string()
4227                .contains("could not coerce a number (with type `number`) to type `string`"),
4228            "Expected error but found {err:?}"
4229        );
4230
4231        let program = r#"
4232a = 42: Plane
4233"#;
4234        let result = parse_execute(program).await;
4235        let err = result.unwrap_err();
4236        assert!(
4237            err.to_string()
4238                .contains("could not coerce a number (with type `number`) to type `Plane`"),
4239            "Expected error but found {err:?}"
4240        );
4241
4242        let program = r#"
4243arr = [0]: [string]
4244"#;
4245        let result = parse_execute(program).await;
4246        let err = result.unwrap_err();
4247        assert!(
4248            err.to_string().contains(
4249                "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
4250            ),
4251            "Expected error but found {err:?}"
4252        );
4253
4254        let program = r#"
4255mixedArr = [0, "a"]: [number(mm)]
4256"#;
4257        let result = parse_execute(program).await;
4258        let err = result.unwrap_err();
4259        assert!(
4260            err.to_string().contains(
4261                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4262            ),
4263            "Expected error but found {err:?}"
4264        );
4265
4266        let program = r#"
4267mixedArr = [0, "a"]: [mm]
4268"#;
4269        let result = parse_execute(program).await;
4270        let err = result.unwrap_err();
4271        assert!(
4272            err.to_string().contains(
4273                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4274            ),
4275            "Expected error but found {err:?}"
4276        );
4277    }
4278
4279    #[tokio::test(flavor = "multi_thread")]
4280    async fn neg_plane() {
4281        let program = r#"
4282p = {
4283  origin = { x = 0, y = 0, z = 0 },
4284  xAxis = { x = 1, y = 0, z = 0 },
4285  yAxis = { x = 0, y = 1, z = 0 },
4286}: Plane
4287p2 = -p
4288"#;
4289
4290        let result = parse_execute(program).await.unwrap();
4291        let mem = result.exec_state.stack();
4292        match mem
4293            .memory
4294            .get_from("p2", result.mem_env, SourceRange::default(), 0)
4295            .unwrap()
4296        {
4297            KclValue::Plane { value } => {
4298                assert_eq!(value.info.x_axis.x, -1.0);
4299                assert_eq!(value.info.x_axis.y, 0.0);
4300                assert_eq!(value.info.x_axis.z, 0.0);
4301            }
4302            _ => unreachable!(),
4303        }
4304    }
4305
4306    #[tokio::test(flavor = "multi_thread")]
4307    async fn multiple_returns() {
4308        let program = r#"fn foo() {
4309  return 0
4310  return 42
4311}
4312
4313a = foo()
4314"#;
4315
4316        let result = parse_execute(program).await;
4317        assert!(result.unwrap_err().to_string().contains("return"));
4318    }
4319
4320    #[tokio::test(flavor = "multi_thread")]
4321    async fn load_all_modules() {
4322        // program a.kcl
4323        let program_a_kcl = r#"
4324export a = 1
4325"#;
4326        // program b.kcl
4327        let program_b_kcl = r#"
4328import a from 'a.kcl'
4329
4330export b = a + 1
4331"#;
4332        // program c.kcl
4333        let program_c_kcl = r#"
4334import a from 'a.kcl'
4335
4336export c = a + 2
4337"#;
4338
4339        // program main.kcl
4340        let main_kcl = r#"
4341import b from 'b.kcl'
4342import c from 'c.kcl'
4343
4344d = b + c
4345"#;
4346
4347        let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
4348            .parse_errs_as_err()
4349            .unwrap();
4350
4351        let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
4352
4353        tokio::fs::File::create(tmpdir.path().join("main.kcl"))
4354            .await
4355            .unwrap()
4356            .write_all(main_kcl.as_bytes())
4357            .await
4358            .unwrap();
4359
4360        tokio::fs::File::create(tmpdir.path().join("a.kcl"))
4361            .await
4362            .unwrap()
4363            .write_all(program_a_kcl.as_bytes())
4364            .await
4365            .unwrap();
4366
4367        tokio::fs::File::create(tmpdir.path().join("b.kcl"))
4368            .await
4369            .unwrap()
4370            .write_all(program_b_kcl.as_bytes())
4371            .await
4372            .unwrap();
4373
4374        tokio::fs::File::create(tmpdir.path().join("c.kcl"))
4375            .await
4376            .unwrap()
4377            .write_all(program_c_kcl.as_bytes())
4378            .await
4379            .unwrap();
4380
4381        let exec_ctxt = ExecutorContext {
4382            engine: Arc::new(Box::new(
4383                crate::engine::conn_mock::EngineConnection::new()
4384                    .map_err(|err| {
4385                        internal_err(
4386                            format!("Failed to create mock engine connection: {err}"),
4387                            SourceRange::default(),
4388                        )
4389                    })
4390                    .unwrap(),
4391            )),
4392            fs: Arc::new(crate::fs::FileManager::new()),
4393            settings: ExecutorSettings {
4394                project_directory: Some(crate::TypedPath(tmpdir.path().into())),
4395                ..Default::default()
4396            },
4397            context_type: ContextType::Mock,
4398        };
4399        let mut exec_state = ExecState::new(&exec_ctxt);
4400
4401        exec_ctxt
4402            .run(
4403                &crate::Program {
4404                    ast: main.clone(),
4405                    original_file_contents: "".to_owned(),
4406                },
4407                &mut exec_state,
4408            )
4409            .await
4410            .unwrap();
4411    }
4412
4413    #[tokio::test(flavor = "multi_thread")]
4414    async fn user_coercion() {
4415        let program = r#"fn foo(x: Axis2d) {
4416  return 0
4417}
4418
4419foo(x = { direction = [0, 0], origin = [0, 0]})
4420"#;
4421
4422        parse_execute(program).await.unwrap();
4423
4424        let program = r#"fn foo(x: Axis3d) {
4425  return 0
4426}
4427
4428foo(x = { direction = [0, 0], origin = [0, 0]})
4429"#;
4430
4431        parse_execute(program).await.unwrap_err();
4432    }
4433
4434    #[tokio::test(flavor = "multi_thread")]
4435    async fn coerce_return() {
4436        let program = r#"fn foo(): number(mm) {
4437  return 42
4438}
4439
4440a = foo()
4441"#;
4442
4443        parse_execute(program).await.unwrap();
4444
4445        let program = r#"fn foo(): mm {
4446  return 42
4447}
4448
4449a = foo()
4450"#;
4451
4452        parse_execute(program).await.unwrap();
4453
4454        let program = r#"fn foo(): number(mm) {
4455  return { bar: 42 }
4456}
4457
4458a = foo()
4459"#;
4460
4461        parse_execute(program).await.unwrap_err();
4462
4463        let program = r#"fn foo(): mm {
4464  return { bar: 42 }
4465}
4466
4467a = foo()
4468"#;
4469
4470        parse_execute(program).await.unwrap_err();
4471    }
4472
4473    #[tokio::test(flavor = "multi_thread")]
4474    async fn test_sensible_error_when_missing_equals_in_kwarg() {
4475        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)"]
4476            .into_iter()
4477            .enumerate()
4478        {
4479            let program = format!(
4480                "fn foo() {{ return 0 }}
4481z = 0
4482fn f(x, y, z) {{ return 0 }}
4483{call}"
4484            );
4485            let err = parse_execute(&program).await.unwrap_err();
4486            let msg = err.message();
4487            assert!(
4488                msg.contains("This argument needs a label, but it doesn't have one"),
4489                "failed test {i}: {msg}"
4490            );
4491            assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
4492            if i == 0 {
4493                assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
4494            }
4495        }
4496    }
4497
4498    #[tokio::test(flavor = "multi_thread")]
4499    async fn default_param_for_unlabeled() {
4500        // Tests that the input param for myExtrude is taken from the pipeline value and same-name
4501        // keyword args.
4502        let ast = r#"fn myExtrude(@sk, length) {
4503  return extrude(sk, length)
4504}
4505sketch001 = startSketchOn(XY)
4506  |> circle(center = [0, 0], radius = 93.75)
4507  |> myExtrude(length = 40)
4508"#;
4509
4510        parse_execute(ast).await.unwrap();
4511    }
4512
4513    #[tokio::test(flavor = "multi_thread")]
4514    async fn dont_use_unlabelled_as_input() {
4515        // `length` should be used as the `length` argument to extrude, not the unlabelled input
4516        let ast = r#"length = 10
4517startSketchOn(XY)
4518  |> circle(center = [0, 0], radius = 93.75)
4519  |> extrude(length)
4520"#;
4521
4522        parse_execute(ast).await.unwrap();
4523    }
4524
4525    #[tokio::test(flavor = "multi_thread")]
4526    async fn ascription_in_binop() {
4527        let ast = r#"foo = tan(0): number(rad) - 4deg"#;
4528        parse_execute(ast).await.unwrap();
4529
4530        let ast = r#"foo = tan(0): rad - 4deg"#;
4531        parse_execute(ast).await.unwrap();
4532    }
4533
4534    #[tokio::test(flavor = "multi_thread")]
4535    async fn neg_sqrt() {
4536        let ast = r#"bad = sqrt(-2)"#;
4537
4538        let e = parse_execute(ast).await.unwrap_err();
4539        // Make sure we get a useful error message and not an engine error.
4540        assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
4541    }
4542
4543    #[tokio::test(flavor = "multi_thread")]
4544    async fn non_array_fns() {
4545        let ast = r#"push(1, item = 2)
4546pop(1)
4547map(1, f = fn(@x) { return x + 1 })
4548reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
4549
4550        parse_execute(ast).await.unwrap();
4551    }
4552
4553    #[tokio::test(flavor = "multi_thread")]
4554    async fn non_array_indexing() {
4555        let good = r#"a = 42
4556good = a[0]
4557"#;
4558        let result = parse_execute(good).await.unwrap();
4559        let mem = result.exec_state.stack();
4560        let num = mem
4561            .memory
4562            .get_from("good", result.mem_env, SourceRange::default(), 0)
4563            .unwrap()
4564            .as_ty_f64()
4565            .unwrap();
4566        assert_eq!(num.n, 42.0);
4567
4568        let bad = r#"a = 42
4569bad = a[1]
4570"#;
4571
4572        parse_execute(bad).await.unwrap_err();
4573    }
4574
4575    #[tokio::test(flavor = "multi_thread")]
4576    async fn coerce_unknown_to_length() {
4577        let ast = r#"x = 2mm * 2mm
4578y = x: number(Length)"#;
4579        let e = parse_execute(ast).await.unwrap_err();
4580        assert!(
4581            e.message().contains("could not coerce"),
4582            "Error message: '{}'",
4583            e.message()
4584        );
4585
4586        let ast = r#"x = 2mm
4587y = x: number(Length)"#;
4588        let result = parse_execute(ast).await.unwrap();
4589        let mem = result.exec_state.stack();
4590        let num = mem
4591            .memory
4592            .get_from("y", result.mem_env, SourceRange::default(), 0)
4593            .unwrap()
4594            .as_ty_f64()
4595            .unwrap();
4596        assert_eq!(num.n, 2.0);
4597        assert_eq!(num.ty, NumericType::mm());
4598    }
4599
4600    #[tokio::test(flavor = "multi_thread")]
4601    async fn one_warning_unknown() {
4602        let ast = r#"
4603// Should warn once
4604a = PI * 2
4605// Should warn once
4606b = (PI * 2) / 3
4607// Should not warn
4608c = ((PI * 2) / 3): number(deg)
4609"#;
4610
4611        let result = parse_execute(ast).await.unwrap();
4612        assert_eq!(result.exec_state.errors().len(), 2);
4613    }
4614
4615    #[tokio::test(flavor = "multi_thread")]
4616    async fn non_count_indexing() {
4617        let ast = r#"x = [0, 0]
4618y = x[1mm]
4619"#;
4620        parse_execute(ast).await.unwrap_err();
4621
4622        let ast = r#"x = [0, 0]
4623y = 1deg
4624z = x[y]
4625"#;
4626        parse_execute(ast).await.unwrap_err();
4627
4628        let ast = r#"x = [0, 0]
4629y = x[0mm + 1]
4630"#;
4631        parse_execute(ast).await.unwrap_err();
4632    }
4633
4634    #[tokio::test(flavor = "multi_thread")]
4635    async fn getting_property_of_plane() {
4636        let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
4637        parse_execute(&ast).await.unwrap();
4638    }
4639
4640    #[cfg(feature = "artifact-graph")]
4641    #[tokio::test(flavor = "multi_thread")]
4642    async fn no_artifacts_from_within_hole_call() {
4643        // Test that executing stdlib KCL, like the `hole` function
4644        // (which is actually implemented in KCL not Rust)
4645        // does not generate artifacts from within the stdlib code,
4646        // only from the user code.
4647        let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
4648        let out = parse_execute(&ast).await.unwrap();
4649
4650        // Get all the operations that occurred.
4651        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4652
4653        // There should be 5, for sketching the cube and applying the hole.
4654        // If the stdlib internal calls are being tracked, that's a bug,
4655        // and the actual number of operations will be something like 35.
4656        let expected = 5;
4657        assert_eq!(
4658            actual_operations.len(),
4659            expected,
4660            "expected {expected} operations, received {}:\n{actual_operations:#?}",
4661            actual_operations.len(),
4662        );
4663    }
4664
4665    #[cfg(feature = "artifact-graph")]
4666    #[tokio::test(flavor = "multi_thread")]
4667    async fn feature_tree_annotation_on_user_defined_kcl() {
4668        // The call to foo() should not generate an operation,
4669        // because its 'feature_tree' attribute has been set to false.
4670        let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4671        let out = parse_execute(&ast).await.unwrap();
4672
4673        // Get all the operations that occurred.
4674        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4675
4676        let expected = 0;
4677        assert_eq!(
4678            actual_operations.len(),
4679            expected,
4680            "expected {expected} operations, received {}:\n{actual_operations:#?}",
4681            actual_operations.len(),
4682        );
4683    }
4684
4685    #[cfg(feature = "artifact-graph")]
4686    #[tokio::test(flavor = "multi_thread")]
4687    async fn no_feature_tree_annotation_on_user_defined_kcl() {
4688        // The call to foo() should generate an operation,
4689        // because @(feature_tree) defaults to true.
4690        let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4691        let out = parse_execute(&ast).await.unwrap();
4692
4693        // Get all the operations that occurred.
4694        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4695
4696        let expected = 2;
4697        assert_eq!(
4698            actual_operations.len(),
4699            expected,
4700            "expected {expected} operations, received {}:\n{actual_operations:#?}",
4701            actual_operations.len(),
4702        );
4703        assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
4704        assert!(matches!(actual_operations[1], Operation::GroupEnd));
4705    }
4706
4707    #[tokio::test(flavor = "multi_thread")]
4708    async fn custom_warning() {
4709        let warn = r#"
4710a = PI * 2
4711"#;
4712        let result = parse_execute(warn).await.unwrap();
4713        assert_eq!(result.exec_state.errors().len(), 1);
4714        assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
4715
4716        let allow = r#"
4717@warnings(allow = unknownUnits)
4718a = PI * 2
4719"#;
4720        let result = parse_execute(allow).await.unwrap();
4721        assert_eq!(result.exec_state.errors().len(), 0);
4722
4723        let deny = r#"
4724@warnings(deny = [unknownUnits])
4725a = PI * 2
4726"#;
4727        let result = parse_execute(deny).await.unwrap();
4728        assert_eq!(result.exec_state.errors().len(), 1);
4729        assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
4730    }
4731
4732    #[tokio::test(flavor = "multi_thread")]
4733    async fn sketch_block_unqualified_functions_use_sketch2() {
4734        let ast = r#"
4735@settings(experimentalFeatures = allow)
4736s = sketch(on = XY) {
4737  line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
4738  line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
4739  coincident([line1.end, line2.start])
4740}
4741"#;
4742        let result = parse_execute(ast).await.unwrap();
4743        let mem = result.exec_state.stack();
4744        let sketch_value = mem
4745            .memory
4746            .get_from("s", result.mem_env, SourceRange::default(), 0)
4747            .unwrap();
4748
4749        let KclValue::Object { value, .. } = sketch_value else {
4750            panic!("Expected sketch block to return an object, got {sketch_value:?}");
4751        };
4752
4753        assert!(value.contains_key("line1"));
4754        assert!(value.contains_key("line2"));
4755        // Ensure sketch2 aliases used during execution are not returned as
4756        // sketch block fields.
4757        assert!(!value.contains_key("line"));
4758        assert!(!value.contains_key("coincident"));
4759    }
4760
4761    #[tokio::test(flavor = "multi_thread")]
4762    async fn cannot_solid_extrude_an_open_profile() {
4763        // This should fail during mock execution, because KCL should catch
4764        // that the profile is not closed.
4765        let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
4766        let program = crate::Program::parse_no_errs(&code).expect("should parse");
4767        let exec_ctxt = ExecutorContext::new_mock(None).await;
4768        let mut exec_state = ExecState::new(&exec_ctxt);
4769
4770        let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
4771        assert!(matches!(err, KclError::Semantic { .. }));
4772        exec_ctxt.close().await;
4773    }
4774}