kcl_lib/execution/
exec_ast.rs

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