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