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            let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1274
1275            exec_state.mod_local.sketch_mode = original_sketch_mode;
1276
1277            let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1278
1279            let block_variables = exec_state
1280                .stack()
1281                .find_all_in_current_env()
1282                .map(|(name, value)| (name.clone(), value.clone()))
1283                .collect::<IndexMap<_, _>>();
1284
1285            exec_state.mut_stack().pop_env();
1286
1287            (result, block_variables, sketch_block_state)
1288        };
1289
1290        // Propagate errors.
1291        return_result?;
1292        let Some(sketch_block_state) = sketch_block_state else {
1293            debug_assert!(false, "Sketch block state should still be set to Some from just above");
1294            return Err(internal_err(
1295                "Sketch block state should still be set to Some from just above",
1296                self,
1297            ));
1298        };
1299        #[cfg(feature = "artifact-graph")]
1300        let mut sketch_block_state = sketch_block_state;
1301
1302        // Translate sketch variables and constraints to solver input.
1303        let constraints = sketch_block_state
1304            .solver_constraints
1305            .iter()
1306            .cloned()
1307            .map(kcl_ezpz::ConstraintRequest::highest_priority)
1308            .chain(
1309                // Optional constraints have a lower priority.
1310                sketch_block_state
1311                    .solver_optional_constraints
1312                    .iter()
1313                    .cloned()
1314                    .map(|c| kcl_ezpz::ConstraintRequest::new(c, 1)),
1315            )
1316            .collect::<Vec<_>>();
1317        let initial_guesses = sketch_block_state
1318            .sketch_vars
1319            .iter()
1320            .map(|v| {
1321                let Some(sketch_var) = v.as_sketch_var() else {
1322                    return Err(internal_err("Expected sketch variable", self));
1323                };
1324                let constraint_id = sketch_var.id.to_constraint_id(range)?;
1325                // Normalize units.
1326                let number_value = KclValue::Number {
1327                    value: sketch_var.initial_value,
1328                    ty: sketch_var.ty,
1329                    meta: sketch_var.meta.clone(),
1330                };
1331                let initial_guess_value =
1332                    normalize_to_solver_unit(&number_value, v.into(), exec_state, "sketch variable initial value")?;
1333                let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1334                    n.n
1335                } else {
1336                    let message = format!(
1337                        "Expected number after coercion, but found {}",
1338                        initial_guess_value.human_friendly_type()
1339                    );
1340                    debug_assert!(false, "{}", &message);
1341                    return Err(internal_err(message, self));
1342                };
1343                Ok((constraint_id, initial_guess))
1344            })
1345            .collect::<Result<Vec<_>, KclError>>()?;
1346        // Solve constraints.
1347        let config = kcl_ezpz::Config::default().with_max_iterations(50);
1348        let solve_result = if exec_state.mod_local.freedom_analysis {
1349            kcl_ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
1350                let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
1351                (outcome.outcome, Some(freedom_analysis))
1352            })
1353        } else {
1354            kcl_ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1355        };
1356        // Build a combined list of all constraints (regular + optional) for conflict detection
1357        let num_required_constraints = sketch_block_state.solver_constraints.len();
1358        let all_constraints: Vec<kcl_ezpz::Constraint> = sketch_block_state
1359            .solver_constraints
1360            .iter()
1361            .cloned()
1362            .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
1363            .collect();
1364
1365        let (solve_outcome, solve_analysis) = match solve_result {
1366            Ok((solved, freedom)) => {
1367                let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
1368                (outcome, freedom)
1369            }
1370            Err(failure) => {
1371                match &failure.error {
1372                    NonLinearSystemError::FaerMatrix { .. }
1373                    | NonLinearSystemError::Faer { .. }
1374                    | NonLinearSystemError::FaerSolve { .. }
1375                    | NonLinearSystemError::FaerSvd(..)
1376                    | NonLinearSystemError::DidNotConverge => {
1377                        // Constraint solver failed to find a solution. Build a
1378                        // solution that is the initial guesses.
1379                        exec_state.warn(
1380                            CompilationError::err(range, "Constraint solver failed to find a solution".to_owned()),
1381                            annotations::WARN_SOLVER,
1382                        );
1383                        let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1384                        (
1385                            Solved {
1386                                final_values,
1387                                iterations: Default::default(),
1388                                warnings: failure.warnings,
1389                                priority_solved: Default::default(),
1390                                variables_in_conflicts: Default::default(),
1391                            },
1392                            None,
1393                        )
1394                    }
1395                    NonLinearSystemError::EmptySystemNotAllowed
1396                    | NonLinearSystemError::WrongNumberGuesses { .. }
1397                    | NonLinearSystemError::MissingGuess { .. }
1398                    | NonLinearSystemError::NotFound(..) => {
1399                        // These indicate something's gone wrong in KCL or ezpz,
1400                        // it's not a user error. We should investigate this.
1401                        #[cfg(target_arch = "wasm32")]
1402                        web_sys::console::error_1(
1403                            &format!("Internal error from constraint solver: {}", &failure.error).into(),
1404                        );
1405                        return Err(internal_err(
1406                            format!("Internal error from constraint solver: {}", &failure.error),
1407                            self,
1408                        ));
1409                    }
1410                    _ => {
1411                        // Catch all error case so that it's not a breaking change to publish new errors.
1412                        return Err(internal_err(
1413                            format!("Error from constraint solver: {}", &failure.error),
1414                            self,
1415                        ));
1416                    }
1417                }
1418            }
1419        };
1420        #[cfg(not(feature = "artifact-graph"))]
1421        let _ = solve_analysis;
1422        // Propagate warnings.
1423        for warning in &solve_outcome.warnings {
1424            let message = if let Some(index) = warning.about_constraint.as_ref() {
1425                format!("{}; constraint index {}", &warning.content, index)
1426            } else {
1427                format!("{}", &warning.content)
1428            };
1429            exec_state.warn(CompilationError::err(range, message), annotations::WARN_SOLVER);
1430        }
1431        // Substitute solutions back into sketch variables.
1432        let sketch_engine_id = exec_state.next_uuid();
1433        let solution_ty = solver_numeric_type(exec_state);
1434        let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1435        for unsolved_segment in &sketch_block_state.needed_by_engine {
1436            solved_segments.push(substitute_sketch_var_in_segment(
1437                unsolved_segment.clone(),
1438                &sketch_surface,
1439                sketch_engine_id,
1440                None,
1441                &solve_outcome,
1442                solver_numeric_type(exec_state),
1443                solve_analysis.as_ref(),
1444            )?);
1445        }
1446        #[cfg(feature = "artifact-graph")]
1447        {
1448            // Store variable solutions so that the sketch refactoring API can
1449            // write them back to the source. When editing a sketch block, we
1450            // exit early so that the sketch block that we're editing is always
1451            // the last one. Therefore, we should overwrite any previous
1452            // solutions.
1453            exec_state.mod_local.artifacts.var_solutions =
1454                sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
1455        }
1456
1457        // Create scene objects after unknowns are solved.
1458        let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1459
1460        // Build the sketch and send everything to the engine.
1461        let sketch = create_segments_in_engine(
1462            &sketch_surface,
1463            sketch_engine_id,
1464            &mut solved_segments,
1465            &sketch_block_state.segment_tags,
1466            ctx,
1467            exec_state,
1468            range,
1469        )
1470        .await?;
1471
1472        // Substitute solutions back into sketch variables. This time, collect
1473        // all the variables in the sketch block. The set of variables may have
1474        // overlap with the objects sent to the engine, but it isn't necessarily
1475        // the same.
1476        let variables = substitute_sketch_vars(
1477            variables,
1478            &sketch_surface,
1479            sketch_engine_id,
1480            sketch,
1481            &solve_outcome,
1482            solution_ty,
1483            solve_analysis.as_ref(),
1484        )?;
1485
1486        #[cfg(not(feature = "artifact-graph"))]
1487        drop(scene_objects);
1488        #[cfg(feature = "artifact-graph")]
1489        {
1490            let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1491            for scene_object in scene_objects {
1492                segment_object_ids.push(scene_object.id);
1493                // Fill in placeholder scene objects.
1494                exec_state.set_scene_object(scene_object);
1495            }
1496            // Update the sketch scene object with the segments.
1497            let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
1498                let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1499                debug_assert!(false, "{}", &message);
1500                return Err(internal_err(message, range));
1501            };
1502            let ObjectKind::Sketch(sketch) = &mut sketch_object.kind else {
1503                let message = format!(
1504                    "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1505                    sketch_id, sketch_object
1506                );
1507                debug_assert!(
1508                    false,
1509                    "{}; scene_objects={:#?}",
1510                    &message, &exec_state.mod_local.artifacts.scene_objects
1511                );
1512                return Err(internal_err(message, range));
1513            };
1514            sketch.segments.extend(segment_object_ids);
1515            // Update the sketch scene object with constraints.
1516            sketch
1517                .constraints
1518                .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1519
1520            // Push sketch solve operation
1521            exec_state.push_op(Operation::SketchSolve {
1522                sketch_id,
1523                node_path: NodePath::placeholder(),
1524                source_range: range,
1525            });
1526        }
1527
1528        let metadata = Metadata {
1529            source_range: SourceRange::from(self),
1530        };
1531        let return_value = KclValue::Object {
1532            value: variables,
1533            constrainable: Default::default(),
1534            meta: vec![metadata],
1535        };
1536        Ok(if self.is_being_edited {
1537            // When the sketch block is being edited, we exit the program
1538            // immediately.
1539            return_value.exit()
1540        } else {
1541            return_value.continue_()
1542        })
1543    }
1544}
1545
1546impl SketchBlock {
1547    fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1548        exec_state.mut_stack().push_new_env_for_call(parent);
1549    }
1550}
1551
1552impl Node<SketchVar> {
1553    pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1554        let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1555            return Err(KclError::new_semantic(KclErrorDetails::new(
1556                "Cannot use a sketch variable outside of a sketch block".to_owned(),
1557                vec![SourceRange::from(self)],
1558            )));
1559        };
1560        let id = sketch_block_state.next_sketch_var_id();
1561        let sketch_var = if let Some(initial) = &self.initial {
1562            KclValue::from_sketch_var_literal(initial, id, exec_state)
1563        } else {
1564            let metadata = Metadata {
1565                source_range: SourceRange::from(self),
1566            };
1567
1568            KclValue::SketchVar {
1569                value: Box::new(super::SketchVar {
1570                    id,
1571                    initial_value: 0.0,
1572                    ty: NumericType::default(),
1573                    meta: vec![metadata],
1574                }),
1575            }
1576        };
1577
1578        let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1579            return Err(KclError::new_semantic(KclErrorDetails::new(
1580                "Cannot use a sketch variable outside of a sketch block".to_owned(),
1581                vec![SourceRange::from(self)],
1582            )));
1583        };
1584        sketch_block_state.sketch_vars.push(sketch_var.clone());
1585
1586        Ok(sketch_var)
1587    }
1588}
1589
1590fn apply_ascription(
1591    value: &KclValue,
1592    ty: &Node<Type>,
1593    exec_state: &mut ExecState,
1594    source_range: SourceRange,
1595) -> Result<KclValue, KclError> {
1596    let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false, false)
1597        .map_err(|e| KclError::new_semantic(e.into()))?;
1598
1599    if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1600        exec_state.clear_units_warnings(&source_range);
1601    }
1602
1603    value.coerce(&ty, false, exec_state).map_err(|_| {
1604        let suggestion = if ty == RuntimeType::length() {
1605            ", you might try coercing to a fully specified numeric type such as `mm`"
1606        } else if ty == RuntimeType::angle() {
1607            ", you might try coercing to a fully specified numeric type such as `deg`"
1608        } else {
1609            ""
1610        };
1611        let ty_str = if let Some(ty) = value.principal_type() {
1612            format!("(with type `{ty}`) ")
1613        } else {
1614            String::new()
1615        };
1616        KclError::new_semantic(KclErrorDetails::new(
1617            format!(
1618                "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1619                value.human_friendly_type()
1620            ),
1621            vec![source_range],
1622        ))
1623    })
1624}
1625
1626impl BinaryPart {
1627    #[async_recursion]
1628    pub(super) async fn get_result(
1629        &self,
1630        exec_state: &mut ExecState,
1631        ctx: &ExecutorContext,
1632    ) -> Result<KclValueControlFlow, KclError> {
1633        match self {
1634            BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
1635            BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned().map(KclValue::continue_),
1636            BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1637            BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1638            BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1639            BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1640            BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1641            BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1642            BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1643            BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1644            BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1645            BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
1646        }
1647    }
1648}
1649
1650impl Node<Name> {
1651    pub(super) async fn get_result<'a>(
1652        &self,
1653        exec_state: &'a mut ExecState,
1654        ctx: &ExecutorContext,
1655    ) -> Result<&'a KclValue, KclError> {
1656        let being_declared = exec_state.mod_local.being_declared.clone();
1657        self.get_result_inner(exec_state, ctx)
1658            .await
1659            .map_err(|e| var_in_own_ref_err(e, &being_declared))
1660    }
1661
1662    async fn get_result_inner<'a>(
1663        &self,
1664        exec_state: &'a mut ExecState,
1665        ctx: &ExecutorContext,
1666    ) -> Result<&'a KclValue, KclError> {
1667        if self.abs_path {
1668            return Err(KclError::new_semantic(KclErrorDetails::new(
1669                "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1670                self.as_source_ranges(),
1671            )));
1672        }
1673
1674        let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1675
1676        if self.path.is_empty() {
1677            let item_value = exec_state.stack().get(&self.name.name, self.into());
1678            if item_value.is_ok() {
1679                return item_value;
1680            }
1681            return exec_state.stack().get(&mod_name, self.into());
1682        }
1683
1684        let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1685        for p in &self.path {
1686            let value = match mem_spec {
1687                Some((env, exports)) => {
1688                    if !exports.contains(&p.name) {
1689                        return Err(KclError::new_semantic(KclErrorDetails::new(
1690                            format!("Item {} not found in module's exported items", p.name),
1691                            p.as_source_ranges(),
1692                        )));
1693                    }
1694
1695                    exec_state
1696                        .stack()
1697                        .memory
1698                        .get_from(&p.name, env, p.as_source_range(), 0)?
1699                }
1700                None => exec_state
1701                    .stack()
1702                    .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
1703            };
1704
1705            let KclValue::Module { value: module_id, .. } = value else {
1706                return Err(KclError::new_semantic(KclErrorDetails::new(
1707                    format!(
1708                        "Identifier in path must refer to a module, found {}",
1709                        value.human_friendly_type()
1710                    ),
1711                    p.as_source_ranges(),
1712                )));
1713            };
1714
1715            mem_spec = Some(
1716                ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1717                    .await?,
1718            );
1719        }
1720
1721        let (env, exports) = mem_spec.unwrap();
1722
1723        let item_exported = exports.contains(&self.name.name);
1724        let item_value = exec_state
1725            .stack()
1726            .memory
1727            .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1728
1729        // Item is defined and exported.
1730        if item_exported && item_value.is_ok() {
1731            return item_value;
1732        }
1733
1734        let mod_exported = exports.contains(&mod_name);
1735        let mod_value = exec_state
1736            .stack()
1737            .memory
1738            .get_from(&mod_name, env, self.name.as_source_range(), 0);
1739
1740        // Module is defined and exported.
1741        if mod_exported && mod_value.is_ok() {
1742            return mod_value;
1743        }
1744
1745        // Neither item or module is defined.
1746        if item_value.is_err() && mod_value.is_err() {
1747            return item_value;
1748        }
1749
1750        // Either item or module is defined, but not exported.
1751        debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1752        Err(KclError::new_semantic(KclErrorDetails::new(
1753            format!("Item {} not found in module's exported items", self.name.name),
1754            self.name.as_source_ranges(),
1755        )))
1756    }
1757}
1758
1759impl Node<MemberExpression> {
1760    async fn get_result(
1761        &self,
1762        exec_state: &mut ExecState,
1763        ctx: &ExecutorContext,
1764    ) -> Result<KclValueControlFlow, KclError> {
1765        let meta = Metadata {
1766            source_range: SourceRange::from(self),
1767        };
1768        // TODO: The order of execution is wrong. We should execute the object
1769        // *before* the property.
1770        let property = Property::try_from(
1771            self.computed,
1772            self.property.clone(),
1773            exec_state,
1774            self.into(),
1775            ctx,
1776            &meta,
1777            &[],
1778            StatementKind::Expression,
1779        )
1780        .await?;
1781        let object_cf = ctx
1782            .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1783            .await?;
1784        let object = control_continue!(object_cf);
1785
1786        // Check the property and object match -- e.g. ints for arrays, strs for objects.
1787        match (object, property, self.computed) {
1788            (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
1789                "at" => match &segment.repr {
1790                    SegmentRepr::Unsolved { segment } => {
1791                        match &segment.kind {
1792                            UnsolvedSegmentKind::Point { position, .. } => {
1793                                // TODO: assert that types of all elements are the same.
1794                                Ok(KclValue::HomArray {
1795                                    value: vec![
1796                                        KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
1797                                        KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
1798                                    ],
1799                                    ty: RuntimeType::any(),
1800                                }
1801                                .continue_())
1802                            }
1803                            _ => Err(KclError::new_undefined_value(
1804                                KclErrorDetails::new(
1805                                    format!("Property '{property}' not found in segment"),
1806                                    vec![self.clone().into()],
1807                                ),
1808                                None,
1809                            )),
1810                        }
1811                    }
1812                    SegmentRepr::Solved { segment } => {
1813                        match &segment.kind {
1814                            SegmentKind::Point { position, .. } => {
1815                                // TODO: assert that types of all elements are the same.
1816                                Ok(KclValue::array_from_point2d(
1817                                    [position[0].n, position[1].n],
1818                                    position[0].ty,
1819                                    segment.meta.clone(),
1820                                )
1821                                .continue_())
1822                            }
1823                            _ => Err(KclError::new_undefined_value(
1824                                KclErrorDetails::new(
1825                                    format!("Property '{property}' not found in segment"),
1826                                    vec![self.clone().into()],
1827                                ),
1828                                None,
1829                            )),
1830                        }
1831                    }
1832                },
1833                "start" => match &segment.repr {
1834                    SegmentRepr::Unsolved { segment } => match &segment.kind {
1835                        UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1836                            KclErrorDetails::new(
1837                                format!("Property '{property}' not found in point segment"),
1838                                vec![self.clone().into()],
1839                            ),
1840                            None,
1841                        )),
1842                        UnsolvedSegmentKind::Line {
1843                            start,
1844                            ctor,
1845                            start_object_id,
1846                            ..
1847                        } => Ok(KclValue::Segment {
1848                            value: Box::new(AbstractSegment {
1849                                repr: SegmentRepr::Unsolved {
1850                                    segment: Box::new(UnsolvedSegment {
1851                                        id: segment.id,
1852                                        object_id: *start_object_id,
1853                                        kind: UnsolvedSegmentKind::Point {
1854                                            position: start.clone(),
1855                                            ctor: Box::new(PointCtor {
1856                                                position: ctor.start.clone(),
1857                                            }),
1858                                        },
1859                                        tag: segment.tag.clone(),
1860                                        meta: segment.meta.clone(),
1861                                    }),
1862                                },
1863                                meta: segment.meta.clone(),
1864                            }),
1865                        }
1866                        .continue_()),
1867                        UnsolvedSegmentKind::Arc {
1868                            start,
1869                            ctor,
1870                            start_object_id,
1871                            ..
1872                        } => Ok(KclValue::Segment {
1873                            value: Box::new(AbstractSegment {
1874                                repr: SegmentRepr::Unsolved {
1875                                    segment: Box::new(UnsolvedSegment {
1876                                        id: segment.id,
1877                                        object_id: *start_object_id,
1878                                        kind: UnsolvedSegmentKind::Point {
1879                                            position: start.clone(),
1880                                            ctor: Box::new(PointCtor {
1881                                                position: ctor.start.clone(),
1882                                            }),
1883                                        },
1884                                        tag: segment.tag.clone(),
1885                                        meta: segment.meta.clone(),
1886                                    }),
1887                                },
1888                                meta: segment.meta.clone(),
1889                            }),
1890                        }
1891                        .continue_()),
1892                    },
1893                    SegmentRepr::Solved { segment } => match &segment.kind {
1894                        SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1895                            KclErrorDetails::new(
1896                                format!("Property '{property}' not found in point segment"),
1897                                vec![self.clone().into()],
1898                            ),
1899                            None,
1900                        )),
1901                        SegmentKind::Line {
1902                            start,
1903                            ctor,
1904                            start_object_id,
1905                            start_freedom,
1906                            ..
1907                        } => Ok(KclValue::Segment {
1908                            value: Box::new(AbstractSegment {
1909                                repr: SegmentRepr::Solved {
1910                                    segment: Box::new(Segment {
1911                                        id: segment.id,
1912                                        object_id: *start_object_id,
1913                                        kind: SegmentKind::Point {
1914                                            position: start.clone(),
1915                                            ctor: Box::new(PointCtor {
1916                                                position: ctor.start.clone(),
1917                                            }),
1918                                            freedom: *start_freedom,
1919                                        },
1920                                        surface: segment.surface.clone(),
1921                                        sketch_id: segment.sketch_id,
1922                                        sketch: None,
1923                                        tag: segment.tag.clone(),
1924                                        meta: segment.meta.clone(),
1925                                    }),
1926                                },
1927                                meta: segment.meta.clone(),
1928                            }),
1929                        }
1930                        .continue_()),
1931                        SegmentKind::Arc {
1932                            start,
1933                            ctor,
1934                            start_object_id,
1935                            start_freedom,
1936                            ..
1937                        } => Ok(KclValue::Segment {
1938                            value: Box::new(AbstractSegment {
1939                                repr: SegmentRepr::Solved {
1940                                    segment: Box::new(Segment {
1941                                        id: segment.id,
1942                                        object_id: *start_object_id,
1943                                        kind: SegmentKind::Point {
1944                                            position: start.clone(),
1945                                            ctor: Box::new(PointCtor {
1946                                                position: ctor.start.clone(),
1947                                            }),
1948                                            freedom: *start_freedom,
1949                                        },
1950                                        surface: segment.surface.clone(),
1951                                        sketch_id: segment.sketch_id,
1952                                        sketch: None,
1953                                        tag: segment.tag.clone(),
1954                                        meta: segment.meta.clone(),
1955                                    }),
1956                                },
1957                                meta: segment.meta.clone(),
1958                            }),
1959                        }
1960                        .continue_()),
1961                    },
1962                },
1963                "end" => match &segment.repr {
1964                    SegmentRepr::Unsolved { segment } => match &segment.kind {
1965                        UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1966                            KclErrorDetails::new(
1967                                format!("Property '{property}' not found in point segment"),
1968                                vec![self.clone().into()],
1969                            ),
1970                            None,
1971                        )),
1972                        UnsolvedSegmentKind::Line {
1973                            end,
1974                            ctor,
1975                            end_object_id,
1976                            ..
1977                        } => Ok(KclValue::Segment {
1978                            value: Box::new(AbstractSegment {
1979                                repr: SegmentRepr::Unsolved {
1980                                    segment: Box::new(UnsolvedSegment {
1981                                        id: segment.id,
1982                                        object_id: *end_object_id,
1983                                        kind: UnsolvedSegmentKind::Point {
1984                                            position: end.clone(),
1985                                            ctor: Box::new(PointCtor {
1986                                                position: ctor.end.clone(),
1987                                            }),
1988                                        },
1989                                        tag: segment.tag.clone(),
1990                                        meta: segment.meta.clone(),
1991                                    }),
1992                                },
1993                                meta: segment.meta.clone(),
1994                            }),
1995                        }
1996                        .continue_()),
1997                        UnsolvedSegmentKind::Arc {
1998                            end,
1999                            ctor,
2000                            end_object_id,
2001                            ..
2002                        } => Ok(KclValue::Segment {
2003                            value: Box::new(AbstractSegment {
2004                                repr: SegmentRepr::Unsolved {
2005                                    segment: Box::new(UnsolvedSegment {
2006                                        id: segment.id,
2007                                        object_id: *end_object_id,
2008                                        kind: UnsolvedSegmentKind::Point {
2009                                            position: end.clone(),
2010                                            ctor: Box::new(PointCtor {
2011                                                position: ctor.end.clone(),
2012                                            }),
2013                                        },
2014                                        tag: segment.tag.clone(),
2015                                        meta: segment.meta.clone(),
2016                                    }),
2017                                },
2018                                meta: segment.meta.clone(),
2019                            }),
2020                        }
2021                        .continue_()),
2022                    },
2023                    SegmentRepr::Solved { segment } => match &segment.kind {
2024                        SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2025                            KclErrorDetails::new(
2026                                format!("Property '{property}' not found in point segment"),
2027                                vec![self.clone().into()],
2028                            ),
2029                            None,
2030                        )),
2031                        SegmentKind::Line {
2032                            end,
2033                            ctor,
2034                            end_object_id,
2035                            end_freedom,
2036                            ..
2037                        } => Ok(KclValue::Segment {
2038                            value: Box::new(AbstractSegment {
2039                                repr: SegmentRepr::Solved {
2040                                    segment: Box::new(Segment {
2041                                        id: segment.id,
2042                                        object_id: *end_object_id,
2043                                        kind: SegmentKind::Point {
2044                                            position: end.clone(),
2045                                            ctor: Box::new(PointCtor {
2046                                                position: ctor.end.clone(),
2047                                            }),
2048                                            freedom: *end_freedom,
2049                                        },
2050                                        surface: segment.surface.clone(),
2051                                        sketch_id: segment.sketch_id,
2052                                        sketch: None,
2053                                        tag: segment.tag.clone(),
2054                                        meta: segment.meta.clone(),
2055                                    }),
2056                                },
2057                                meta: segment.meta.clone(),
2058                            }),
2059                        }
2060                        .continue_()),
2061                        SegmentKind::Arc {
2062                            end,
2063                            ctor,
2064                            end_object_id,
2065                            end_freedom,
2066                            ..
2067                        } => Ok(KclValue::Segment {
2068                            value: Box::new(AbstractSegment {
2069                                repr: SegmentRepr::Solved {
2070                                    segment: Box::new(Segment {
2071                                        id: segment.id,
2072                                        object_id: *end_object_id,
2073                                        kind: SegmentKind::Point {
2074                                            position: end.clone(),
2075                                            ctor: Box::new(PointCtor {
2076                                                position: ctor.end.clone(),
2077                                            }),
2078                                            freedom: *end_freedom,
2079                                        },
2080                                        surface: segment.surface.clone(),
2081                                        sketch_id: segment.sketch_id,
2082                                        sketch: None,
2083                                        tag: segment.tag.clone(),
2084                                        meta: segment.meta.clone(),
2085                                    }),
2086                                },
2087                                meta: segment.meta.clone(),
2088                            }),
2089                        }
2090                        .continue_()),
2091                    },
2092                },
2093                "center" => match &segment.repr {
2094                    SegmentRepr::Unsolved { segment } => match &segment.kind {
2095                        UnsolvedSegmentKind::Arc {
2096                            center,
2097                            ctor,
2098                            center_object_id,
2099                            ..
2100                        } => Ok(KclValue::Segment {
2101                            value: Box::new(AbstractSegment {
2102                                repr: SegmentRepr::Unsolved {
2103                                    segment: Box::new(UnsolvedSegment {
2104                                        id: segment.id,
2105                                        object_id: *center_object_id,
2106                                        kind: UnsolvedSegmentKind::Point {
2107                                            position: center.clone(),
2108                                            ctor: Box::new(PointCtor {
2109                                                position: ctor.center.clone(),
2110                                            }),
2111                                        },
2112                                        tag: segment.tag.clone(),
2113                                        meta: segment.meta.clone(),
2114                                    }),
2115                                },
2116                                meta: segment.meta.clone(),
2117                            }),
2118                        }
2119                        .continue_()),
2120                        _ => Err(KclError::new_undefined_value(
2121                            KclErrorDetails::new(
2122                                format!("Property '{property}' not found in segment"),
2123                                vec![self.clone().into()],
2124                            ),
2125                            None,
2126                        )),
2127                    },
2128                    SegmentRepr::Solved { segment } => match &segment.kind {
2129                        SegmentKind::Arc {
2130                            center,
2131                            ctor,
2132                            center_object_id,
2133                            center_freedom,
2134                            ..
2135                        } => Ok(KclValue::Segment {
2136                            value: Box::new(AbstractSegment {
2137                                repr: SegmentRepr::Solved {
2138                                    segment: Box::new(Segment {
2139                                        id: segment.id,
2140                                        object_id: *center_object_id,
2141                                        kind: SegmentKind::Point {
2142                                            position: center.clone(),
2143                                            ctor: Box::new(PointCtor {
2144                                                position: ctor.center.clone(),
2145                                            }),
2146                                            freedom: *center_freedom,
2147                                        },
2148                                        surface: segment.surface.clone(),
2149                                        sketch_id: segment.sketch_id,
2150                                        sketch: None,
2151                                        tag: segment.tag.clone(),
2152                                        meta: segment.meta.clone(),
2153                                    }),
2154                                },
2155                                meta: segment.meta.clone(),
2156                            }),
2157                        }
2158                        .continue_()),
2159                        _ => Err(KclError::new_undefined_value(
2160                            KclErrorDetails::new(
2161                                format!("Property '{property}' not found in segment"),
2162                                vec![self.clone().into()],
2163                            ),
2164                            None,
2165                        )),
2166                    },
2167                },
2168                other => Err(KclError::new_undefined_value(
2169                    KclErrorDetails::new(
2170                        format!("Property '{other}' not found in segment"),
2171                        vec![self.clone().into()],
2172                    ),
2173                    None,
2174                )),
2175            },
2176            (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
2177                "zAxis" => {
2178                    let (p, u) = plane.info.z_axis.as_3_dims();
2179                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2180                }
2181                "yAxis" => {
2182                    let (p, u) = plane.info.y_axis.as_3_dims();
2183                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2184                }
2185                "xAxis" => {
2186                    let (p, u) = plane.info.x_axis.as_3_dims();
2187                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2188                }
2189                "origin" => {
2190                    let (p, u) = plane.info.origin.as_3_dims();
2191                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2192                }
2193                other => Err(KclError::new_undefined_value(
2194                    KclErrorDetails::new(
2195                        format!("Property '{other}' not found in plane"),
2196                        vec![self.clone().into()],
2197                    ),
2198                    None,
2199                )),
2200            },
2201            (KclValue::Object { value: map, .. }, Property::String(property), false) => {
2202                if let Some(value) = map.get(&property) {
2203                    Ok(value.to_owned().continue_())
2204                } else {
2205                    Err(KclError::new_undefined_value(
2206                        KclErrorDetails::new(
2207                            format!("Property '{property}' not found in object"),
2208                            vec![self.clone().into()],
2209                        ),
2210                        None,
2211                    ))
2212                }
2213            }
2214            (KclValue::Object { .. }, Property::String(property), true) => {
2215                Err(KclError::new_semantic(KclErrorDetails::new(
2216                    format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
2217                    vec![self.clone().into()],
2218                )))
2219            }
2220            (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
2221                if i == 0
2222                    && let Some(value) = map.get("x")
2223                {
2224                    return Ok(value.to_owned().continue_());
2225                }
2226                if i == 1
2227                    && let Some(value) = map.get("y")
2228                {
2229                    return Ok(value.to_owned().continue_());
2230                }
2231                if i == 2
2232                    && let Some(value) = map.get("z")
2233                {
2234                    return Ok(value.to_owned().continue_());
2235                }
2236                let t = p.type_name();
2237                let article = article_for(t);
2238                Err(KclError::new_semantic(KclErrorDetails::new(
2239                    format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
2240                    vec![self.clone().into()],
2241                )))
2242            }
2243            (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
2244                let value_of_arr = arr.get(index);
2245                if let Some(value) = value_of_arr {
2246                    Ok(value.to_owned().continue_())
2247                } else {
2248                    Err(KclError::new_undefined_value(
2249                        KclErrorDetails::new(
2250                            format!("The array doesn't have any item at index {index}"),
2251                            vec![self.clone().into()],
2252                        ),
2253                        None,
2254                    ))
2255                }
2256            }
2257            // Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
2258            // This is kind of a silly property, but it's possible it occurs in generic code or something.
2259            (obj, Property::UInt(0), _) => Ok(obj.continue_()),
2260            (KclValue::HomArray { .. }, p, _) => {
2261                let t = p.type_name();
2262                let article = article_for(t);
2263                Err(KclError::new_semantic(KclErrorDetails::new(
2264                    format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
2265                    vec![self.clone().into()],
2266                )))
2267            }
2268            (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
2269                let Some(sketch) = value.sketch() else {
2270                    return Err(KclError::new_semantic(KclErrorDetails::new(
2271                        "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
2272                        vec![self.clone().into()],
2273                    )));
2274                };
2275                Ok(KclValue::Sketch {
2276                    value: Box::new(sketch.clone()),
2277                }
2278                .continue_())
2279            }
2280            (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
2281                // This is a common mistake.
2282                Err(KclError::new_semantic(KclErrorDetails::new(
2283                    format!(
2284                        "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
2285                        geometry.human_friendly_type()
2286                    ),
2287                    vec![self.clone().into()],
2288                )))
2289            }
2290            (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
2291                meta: vec![Metadata {
2292                    source_range: SourceRange::from(self.clone()),
2293                }],
2294                value: sk
2295                    .tags
2296                    .iter()
2297                    .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
2298                    .collect(),
2299                constrainable: false,
2300            }
2301            .continue_()),
2302            (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
2303                Err(KclError::new_semantic(KclErrorDetails::new(
2304                    format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
2305                    vec![self.clone().into()],
2306                )))
2307            }
2308            (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
2309                format!(
2310                    "Only objects can have members accessed with dot notation, but you're trying to access {}",
2311                    being_indexed.human_friendly_type()
2312                ),
2313                vec![self.clone().into()],
2314            ))),
2315            (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
2316                format!(
2317                    "Only arrays can be indexed, but you're trying to index {}",
2318                    being_indexed.human_friendly_type()
2319                ),
2320                vec![self.clone().into()],
2321            ))),
2322        }
2323    }
2324}
2325
2326impl Node<BinaryExpression> {
2327    pub(super) async fn get_result(
2328        &self,
2329        exec_state: &mut ExecState,
2330        ctx: &ExecutorContext,
2331    ) -> Result<KclValueControlFlow, KclError> {
2332        enum State {
2333            EvaluateLeft(Node<BinaryExpression>),
2334            FromLeft {
2335                node: Node<BinaryExpression>,
2336            },
2337            EvaluateRight {
2338                node: Node<BinaryExpression>,
2339                left: KclValue,
2340            },
2341            FromRight {
2342                node: Node<BinaryExpression>,
2343                left: KclValue,
2344            },
2345        }
2346
2347        let mut stack = vec![State::EvaluateLeft(self.clone())];
2348        let mut last_result: Option<KclValue> = None;
2349
2350        while let Some(state) = stack.pop() {
2351            match state {
2352                State::EvaluateLeft(node) => {
2353                    let left_part = node.left.clone();
2354                    match left_part {
2355                        BinaryPart::BinaryExpression(child) => {
2356                            stack.push(State::FromLeft { node });
2357                            stack.push(State::EvaluateLeft(*child));
2358                        }
2359                        part => {
2360                            let left_value = part.get_result(exec_state, ctx).await?;
2361                            let left_value = control_continue!(left_value);
2362                            stack.push(State::EvaluateRight { node, left: left_value });
2363                        }
2364                    }
2365                }
2366                State::FromLeft { node } => {
2367                    let Some(left_value) = last_result.take() else {
2368                        return Err(Self::missing_result_error(&node));
2369                    };
2370                    stack.push(State::EvaluateRight { node, left: left_value });
2371                }
2372                State::EvaluateRight { node, left } => {
2373                    let right_part = node.right.clone();
2374                    match right_part {
2375                        BinaryPart::BinaryExpression(child) => {
2376                            stack.push(State::FromRight { node, left });
2377                            stack.push(State::EvaluateLeft(*child));
2378                        }
2379                        part => {
2380                            let right_value = part.get_result(exec_state, ctx).await?;
2381                            let right_value = control_continue!(right_value);
2382                            let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2383                            last_result = Some(result);
2384                        }
2385                    }
2386                }
2387                State::FromRight { node, left } => {
2388                    let Some(right_value) = last_result.take() else {
2389                        return Err(Self::missing_result_error(&node));
2390                    };
2391                    let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2392                    last_result = Some(result);
2393                }
2394            }
2395        }
2396
2397        last_result
2398            .map(KclValue::continue_)
2399            .ok_or_else(|| Self::missing_result_error(self))
2400    }
2401
2402    async fn apply_operator(
2403        &self,
2404        exec_state: &mut ExecState,
2405        ctx: &ExecutorContext,
2406        left_value: KclValue,
2407        right_value: KclValue,
2408    ) -> Result<KclValue, KclError> {
2409        let mut meta = left_value.metadata();
2410        meta.extend(right_value.metadata());
2411
2412        // First check if we are doing string concatenation.
2413        if self.operator == BinaryOperator::Add
2414            && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
2415                (&left_value, &right_value)
2416        {
2417            return Ok(KclValue::String {
2418                value: format!("{left}{right}"),
2419                meta,
2420            });
2421        }
2422
2423        // Then check if we have solids.
2424        if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
2425            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2426                let args = Args::new_no_args(self.into(), ctx.clone(), Some("union".to_owned()));
2427                let result = crate::std::csg::inner_union(
2428                    vec![*left.clone(), *right.clone()],
2429                    Default::default(),
2430                    exec_state,
2431                    args,
2432                )
2433                .await?;
2434                return Ok(result.into());
2435            }
2436        } else if self.operator == BinaryOperator::Sub {
2437            // Check if we have solids.
2438            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2439                let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
2440                let result = crate::std::csg::inner_subtract(
2441                    vec![*left.clone()],
2442                    vec![*right.clone()],
2443                    Default::default(),
2444                    exec_state,
2445                    args,
2446                )
2447                .await?;
2448                return Ok(result.into());
2449            }
2450        } else if self.operator == BinaryOperator::And
2451            && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2452        {
2453            // Check if we have solids.
2454            let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
2455            let result = crate::std::csg::inner_intersect(
2456                vec![*left.clone(), *right.clone()],
2457                Default::default(),
2458                exec_state,
2459                args,
2460            )
2461            .await?;
2462            return Ok(result.into());
2463        }
2464
2465        // Check if we are doing logical operations on booleans.
2466        if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2467            let KclValue::Bool { value: left_value, .. } = left_value else {
2468                return Err(KclError::new_semantic(KclErrorDetails::new(
2469                    format!(
2470                        "Cannot apply logical operator to non-boolean value: {}",
2471                        left_value.human_friendly_type()
2472                    ),
2473                    vec![self.left.clone().into()],
2474                )));
2475            };
2476            let KclValue::Bool { value: right_value, .. } = right_value else {
2477                return Err(KclError::new_semantic(KclErrorDetails::new(
2478                    format!(
2479                        "Cannot apply logical operator to non-boolean value: {}",
2480                        right_value.human_friendly_type()
2481                    ),
2482                    vec![self.right.clone().into()],
2483                )));
2484            };
2485            let raw_value = match self.operator {
2486                BinaryOperator::Or => left_value || right_value,
2487                BinaryOperator::And => left_value && right_value,
2488                _ => unreachable!(),
2489            };
2490            return Ok(KclValue::Bool { value: raw_value, meta });
2491        }
2492
2493        // Check if we're doing equivalence in sketch mode.
2494        if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2495            match (&left_value, &right_value) {
2496                // Same sketch variables.
2497                (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2498                    if left_value.id == right_value.id =>
2499                {
2500                    return Ok(KclValue::Bool { value: true, meta });
2501                }
2502                // Different sketch variables.
2503                (KclValue::SketchVar { .. }, KclValue::SketchVar { .. }) => {
2504                    // TODO: sketch-api: Collapse the two sketch variables into
2505                    // one constraint variable.
2506                    return Err(KclError::new_semantic(KclErrorDetails::new(
2507                        "TODO: Different sketch variables".to_owned(),
2508                        vec![self.into()],
2509                    )));
2510                }
2511                // One sketch variable, one number.
2512                (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2513                | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2514                    let number_value = normalize_to_solver_unit(
2515                        input_number,
2516                        input_number.into(),
2517                        exec_state,
2518                        "fixed constraint value",
2519                    )?;
2520                    let Some(n) = number_value.as_ty_f64() else {
2521                        let message = format!(
2522                            "Expected number after coercion, but found {}",
2523                            number_value.human_friendly_type()
2524                        );
2525                        debug_assert!(false, "{}", &message);
2526                        return Err(internal_err(message, self));
2527                    };
2528                    let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2529                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2530                        let message = "Being inside a sketch block should have already been checked above".to_owned();
2531                        debug_assert!(false, "{}", &message);
2532                        return Err(internal_err(message, self));
2533                    };
2534                    sketch_block_state.solver_constraints.push(constraint);
2535                    return Ok(KclValue::Bool { value: true, meta });
2536                }
2537                // One sketch constraint, one number.
2538                (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
2539                | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
2540                    let number_value = normalize_to_solver_unit(
2541                        input_number,
2542                        input_number.into(),
2543                        exec_state,
2544                        "fixed constraint value",
2545                    )?;
2546                    let Some(n) = number_value.as_ty_f64() else {
2547                        let message = format!(
2548                            "Expected number after coercion, but found {}",
2549                            number_value.human_friendly_type()
2550                        );
2551                        debug_assert!(false, "{}", &message);
2552                        return Err(internal_err(message, self));
2553                    };
2554                    // Recast the number side of == to get the source expression text.
2555                    #[cfg(feature = "artifact-graph")]
2556                    let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
2557                        &self.right
2558                    } else {
2559                        &self.left
2560                    };
2561                    #[cfg(feature = "artifact-graph")]
2562                    let source = {
2563                        use crate::unparser::ExprContext;
2564                        let mut buf = String::new();
2565                        number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
2566                        crate::frontend::sketch::ConstraintSource {
2567                            expr: buf,
2568                            is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
2569                        }
2570                    };
2571
2572                    match &constraint.kind {
2573                        SketchConstraintKind::Distance { points } => {
2574                            let range = self.as_source_range();
2575                            let p0 = &points[0];
2576                            let p1 = &points[1];
2577                            let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2578                                p0.vars.x.to_constraint_id(range)?,
2579                                p0.vars.y.to_constraint_id(range)?,
2580                            );
2581                            let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2582                                p1.vars.x.to_constraint_id(range)?,
2583                                p1.vars.y.to_constraint_id(range)?,
2584                            );
2585                            let solver_constraint = Constraint::Distance(solver_pt0, solver_pt1, n.n);
2586
2587                            #[cfg(feature = "artifact-graph")]
2588                            let constraint_id = exec_state.next_object_id();
2589                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2590                                let message =
2591                                    "Being inside a sketch block should have already been checked above".to_owned();
2592                                debug_assert!(false, "{}", &message);
2593                                return Err(internal_err(message, self));
2594                            };
2595                            sketch_block_state.solver_constraints.push(solver_constraint);
2596                            #[cfg(feature = "artifact-graph")]
2597                            {
2598                                use crate::{
2599                                    execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
2600                                    front::Distance,
2601                                };
2602
2603                                let Some(sketch_id) = sketch_block_state.sketch_id else {
2604                                    let message = "Sketch id missing for constraint artifact".to_owned();
2605                                    debug_assert!(false, "{}", &message);
2606                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
2607                                };
2608                                let sketch_constraint = crate::front::Constraint::Distance(Distance {
2609                                    points: vec![p0.object_id, p1.object_id],
2610                                    distance: n.try_into().map_err(|_| {
2611                                        internal_err("Failed to convert distance units numeric suffix:", range)
2612                                    })?,
2613                                    source,
2614                                });
2615                                sketch_block_state.sketch_constraints.push(constraint_id);
2616                                let artifact_id = exec_state.next_artifact_id();
2617                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2618                                    id: artifact_id,
2619                                    sketch_id,
2620                                    constraint_id,
2621                                    constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
2622                                    code_ref: CodeRef::placeholder(range),
2623                                }));
2624                                exec_state.add_scene_object(
2625                                    Object {
2626                                        id: constraint_id,
2627                                        kind: ObjectKind::Constraint {
2628                                            constraint: sketch_constraint,
2629                                        },
2630                                        label: Default::default(),
2631                                        comments: Default::default(),
2632                                        artifact_id,
2633                                        source: range.into(),
2634                                    },
2635                                    range,
2636                                );
2637                            }
2638                        }
2639                        SketchConstraintKind::Radius { points } | SketchConstraintKind::Diameter { points } => {
2640                            let range = self.as_source_range();
2641                            let center = &points[0];
2642                            let start = &points[1];
2643                            // Find the arc segment that has matching center and start to get its end point
2644                            let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
2645                                return Err(internal_err(
2646                                    "Being inside a sketch block should have already been checked above",
2647                                    self,
2648                                ));
2649                            };
2650                            // Find the arc segment with matching center and start
2651                            let (constraint_name, is_diameter) = match &constraint.kind {
2652                                SketchConstraintKind::Radius { .. } => ("radius", false),
2653                                SketchConstraintKind::Diameter { .. } => ("diameter", true),
2654                                _ => unreachable!(),
2655                            };
2656                            let arc_segment = sketch_block_state
2657                                .needed_by_engine
2658                                .iter()
2659                                .find(|seg| {
2660                                    matches!(&seg.kind, UnsolvedSegmentKind::Arc {
2661                                        center_object_id,
2662                                        start_object_id,
2663                                        ..
2664                                    } if *center_object_id == center.object_id && *start_object_id == start.object_id)
2665                                })
2666                                .ok_or_else(|| {
2667                                    internal_err(
2668                                        format!("Could not find arc segment for {} constraint", constraint_name),
2669                                        range,
2670                                    )
2671                                })?;
2672                            let UnsolvedSegmentKind::Arc { end, .. } = &arc_segment.kind else {
2673                                return Err(internal_err("Expected arc segment", range));
2674                            };
2675                            // Extract end point coordinates
2676                            let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
2677                                (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => (*end_x, *end_y),
2678                                _ => {
2679                                    return Err(internal_err(
2680                                        "Arc end point must have sketch vars in all coordinates",
2681                                        range,
2682                                    ));
2683                                }
2684                            };
2685                            let solver_arc = kcl_ezpz::datatypes::inputs::DatumCircularArc {
2686                                center: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2687                                    center.vars.x.to_constraint_id(range)?,
2688                                    center.vars.y.to_constraint_id(range)?,
2689                                ),
2690                                start: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2691                                    start.vars.x.to_constraint_id(range)?,
2692                                    start.vars.y.to_constraint_id(range)?,
2693                                ),
2694                                end: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2695                                    end_x_var.to_constraint_id(range)?,
2696                                    end_y_var.to_constraint_id(range)?,
2697                                ),
2698                            };
2699                            // Use ArcRadius constraint from ezpz solver
2700                            // Diameter is twice the radius, so we divide by 2 before passing to the solver
2701                            let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
2702                            let solver_constraint = Constraint::ArcRadius(solver_arc, radius_value);
2703
2704                            #[cfg(feature = "artifact-graph")]
2705                            let constraint_id = exec_state.next_object_id();
2706                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2707                                let message =
2708                                    "Being inside a sketch block should have already been checked above".to_owned();
2709                                debug_assert!(false, "{}", &message);
2710                                return Err(internal_err(message, self));
2711                            };
2712                            sketch_block_state.solver_constraints.push(solver_constraint);
2713                            #[cfg(feature = "artifact-graph")]
2714                            {
2715                                use crate::execution::{
2716                                    Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType,
2717                                };
2718                                // Find the arc segment object ID from the sketch block state
2719
2720                                let arc_object_id = sketch_block_state
2721                                    .needed_by_engine
2722                                    .iter()
2723                                    .find(|seg| {
2724                                        matches!(&seg.kind, UnsolvedSegmentKind::Arc {
2725                                            center_object_id,
2726                                            start_object_id,
2727                                            ..
2728                                        } if *center_object_id == center.object_id && *start_object_id == start.object_id)
2729                                    })
2730                                    .map(|seg| seg.object_id)
2731                                    .ok_or_else(|| {
2732                                        internal_err(
2733                                            format!(
2734                                                "Could not find arc segment object ID for {} constraint",
2735                                                constraint_name
2736                                            ),
2737                                            range,
2738                                        )
2739                                    })?;
2740
2741                                let constraint = if is_diameter {
2742                                    use crate::frontend::sketch::Diameter;
2743                                    crate::front::Constraint::Diameter(Diameter {
2744                                        arc: arc_object_id,
2745                                        diameter: n.try_into().map_err(|_| {
2746                                            internal_err("Failed to convert diameter units numeric suffix:", range)
2747                                        })?,
2748                                    })
2749                                } else {
2750                                    use crate::frontend::sketch::Radius;
2751                                    crate::front::Constraint::Radius(Radius {
2752                                        arc: arc_object_id,
2753                                        radius: n.try_into().map_err(|_| {
2754                                            internal_err("Failed to convert radius units numeric suffix:", range)
2755                                        })?,
2756                                    })
2757                                };
2758                                sketch_block_state.sketch_constraints.push(constraint_id);
2759                                let Some(sketch_id) = sketch_block_state.sketch_id else {
2760                                    let message = "Sketch id missing for constraint artifact".to_owned();
2761                                    debug_assert!(false, "{}", &message);
2762                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
2763                                };
2764                                let artifact_id = exec_state.next_artifact_id();
2765                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2766                                    id: artifact_id,
2767                                    sketch_id,
2768                                    constraint_id,
2769                                    constraint_type: SketchBlockConstraintType::from(&constraint),
2770                                    code_ref: CodeRef::placeholder(range),
2771                                }));
2772                                exec_state.add_scene_object(
2773                                    Object {
2774                                        id: constraint_id,
2775                                        kind: ObjectKind::Constraint { constraint },
2776                                        label: Default::default(),
2777                                        comments: Default::default(),
2778                                        artifact_id,
2779                                        source: range.into(),
2780                                    },
2781                                    range,
2782                                );
2783                            }
2784                        }
2785                        SketchConstraintKind::HorizontalDistance { points } => {
2786                            let range = self.as_source_range();
2787                            let p0 = &points[0];
2788                            let p1 = &points[1];
2789                            let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2790                                p0.vars.x.to_constraint_id(range)?,
2791                                p0.vars.y.to_constraint_id(range)?,
2792                            );
2793                            let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2794                                p1.vars.x.to_constraint_id(range)?,
2795                                p1.vars.y.to_constraint_id(range)?,
2796                            );
2797                            // Horizontal distance: p1.x - p0.x = n
2798                            // Note: EZPZ's HorizontalDistance(p0, p1, d) means p0.x - p1.x = d
2799                            // So we swap the points to get p1.x - p0.x = n
2800                            let solver_constraint =
2801                                kcl_ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n);
2802
2803                            #[cfg(feature = "artifact-graph")]
2804                            let constraint_id = exec_state.next_object_id();
2805                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2806                                let message =
2807                                    "Being inside a sketch block should have already been checked above".to_owned();
2808                                debug_assert!(false, "{}", &message);
2809                                return Err(internal_err(message, self));
2810                            };
2811                            sketch_block_state.solver_constraints.push(solver_constraint);
2812                            #[cfg(feature = "artifact-graph")]
2813                            {
2814                                use crate::{
2815                                    execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
2816                                    front::Distance,
2817                                };
2818
2819                                let constraint = crate::front::Constraint::HorizontalDistance(Distance {
2820                                    points: vec![p0.object_id, p1.object_id],
2821                                    distance: n.try_into().map_err(|_| {
2822                                        internal_err("Failed to convert distance units numeric suffix:", range)
2823                                    })?,
2824                                    source,
2825                                });
2826                                sketch_block_state.sketch_constraints.push(constraint_id);
2827                                let Some(sketch_id) = sketch_block_state.sketch_id else {
2828                                    let message = "Sketch id missing for constraint artifact".to_owned();
2829                                    debug_assert!(false, "{}", &message);
2830                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
2831                                };
2832                                let artifact_id = exec_state.next_artifact_id();
2833                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2834                                    id: artifact_id,
2835                                    sketch_id,
2836                                    constraint_id,
2837                                    constraint_type: SketchBlockConstraintType::from(&constraint),
2838                                    code_ref: CodeRef::placeholder(range),
2839                                }));
2840                                exec_state.add_scene_object(
2841                                    Object {
2842                                        id: constraint_id,
2843                                        kind: ObjectKind::Constraint { constraint },
2844                                        label: Default::default(),
2845                                        comments: Default::default(),
2846                                        artifact_id,
2847                                        source: range.into(),
2848                                    },
2849                                    range,
2850                                );
2851                            }
2852                        }
2853                        SketchConstraintKind::VerticalDistance { points } => {
2854                            let range = self.as_source_range();
2855                            let p0 = &points[0];
2856                            let p1 = &points[1];
2857                            let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2858                                p0.vars.x.to_constraint_id(range)?,
2859                                p0.vars.y.to_constraint_id(range)?,
2860                            );
2861                            let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2862                                p1.vars.x.to_constraint_id(range)?,
2863                                p1.vars.y.to_constraint_id(range)?,
2864                            );
2865                            // Vertical distance: p1.y - p0.y = n
2866                            // Note: EZPZ's VerticalDistance(p0, p1, d) means p0.y - p1.y = d
2867                            // So we swap the points to get p1.y - p0.y = n
2868                            let solver_constraint = kcl_ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n);
2869
2870                            #[cfg(feature = "artifact-graph")]
2871                            let constraint_id = exec_state.next_object_id();
2872                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2873                                let message =
2874                                    "Being inside a sketch block should have already been checked above".to_owned();
2875                                debug_assert!(false, "{}", &message);
2876                                return Err(internal_err(message, self));
2877                            };
2878                            sketch_block_state.solver_constraints.push(solver_constraint);
2879                            #[cfg(feature = "artifact-graph")]
2880                            {
2881                                use crate::{
2882                                    execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
2883                                    front::Distance,
2884                                };
2885
2886                                let constraint = crate::front::Constraint::VerticalDistance(Distance {
2887                                    points: vec![p0.object_id, p1.object_id],
2888                                    distance: n.try_into().map_err(|_| {
2889                                        internal_err("Failed to convert distance units numeric suffix:", range)
2890                                    })?,
2891                                    source,
2892                                });
2893                                sketch_block_state.sketch_constraints.push(constraint_id);
2894                                let Some(sketch_id) = sketch_block_state.sketch_id else {
2895                                    let message = "Sketch id missing for constraint artifact".to_owned();
2896                                    debug_assert!(false, "{}", &message);
2897                                    return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
2898                                };
2899                                let artifact_id = exec_state.next_artifact_id();
2900                                exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2901                                    id: artifact_id,
2902                                    sketch_id,
2903                                    constraint_id,
2904                                    constraint_type: SketchBlockConstraintType::from(&constraint),
2905                                    code_ref: CodeRef::placeholder(range),
2906                                }));
2907                                exec_state.add_scene_object(
2908                                    Object {
2909                                        id: constraint_id,
2910                                        kind: ObjectKind::Constraint { constraint },
2911                                        label: Default::default(),
2912                                        comments: Default::default(),
2913                                        artifact_id,
2914                                        source: range.into(),
2915                                    },
2916                                    range,
2917                                );
2918                            }
2919                        }
2920                    }
2921                    return Ok(KclValue::Bool { value: true, meta });
2922                }
2923                _ => {
2924                    return Err(KclError::new_semantic(KclErrorDetails::new(
2925                        format!(
2926                            "Cannot create an equivalence constraint between values of these types: {} and {}",
2927                            left_value.human_friendly_type(),
2928                            right_value.human_friendly_type()
2929                        ),
2930                        vec![self.into()],
2931                    )));
2932                }
2933            }
2934        }
2935
2936        let left = number_as_f64(&left_value, self.left.clone().into())?;
2937        let right = number_as_f64(&right_value, self.right.clone().into())?;
2938
2939        let value = match self.operator {
2940            BinaryOperator::Add => {
2941                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2942                self.warn_on_unknown(&ty, "Adding", exec_state);
2943                KclValue::Number { value: l + r, meta, ty }
2944            }
2945            BinaryOperator::Sub => {
2946                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2947                self.warn_on_unknown(&ty, "Subtracting", exec_state);
2948                KclValue::Number { value: l - r, meta, ty }
2949            }
2950            BinaryOperator::Mul => {
2951                let (l, r, ty) = NumericType::combine_mul(left, right);
2952                self.warn_on_unknown(&ty, "Multiplying", exec_state);
2953                KclValue::Number { value: l * r, meta, ty }
2954            }
2955            BinaryOperator::Div => {
2956                let (l, r, ty) = NumericType::combine_div(left, right);
2957                self.warn_on_unknown(&ty, "Dividing", exec_state);
2958                KclValue::Number { value: l / r, meta, ty }
2959            }
2960            BinaryOperator::Mod => {
2961                let (l, r, ty) = NumericType::combine_mod(left, right);
2962                self.warn_on_unknown(&ty, "Modulo of", exec_state);
2963                KclValue::Number { value: l % r, meta, ty }
2964            }
2965            BinaryOperator::Pow => KclValue::Number {
2966                value: left.n.powf(right.n),
2967                meta,
2968                ty: exec_state.current_default_units(),
2969            },
2970            BinaryOperator::Neq => {
2971                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2972                self.warn_on_unknown(&ty, "Comparing", exec_state);
2973                KclValue::Bool { value: l != r, meta }
2974            }
2975            BinaryOperator::Gt => {
2976                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2977                self.warn_on_unknown(&ty, "Comparing", exec_state);
2978                KclValue::Bool { value: l > r, meta }
2979            }
2980            BinaryOperator::Gte => {
2981                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2982                self.warn_on_unknown(&ty, "Comparing", exec_state);
2983                KclValue::Bool { value: l >= r, meta }
2984            }
2985            BinaryOperator::Lt => {
2986                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2987                self.warn_on_unknown(&ty, "Comparing", exec_state);
2988                KclValue::Bool { value: l < r, meta }
2989            }
2990            BinaryOperator::Lte => {
2991                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2992                self.warn_on_unknown(&ty, "Comparing", exec_state);
2993                KclValue::Bool { value: l <= r, meta }
2994            }
2995            BinaryOperator::Eq => {
2996                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2997                self.warn_on_unknown(&ty, "Comparing", exec_state);
2998                KclValue::Bool { value: l == r, meta }
2999            }
3000            BinaryOperator::And | BinaryOperator::Or => unreachable!(),
3001        };
3002
3003        Ok(value)
3004    }
3005
3006    fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
3007        internal_err("missing result while evaluating binary expression", node)
3008    }
3009
3010    fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
3011        if ty == &NumericType::Unknown {
3012            let sr = self.as_source_range();
3013            exec_state.clear_units_warnings(&sr);
3014            let mut err = CompilationError::err(
3015                sr,
3016                format!(
3017                    "{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)`."
3018                ),
3019            );
3020            err.tag = crate::errors::Tag::UnknownNumericUnits;
3021            exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
3022        }
3023    }
3024}
3025
3026impl Node<UnaryExpression> {
3027    pub(super) async fn get_result(
3028        &self,
3029        exec_state: &mut ExecState,
3030        ctx: &ExecutorContext,
3031    ) -> Result<KclValueControlFlow, KclError> {
3032        match self.operator {
3033            UnaryOperator::Not => {
3034                let value = self.argument.get_result(exec_state, ctx).await?;
3035                let value = control_continue!(value);
3036                let KclValue::Bool {
3037                    value: bool_value,
3038                    meta: _,
3039                } = value
3040                else {
3041                    return Err(KclError::new_semantic(KclErrorDetails::new(
3042                        format!(
3043                            "Cannot apply unary operator ! to non-boolean value: {}",
3044                            value.human_friendly_type()
3045                        ),
3046                        vec![self.into()],
3047                    )));
3048                };
3049                let meta = vec![Metadata {
3050                    source_range: self.into(),
3051                }];
3052                let negated = KclValue::Bool {
3053                    value: !bool_value,
3054                    meta,
3055                };
3056
3057                Ok(negated.continue_())
3058            }
3059            UnaryOperator::Neg => {
3060                let value = self.argument.get_result(exec_state, ctx).await?;
3061                let value = control_continue!(value);
3062                let err = || {
3063                    KclError::new_semantic(KclErrorDetails::new(
3064                        format!(
3065                            "You can only negate numbers, planes, or lines, but this is a {}",
3066                            value.human_friendly_type()
3067                        ),
3068                        vec![self.into()],
3069                    ))
3070                };
3071                match &value {
3072                    KclValue::Number { value, ty, .. } => {
3073                        let meta = vec![Metadata {
3074                            source_range: self.into(),
3075                        }];
3076                        Ok(KclValue::Number {
3077                            value: -value,
3078                            meta,
3079                            ty: *ty,
3080                        }
3081                        .continue_())
3082                    }
3083                    KclValue::Plane { value } => {
3084                        let mut plane = value.clone();
3085                        if plane.info.x_axis.x != 0.0 {
3086                            plane.info.x_axis.x *= -1.0;
3087                        }
3088                        if plane.info.x_axis.y != 0.0 {
3089                            plane.info.x_axis.y *= -1.0;
3090                        }
3091                        if plane.info.x_axis.z != 0.0 {
3092                            plane.info.x_axis.z *= -1.0;
3093                        }
3094
3095                        plane.id = exec_state.next_uuid();
3096                        plane.object_id = None;
3097                        Ok(KclValue::Plane { value: plane }.continue_())
3098                    }
3099                    KclValue::Object {
3100                        value: values, meta, ..
3101                    } => {
3102                        // Special-case for negating line-like objects.
3103                        let Some(direction) = values.get("direction") else {
3104                            return Err(err());
3105                        };
3106
3107                        let direction = match direction {
3108                            KclValue::Tuple { value: values, meta } => {
3109                                let values = values
3110                                    .iter()
3111                                    .map(|v| match v {
3112                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3113                                            value: *value * -1.0,
3114                                            ty: *ty,
3115                                            meta: meta.clone(),
3116                                        }),
3117                                        _ => Err(err()),
3118                                    })
3119                                    .collect::<Result<Vec<_>, _>>()?;
3120
3121                                KclValue::Tuple {
3122                                    value: values,
3123                                    meta: meta.clone(),
3124                                }
3125                            }
3126                            KclValue::HomArray {
3127                                value: values,
3128                                ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
3129                            } => {
3130                                let values = values
3131                                    .iter()
3132                                    .map(|v| match v {
3133                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3134                                            value: *value * -1.0,
3135                                            ty: *ty,
3136                                            meta: meta.clone(),
3137                                        }),
3138                                        _ => Err(err()),
3139                                    })
3140                                    .collect::<Result<Vec<_>, _>>()?;
3141
3142                                KclValue::HomArray {
3143                                    value: values,
3144                                    ty: ty.clone(),
3145                                }
3146                            }
3147                            _ => return Err(err()),
3148                        };
3149
3150                        let mut value = values.clone();
3151                        value.insert("direction".to_owned(), direction);
3152                        Ok(KclValue::Object {
3153                            value,
3154                            meta: meta.clone(),
3155                            constrainable: false,
3156                        }
3157                        .continue_())
3158                    }
3159                    _ => Err(err()),
3160                }
3161            }
3162            UnaryOperator::Plus => {
3163                let operand = self.argument.get_result(exec_state, ctx).await?;
3164                let operand = control_continue!(operand);
3165                match operand {
3166                    KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
3167                    _ => Err(KclError::new_semantic(KclErrorDetails::new(
3168                        format!(
3169                            "You can only apply unary + to numbers or planes, but this is a {}",
3170                            operand.human_friendly_type()
3171                        ),
3172                        vec![self.into()],
3173                    ))),
3174                }
3175            }
3176        }
3177    }
3178}
3179
3180pub(crate) async fn execute_pipe_body(
3181    exec_state: &mut ExecState,
3182    body: &[Expr],
3183    source_range: SourceRange,
3184    ctx: &ExecutorContext,
3185) -> Result<KclValueControlFlow, KclError> {
3186    let Some((first, body)) = body.split_first() else {
3187        return Err(KclError::new_semantic(KclErrorDetails::new(
3188            "Pipe expressions cannot be empty".to_owned(),
3189            vec![source_range],
3190        )));
3191    };
3192    // Evaluate the first element in the pipeline.
3193    // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
3194    // they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
3195    // of its own.
3196    let meta = Metadata {
3197        source_range: SourceRange::from(first),
3198    };
3199    let output = ctx
3200        .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
3201        .await?;
3202    let output = control_continue!(output);
3203
3204    // Now that we've evaluated the first child expression in the pipeline, following child expressions
3205    // should use the previous child expression for %.
3206    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
3207    let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
3208    // Evaluate remaining elements.
3209    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
3210    // Restore the previous pipe value.
3211    exec_state.mod_local.pipe_value = previous_pipe_value;
3212
3213    result
3214}
3215
3216/// Execute the tail of a pipe expression.  exec_state.pipe_value must be set by
3217/// the caller.
3218#[async_recursion]
3219async fn inner_execute_pipe_body(
3220    exec_state: &mut ExecState,
3221    body: &[Expr],
3222    ctx: &ExecutorContext,
3223) -> Result<KclValueControlFlow, KclError> {
3224    for expression in body {
3225        if let Expr::TagDeclarator(_) = expression {
3226            return Err(KclError::new_semantic(KclErrorDetails::new(
3227                format!("This cannot be in a PipeExpression: {expression:?}"),
3228                vec![expression.into()],
3229            )));
3230        }
3231        let metadata = Metadata {
3232            source_range: SourceRange::from(expression),
3233        };
3234        let output = ctx
3235            .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
3236            .await?;
3237        let output = control_continue!(output);
3238        exec_state.mod_local.pipe_value = Some(output);
3239    }
3240    // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
3241    let final_output = exec_state.mod_local.pipe_value.take().unwrap();
3242    Ok(final_output.continue_())
3243}
3244
3245impl Node<TagDeclarator> {
3246    pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
3247        let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
3248            value: self.name.clone(),
3249            info: Vec::new(),
3250            meta: vec![Metadata {
3251                source_range: self.into(),
3252            }],
3253        }));
3254
3255        exec_state
3256            .mut_stack()
3257            .add(self.name.clone(), memory_item, self.into())?;
3258
3259        Ok(self.into())
3260    }
3261}
3262
3263impl Node<ArrayExpression> {
3264    #[async_recursion]
3265    pub(super) async fn execute(
3266        &self,
3267        exec_state: &mut ExecState,
3268        ctx: &ExecutorContext,
3269    ) -> Result<KclValueControlFlow, KclError> {
3270        let mut results = Vec::with_capacity(self.elements.len());
3271
3272        for element in &self.elements {
3273            let metadata = Metadata::from(element);
3274            // TODO: Carry statement kind here so that we know if we're
3275            // inside a variable declaration.
3276            let value = ctx
3277                .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
3278                .await?;
3279            let value = control_continue!(value);
3280
3281            results.push(value);
3282        }
3283
3284        Ok(KclValue::HomArray {
3285            value: results,
3286            ty: RuntimeType::Primitive(PrimitiveType::Any),
3287        }
3288        .continue_())
3289    }
3290}
3291
3292impl Node<ArrayRangeExpression> {
3293    #[async_recursion]
3294    pub(super) async fn execute(
3295        &self,
3296        exec_state: &mut ExecState,
3297        ctx: &ExecutorContext,
3298    ) -> Result<KclValueControlFlow, KclError> {
3299        let metadata = Metadata::from(&self.start_element);
3300        let start_val = ctx
3301            .execute_expr(
3302                &self.start_element,
3303                exec_state,
3304                &metadata,
3305                &[],
3306                StatementKind::Expression,
3307            )
3308            .await?;
3309        let start_val = control_continue!(start_val);
3310        let start = start_val
3311            .as_ty_f64()
3312            .ok_or(KclError::new_semantic(KclErrorDetails::new(
3313                format!(
3314                    "Expected number for range start but found {}",
3315                    start_val.human_friendly_type()
3316                ),
3317                vec![self.into()],
3318            )))?;
3319        let metadata = Metadata::from(&self.end_element);
3320        let end_val = ctx
3321            .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
3322            .await?;
3323        let end_val = control_continue!(end_val);
3324        let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
3325            format!(
3326                "Expected number for range end but found {}",
3327                end_val.human_friendly_type()
3328            ),
3329            vec![self.into()],
3330        )))?;
3331
3332        let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
3333        let Some(start) = crate::try_f64_to_i64(start) else {
3334            return Err(KclError::new_semantic(KclErrorDetails::new(
3335                format!("Range start must be an integer, but found {start}"),
3336                vec![self.into()],
3337            )));
3338        };
3339        let Some(end) = crate::try_f64_to_i64(end) else {
3340            return Err(KclError::new_semantic(KclErrorDetails::new(
3341                format!("Range end must be an integer, but found {end}"),
3342                vec![self.into()],
3343            )));
3344        };
3345
3346        if end < start {
3347            return Err(KclError::new_semantic(KclErrorDetails::new(
3348                format!("Range start is greater than range end: {start} .. {end}"),
3349                vec![self.into()],
3350            )));
3351        }
3352
3353        let range: Vec<_> = if self.end_inclusive {
3354            (start..=end).collect()
3355        } else {
3356            (start..end).collect()
3357        };
3358
3359        let meta = vec![Metadata {
3360            source_range: self.into(),
3361        }];
3362
3363        Ok(KclValue::HomArray {
3364            value: range
3365                .into_iter()
3366                .map(|num| KclValue::Number {
3367                    value: num as f64,
3368                    ty,
3369                    meta: meta.clone(),
3370                })
3371                .collect(),
3372            ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
3373        }
3374        .continue_())
3375    }
3376}
3377
3378impl Node<ObjectExpression> {
3379    #[async_recursion]
3380    pub(super) async fn execute(
3381        &self,
3382        exec_state: &mut ExecState,
3383        ctx: &ExecutorContext,
3384    ) -> Result<KclValueControlFlow, KclError> {
3385        let mut object = HashMap::with_capacity(self.properties.len());
3386        for property in &self.properties {
3387            let metadata = Metadata::from(&property.value);
3388            let result = ctx
3389                .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
3390                .await?;
3391            let result = control_continue!(result);
3392            object.insert(property.key.name.clone(), result);
3393        }
3394
3395        Ok(KclValue::Object {
3396            value: object,
3397            meta: vec![Metadata {
3398                source_range: self.into(),
3399            }],
3400            constrainable: false,
3401        }
3402        .continue_())
3403    }
3404}
3405
3406fn article_for<S: AsRef<str>>(s: S) -> &'static str {
3407    // '[' is included since it's an array.
3408    if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
3409        "an"
3410    } else {
3411        "a"
3412    }
3413}
3414
3415fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
3416    v.as_ty_f64().ok_or_else(|| {
3417        let actual_type = v.human_friendly_type();
3418        KclError::new_semantic(KclErrorDetails::new(
3419            format!("Expected a number, but found {actual_type}",),
3420            vec![source_range],
3421        ))
3422    })
3423}
3424
3425impl Node<IfExpression> {
3426    #[async_recursion]
3427    pub(super) async fn get_result(
3428        &self,
3429        exec_state: &mut ExecState,
3430        ctx: &ExecutorContext,
3431    ) -> Result<KclValueControlFlow, KclError> {
3432        // Check the `if` branch.
3433        let cond_value = ctx
3434            .execute_expr(
3435                &self.cond,
3436                exec_state,
3437                &Metadata::from(self),
3438                &[],
3439                StatementKind::Expression,
3440            )
3441            .await?;
3442        let cond_value = control_continue!(cond_value);
3443        if cond_value.get_bool()? {
3444            let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
3445            // Block must end in an expression, so this has to be Some.
3446            // Enforced by the parser.
3447            // See https://github.com/KittyCAD/modeling-app/issues/4015
3448            return Ok(block_result.unwrap());
3449        }
3450
3451        // Check any `else if` branches.
3452        for else_if in &self.else_ifs {
3453            let cond_value = ctx
3454                .execute_expr(
3455                    &else_if.cond,
3456                    exec_state,
3457                    &Metadata::from(self),
3458                    &[],
3459                    StatementKind::Expression,
3460                )
3461                .await?;
3462            let cond_value = control_continue!(cond_value);
3463            if cond_value.get_bool()? {
3464                let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
3465                // Block must end in an expression, so this has to be Some.
3466                // Enforced by the parser.
3467                // See https://github.com/KittyCAD/modeling-app/issues/4015
3468                return Ok(block_result.unwrap());
3469            }
3470        }
3471
3472        // Run the final `else` branch.
3473        ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
3474            .await
3475            .map(|expr| expr.unwrap())
3476    }
3477}
3478
3479#[derive(Debug)]
3480enum Property {
3481    UInt(usize),
3482    String(String),
3483}
3484
3485impl Property {
3486    #[allow(clippy::too_many_arguments)]
3487    async fn try_from<'a>(
3488        computed: bool,
3489        value: Expr,
3490        exec_state: &mut ExecState,
3491        sr: SourceRange,
3492        ctx: &ExecutorContext,
3493        metadata: &Metadata,
3494        annotations: &[Node<Annotation>],
3495        statement_kind: StatementKind<'a>,
3496    ) -> Result<Self, KclError> {
3497        let property_sr = vec![sr];
3498        if !computed {
3499            let Expr::Name(identifier) = value else {
3500                // Should actually be impossible because the parser would reject it.
3501                return Err(KclError::new_semantic(KclErrorDetails::new(
3502                    "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
3503                        .to_owned(),
3504                    property_sr,
3505                )));
3506            };
3507            return Ok(Property::String(identifier.to_string()));
3508        }
3509
3510        let prop_value = ctx
3511            .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
3512            .await?;
3513        let prop_value = match prop_value.control {
3514            ControlFlowKind::Continue => prop_value.into_value(),
3515            ControlFlowKind::Exit => {
3516                let message = "Early return inside array brackets is currently not supported".to_owned();
3517                debug_assert!(false, "{}", &message);
3518                return Err(internal_err(message, sr));
3519            }
3520        };
3521        match prop_value {
3522            KclValue::Number { value, ty, meta: _ } => {
3523                if !matches!(
3524                    ty,
3525                    NumericType::Unknown
3526                        | NumericType::Default { .. }
3527                        | NumericType::Known(crate::exec::UnitType::Count)
3528                ) {
3529                    return Err(KclError::new_semantic(KclErrorDetails::new(
3530                        format!(
3531                            "{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"
3532                        ),
3533                        property_sr,
3534                    )));
3535                }
3536                if let Some(x) = crate::try_f64_to_usize(value) {
3537                    Ok(Property::UInt(x))
3538                } else {
3539                    Err(KclError::new_semantic(KclErrorDetails::new(
3540                        format!("{value} is not a valid index, indices must be whole numbers >= 0"),
3541                        property_sr,
3542                    )))
3543                }
3544            }
3545            _ => Err(KclError::new_semantic(KclErrorDetails::new(
3546                "Only numbers (>= 0) can be indexes".to_owned(),
3547                vec![sr],
3548            ))),
3549        }
3550    }
3551}
3552
3553impl Property {
3554    fn type_name(&self) -> &'static str {
3555        match self {
3556            Property::UInt(_) => "number",
3557            Property::String(_) => "string",
3558        }
3559    }
3560}
3561
3562impl Node<PipeExpression> {
3563    #[async_recursion]
3564    pub(super) async fn get_result(
3565        &self,
3566        exec_state: &mut ExecState,
3567        ctx: &ExecutorContext,
3568    ) -> Result<KclValueControlFlow, KclError> {
3569        execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
3570    }
3571}
3572
3573#[cfg(test)]
3574mod test {
3575    use std::sync::Arc;
3576
3577    use tokio::io::AsyncWriteExt;
3578
3579    use super::*;
3580    use crate::{
3581        ExecutorSettings,
3582        errors::Severity,
3583        exec::UnitType,
3584        execution::{ContextType, parse_execute},
3585    };
3586
3587    #[tokio::test(flavor = "multi_thread")]
3588    async fn ascription() {
3589        let program = r#"
3590a = 42: number
3591b = a: number
3592p = {
3593  origin = { x = 0, y = 0, z = 0 },
3594  xAxis = { x = 1, y = 0, z = 0 },
3595  yAxis = { x = 0, y = 1, z = 0 },
3596  zAxis = { x = 0, y = 0, z = 1 }
3597}: Plane
3598arr1 = [42]: [number(cm)]
3599"#;
3600
3601        let result = parse_execute(program).await.unwrap();
3602        let mem = result.exec_state.stack();
3603        assert!(matches!(
3604            mem.memory
3605                .get_from("p", result.mem_env, SourceRange::default(), 0)
3606                .unwrap(),
3607            KclValue::Plane { .. }
3608        ));
3609        let arr1 = mem
3610            .memory
3611            .get_from("arr1", result.mem_env, SourceRange::default(), 0)
3612            .unwrap();
3613        if let KclValue::HomArray { value, ty } = arr1 {
3614            assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
3615            assert_eq!(
3616                *ty,
3617                RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
3618            );
3619            // Compare, ignoring meta.
3620            if let KclValue::Number { value, ty, .. } = &value[0] {
3621                // It should not convert units.
3622                assert_eq!(*value, 42.0);
3623                assert_eq!(
3624                    *ty,
3625                    NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
3626                );
3627            } else {
3628                panic!("Expected a number; found {:?}", value[0]);
3629            }
3630        } else {
3631            panic!("Expected HomArray; found {arr1:?}");
3632        }
3633
3634        let program = r#"
3635a = 42: string
3636"#;
3637        let result = parse_execute(program).await;
3638        let err = result.unwrap_err();
3639        assert!(
3640            err.to_string()
3641                .contains("could not coerce a number (with type `number`) to type `string`"),
3642            "Expected error but found {err:?}"
3643        );
3644
3645        let program = r#"
3646a = 42: Plane
3647"#;
3648        let result = parse_execute(program).await;
3649        let err = result.unwrap_err();
3650        assert!(
3651            err.to_string()
3652                .contains("could not coerce a number (with type `number`) to type `Plane`"),
3653            "Expected error but found {err:?}"
3654        );
3655
3656        let program = r#"
3657arr = [0]: [string]
3658"#;
3659        let result = parse_execute(program).await;
3660        let err = result.unwrap_err();
3661        assert!(
3662            err.to_string().contains(
3663                "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
3664            ),
3665            "Expected error but found {err:?}"
3666        );
3667
3668        let program = r#"
3669mixedArr = [0, "a"]: [number(mm)]
3670"#;
3671        let result = parse_execute(program).await;
3672        let err = result.unwrap_err();
3673        assert!(
3674            err.to_string().contains(
3675                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3676            ),
3677            "Expected error but found {err:?}"
3678        );
3679
3680        let program = r#"
3681mixedArr = [0, "a"]: [mm]
3682"#;
3683        let result = parse_execute(program).await;
3684        let err = result.unwrap_err();
3685        assert!(
3686            err.to_string().contains(
3687                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3688            ),
3689            "Expected error but found {err:?}"
3690        );
3691    }
3692
3693    #[tokio::test(flavor = "multi_thread")]
3694    async fn neg_plane() {
3695        let program = r#"
3696p = {
3697  origin = { x = 0, y = 0, z = 0 },
3698  xAxis = { x = 1, y = 0, z = 0 },
3699  yAxis = { x = 0, y = 1, z = 0 },
3700}: Plane
3701p2 = -p
3702"#;
3703
3704        let result = parse_execute(program).await.unwrap();
3705        let mem = result.exec_state.stack();
3706        match mem
3707            .memory
3708            .get_from("p2", result.mem_env, SourceRange::default(), 0)
3709            .unwrap()
3710        {
3711            KclValue::Plane { value } => {
3712                assert_eq!(value.info.x_axis.x, -1.0);
3713                assert_eq!(value.info.x_axis.y, 0.0);
3714                assert_eq!(value.info.x_axis.z, 0.0);
3715            }
3716            _ => unreachable!(),
3717        }
3718    }
3719
3720    #[tokio::test(flavor = "multi_thread")]
3721    async fn multiple_returns() {
3722        let program = r#"fn foo() {
3723  return 0
3724  return 42
3725}
3726
3727a = foo()
3728"#;
3729
3730        let result = parse_execute(program).await;
3731        assert!(result.unwrap_err().to_string().contains("return"));
3732    }
3733
3734    #[tokio::test(flavor = "multi_thread")]
3735    async fn load_all_modules() {
3736        // program a.kcl
3737        let program_a_kcl = r#"
3738export a = 1
3739"#;
3740        // program b.kcl
3741        let program_b_kcl = r#"
3742import a from 'a.kcl'
3743
3744export b = a + 1
3745"#;
3746        // program c.kcl
3747        let program_c_kcl = r#"
3748import a from 'a.kcl'
3749
3750export c = a + 2
3751"#;
3752
3753        // program main.kcl
3754        let main_kcl = r#"
3755import b from 'b.kcl'
3756import c from 'c.kcl'
3757
3758d = b + c
3759"#;
3760
3761        let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
3762            .parse_errs_as_err()
3763            .unwrap();
3764
3765        let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
3766
3767        tokio::fs::File::create(tmpdir.path().join("main.kcl"))
3768            .await
3769            .unwrap()
3770            .write_all(main_kcl.as_bytes())
3771            .await
3772            .unwrap();
3773
3774        tokio::fs::File::create(tmpdir.path().join("a.kcl"))
3775            .await
3776            .unwrap()
3777            .write_all(program_a_kcl.as_bytes())
3778            .await
3779            .unwrap();
3780
3781        tokio::fs::File::create(tmpdir.path().join("b.kcl"))
3782            .await
3783            .unwrap()
3784            .write_all(program_b_kcl.as_bytes())
3785            .await
3786            .unwrap();
3787
3788        tokio::fs::File::create(tmpdir.path().join("c.kcl"))
3789            .await
3790            .unwrap()
3791            .write_all(program_c_kcl.as_bytes())
3792            .await
3793            .unwrap();
3794
3795        let exec_ctxt = ExecutorContext {
3796            engine: Arc::new(Box::new(
3797                crate::engine::conn_mock::EngineConnection::new()
3798                    .map_err(|err| {
3799                        internal_err(
3800                            format!("Failed to create mock engine connection: {err}"),
3801                            SourceRange::default(),
3802                        )
3803                    })
3804                    .unwrap(),
3805            )),
3806            fs: Arc::new(crate::fs::FileManager::new()),
3807            settings: ExecutorSettings {
3808                project_directory: Some(crate::TypedPath(tmpdir.path().into())),
3809                ..Default::default()
3810            },
3811            context_type: ContextType::Mock,
3812        };
3813        let mut exec_state = ExecState::new(&exec_ctxt);
3814
3815        exec_ctxt
3816            .run(
3817                &crate::Program {
3818                    ast: main.clone(),
3819                    original_file_contents: "".to_owned(),
3820                },
3821                &mut exec_state,
3822            )
3823            .await
3824            .unwrap();
3825    }
3826
3827    #[tokio::test(flavor = "multi_thread")]
3828    async fn user_coercion() {
3829        let program = r#"fn foo(x: Axis2d) {
3830  return 0
3831}
3832
3833foo(x = { direction = [0, 0], origin = [0, 0]})
3834"#;
3835
3836        parse_execute(program).await.unwrap();
3837
3838        let program = r#"fn foo(x: Axis3d) {
3839  return 0
3840}
3841
3842foo(x = { direction = [0, 0], origin = [0, 0]})
3843"#;
3844
3845        parse_execute(program).await.unwrap_err();
3846    }
3847
3848    #[tokio::test(flavor = "multi_thread")]
3849    async fn coerce_return() {
3850        let program = r#"fn foo(): number(mm) {
3851  return 42
3852}
3853
3854a = foo()
3855"#;
3856
3857        parse_execute(program).await.unwrap();
3858
3859        let program = r#"fn foo(): mm {
3860  return 42
3861}
3862
3863a = foo()
3864"#;
3865
3866        parse_execute(program).await.unwrap();
3867
3868        let program = r#"fn foo(): number(mm) {
3869  return { bar: 42 }
3870}
3871
3872a = foo()
3873"#;
3874
3875        parse_execute(program).await.unwrap_err();
3876
3877        let program = r#"fn foo(): mm {
3878  return { bar: 42 }
3879}
3880
3881a = foo()
3882"#;
3883
3884        parse_execute(program).await.unwrap_err();
3885    }
3886
3887    #[tokio::test(flavor = "multi_thread")]
3888    async fn test_sensible_error_when_missing_equals_in_kwarg() {
3889        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)"]
3890            .into_iter()
3891            .enumerate()
3892        {
3893            let program = format!(
3894                "fn foo() {{ return 0 }}
3895z = 0
3896fn f(x, y, z) {{ return 0 }}
3897{call}"
3898            );
3899            let err = parse_execute(&program).await.unwrap_err();
3900            let msg = err.message();
3901            assert!(
3902                msg.contains("This argument needs a label, but it doesn't have one"),
3903                "failed test {i}: {msg}"
3904            );
3905            assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
3906            if i == 0 {
3907                assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
3908            }
3909        }
3910    }
3911
3912    #[tokio::test(flavor = "multi_thread")]
3913    async fn default_param_for_unlabeled() {
3914        // Tests that the input param for myExtrude is taken from the pipeline value and same-name
3915        // keyword args.
3916        let ast = r#"fn myExtrude(@sk, length) {
3917  return extrude(sk, length)
3918}
3919sketch001 = startSketchOn(XY)
3920  |> circle(center = [0, 0], radius = 93.75)
3921  |> myExtrude(length = 40)
3922"#;
3923
3924        parse_execute(ast).await.unwrap();
3925    }
3926
3927    #[tokio::test(flavor = "multi_thread")]
3928    async fn dont_use_unlabelled_as_input() {
3929        // `length` should be used as the `length` argument to extrude, not the unlabelled input
3930        let ast = r#"length = 10
3931startSketchOn(XY)
3932  |> circle(center = [0, 0], radius = 93.75)
3933  |> extrude(length)
3934"#;
3935
3936        parse_execute(ast).await.unwrap();
3937    }
3938
3939    #[tokio::test(flavor = "multi_thread")]
3940    async fn ascription_in_binop() {
3941        let ast = r#"foo = tan(0): number(rad) - 4deg"#;
3942        parse_execute(ast).await.unwrap();
3943
3944        let ast = r#"foo = tan(0): rad - 4deg"#;
3945        parse_execute(ast).await.unwrap();
3946    }
3947
3948    #[tokio::test(flavor = "multi_thread")]
3949    async fn neg_sqrt() {
3950        let ast = r#"bad = sqrt(-2)"#;
3951
3952        let e = parse_execute(ast).await.unwrap_err();
3953        // Make sure we get a useful error message and not an engine error.
3954        assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
3955    }
3956
3957    #[tokio::test(flavor = "multi_thread")]
3958    async fn non_array_fns() {
3959        let ast = r#"push(1, item = 2)
3960pop(1)
3961map(1, f = fn(@x) { return x + 1 })
3962reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
3963
3964        parse_execute(ast).await.unwrap();
3965    }
3966
3967    #[tokio::test(flavor = "multi_thread")]
3968    async fn non_array_indexing() {
3969        let good = r#"a = 42
3970good = a[0]
3971"#;
3972        let result = parse_execute(good).await.unwrap();
3973        let mem = result.exec_state.stack();
3974        let num = mem
3975            .memory
3976            .get_from("good", result.mem_env, SourceRange::default(), 0)
3977            .unwrap()
3978            .as_ty_f64()
3979            .unwrap();
3980        assert_eq!(num.n, 42.0);
3981
3982        let bad = r#"a = 42
3983bad = a[1]
3984"#;
3985
3986        parse_execute(bad).await.unwrap_err();
3987    }
3988
3989    #[tokio::test(flavor = "multi_thread")]
3990    async fn coerce_unknown_to_length() {
3991        let ast = r#"x = 2mm * 2mm
3992y = x: number(Length)"#;
3993        let e = parse_execute(ast).await.unwrap_err();
3994        assert!(
3995            e.message().contains("could not coerce"),
3996            "Error message: '{}'",
3997            e.message()
3998        );
3999
4000        let ast = r#"x = 2mm
4001y = x: number(Length)"#;
4002        let result = parse_execute(ast).await.unwrap();
4003        let mem = result.exec_state.stack();
4004        let num = mem
4005            .memory
4006            .get_from("y", result.mem_env, SourceRange::default(), 0)
4007            .unwrap()
4008            .as_ty_f64()
4009            .unwrap();
4010        assert_eq!(num.n, 2.0);
4011        assert_eq!(num.ty, NumericType::mm());
4012    }
4013
4014    #[tokio::test(flavor = "multi_thread")]
4015    async fn one_warning_unknown() {
4016        let ast = r#"
4017// Should warn once
4018a = PI * 2
4019// Should warn once
4020b = (PI * 2) / 3
4021// Should not warn
4022c = ((PI * 2) / 3): number(deg)
4023"#;
4024
4025        let result = parse_execute(ast).await.unwrap();
4026        assert_eq!(result.exec_state.errors().len(), 2);
4027    }
4028
4029    #[tokio::test(flavor = "multi_thread")]
4030    async fn non_count_indexing() {
4031        let ast = r#"x = [0, 0]
4032y = x[1mm]
4033"#;
4034        parse_execute(ast).await.unwrap_err();
4035
4036        let ast = r#"x = [0, 0]
4037y = 1deg
4038z = x[y]
4039"#;
4040        parse_execute(ast).await.unwrap_err();
4041
4042        let ast = r#"x = [0, 0]
4043y = x[0mm + 1]
4044"#;
4045        parse_execute(ast).await.unwrap_err();
4046    }
4047
4048    #[tokio::test(flavor = "multi_thread")]
4049    async fn getting_property_of_plane() {
4050        let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
4051        parse_execute(&ast).await.unwrap();
4052    }
4053
4054    #[cfg(feature = "artifact-graph")]
4055    #[tokio::test(flavor = "multi_thread")]
4056    async fn no_artifacts_from_within_hole_call() {
4057        // Test that executing stdlib KCL, like the `hole` function
4058        // (which is actually implemented in KCL not Rust)
4059        // does not generate artifacts from within the stdlib code,
4060        // only from the user code.
4061        let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
4062        let out = parse_execute(&ast).await.unwrap();
4063
4064        // Get all the operations that occurred.
4065        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4066
4067        // There should be 5, for sketching the cube and applying the hole.
4068        // If the stdlib internal calls are being tracked, that's a bug,
4069        // and the actual number of operations will be something like 35.
4070        let expected = 5;
4071        assert_eq!(
4072            actual_operations.len(),
4073            expected,
4074            "expected {expected} operations, received {}:\n{actual_operations:#?}",
4075            actual_operations.len(),
4076        );
4077    }
4078
4079    #[cfg(feature = "artifact-graph")]
4080    #[tokio::test(flavor = "multi_thread")]
4081    async fn feature_tree_annotation_on_user_defined_kcl() {
4082        // The call to foo() should not generate an operation,
4083        // because its 'feature_tree' attribute has been set to false.
4084        let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4085        let out = parse_execute(&ast).await.unwrap();
4086
4087        // Get all the operations that occurred.
4088        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4089
4090        let expected = 0;
4091        assert_eq!(
4092            actual_operations.len(),
4093            expected,
4094            "expected {expected} operations, received {}:\n{actual_operations:#?}",
4095            actual_operations.len(),
4096        );
4097    }
4098
4099    #[cfg(feature = "artifact-graph")]
4100    #[tokio::test(flavor = "multi_thread")]
4101    async fn no_feature_tree_annotation_on_user_defined_kcl() {
4102        // The call to foo() should generate an operation,
4103        // because @(feature_tree) defaults to true.
4104        let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4105        let out = parse_execute(&ast).await.unwrap();
4106
4107        // Get all the operations that occurred.
4108        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4109
4110        let expected = 2;
4111        assert_eq!(
4112            actual_operations.len(),
4113            expected,
4114            "expected {expected} operations, received {}:\n{actual_operations:#?}",
4115            actual_operations.len(),
4116        );
4117        assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
4118        assert!(matches!(actual_operations[1], Operation::GroupEnd));
4119    }
4120
4121    #[tokio::test(flavor = "multi_thread")]
4122    async fn custom_warning() {
4123        let warn = r#"
4124a = PI * 2
4125"#;
4126        let result = parse_execute(warn).await.unwrap();
4127        assert_eq!(result.exec_state.errors().len(), 1);
4128        assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
4129
4130        let allow = r#"
4131@warnings(allow = unknownUnits)
4132a = PI * 2
4133"#;
4134        let result = parse_execute(allow).await.unwrap();
4135        assert_eq!(result.exec_state.errors().len(), 0);
4136
4137        let deny = r#"
4138@warnings(deny = [unknownUnits])
4139a = PI * 2
4140"#;
4141        let result = parse_execute(deny).await.unwrap();
4142        assert_eq!(result.exec_state.errors().len(), 1);
4143        assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
4144    }
4145
4146    #[tokio::test(flavor = "multi_thread")]
4147    async fn cannot_solid_extrude_an_open_profile() {
4148        // This should fail during mock execution, because KCL should catch
4149        // that the profile is not closed.
4150        let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
4151        let program = crate::Program::parse_no_errs(&code).expect("should parse");
4152        let exec_ctxt = ExecutorContext::new_mock(None).await;
4153        let mut exec_state = ExecState::new(&exec_ctxt);
4154
4155        let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
4156        assert!(matches!(err, KclError::Semantic { .. }));
4157        exec_ctxt.close().await;
4158    }
4159}