Skip to main content

kcl_lib/execution/
exec_ast.rs

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