Skip to main content

kcl_lib/execution/
exec_ast.rs

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