kcl_lib/execution/
exec_ast.rs

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