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, Segment,
15        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: bool,
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 {
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 {
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 = if preserve_mem {
178            exec_state.mut_stack().pop_and_preserve_env()
179        } else {
180            exec_state.mut_stack().pop_env()
181        };
182        let module_artifacts = if !preserve_mem {
183            std::mem::swap(&mut exec_state.mod_local, &mut local_state);
184            local_state.artifacts
185        } else {
186            std::mem::take(&mut exec_state.mod_local.artifacts)
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, false)
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, false)
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: bool,
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 closure = 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                        KclValue::Function {
837                            value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
838                            meta: vec![metadata.to_owned()],
839                        }
840                    } else {
841                        return Err(KclError::new_semantic(KclErrorDetails::new(
842                            "Rust implementation of functions is restricted to the standard library".to_owned(),
843                            vec![metadata.source_range],
844                        )));
845                    }
846                } else {
847                    // Snapshotting memory here is crucial for semantics so that we close
848                    // over variables. Variables defined lexically later shouldn't
849                    // be available to the function body.
850                    KclValue::Function {
851                        value: Box::new(FunctionSource::kcl(
852                            function_expression.clone(),
853                            exec_state.mut_stack().snapshot(),
854                            KclFunctionSourceParams {
855                                is_std,
856                                experimental,
857                                include_in_feature_tree,
858                            },
859                        )),
860                        meta: vec![metadata.to_owned()],
861                    }
862                };
863
864                // If the function expression has a name, i.e. `fn name() {}`,
865                // bind it in the current scope.
866                if let Some(fn_name) = &function_expression.name {
867                    exec_state
868                        .mut_stack()
869                        .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
870                }
871
872                closure.continue_()
873            }
874            Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
875            Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
876            Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
877                StatementKind::Declaration { name } => {
878                    let message = format!(
879                        "you cannot declare variable {name} as %, because % can only be used in function calls"
880                    );
881
882                    return Err(KclError::new_semantic(KclErrorDetails::new(
883                        message,
884                        vec![pipe_substitution.into()],
885                    )));
886                }
887                StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
888                    Some(x) => x.continue_(),
889                    None => {
890                        return Err(KclError::new_semantic(KclErrorDetails::new(
891                            "cannot use % outside a pipe expression".to_owned(),
892                            vec![pipe_substitution.into()],
893                        )));
894                    }
895                },
896            },
897            Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
898            Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
899            Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
900            Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
901            Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
902            Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
903            Expr::LabelledExpression(expr) => {
904                let value_cf = self
905                    .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
906                    .await?;
907                let value = control_continue!(value_cf);
908                exec_state
909                    .mut_stack()
910                    .add(expr.label.name.clone(), value.clone(), init.into())?;
911                // TODO this lets us use the label as a variable name, but not as a tag in most cases
912                value.continue_()
913            }
914            Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
915            Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
916            Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
917        };
918        Ok(item)
919    }
920}
921
922/// If the error is about an undefined name, and that name matches the name being defined,
923/// make the error message more specific.
924fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
925    let KclError::UndefinedValue { name, mut details } = e else {
926        return e;
927    };
928    // TODO after June 26th: replace this with a let-chain,
929    // which will be available in Rust 1.88
930    // https://rust-lang.github.io/rfcs/2497-if-let-chains.html
931    if let (Some(name0), Some(name1)) = (&being_declared, &name)
932        && name0 == name1
933    {
934        details.message = format!(
935            "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
936        );
937    }
938    KclError::UndefinedValue { details, name }
939}
940
941impl Node<AscribedExpression> {
942    #[async_recursion]
943    pub(super) async fn get_result(
944        &self,
945        exec_state: &mut ExecState,
946        ctx: &ExecutorContext,
947    ) -> Result<KclValueControlFlow, KclError> {
948        let metadata = Metadata {
949            source_range: SourceRange::from(self),
950        };
951        let result = ctx
952            .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
953            .await?;
954        let result = control_continue!(result);
955        apply_ascription(&result, &self.ty, exec_state, self.into()).map(KclValue::continue_)
956    }
957}
958
959impl Node<SketchBlock> {
960    pub(super) async fn get_result(
961        &self,
962        exec_state: &mut ExecState,
963        ctx: &ExecutorContext,
964    ) -> Result<KclValueControlFlow, KclError> {
965        if exec_state.mod_local.sketch_block.is_some() {
966            // Disallow nested sketch blocks for now.
967            return Err(KclError::new_semantic(KclErrorDetails::new(
968                "Cannot execute a sketch block from within another sketch block".to_owned(),
969                vec![SourceRange::from(self)],
970            )));
971        }
972
973        let range = SourceRange::from(self);
974
975        // Evaluate arguments.
976        let mut labeled = IndexMap::new();
977        for labeled_arg in &self.arguments {
978            let source_range = SourceRange::from(labeled_arg.arg.clone());
979            let metadata = Metadata { source_range };
980            let value_cf = ctx
981                .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
982                .await?;
983            let value = control_continue!(value_cf);
984            let arg = Arg::new(value, source_range);
985            match &labeled_arg.label {
986                Some(label) => {
987                    labeled.insert(label.name.clone(), arg);
988                }
989                None => {
990                    let name = labeled_arg.arg.ident_name();
991                    if let Some(name) = name {
992                        labeled.insert(name.to_owned(), arg);
993                    } else {
994                        return Err(KclError::new_semantic(KclErrorDetails::new(
995                            "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
996                            vec![SourceRange::from(&labeled_arg.arg)],
997                        )));
998                    }
999                }
1000            }
1001        }
1002        let mut args = Args::new_no_args(range, ctx.clone(), Some("sketch block".to_owned()));
1003        args.labeled = labeled;
1004
1005        #[cfg(feature = "artifact-graph")]
1006        let sketch_id = {
1007            // Create the sketch block scene object. This needs to happen before
1008            // scene objects created inside the sketch block so that its ID is
1009            // stable across sketch block edits.
1010
1011            use crate::{
1012                engine::PlaneName,
1013                execution::{Artifact, ArtifactId, CodeRef, SketchBlock},
1014            };
1015            let sketch_id = exec_state.next_object_id();
1016            let arg_on: Option<crate::execution::Plane> =
1017                args.get_kw_arg_opt("on", &RuntimeType::plane(), exec_state)?;
1018            let on_object = arg_on.as_ref().and_then(|plane| plane.object_id);
1019
1020            // Get the plane artifact ID if the plane is an object plane
1021            let plane_artifact_id = arg_on.as_ref().map(|plane| plane.artifact_id);
1022
1023            let artifact_id = ArtifactId::from(exec_state.next_uuid());
1024            let sketch_scene_object = Object {
1025                id: sketch_id,
1026                kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
1027                    args: crate::front::SketchArgs {
1028                        on: on_object
1029                            .map(crate::front::Plane::Object)
1030                            .unwrap_or(crate::front::Plane::Default(PlaneName::Xy)),
1031                    },
1032                    segments: Default::default(),
1033                    constraints: Default::default(),
1034                }),
1035                label: Default::default(),
1036                comments: Default::default(),
1037                artifact_id,
1038                source: range.into(),
1039            };
1040            exec_state.add_scene_object(sketch_scene_object, range);
1041
1042            // Create and add the sketch block artifact
1043            exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
1044                id: artifact_id,
1045                plane_id: plane_artifact_id,
1046                code_ref: CodeRef::placeholder(range),
1047                sketch_id,
1048            }));
1049
1050            sketch_id
1051        };
1052
1053        let (return_result, variables, sketch_block_state) = {
1054            // Don't early return until the stack frame is popped!
1055            self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
1056
1057            // Track that we're executing a sketch block.
1058            let original_value = exec_state.mod_local.sketch_block.replace(SketchBlockState::default());
1059
1060            let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1061
1062            let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1063
1064            let block_variables = exec_state
1065                .stack()
1066                .find_all_in_current_env()
1067                .map(|(name, value)| (name.clone(), value.clone()))
1068                .collect::<IndexMap<_, _>>();
1069
1070            exec_state.mut_stack().pop_env();
1071
1072            (result, block_variables, sketch_block_state)
1073        };
1074
1075        // Propagate errors.
1076        return_result?;
1077        let Some(sketch_block_state) = sketch_block_state else {
1078            debug_assert!(false, "Sketch block state should still be set to Some from just above");
1079            return Err(KclError::new_internal(KclErrorDetails::new(
1080                "Sketch block state should still be set to Some from just above".to_owned(),
1081                vec![SourceRange::from(self)],
1082            )));
1083        };
1084
1085        // Translate sketch variables and constraints to solver input.
1086        let constraints = sketch_block_state
1087            .solver_constraints
1088            .iter()
1089            .cloned()
1090            .map(kcl_ezpz::ConstraintRequest::highest_priority)
1091            .chain(
1092                // Optional constraints have a lower priority.
1093                sketch_block_state
1094                    .solver_optional_constraints
1095                    .iter()
1096                    .cloned()
1097                    .map(|c| kcl_ezpz::ConstraintRequest::new(c, 1)),
1098            )
1099            .collect::<Vec<_>>();
1100        let initial_guesses = sketch_block_state
1101            .sketch_vars
1102            .iter()
1103            .map(|v| {
1104                let Some(sketch_var) = v.as_sketch_var() else {
1105                    return Err(KclError::new_internal(KclErrorDetails::new(
1106                        "Expected sketch variable".to_owned(),
1107                        vec![SourceRange::from(self)],
1108                    )));
1109                };
1110                let constraint_id = sketch_var.id.to_constraint_id(range)?;
1111                // Normalize units.
1112                let number_value = KclValue::Number {
1113                    value: sketch_var.initial_value,
1114                    ty: sketch_var.ty,
1115                    meta: sketch_var.meta.clone(),
1116                };
1117                let initial_guess_value =
1118                    normalize_to_solver_unit(&number_value, v.into(), exec_state, "sketch variable initial value")?;
1119                let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1120                    n.n
1121                } else {
1122                    let message = format!(
1123                        "Expected number after coercion, but found {}",
1124                        initial_guess_value.human_friendly_type()
1125                    );
1126                    debug_assert!(false, "{}", &message);
1127                    return Err(KclError::new_internal(KclErrorDetails::new(
1128                        message,
1129                        vec![SourceRange::from(self)],
1130                    )));
1131                };
1132                Ok((constraint_id, initial_guess))
1133            })
1134            .collect::<Result<Vec<_>, KclError>>()?;
1135        // Solve constraints.
1136        let config = kcl_ezpz::Config::default().with_max_iterations(50);
1137        let solve_result = if exec_state.mod_local.freedom_analysis {
1138            kcl_ezpz::solve_analysis(&constraints, initial_guesses.clone(), config)
1139                .map(|outcome| (outcome.outcome, Some(FreedomAnalysis::from(outcome.analysis))))
1140        } else {
1141            kcl_ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1142        };
1143        let (solve_outcome, solve_analysis) = match solve_result {
1144            Ok((solved, freedom)) => (Solved::from(solved), freedom),
1145            Err(failure) => {
1146                match &failure.error {
1147                    NonLinearSystemError::FaerMatrix { .. }
1148                    | NonLinearSystemError::Faer { .. }
1149                    | NonLinearSystemError::FaerSolve { .. }
1150                    | NonLinearSystemError::FaerSvd(..)
1151                    | NonLinearSystemError::DidNotConverge => {
1152                        // Constraint solver failed to find a solution. Build a
1153                        // solution that is the initial guesses.
1154                        exec_state.warn(
1155                            CompilationError::err(range, "Constraint solver failed to find a solution".to_owned()),
1156                            annotations::WARN_SOLVER,
1157                        );
1158                        let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1159                        (
1160                            Solved {
1161                                final_values,
1162                                iterations: Default::default(),
1163                                warnings: failure.warnings,
1164                                unsatisfied: Default::default(),
1165                                priority_solved: Default::default(),
1166                            },
1167                            None,
1168                        )
1169                    }
1170                    NonLinearSystemError::EmptySystemNotAllowed
1171                    | NonLinearSystemError::WrongNumberGuesses { .. }
1172                    | NonLinearSystemError::MissingGuess { .. }
1173                    | NonLinearSystemError::NotFound(..) => {
1174                        // These indicate something's gone wrong in KCL or ezpz,
1175                        // it's not a user error. We should investigate this.
1176                        #[cfg(target_arch = "wasm32")]
1177                        web_sys::console::error_1(
1178                            &format!("Internal error from constraint solver: {}", &failure.error).into(),
1179                        );
1180                        return Err(KclError::new_internal(KclErrorDetails::new(
1181                            format!("Internal error from constraint solver: {}", &failure.error),
1182                            vec![SourceRange::from(self)],
1183                        )));
1184                    }
1185                    _ => {
1186                        // Catch all error case so that it's not a breaking change to publish new errors.
1187                        return Err(KclError::new_internal(KclErrorDetails::new(
1188                            format!("Error from constraint solver: {}", &failure.error),
1189                            vec![SourceRange::from(self)],
1190                        )));
1191                    }
1192                }
1193            }
1194        };
1195        #[cfg(not(feature = "artifact-graph"))]
1196        let _ = solve_analysis;
1197        // Propagate warnings.
1198        for warning in &solve_outcome.warnings {
1199            let message = if let Some(index) = warning.about_constraint.as_ref() {
1200                format!("{}; constraint index {}", &warning.content, index)
1201            } else {
1202                format!("{}", &warning.content)
1203            };
1204            exec_state.warn(CompilationError::err(range, message), annotations::WARN_SOLVER);
1205        }
1206        // Substitute solutions back into sketch variables.
1207        let solution_ty = solver_numeric_type(exec_state);
1208        let variables = substitute_sketch_vars(variables, &solve_outcome, solution_ty, solve_analysis.as_ref())?;
1209        let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1210        for unsolved_segment in &sketch_block_state.needed_by_engine {
1211            solved_segments.push(substitute_sketch_var_in_segment(
1212                unsolved_segment.clone(),
1213                &solve_outcome,
1214                solver_numeric_type(exec_state),
1215                solve_analysis.as_ref(),
1216            )?);
1217        }
1218        #[cfg(feature = "artifact-graph")]
1219        {
1220            // Store variable solutions so that the sketch refactoring API can
1221            // write them back to the source. When editing a sketch block, we
1222            // exit early so that the sketch block that we're editing is always
1223            // the last one. Therefore, we should overwrite any previous
1224            // solutions.
1225            exec_state.mod_local.artifacts.var_solutions =
1226                sketch_block_state.var_solutions(solve_outcome, solution_ty, SourceRange::from(self))?;
1227        }
1228
1229        // Create scene objects after unknowns are solved.
1230        let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1231
1232        #[cfg(not(feature = "artifact-graph"))]
1233        drop(scene_objects);
1234        #[cfg(feature = "artifact-graph")]
1235        {
1236            let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1237            for scene_object in scene_objects {
1238                segment_object_ids.push(scene_object.id);
1239                // Fill in placeholder scene objects.
1240                exec_state.set_scene_object(scene_object);
1241            }
1242            // Update the sketch scene object with the segments.
1243            let Some(sketch_object) = exec_state.mod_local.artifacts.scene_objects.get_mut(sketch_id.0) else {
1244                let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1245                debug_assert!(false, "{}", &message);
1246                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1247            };
1248            let ObjectKind::Sketch(sketch) = &mut sketch_object.kind else {
1249                let message = format!(
1250                    "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1251                    sketch_id, sketch_object
1252                );
1253                debug_assert!(false, "{}", &message);
1254                return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1255            };
1256            sketch.segments.extend(segment_object_ids);
1257            // Update the sketch scene object with constraints.
1258            let mut sketch_block_state = sketch_block_state;
1259            sketch
1260                .constraints
1261                .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1262
1263            // Push sketch solve operation
1264            exec_state.push_op(Operation::SketchSolve {
1265                sketch_id,
1266                node_path: NodePath::placeholder(),
1267                source_range: range,
1268            });
1269        }
1270
1271        // TODO: sketch-api: send everything to the engine.
1272
1273        let metadata = Metadata {
1274            source_range: SourceRange::from(self),
1275        };
1276        let return_value = KclValue::Object {
1277            value: variables,
1278            constrainable: Default::default(),
1279            meta: vec![metadata],
1280        };
1281        Ok(if self.is_being_edited {
1282            // When the sketch block is being edited, we exit the program
1283            // immediately.
1284            return_value.exit()
1285        } else {
1286            return_value.continue_()
1287        })
1288    }
1289}
1290
1291impl SketchBlock {
1292    fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1293        exec_state.mut_stack().push_new_env_for_call(parent);
1294    }
1295}
1296
1297impl Node<SketchVar> {
1298    pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1299        let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1300            return Err(KclError::new_semantic(KclErrorDetails::new(
1301                "Cannot use a sketch variable outside of a sketch block".to_owned(),
1302                vec![SourceRange::from(self)],
1303            )));
1304        };
1305        let id = sketch_block_state.next_sketch_var_id();
1306        let sketch_var = if let Some(initial) = &self.initial {
1307            KclValue::from_sketch_var_literal(initial, id, exec_state)
1308        } else {
1309            let metadata = Metadata {
1310                source_range: SourceRange::from(self),
1311            };
1312
1313            KclValue::SketchVar {
1314                value: Box::new(super::SketchVar {
1315                    id,
1316                    initial_value: 0.0,
1317                    ty: NumericType::default(),
1318                    meta: vec![metadata],
1319                }),
1320            }
1321        };
1322
1323        let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1324            return Err(KclError::new_semantic(KclErrorDetails::new(
1325                "Cannot use a sketch variable outside of a sketch block".to_owned(),
1326                vec![SourceRange::from(self)],
1327            )));
1328        };
1329        sketch_block_state.sketch_vars.push(sketch_var.clone());
1330
1331        Ok(sketch_var)
1332    }
1333}
1334
1335fn apply_ascription(
1336    value: &KclValue,
1337    ty: &Node<Type>,
1338    exec_state: &mut ExecState,
1339    source_range: SourceRange,
1340) -> Result<KclValue, KclError> {
1341    let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false)
1342        .map_err(|e| KclError::new_semantic(e.into()))?;
1343
1344    if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1345        exec_state.clear_units_warnings(&source_range);
1346    }
1347
1348    value.coerce(&ty, false, exec_state).map_err(|_| {
1349        let suggestion = if ty == RuntimeType::length() {
1350            ", you might try coercing to a fully specified numeric type such as `mm`"
1351        } else if ty == RuntimeType::angle() {
1352            ", you might try coercing to a fully specified numeric type such as `deg`"
1353        } else {
1354            ""
1355        };
1356        let ty_str = if let Some(ty) = value.principal_type() {
1357            format!("(with type `{ty}`) ")
1358        } else {
1359            String::new()
1360        };
1361        KclError::new_semantic(KclErrorDetails::new(
1362            format!(
1363                "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1364                value.human_friendly_type()
1365            ),
1366            vec![source_range],
1367        ))
1368    })
1369}
1370
1371impl BinaryPart {
1372    #[async_recursion]
1373    pub(super) async fn get_result(
1374        &self,
1375        exec_state: &mut ExecState,
1376        ctx: &ExecutorContext,
1377    ) -> Result<KclValueControlFlow, KclError> {
1378        match self {
1379            BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
1380            BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned().map(KclValue::continue_),
1381            BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1382            BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1383            BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1384            BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1385            BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1386            BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1387            BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1388            BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1389            BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1390            BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
1391        }
1392    }
1393}
1394
1395impl Node<Name> {
1396    pub(super) async fn get_result<'a>(
1397        &self,
1398        exec_state: &'a mut ExecState,
1399        ctx: &ExecutorContext,
1400    ) -> Result<&'a KclValue, KclError> {
1401        let being_declared = exec_state.mod_local.being_declared.clone();
1402        self.get_result_inner(exec_state, ctx)
1403            .await
1404            .map_err(|e| var_in_own_ref_err(e, &being_declared))
1405    }
1406
1407    async fn get_result_inner<'a>(
1408        &self,
1409        exec_state: &'a mut ExecState,
1410        ctx: &ExecutorContext,
1411    ) -> Result<&'a KclValue, KclError> {
1412        if self.abs_path {
1413            return Err(KclError::new_semantic(KclErrorDetails::new(
1414                "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1415                self.as_source_ranges(),
1416            )));
1417        }
1418
1419        let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1420
1421        if self.path.is_empty() {
1422            let item_value = exec_state.stack().get(&self.name.name, self.into());
1423            if item_value.is_ok() {
1424                return item_value;
1425            }
1426            return exec_state.stack().get(&mod_name, self.into());
1427        }
1428
1429        let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1430        for p in &self.path {
1431            let value = match mem_spec {
1432                Some((env, exports)) => {
1433                    if !exports.contains(&p.name) {
1434                        return Err(KclError::new_semantic(KclErrorDetails::new(
1435                            format!("Item {} not found in module's exported items", p.name),
1436                            p.as_source_ranges(),
1437                        )));
1438                    }
1439
1440                    exec_state
1441                        .stack()
1442                        .memory
1443                        .get_from(&p.name, env, p.as_source_range(), 0)?
1444                }
1445                None => exec_state
1446                    .stack()
1447                    .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
1448            };
1449
1450            let KclValue::Module { value: module_id, .. } = value else {
1451                return Err(KclError::new_semantic(KclErrorDetails::new(
1452                    format!(
1453                        "Identifier in path must refer to a module, found {}",
1454                        value.human_friendly_type()
1455                    ),
1456                    p.as_source_ranges(),
1457                )));
1458            };
1459
1460            mem_spec = Some(
1461                ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1462                    .await?,
1463            );
1464        }
1465
1466        let (env, exports) = mem_spec.unwrap();
1467
1468        let item_exported = exports.contains(&self.name.name);
1469        let item_value = exec_state
1470            .stack()
1471            .memory
1472            .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1473
1474        // Item is defined and exported.
1475        if item_exported && item_value.is_ok() {
1476            return item_value;
1477        }
1478
1479        let mod_exported = exports.contains(&mod_name);
1480        let mod_value = exec_state
1481            .stack()
1482            .memory
1483            .get_from(&mod_name, env, self.name.as_source_range(), 0);
1484
1485        // Module is defined and exported.
1486        if mod_exported && mod_value.is_ok() {
1487            return mod_value;
1488        }
1489
1490        // Neither item or module is defined.
1491        if item_value.is_err() && mod_value.is_err() {
1492            return item_value;
1493        }
1494
1495        // Either item or module is defined, but not exported.
1496        debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1497        Err(KclError::new_semantic(KclErrorDetails::new(
1498            format!("Item {} not found in module's exported items", self.name.name),
1499            self.name.as_source_ranges(),
1500        )))
1501    }
1502}
1503
1504impl Node<MemberExpression> {
1505    async fn get_result(
1506        &self,
1507        exec_state: &mut ExecState,
1508        ctx: &ExecutorContext,
1509    ) -> Result<KclValueControlFlow, KclError> {
1510        let meta = Metadata {
1511            source_range: SourceRange::from(self),
1512        };
1513        // TODO: The order of execution is wrong. We should execute the object
1514        // *before* the property.
1515        let property = Property::try_from(
1516            self.computed,
1517            self.property.clone(),
1518            exec_state,
1519            self.into(),
1520            ctx,
1521            &meta,
1522            &[],
1523            StatementKind::Expression,
1524        )
1525        .await?;
1526        let object_cf = ctx
1527            .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1528            .await?;
1529        let object = control_continue!(object_cf);
1530
1531        // Check the property and object match -- e.g. ints for arrays, strs for objects.
1532        match (object, property, self.computed) {
1533            (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
1534                "at" => match &segment.repr {
1535                    SegmentRepr::Unsolved { segment } => {
1536                        match &segment.kind {
1537                            UnsolvedSegmentKind::Point { position, .. } => {
1538                                // TODO: assert that types of all elements are the same.
1539                                Ok(KclValue::HomArray {
1540                                    value: vec![
1541                                        KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
1542                                        KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
1543                                    ],
1544                                    ty: RuntimeType::any(),
1545                                }
1546                                .continue_())
1547                            }
1548                            _ => Err(KclError::new_undefined_value(
1549                                KclErrorDetails::new(
1550                                    format!("Property '{property}' not found in segment"),
1551                                    vec![self.clone().into()],
1552                                ),
1553                                None,
1554                            )),
1555                        }
1556                    }
1557                    SegmentRepr::Solved { segment } => {
1558                        match &segment.kind {
1559                            SegmentKind::Point { position, .. } => {
1560                                // TODO: assert that types of all elements are the same.
1561                                Ok(KclValue::array_from_point2d(
1562                                    [position[0].n, position[1].n],
1563                                    position[0].ty,
1564                                    segment.meta.clone(),
1565                                )
1566                                .continue_())
1567                            }
1568                            _ => Err(KclError::new_undefined_value(
1569                                KclErrorDetails::new(
1570                                    format!("Property '{property}' not found in segment"),
1571                                    vec![self.clone().into()],
1572                                ),
1573                                None,
1574                            )),
1575                        }
1576                    }
1577                },
1578                "start" => match &segment.repr {
1579                    SegmentRepr::Unsolved { segment } => match &segment.kind {
1580                        UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1581                            KclErrorDetails::new(
1582                                format!("Property '{property}' not found in point segment"),
1583                                vec![self.clone().into()],
1584                            ),
1585                            None,
1586                        )),
1587                        UnsolvedSegmentKind::Line {
1588                            start,
1589                            ctor,
1590                            start_object_id,
1591                            ..
1592                        } => Ok(KclValue::Segment {
1593                            value: Box::new(AbstractSegment {
1594                                repr: SegmentRepr::Unsolved {
1595                                    segment: UnsolvedSegment {
1596                                        object_id: *start_object_id,
1597                                        kind: UnsolvedSegmentKind::Point {
1598                                            position: start.clone(),
1599                                            ctor: Box::new(PointCtor {
1600                                                position: ctor.start.clone(),
1601                                            }),
1602                                        },
1603                                        meta: segment.meta.clone(),
1604                                    },
1605                                },
1606                                meta: segment.meta.clone(),
1607                            }),
1608                        }
1609                        .continue_()),
1610                        UnsolvedSegmentKind::Arc {
1611                            start,
1612                            ctor,
1613                            start_object_id,
1614                            ..
1615                        } => Ok(KclValue::Segment {
1616                            value: Box::new(AbstractSegment {
1617                                repr: SegmentRepr::Unsolved {
1618                                    segment: UnsolvedSegment {
1619                                        object_id: *start_object_id,
1620                                        kind: UnsolvedSegmentKind::Point {
1621                                            position: start.clone(),
1622                                            ctor: Box::new(PointCtor {
1623                                                position: ctor.start.clone(),
1624                                            }),
1625                                        },
1626                                        meta: segment.meta.clone(),
1627                                    },
1628                                },
1629                                meta: segment.meta.clone(),
1630                            }),
1631                        }
1632                        .continue_()),
1633                    },
1634                    SegmentRepr::Solved { segment } => match &segment.kind {
1635                        SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1636                            KclErrorDetails::new(
1637                                format!("Property '{property}' not found in point segment"),
1638                                vec![self.clone().into()],
1639                            ),
1640                            None,
1641                        )),
1642                        SegmentKind::Line {
1643                            start,
1644                            ctor,
1645                            start_object_id,
1646                            start_freedom,
1647                            ..
1648                        } => Ok(KclValue::Segment {
1649                            value: Box::new(AbstractSegment {
1650                                repr: SegmentRepr::Solved {
1651                                    segment: Segment {
1652                                        object_id: *start_object_id,
1653                                        kind: SegmentKind::Point {
1654                                            position: start.clone(),
1655                                            ctor: Box::new(PointCtor {
1656                                                position: ctor.start.clone(),
1657                                            }),
1658                                            freedom: *start_freedom,
1659                                        },
1660                                        meta: segment.meta.clone(),
1661                                    },
1662                                },
1663                                meta: segment.meta.clone(),
1664                            }),
1665                        }
1666                        .continue_()),
1667                        SegmentKind::Arc {
1668                            start,
1669                            ctor,
1670                            start_object_id,
1671                            start_freedom,
1672                            ..
1673                        } => Ok(KclValue::Segment {
1674                            value: Box::new(AbstractSegment {
1675                                repr: SegmentRepr::Solved {
1676                                    segment: Segment {
1677                                        object_id: *start_object_id,
1678                                        kind: SegmentKind::Point {
1679                                            position: start.clone(),
1680                                            ctor: Box::new(PointCtor {
1681                                                position: ctor.start.clone(),
1682                                            }),
1683                                            freedom: *start_freedom,
1684                                        },
1685                                        meta: segment.meta.clone(),
1686                                    },
1687                                },
1688                                meta: segment.meta.clone(),
1689                            }),
1690                        }
1691                        .continue_()),
1692                    },
1693                },
1694                "end" => match &segment.repr {
1695                    SegmentRepr::Unsolved { segment } => match &segment.kind {
1696                        UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1697                            KclErrorDetails::new(
1698                                format!("Property '{property}' not found in point segment"),
1699                                vec![self.clone().into()],
1700                            ),
1701                            None,
1702                        )),
1703                        UnsolvedSegmentKind::Line {
1704                            end,
1705                            ctor,
1706                            end_object_id,
1707                            ..
1708                        } => Ok(KclValue::Segment {
1709                            value: Box::new(AbstractSegment {
1710                                repr: SegmentRepr::Unsolved {
1711                                    segment: UnsolvedSegment {
1712                                        object_id: *end_object_id,
1713                                        kind: UnsolvedSegmentKind::Point {
1714                                            position: end.clone(),
1715                                            ctor: Box::new(PointCtor {
1716                                                position: ctor.end.clone(),
1717                                            }),
1718                                        },
1719                                        meta: segment.meta.clone(),
1720                                    },
1721                                },
1722                                meta: segment.meta.clone(),
1723                            }),
1724                        }
1725                        .continue_()),
1726                        UnsolvedSegmentKind::Arc {
1727                            end,
1728                            ctor,
1729                            end_object_id,
1730                            ..
1731                        } => Ok(KclValue::Segment {
1732                            value: Box::new(AbstractSegment {
1733                                repr: SegmentRepr::Unsolved {
1734                                    segment: UnsolvedSegment {
1735                                        object_id: *end_object_id,
1736                                        kind: UnsolvedSegmentKind::Point {
1737                                            position: end.clone(),
1738                                            ctor: Box::new(PointCtor {
1739                                                position: ctor.end.clone(),
1740                                            }),
1741                                        },
1742                                        meta: segment.meta.clone(),
1743                                    },
1744                                },
1745                                meta: segment.meta.clone(),
1746                            }),
1747                        }
1748                        .continue_()),
1749                    },
1750                    SegmentRepr::Solved { segment } => match &segment.kind {
1751                        SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1752                            KclErrorDetails::new(
1753                                format!("Property '{property}' not found in point segment"),
1754                                vec![self.clone().into()],
1755                            ),
1756                            None,
1757                        )),
1758                        SegmentKind::Line {
1759                            end,
1760                            ctor,
1761                            end_object_id,
1762                            end_freedom,
1763                            ..
1764                        } => Ok(KclValue::Segment {
1765                            value: Box::new(AbstractSegment {
1766                                repr: SegmentRepr::Solved {
1767                                    segment: Segment {
1768                                        object_id: *end_object_id,
1769                                        kind: SegmentKind::Point {
1770                                            position: end.clone(),
1771                                            ctor: Box::new(PointCtor {
1772                                                position: ctor.end.clone(),
1773                                            }),
1774                                            freedom: *end_freedom,
1775                                        },
1776                                        meta: segment.meta.clone(),
1777                                    },
1778                                },
1779                                meta: segment.meta.clone(),
1780                            }),
1781                        }
1782                        .continue_()),
1783                        SegmentKind::Arc {
1784                            end,
1785                            ctor,
1786                            end_object_id,
1787                            end_freedom,
1788                            ..
1789                        } => Ok(KclValue::Segment {
1790                            value: Box::new(AbstractSegment {
1791                                repr: SegmentRepr::Solved {
1792                                    segment: Segment {
1793                                        object_id: *end_object_id,
1794                                        kind: SegmentKind::Point {
1795                                            position: end.clone(),
1796                                            ctor: Box::new(PointCtor {
1797                                                position: ctor.end.clone(),
1798                                            }),
1799                                            freedom: *end_freedom,
1800                                        },
1801                                        meta: segment.meta.clone(),
1802                                    },
1803                                },
1804                                meta: segment.meta.clone(),
1805                            }),
1806                        }
1807                        .continue_()),
1808                    },
1809                },
1810                "center" => match &segment.repr {
1811                    SegmentRepr::Unsolved { segment } => match &segment.kind {
1812                        UnsolvedSegmentKind::Arc {
1813                            center,
1814                            ctor,
1815                            center_object_id,
1816                            ..
1817                        } => Ok(KclValue::Segment {
1818                            value: Box::new(AbstractSegment {
1819                                repr: SegmentRepr::Unsolved {
1820                                    segment: UnsolvedSegment {
1821                                        object_id: *center_object_id,
1822                                        kind: UnsolvedSegmentKind::Point {
1823                                            position: center.clone(),
1824                                            ctor: Box::new(PointCtor {
1825                                                position: ctor.center.clone(),
1826                                            }),
1827                                        },
1828                                        meta: segment.meta.clone(),
1829                                    },
1830                                },
1831                                meta: segment.meta.clone(),
1832                            }),
1833                        }
1834                        .continue_()),
1835                        _ => Err(KclError::new_undefined_value(
1836                            KclErrorDetails::new(
1837                                format!("Property '{property}' not found in segment"),
1838                                vec![self.clone().into()],
1839                            ),
1840                            None,
1841                        )),
1842                    },
1843                    SegmentRepr::Solved { segment } => match &segment.kind {
1844                        SegmentKind::Arc {
1845                            center,
1846                            ctor,
1847                            center_object_id,
1848                            center_freedom,
1849                            ..
1850                        } => Ok(KclValue::Segment {
1851                            value: Box::new(AbstractSegment {
1852                                repr: SegmentRepr::Solved {
1853                                    segment: Segment {
1854                                        object_id: *center_object_id,
1855                                        kind: SegmentKind::Point {
1856                                            position: center.clone(),
1857                                            ctor: Box::new(PointCtor {
1858                                                position: ctor.center.clone(),
1859                                            }),
1860                                            freedom: *center_freedom,
1861                                        },
1862                                        meta: segment.meta.clone(),
1863                                    },
1864                                },
1865                                meta: segment.meta.clone(),
1866                            }),
1867                        }
1868                        .continue_()),
1869                        _ => Err(KclError::new_undefined_value(
1870                            KclErrorDetails::new(
1871                                format!("Property '{property}' not found in segment"),
1872                                vec![self.clone().into()],
1873                            ),
1874                            None,
1875                        )),
1876                    },
1877                },
1878                other => Err(KclError::new_undefined_value(
1879                    KclErrorDetails::new(
1880                        format!("Property '{other}' not found in segment"),
1881                        vec![self.clone().into()],
1882                    ),
1883                    None,
1884                )),
1885            },
1886            (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
1887                "zAxis" => {
1888                    let (p, u) = plane.info.z_axis.as_3_dims();
1889                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
1890                }
1891                "yAxis" => {
1892                    let (p, u) = plane.info.y_axis.as_3_dims();
1893                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
1894                }
1895                "xAxis" => {
1896                    let (p, u) = plane.info.x_axis.as_3_dims();
1897                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
1898                }
1899                "origin" => {
1900                    let (p, u) = plane.info.origin.as_3_dims();
1901                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
1902                }
1903                other => Err(KclError::new_undefined_value(
1904                    KclErrorDetails::new(
1905                        format!("Property '{other}' not found in plane"),
1906                        vec![self.clone().into()],
1907                    ),
1908                    None,
1909                )),
1910            },
1911            (KclValue::Object { value: map, .. }, Property::String(property), false) => {
1912                if let Some(value) = map.get(&property) {
1913                    Ok(value.to_owned().continue_())
1914                } else {
1915                    Err(KclError::new_undefined_value(
1916                        KclErrorDetails::new(
1917                            format!("Property '{property}' not found in object"),
1918                            vec![self.clone().into()],
1919                        ),
1920                        None,
1921                    ))
1922                }
1923            }
1924            (KclValue::Object { .. }, Property::String(property), true) => {
1925                Err(KclError::new_semantic(KclErrorDetails::new(
1926                    format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
1927                    vec![self.clone().into()],
1928                )))
1929            }
1930            (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
1931                if i == 0
1932                    && let Some(value) = map.get("x")
1933                {
1934                    return Ok(value.to_owned().continue_());
1935                }
1936                if i == 1
1937                    && let Some(value) = map.get("y")
1938                {
1939                    return Ok(value.to_owned().continue_());
1940                }
1941                if i == 2
1942                    && let Some(value) = map.get("z")
1943                {
1944                    return Ok(value.to_owned().continue_());
1945                }
1946                let t = p.type_name();
1947                let article = article_for(t);
1948                Err(KclError::new_semantic(KclErrorDetails::new(
1949                    format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
1950                    vec![self.clone().into()],
1951                )))
1952            }
1953            (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
1954                let value_of_arr = arr.get(index);
1955                if let Some(value) = value_of_arr {
1956                    Ok(value.to_owned().continue_())
1957                } else {
1958                    Err(KclError::new_undefined_value(
1959                        KclErrorDetails::new(
1960                            format!("The array doesn't have any item at index {index}"),
1961                            vec![self.clone().into()],
1962                        ),
1963                        None,
1964                    ))
1965                }
1966            }
1967            // Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
1968            // This is kind of a silly property, but it's possible it occurs in generic code or something.
1969            (obj, Property::UInt(0), _) => Ok(obj.continue_()),
1970            (KclValue::HomArray { .. }, p, _) => {
1971                let t = p.type_name();
1972                let article = article_for(t);
1973                Err(KclError::new_semantic(KclErrorDetails::new(
1974                    format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
1975                    vec![self.clone().into()],
1976                )))
1977            }
1978            (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
1979                value: Box::new(value.sketch),
1980            }
1981            .continue_()),
1982            (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
1983                // This is a common mistake.
1984                Err(KclError::new_semantic(KclErrorDetails::new(
1985                    format!(
1986                        "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
1987                        geometry.human_friendly_type()
1988                    ),
1989                    vec![self.clone().into()],
1990                )))
1991            }
1992            (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
1993                meta: vec![Metadata {
1994                    source_range: SourceRange::from(self.clone()),
1995                }],
1996                value: sk
1997                    .tags
1998                    .iter()
1999                    .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
2000                    .collect(),
2001                constrainable: false,
2002            }
2003            .continue_()),
2004            (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
2005                Err(KclError::new_semantic(KclErrorDetails::new(
2006                    format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
2007                    vec![self.clone().into()],
2008                )))
2009            }
2010            (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
2011                format!(
2012                    "Only objects can have members accessed with dot notation, but you're trying to access {}",
2013                    being_indexed.human_friendly_type()
2014                ),
2015                vec![self.clone().into()],
2016            ))),
2017            (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
2018                format!(
2019                    "Only arrays can be indexed, but you're trying to index {}",
2020                    being_indexed.human_friendly_type()
2021                ),
2022                vec![self.clone().into()],
2023            ))),
2024        }
2025    }
2026}
2027
2028impl Node<BinaryExpression> {
2029    pub(super) async fn get_result(
2030        &self,
2031        exec_state: &mut ExecState,
2032        ctx: &ExecutorContext,
2033    ) -> Result<KclValueControlFlow, KclError> {
2034        enum State {
2035            EvaluateLeft(Node<BinaryExpression>),
2036            FromLeft {
2037                node: Node<BinaryExpression>,
2038            },
2039            EvaluateRight {
2040                node: Node<BinaryExpression>,
2041                left: KclValue,
2042            },
2043            FromRight {
2044                node: Node<BinaryExpression>,
2045                left: KclValue,
2046            },
2047        }
2048
2049        let mut stack = vec![State::EvaluateLeft(self.clone())];
2050        let mut last_result: Option<KclValue> = None;
2051
2052        while let Some(state) = stack.pop() {
2053            match state {
2054                State::EvaluateLeft(node) => {
2055                    let left_part = node.left.clone();
2056                    match left_part {
2057                        BinaryPart::BinaryExpression(child) => {
2058                            stack.push(State::FromLeft { node });
2059                            stack.push(State::EvaluateLeft(*child));
2060                        }
2061                        part => {
2062                            let left_value = part.get_result(exec_state, ctx).await?;
2063                            let left_value = control_continue!(left_value);
2064                            stack.push(State::EvaluateRight { node, left: left_value });
2065                        }
2066                    }
2067                }
2068                State::FromLeft { node } => {
2069                    let Some(left_value) = last_result.take() else {
2070                        return Err(Self::missing_result_error(&node));
2071                    };
2072                    stack.push(State::EvaluateRight { node, left: left_value });
2073                }
2074                State::EvaluateRight { node, left } => {
2075                    let right_part = node.right.clone();
2076                    match right_part {
2077                        BinaryPart::BinaryExpression(child) => {
2078                            stack.push(State::FromRight { node, left });
2079                            stack.push(State::EvaluateLeft(*child));
2080                        }
2081                        part => {
2082                            let right_value = part.get_result(exec_state, ctx).await?;
2083                            let right_value = control_continue!(right_value);
2084                            let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2085                            last_result = Some(result);
2086                        }
2087                    }
2088                }
2089                State::FromRight { node, left } => {
2090                    let Some(right_value) = last_result.take() else {
2091                        return Err(Self::missing_result_error(&node));
2092                    };
2093                    let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2094                    last_result = Some(result);
2095                }
2096            }
2097        }
2098
2099        last_result
2100            .map(KclValue::continue_)
2101            .ok_or_else(|| Self::missing_result_error(self))
2102    }
2103
2104    async fn apply_operator(
2105        &self,
2106        exec_state: &mut ExecState,
2107        ctx: &ExecutorContext,
2108        left_value: KclValue,
2109        right_value: KclValue,
2110    ) -> Result<KclValue, KclError> {
2111        let mut meta = left_value.metadata();
2112        meta.extend(right_value.metadata());
2113
2114        // First check if we are doing string concatenation.
2115        if self.operator == BinaryOperator::Add
2116            && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
2117                (&left_value, &right_value)
2118        {
2119            return Ok(KclValue::String {
2120                value: format!("{left}{right}"),
2121                meta,
2122            });
2123        }
2124
2125        // Then check if we have solids.
2126        if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
2127            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2128                let args = Args::new_no_args(self.into(), ctx.clone(), Some("union".to_owned()));
2129                let result = crate::std::csg::inner_union(
2130                    vec![*left.clone(), *right.clone()],
2131                    Default::default(),
2132                    exec_state,
2133                    args,
2134                )
2135                .await?;
2136                return Ok(result.into());
2137            }
2138        } else if self.operator == BinaryOperator::Sub {
2139            // Check if we have solids.
2140            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2141                let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
2142                let result = crate::std::csg::inner_subtract(
2143                    vec![*left.clone()],
2144                    vec![*right.clone()],
2145                    Default::default(),
2146                    exec_state,
2147                    args,
2148                )
2149                .await?;
2150                return Ok(result.into());
2151            }
2152        } else if self.operator == BinaryOperator::And
2153            && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2154        {
2155            // Check if we have solids.
2156            let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
2157            let result = crate::std::csg::inner_intersect(
2158                vec![*left.clone(), *right.clone()],
2159                Default::default(),
2160                exec_state,
2161                args,
2162            )
2163            .await?;
2164            return Ok(result.into());
2165        }
2166
2167        // Check if we are doing logical operations on booleans.
2168        if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2169            let KclValue::Bool { value: left_value, .. } = left_value else {
2170                return Err(KclError::new_semantic(KclErrorDetails::new(
2171                    format!(
2172                        "Cannot apply logical operator to non-boolean value: {}",
2173                        left_value.human_friendly_type()
2174                    ),
2175                    vec![self.left.clone().into()],
2176                )));
2177            };
2178            let KclValue::Bool { value: right_value, .. } = right_value else {
2179                return Err(KclError::new_semantic(KclErrorDetails::new(
2180                    format!(
2181                        "Cannot apply logical operator to non-boolean value: {}",
2182                        right_value.human_friendly_type()
2183                    ),
2184                    vec![self.right.clone().into()],
2185                )));
2186            };
2187            let raw_value = match self.operator {
2188                BinaryOperator::Or => left_value || right_value,
2189                BinaryOperator::And => left_value && right_value,
2190                _ => unreachable!(),
2191            };
2192            return Ok(KclValue::Bool { value: raw_value, meta });
2193        }
2194
2195        // Check if we're doing equivalence in sketch mode.
2196        if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2197            match (&left_value, &right_value) {
2198                // Same sketch variables.
2199                (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2200                    if left_value.id == right_value.id =>
2201                {
2202                    return Ok(KclValue::Bool { value: true, meta });
2203                }
2204                // Different sketch variables.
2205                (KclValue::SketchVar { .. }, KclValue::SketchVar { .. }) => {
2206                    // TODO: sketch-api: Collapse the two sketch variables into
2207                    // one constraint variable.
2208                    return Err(KclError::new_semantic(KclErrorDetails::new(
2209                        "TODO: Different sketch variables".to_owned(),
2210                        vec![self.into()],
2211                    )));
2212                }
2213                // One sketch variable, one number.
2214                (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2215                | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2216                    let number_value = normalize_to_solver_unit(
2217                        input_number,
2218                        input_number.into(),
2219                        exec_state,
2220                        "fixed constraint value",
2221                    )?;
2222                    let Some(n) = number_value.as_ty_f64() else {
2223                        let message = format!(
2224                            "Expected number after coercion, but found {}",
2225                            number_value.human_friendly_type()
2226                        );
2227                        debug_assert!(false, "{}", &message);
2228                        return Err(KclError::new_internal(KclErrorDetails::new(message, vec![self.into()])));
2229                    };
2230                    let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2231                    let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2232                        let message = "Being inside a sketch block should have already been checked above".to_owned();
2233                        debug_assert!(false, "{}", &message);
2234                        return Err(KclError::new_internal(KclErrorDetails::new(
2235                            message,
2236                            vec![SourceRange::from(self)],
2237                        )));
2238                    };
2239                    sketch_block_state.solver_constraints.push(constraint);
2240                    return Ok(KclValue::Bool { value: true, meta });
2241                }
2242                // One sketch constraint, one number.
2243                (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
2244                | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
2245                    let number_value = normalize_to_solver_unit(
2246                        input_number,
2247                        input_number.into(),
2248                        exec_state,
2249                        "fixed constraint value",
2250                    )?;
2251                    let Some(n) = number_value.as_ty_f64() else {
2252                        let message = format!(
2253                            "Expected number after coercion, but found {}",
2254                            number_value.human_friendly_type()
2255                        );
2256                        debug_assert!(false, "{}", &message);
2257                        return Err(KclError::new_internal(KclErrorDetails::new(message, vec![self.into()])));
2258                    };
2259                    match &constraint.kind {
2260                        SketchConstraintKind::Distance { points } => {
2261                            let range = self.as_source_range();
2262                            let p0 = &points[0];
2263                            let p1 = &points[1];
2264                            let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2265                                p0.vars.x.to_constraint_id(range)?,
2266                                p0.vars.y.to_constraint_id(range)?,
2267                            );
2268                            let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2269                                p1.vars.x.to_constraint_id(range)?,
2270                                p1.vars.y.to_constraint_id(range)?,
2271                            );
2272                            let solver_constraint = Constraint::Distance(solver_pt0, solver_pt1, n.n);
2273
2274                            #[cfg(feature = "artifact-graph")]
2275                            let constraint_id = exec_state.next_object_id();
2276                            let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2277                                let message =
2278                                    "Being inside a sketch block should have already been checked above".to_owned();
2279                                debug_assert!(false, "{}", &message);
2280                                return Err(KclError::new_internal(KclErrorDetails::new(
2281                                    message,
2282                                    vec![SourceRange::from(self)],
2283                                )));
2284                            };
2285                            sketch_block_state.solver_constraints.push(solver_constraint);
2286                            #[cfg(feature = "artifact-graph")]
2287                            {
2288                                use crate::{execution::ArtifactId, front::Distance};
2289
2290                                let constraint = crate::front::Constraint::Distance(Distance {
2291                                    points: vec![p0.object_id, p1.object_id],
2292                                    distance: n.try_into().map_err(|_| {
2293                                        KclError::new_internal(KclErrorDetails::new(
2294                                            "Failed to convert distance units numeric suffix:".to_owned(),
2295                                            vec![range],
2296                                        ))
2297                                    })?,
2298                                });
2299                                sketch_block_state.sketch_constraints.push(constraint_id);
2300                                exec_state.add_scene_object(
2301                                    Object {
2302                                        id: constraint_id,
2303                                        kind: ObjectKind::Constraint { constraint },
2304                                        label: Default::default(),
2305                                        comments: Default::default(),
2306                                        artifact_id: ArtifactId::constraint(),
2307                                        source: range.into(),
2308                                    },
2309                                    range,
2310                                );
2311                            }
2312                        }
2313                    }
2314                    return Ok(KclValue::Bool { value: true, meta });
2315                }
2316                _ => {
2317                    return Err(KclError::new_semantic(KclErrorDetails::new(
2318                        format!(
2319                            "Cannot create an equivalence constraint between values of these types: {} and {}",
2320                            left_value.human_friendly_type(),
2321                            right_value.human_friendly_type()
2322                        ),
2323                        vec![self.into()],
2324                    )));
2325                }
2326            }
2327        }
2328
2329        let left = number_as_f64(&left_value, self.left.clone().into())?;
2330        let right = number_as_f64(&right_value, self.right.clone().into())?;
2331
2332        let value = match self.operator {
2333            BinaryOperator::Add => {
2334                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2335                self.warn_on_unknown(&ty, "Adding", exec_state);
2336                KclValue::Number { value: l + r, meta, ty }
2337            }
2338            BinaryOperator::Sub => {
2339                let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2340                self.warn_on_unknown(&ty, "Subtracting", exec_state);
2341                KclValue::Number { value: l - r, meta, ty }
2342            }
2343            BinaryOperator::Mul => {
2344                let (l, r, ty) = NumericType::combine_mul(left, right);
2345                self.warn_on_unknown(&ty, "Multiplying", exec_state);
2346                KclValue::Number { value: l * r, meta, ty }
2347            }
2348            BinaryOperator::Div => {
2349                let (l, r, ty) = NumericType::combine_div(left, right);
2350                self.warn_on_unknown(&ty, "Dividing", exec_state);
2351                KclValue::Number { value: l / r, meta, ty }
2352            }
2353            BinaryOperator::Mod => {
2354                let (l, r, ty) = NumericType::combine_mod(left, right);
2355                self.warn_on_unknown(&ty, "Modulo of", exec_state);
2356                KclValue::Number { value: l % r, meta, ty }
2357            }
2358            BinaryOperator::Pow => KclValue::Number {
2359                value: left.n.powf(right.n),
2360                meta,
2361                ty: exec_state.current_default_units(),
2362            },
2363            BinaryOperator::Neq => {
2364                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2365                self.warn_on_unknown(&ty, "Comparing", exec_state);
2366                KclValue::Bool { value: l != r, meta }
2367            }
2368            BinaryOperator::Gt => {
2369                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2370                self.warn_on_unknown(&ty, "Comparing", exec_state);
2371                KclValue::Bool { value: l > r, meta }
2372            }
2373            BinaryOperator::Gte => {
2374                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2375                self.warn_on_unknown(&ty, "Comparing", exec_state);
2376                KclValue::Bool { value: l >= r, meta }
2377            }
2378            BinaryOperator::Lt => {
2379                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2380                self.warn_on_unknown(&ty, "Comparing", exec_state);
2381                KclValue::Bool { value: l < r, meta }
2382            }
2383            BinaryOperator::Lte => {
2384                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2385                self.warn_on_unknown(&ty, "Comparing", exec_state);
2386                KclValue::Bool { value: l <= r, meta }
2387            }
2388            BinaryOperator::Eq => {
2389                let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2390                self.warn_on_unknown(&ty, "Comparing", exec_state);
2391                KclValue::Bool { value: l == r, meta }
2392            }
2393            BinaryOperator::And | BinaryOperator::Or => unreachable!(),
2394        };
2395
2396        Ok(value)
2397    }
2398
2399    fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
2400        KclError::new_internal(KclErrorDetails::new(
2401            "missing result while evaluating binary expression".to_owned(),
2402            vec![SourceRange::from(node)],
2403        ))
2404    }
2405
2406    fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
2407        if ty == &NumericType::Unknown {
2408            let sr = self.as_source_range();
2409            exec_state.clear_units_warnings(&sr);
2410            let mut err = CompilationError::err(
2411                sr,
2412                format!(
2413                    "{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)`."
2414                ),
2415            );
2416            err.tag = crate::errors::Tag::UnknownNumericUnits;
2417            exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
2418        }
2419    }
2420}
2421
2422impl Node<UnaryExpression> {
2423    pub(super) async fn get_result(
2424        &self,
2425        exec_state: &mut ExecState,
2426        ctx: &ExecutorContext,
2427    ) -> Result<KclValueControlFlow, KclError> {
2428        match self.operator {
2429            UnaryOperator::Not => {
2430                let value = self.argument.get_result(exec_state, ctx).await?;
2431                let value = control_continue!(value);
2432                let KclValue::Bool {
2433                    value: bool_value,
2434                    meta: _,
2435                } = value
2436                else {
2437                    return Err(KclError::new_semantic(KclErrorDetails::new(
2438                        format!(
2439                            "Cannot apply unary operator ! to non-boolean value: {}",
2440                            value.human_friendly_type()
2441                        ),
2442                        vec![self.into()],
2443                    )));
2444                };
2445                let meta = vec![Metadata {
2446                    source_range: self.into(),
2447                }];
2448                let negated = KclValue::Bool {
2449                    value: !bool_value,
2450                    meta,
2451                };
2452
2453                Ok(negated.continue_())
2454            }
2455            UnaryOperator::Neg => {
2456                let value = self.argument.get_result(exec_state, ctx).await?;
2457                let value = control_continue!(value);
2458                let err = || {
2459                    KclError::new_semantic(KclErrorDetails::new(
2460                        format!(
2461                            "You can only negate numbers, planes, or lines, but this is a {}",
2462                            value.human_friendly_type()
2463                        ),
2464                        vec![self.into()],
2465                    ))
2466                };
2467                match &value {
2468                    KclValue::Number { value, ty, .. } => {
2469                        let meta = vec![Metadata {
2470                            source_range: self.into(),
2471                        }];
2472                        Ok(KclValue::Number {
2473                            value: -value,
2474                            meta,
2475                            ty: *ty,
2476                        }
2477                        .continue_())
2478                    }
2479                    KclValue::Plane { value } => {
2480                        let mut plane = value.clone();
2481                        if plane.info.x_axis.x != 0.0 {
2482                            plane.info.x_axis.x *= -1.0;
2483                        }
2484                        if plane.info.x_axis.y != 0.0 {
2485                            plane.info.x_axis.y *= -1.0;
2486                        }
2487                        if plane.info.x_axis.z != 0.0 {
2488                            plane.info.x_axis.z *= -1.0;
2489                        }
2490
2491                        plane.value = PlaneType::Uninit;
2492                        plane.id = exec_state.next_uuid();
2493                        Ok(KclValue::Plane { value: plane }.continue_())
2494                    }
2495                    KclValue::Object {
2496                        value: values, meta, ..
2497                    } => {
2498                        // Special-case for negating line-like objects.
2499                        let Some(direction) = values.get("direction") else {
2500                            return Err(err());
2501                        };
2502
2503                        let direction = match direction {
2504                            KclValue::Tuple { value: values, meta } => {
2505                                let values = values
2506                                    .iter()
2507                                    .map(|v| match v {
2508                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
2509                                            value: *value * -1.0,
2510                                            ty: *ty,
2511                                            meta: meta.clone(),
2512                                        }),
2513                                        _ => Err(err()),
2514                                    })
2515                                    .collect::<Result<Vec<_>, _>>()?;
2516
2517                                KclValue::Tuple {
2518                                    value: values,
2519                                    meta: meta.clone(),
2520                                }
2521                            }
2522                            KclValue::HomArray {
2523                                value: values,
2524                                ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
2525                            } => {
2526                                let values = values
2527                                    .iter()
2528                                    .map(|v| match v {
2529                                        KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
2530                                            value: *value * -1.0,
2531                                            ty: *ty,
2532                                            meta: meta.clone(),
2533                                        }),
2534                                        _ => Err(err()),
2535                                    })
2536                                    .collect::<Result<Vec<_>, _>>()?;
2537
2538                                KclValue::HomArray {
2539                                    value: values,
2540                                    ty: ty.clone(),
2541                                }
2542                            }
2543                            _ => return Err(err()),
2544                        };
2545
2546                        let mut value = values.clone();
2547                        value.insert("direction".to_owned(), direction);
2548                        Ok(KclValue::Object {
2549                            value,
2550                            meta: meta.clone(),
2551                            constrainable: false,
2552                        }
2553                        .continue_())
2554                    }
2555                    _ => Err(err()),
2556                }
2557            }
2558            UnaryOperator::Plus => {
2559                let operand = self.argument.get_result(exec_state, ctx).await?;
2560                let operand = control_continue!(operand);
2561                match operand {
2562                    KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
2563                    _ => Err(KclError::new_semantic(KclErrorDetails::new(
2564                        format!(
2565                            "You can only apply unary + to numbers or planes, but this is a {}",
2566                            operand.human_friendly_type()
2567                        ),
2568                        vec![self.into()],
2569                    ))),
2570                }
2571            }
2572        }
2573    }
2574}
2575
2576pub(crate) async fn execute_pipe_body(
2577    exec_state: &mut ExecState,
2578    body: &[Expr],
2579    source_range: SourceRange,
2580    ctx: &ExecutorContext,
2581) -> Result<KclValueControlFlow, KclError> {
2582    let Some((first, body)) = body.split_first() else {
2583        return Err(KclError::new_semantic(KclErrorDetails::new(
2584            "Pipe expressions cannot be empty".to_owned(),
2585            vec![source_range],
2586        )));
2587    };
2588    // Evaluate the first element in the pipeline.
2589    // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
2590    // they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
2591    // of its own.
2592    let meta = Metadata {
2593        source_range: SourceRange::from(first),
2594    };
2595    let output = ctx
2596        .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
2597        .await?;
2598    let output = control_continue!(output);
2599
2600    // Now that we've evaluated the first child expression in the pipeline, following child expressions
2601    // should use the previous child expression for %.
2602    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
2603    let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
2604    // Evaluate remaining elements.
2605    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
2606    // Restore the previous pipe value.
2607    exec_state.mod_local.pipe_value = previous_pipe_value;
2608
2609    result
2610}
2611
2612/// Execute the tail of a pipe expression.  exec_state.pipe_value must be set by
2613/// the caller.
2614#[async_recursion]
2615async fn inner_execute_pipe_body(
2616    exec_state: &mut ExecState,
2617    body: &[Expr],
2618    ctx: &ExecutorContext,
2619) -> Result<KclValueControlFlow, KclError> {
2620    for expression in body {
2621        if let Expr::TagDeclarator(_) = expression {
2622            return Err(KclError::new_semantic(KclErrorDetails::new(
2623                format!("This cannot be in a PipeExpression: {expression:?}"),
2624                vec![expression.into()],
2625            )));
2626        }
2627        let metadata = Metadata {
2628            source_range: SourceRange::from(expression),
2629        };
2630        let output = ctx
2631            .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
2632            .await?;
2633        let output = control_continue!(output);
2634        exec_state.mod_local.pipe_value = Some(output);
2635    }
2636    // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
2637    let final_output = exec_state.mod_local.pipe_value.take().unwrap();
2638    Ok(final_output.continue_())
2639}
2640
2641impl Node<TagDeclarator> {
2642    pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
2643        let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
2644            value: self.name.clone(),
2645            info: Vec::new(),
2646            meta: vec![Metadata {
2647                source_range: self.into(),
2648            }],
2649        }));
2650
2651        exec_state
2652            .mut_stack()
2653            .add(self.name.clone(), memory_item, self.into())?;
2654
2655        Ok(self.into())
2656    }
2657}
2658
2659impl Node<ArrayExpression> {
2660    #[async_recursion]
2661    pub(super) async fn execute(
2662        &self,
2663        exec_state: &mut ExecState,
2664        ctx: &ExecutorContext,
2665    ) -> Result<KclValueControlFlow, KclError> {
2666        let mut results = Vec::with_capacity(self.elements.len());
2667
2668        for element in &self.elements {
2669            let metadata = Metadata::from(element);
2670            // TODO: Carry statement kind here so that we know if we're
2671            // inside a variable declaration.
2672            let value = ctx
2673                .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
2674                .await?;
2675            let value = control_continue!(value);
2676
2677            results.push(value);
2678        }
2679
2680        Ok(KclValue::HomArray {
2681            value: results,
2682            ty: RuntimeType::Primitive(PrimitiveType::Any),
2683        }
2684        .continue_())
2685    }
2686}
2687
2688impl Node<ArrayRangeExpression> {
2689    #[async_recursion]
2690    pub(super) async fn execute(
2691        &self,
2692        exec_state: &mut ExecState,
2693        ctx: &ExecutorContext,
2694    ) -> Result<KclValueControlFlow, KclError> {
2695        let metadata = Metadata::from(&self.start_element);
2696        let start_val = ctx
2697            .execute_expr(
2698                &self.start_element,
2699                exec_state,
2700                &metadata,
2701                &[],
2702                StatementKind::Expression,
2703            )
2704            .await?;
2705        let start_val = control_continue!(start_val);
2706        let start = start_val
2707            .as_ty_f64()
2708            .ok_or(KclError::new_semantic(KclErrorDetails::new(
2709                format!(
2710                    "Expected number for range start but found {}",
2711                    start_val.human_friendly_type()
2712                ),
2713                vec![self.into()],
2714            )))?;
2715        let metadata = Metadata::from(&self.end_element);
2716        let end_val = ctx
2717            .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
2718            .await?;
2719        let end_val = control_continue!(end_val);
2720        let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
2721            format!(
2722                "Expected number for range end but found {}",
2723                end_val.human_friendly_type()
2724            ),
2725            vec![self.into()],
2726        )))?;
2727
2728        let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
2729        let Some(start) = crate::try_f64_to_i64(start) else {
2730            return Err(KclError::new_semantic(KclErrorDetails::new(
2731                format!("Range start must be an integer, but found {start}"),
2732                vec![self.into()],
2733            )));
2734        };
2735        let Some(end) = crate::try_f64_to_i64(end) else {
2736            return Err(KclError::new_semantic(KclErrorDetails::new(
2737                format!("Range end must be an integer, but found {end}"),
2738                vec![self.into()],
2739            )));
2740        };
2741
2742        if end < start {
2743            return Err(KclError::new_semantic(KclErrorDetails::new(
2744                format!("Range start is greater than range end: {start} .. {end}"),
2745                vec![self.into()],
2746            )));
2747        }
2748
2749        let range: Vec<_> = if self.end_inclusive {
2750            (start..=end).collect()
2751        } else {
2752            (start..end).collect()
2753        };
2754
2755        let meta = vec![Metadata {
2756            source_range: self.into(),
2757        }];
2758
2759        Ok(KclValue::HomArray {
2760            value: range
2761                .into_iter()
2762                .map(|num| KclValue::Number {
2763                    value: num as f64,
2764                    ty,
2765                    meta: meta.clone(),
2766                })
2767                .collect(),
2768            ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
2769        }
2770        .continue_())
2771    }
2772}
2773
2774impl Node<ObjectExpression> {
2775    #[async_recursion]
2776    pub(super) async fn execute(
2777        &self,
2778        exec_state: &mut ExecState,
2779        ctx: &ExecutorContext,
2780    ) -> Result<KclValueControlFlow, KclError> {
2781        let mut object = HashMap::with_capacity(self.properties.len());
2782        for property in &self.properties {
2783            let metadata = Metadata::from(&property.value);
2784            let result = ctx
2785                .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
2786                .await?;
2787            let result = control_continue!(result);
2788            object.insert(property.key.name.clone(), result);
2789        }
2790
2791        Ok(KclValue::Object {
2792            value: object,
2793            meta: vec![Metadata {
2794                source_range: self.into(),
2795            }],
2796            constrainable: false,
2797        }
2798        .continue_())
2799    }
2800}
2801
2802fn article_for<S: AsRef<str>>(s: S) -> &'static str {
2803    // '[' is included since it's an array.
2804    if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
2805        "an"
2806    } else {
2807        "a"
2808    }
2809}
2810
2811fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
2812    v.as_ty_f64().ok_or_else(|| {
2813        let actual_type = v.human_friendly_type();
2814        KclError::new_semantic(KclErrorDetails::new(
2815            format!("Expected a number, but found {actual_type}",),
2816            vec![source_range],
2817        ))
2818    })
2819}
2820
2821impl Node<IfExpression> {
2822    #[async_recursion]
2823    pub(super) async fn get_result(
2824        &self,
2825        exec_state: &mut ExecState,
2826        ctx: &ExecutorContext,
2827    ) -> Result<KclValueControlFlow, KclError> {
2828        // Check the `if` branch.
2829        let cond_value = ctx
2830            .execute_expr(
2831                &self.cond,
2832                exec_state,
2833                &Metadata::from(self),
2834                &[],
2835                StatementKind::Expression,
2836            )
2837            .await?;
2838        let cond_value = control_continue!(cond_value);
2839        if cond_value.get_bool()? {
2840            let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
2841            // Block must end in an expression, so this has to be Some.
2842            // Enforced by the parser.
2843            // See https://github.com/KittyCAD/modeling-app/issues/4015
2844            return Ok(block_result.unwrap());
2845        }
2846
2847        // Check any `else if` branches.
2848        for else_if in &self.else_ifs {
2849            let cond_value = ctx
2850                .execute_expr(
2851                    &else_if.cond,
2852                    exec_state,
2853                    &Metadata::from(self),
2854                    &[],
2855                    StatementKind::Expression,
2856                )
2857                .await?;
2858            let cond_value = control_continue!(cond_value);
2859            if cond_value.get_bool()? {
2860                let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
2861                // Block must end in an expression, so this has to be Some.
2862                // Enforced by the parser.
2863                // See https://github.com/KittyCAD/modeling-app/issues/4015
2864                return Ok(block_result.unwrap());
2865            }
2866        }
2867
2868        // Run the final `else` branch.
2869        ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
2870            .await
2871            .map(|expr| expr.unwrap())
2872    }
2873}
2874
2875#[derive(Debug)]
2876enum Property {
2877    UInt(usize),
2878    String(String),
2879}
2880
2881impl Property {
2882    #[allow(clippy::too_many_arguments)]
2883    async fn try_from<'a>(
2884        computed: bool,
2885        value: Expr,
2886        exec_state: &mut ExecState,
2887        sr: SourceRange,
2888        ctx: &ExecutorContext,
2889        metadata: &Metadata,
2890        annotations: &[Node<Annotation>],
2891        statement_kind: StatementKind<'a>,
2892    ) -> Result<Self, KclError> {
2893        let property_sr = vec![sr];
2894        if !computed {
2895            let Expr::Name(identifier) = value else {
2896                // Should actually be impossible because the parser would reject it.
2897                return Err(KclError::new_semantic(KclErrorDetails::new(
2898                    "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
2899                        .to_owned(),
2900                    property_sr,
2901                )));
2902            };
2903            return Ok(Property::String(identifier.to_string()));
2904        }
2905
2906        let prop_value = ctx
2907            .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
2908            .await?;
2909        let prop_value = match prop_value.control {
2910            ControlFlowKind::Continue => prop_value.into_value(),
2911            ControlFlowKind::Exit => {
2912                let message = "Early return inside array brackets is currently not supported".to_owned();
2913                debug_assert!(false, "{}", &message);
2914                return Err(KclError::new_internal(KclErrorDetails::new(message, property_sr)));
2915            }
2916        };
2917        match prop_value {
2918            KclValue::Number { value, ty, meta: _ } => {
2919                if !matches!(
2920                    ty,
2921                    NumericType::Unknown
2922                        | NumericType::Default { .. }
2923                        | NumericType::Known(crate::exec::UnitType::Count)
2924                ) {
2925                    return Err(KclError::new_semantic(KclErrorDetails::new(
2926                        format!(
2927                            "{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"
2928                        ),
2929                        property_sr,
2930                    )));
2931                }
2932                if let Some(x) = crate::try_f64_to_usize(value) {
2933                    Ok(Property::UInt(x))
2934                } else {
2935                    Err(KclError::new_semantic(KclErrorDetails::new(
2936                        format!("{value} is not a valid index, indices must be whole numbers >= 0"),
2937                        property_sr,
2938                    )))
2939                }
2940            }
2941            _ => Err(KclError::new_semantic(KclErrorDetails::new(
2942                "Only numbers (>= 0) can be indexes".to_owned(),
2943                vec![sr],
2944            ))),
2945        }
2946    }
2947}
2948
2949impl Property {
2950    fn type_name(&self) -> &'static str {
2951        match self {
2952            Property::UInt(_) => "number",
2953            Property::String(_) => "string",
2954        }
2955    }
2956}
2957
2958impl Node<PipeExpression> {
2959    #[async_recursion]
2960    pub(super) async fn get_result(
2961        &self,
2962        exec_state: &mut ExecState,
2963        ctx: &ExecutorContext,
2964    ) -> Result<KclValueControlFlow, KclError> {
2965        execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
2966    }
2967}
2968
2969#[cfg(test)]
2970mod test {
2971    use std::sync::Arc;
2972
2973    use tokio::io::AsyncWriteExt;
2974
2975    use super::*;
2976    use crate::{
2977        ExecutorSettings,
2978        errors::Severity,
2979        exec::UnitType,
2980        execution::{ContextType, parse_execute},
2981    };
2982
2983    #[tokio::test(flavor = "multi_thread")]
2984    async fn ascription() {
2985        let program = r#"
2986a = 42: number
2987b = a: number
2988p = {
2989  origin = { x = 0, y = 0, z = 0 },
2990  xAxis = { x = 1, y = 0, z = 0 },
2991  yAxis = { x = 0, y = 1, z = 0 },
2992  zAxis = { x = 0, y = 0, z = 1 }
2993}: Plane
2994arr1 = [42]: [number(cm)]
2995"#;
2996
2997        let result = parse_execute(program).await.unwrap();
2998        let mem = result.exec_state.stack();
2999        assert!(matches!(
3000            mem.memory
3001                .get_from("p", result.mem_env, SourceRange::default(), 0)
3002                .unwrap(),
3003            KclValue::Plane { .. }
3004        ));
3005        let arr1 = mem
3006            .memory
3007            .get_from("arr1", result.mem_env, SourceRange::default(), 0)
3008            .unwrap();
3009        if let KclValue::HomArray { value, ty } = arr1 {
3010            assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
3011            assert_eq!(
3012                *ty,
3013                RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
3014            );
3015            // Compare, ignoring meta.
3016            if let KclValue::Number { value, ty, .. } = &value[0] {
3017                // It should not convert units.
3018                assert_eq!(*value, 42.0);
3019                assert_eq!(
3020                    *ty,
3021                    NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
3022                );
3023            } else {
3024                panic!("Expected a number; found {:?}", value[0]);
3025            }
3026        } else {
3027            panic!("Expected HomArray; found {arr1:?}");
3028        }
3029
3030        let program = r#"
3031a = 42: string
3032"#;
3033        let result = parse_execute(program).await;
3034        let err = result.unwrap_err();
3035        assert!(
3036            err.to_string()
3037                .contains("could not coerce a number (with type `number`) to type `string`"),
3038            "Expected error but found {err:?}"
3039        );
3040
3041        let program = r#"
3042a = 42: Plane
3043"#;
3044        let result = parse_execute(program).await;
3045        let err = result.unwrap_err();
3046        assert!(
3047            err.to_string()
3048                .contains("could not coerce a number (with type `number`) to type `Plane`"),
3049            "Expected error but found {err:?}"
3050        );
3051
3052        let program = r#"
3053arr = [0]: [string]
3054"#;
3055        let result = parse_execute(program).await;
3056        let err = result.unwrap_err();
3057        assert!(
3058            err.to_string().contains(
3059                "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
3060            ),
3061            "Expected error but found {err:?}"
3062        );
3063
3064        let program = r#"
3065mixedArr = [0, "a"]: [number(mm)]
3066"#;
3067        let result = parse_execute(program).await;
3068        let err = result.unwrap_err();
3069        assert!(
3070            err.to_string().contains(
3071                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3072            ),
3073            "Expected error but found {err:?}"
3074        );
3075
3076        let program = r#"
3077mixedArr = [0, "a"]: [mm]
3078"#;
3079        let result = parse_execute(program).await;
3080        let err = result.unwrap_err();
3081        assert!(
3082            err.to_string().contains(
3083                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3084            ),
3085            "Expected error but found {err:?}"
3086        );
3087    }
3088
3089    #[tokio::test(flavor = "multi_thread")]
3090    async fn neg_plane() {
3091        let program = r#"
3092p = {
3093  origin = { x = 0, y = 0, z = 0 },
3094  xAxis = { x = 1, y = 0, z = 0 },
3095  yAxis = { x = 0, y = 1, z = 0 },
3096}: Plane
3097p2 = -p
3098"#;
3099
3100        let result = parse_execute(program).await.unwrap();
3101        let mem = result.exec_state.stack();
3102        match mem
3103            .memory
3104            .get_from("p2", result.mem_env, SourceRange::default(), 0)
3105            .unwrap()
3106        {
3107            KclValue::Plane { value } => {
3108                assert_eq!(value.info.x_axis.x, -1.0);
3109                assert_eq!(value.info.x_axis.y, 0.0);
3110                assert_eq!(value.info.x_axis.z, 0.0);
3111            }
3112            _ => unreachable!(),
3113        }
3114    }
3115
3116    #[tokio::test(flavor = "multi_thread")]
3117    async fn multiple_returns() {
3118        let program = r#"fn foo() {
3119  return 0
3120  return 42
3121}
3122
3123a = foo()
3124"#;
3125
3126        let result = parse_execute(program).await;
3127        assert!(result.unwrap_err().to_string().contains("return"));
3128    }
3129
3130    #[tokio::test(flavor = "multi_thread")]
3131    async fn load_all_modules() {
3132        // program a.kcl
3133        let program_a_kcl = r#"
3134export a = 1
3135"#;
3136        // program b.kcl
3137        let program_b_kcl = r#"
3138import a from 'a.kcl'
3139
3140export b = a + 1
3141"#;
3142        // program c.kcl
3143        let program_c_kcl = r#"
3144import a from 'a.kcl'
3145
3146export c = a + 2
3147"#;
3148
3149        // program main.kcl
3150        let main_kcl = r#"
3151import b from 'b.kcl'
3152import c from 'c.kcl'
3153
3154d = b + c
3155"#;
3156
3157        let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
3158            .parse_errs_as_err()
3159            .unwrap();
3160
3161        let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
3162
3163        tokio::fs::File::create(tmpdir.path().join("main.kcl"))
3164            .await
3165            .unwrap()
3166            .write_all(main_kcl.as_bytes())
3167            .await
3168            .unwrap();
3169
3170        tokio::fs::File::create(tmpdir.path().join("a.kcl"))
3171            .await
3172            .unwrap()
3173            .write_all(program_a_kcl.as_bytes())
3174            .await
3175            .unwrap();
3176
3177        tokio::fs::File::create(tmpdir.path().join("b.kcl"))
3178            .await
3179            .unwrap()
3180            .write_all(program_b_kcl.as_bytes())
3181            .await
3182            .unwrap();
3183
3184        tokio::fs::File::create(tmpdir.path().join("c.kcl"))
3185            .await
3186            .unwrap()
3187            .write_all(program_c_kcl.as_bytes())
3188            .await
3189            .unwrap();
3190
3191        let exec_ctxt = ExecutorContext {
3192            engine: Arc::new(Box::new(
3193                crate::engine::conn_mock::EngineConnection::new()
3194                    .map_err(|err| {
3195                        KclError::new_internal(KclErrorDetails::new(
3196                            format!("Failed to create mock engine connection: {err}"),
3197                            vec![SourceRange::default()],
3198                        ))
3199                    })
3200                    .unwrap(),
3201            )),
3202            fs: Arc::new(crate::fs::FileManager::new()),
3203            settings: ExecutorSettings {
3204                project_directory: Some(crate::TypedPath(tmpdir.path().into())),
3205                ..Default::default()
3206            },
3207            context_type: ContextType::Mock,
3208        };
3209        let mut exec_state = ExecState::new(&exec_ctxt);
3210
3211        exec_ctxt
3212            .run(
3213                &crate::Program {
3214                    ast: main.clone(),
3215                    original_file_contents: "".to_owned(),
3216                },
3217                &mut exec_state,
3218            )
3219            .await
3220            .unwrap();
3221    }
3222
3223    #[tokio::test(flavor = "multi_thread")]
3224    async fn user_coercion() {
3225        let program = r#"fn foo(x: Axis2d) {
3226  return 0
3227}
3228
3229foo(x = { direction = [0, 0], origin = [0, 0]})
3230"#;
3231
3232        parse_execute(program).await.unwrap();
3233
3234        let program = r#"fn foo(x: Axis3d) {
3235  return 0
3236}
3237
3238foo(x = { direction = [0, 0], origin = [0, 0]})
3239"#;
3240
3241        parse_execute(program).await.unwrap_err();
3242    }
3243
3244    #[tokio::test(flavor = "multi_thread")]
3245    async fn coerce_return() {
3246        let program = r#"fn foo(): number(mm) {
3247  return 42
3248}
3249
3250a = foo()
3251"#;
3252
3253        parse_execute(program).await.unwrap();
3254
3255        let program = r#"fn foo(): mm {
3256  return 42
3257}
3258
3259a = foo()
3260"#;
3261
3262        parse_execute(program).await.unwrap();
3263
3264        let program = r#"fn foo(): number(mm) {
3265  return { bar: 42 }
3266}
3267
3268a = foo()
3269"#;
3270
3271        parse_execute(program).await.unwrap_err();
3272
3273        let program = r#"fn foo(): mm {
3274  return { bar: 42 }
3275}
3276
3277a = foo()
3278"#;
3279
3280        parse_execute(program).await.unwrap_err();
3281    }
3282
3283    #[tokio::test(flavor = "multi_thread")]
3284    async fn test_sensible_error_when_missing_equals_in_kwarg() {
3285        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)"]
3286            .into_iter()
3287            .enumerate()
3288        {
3289            let program = format!(
3290                "fn foo() {{ return 0 }}
3291z = 0
3292fn f(x, y, z) {{ return 0 }}
3293{call}"
3294            );
3295            let err = parse_execute(&program).await.unwrap_err();
3296            let msg = err.message();
3297            assert!(
3298                msg.contains("This argument needs a label, but it doesn't have one"),
3299                "failed test {i}: {msg}"
3300            );
3301            assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
3302            if i == 0 {
3303                assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
3304            }
3305        }
3306    }
3307
3308    #[tokio::test(flavor = "multi_thread")]
3309    async fn default_param_for_unlabeled() {
3310        // Tests that the input param for myExtrude is taken from the pipeline value and same-name
3311        // keyword args.
3312        let ast = r#"fn myExtrude(@sk, length) {
3313  return extrude(sk, length)
3314}
3315sketch001 = startSketchOn(XY)
3316  |> circle(center = [0, 0], radius = 93.75)
3317  |> myExtrude(length = 40)
3318"#;
3319
3320        parse_execute(ast).await.unwrap();
3321    }
3322
3323    #[tokio::test(flavor = "multi_thread")]
3324    async fn dont_use_unlabelled_as_input() {
3325        // `length` should be used as the `length` argument to extrude, not the unlabelled input
3326        let ast = r#"length = 10
3327startSketchOn(XY)
3328  |> circle(center = [0, 0], radius = 93.75)
3329  |> extrude(length)
3330"#;
3331
3332        parse_execute(ast).await.unwrap();
3333    }
3334
3335    #[tokio::test(flavor = "multi_thread")]
3336    async fn ascription_in_binop() {
3337        let ast = r#"foo = tan(0): number(rad) - 4deg"#;
3338        parse_execute(ast).await.unwrap();
3339
3340        let ast = r#"foo = tan(0): rad - 4deg"#;
3341        parse_execute(ast).await.unwrap();
3342    }
3343
3344    #[tokio::test(flavor = "multi_thread")]
3345    async fn neg_sqrt() {
3346        let ast = r#"bad = sqrt(-2)"#;
3347
3348        let e = parse_execute(ast).await.unwrap_err();
3349        // Make sure we get a useful error message and not an engine error.
3350        assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
3351    }
3352
3353    #[tokio::test(flavor = "multi_thread")]
3354    async fn non_array_fns() {
3355        let ast = r#"push(1, item = 2)
3356pop(1)
3357map(1, f = fn(@x) { return x + 1 })
3358reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
3359
3360        parse_execute(ast).await.unwrap();
3361    }
3362
3363    #[tokio::test(flavor = "multi_thread")]
3364    async fn non_array_indexing() {
3365        let good = r#"a = 42
3366good = a[0]
3367"#;
3368        let result = parse_execute(good).await.unwrap();
3369        let mem = result.exec_state.stack();
3370        let num = mem
3371            .memory
3372            .get_from("good", result.mem_env, SourceRange::default(), 0)
3373            .unwrap()
3374            .as_ty_f64()
3375            .unwrap();
3376        assert_eq!(num.n, 42.0);
3377
3378        let bad = r#"a = 42
3379bad = a[1]
3380"#;
3381
3382        parse_execute(bad).await.unwrap_err();
3383    }
3384
3385    #[tokio::test(flavor = "multi_thread")]
3386    async fn coerce_unknown_to_length() {
3387        let ast = r#"x = 2mm * 2mm
3388y = x: number(Length)"#;
3389        let e = parse_execute(ast).await.unwrap_err();
3390        assert!(
3391            e.message().contains("could not coerce"),
3392            "Error message: '{}'",
3393            e.message()
3394        );
3395
3396        let ast = r#"x = 2mm
3397y = x: number(Length)"#;
3398        let result = parse_execute(ast).await.unwrap();
3399        let mem = result.exec_state.stack();
3400        let num = mem
3401            .memory
3402            .get_from("y", result.mem_env, SourceRange::default(), 0)
3403            .unwrap()
3404            .as_ty_f64()
3405            .unwrap();
3406        assert_eq!(num.n, 2.0);
3407        assert_eq!(num.ty, NumericType::mm());
3408    }
3409
3410    #[tokio::test(flavor = "multi_thread")]
3411    async fn one_warning_unknown() {
3412        let ast = r#"
3413// Should warn once
3414a = PI * 2
3415// Should warn once
3416b = (PI * 2) / 3
3417// Should not warn
3418c = ((PI * 2) / 3): number(deg)
3419"#;
3420
3421        let result = parse_execute(ast).await.unwrap();
3422        assert_eq!(result.exec_state.errors().len(), 2);
3423    }
3424
3425    #[tokio::test(flavor = "multi_thread")]
3426    async fn non_count_indexing() {
3427        let ast = r#"x = [0, 0]
3428y = x[1mm]
3429"#;
3430        parse_execute(ast).await.unwrap_err();
3431
3432        let ast = r#"x = [0, 0]
3433y = 1deg
3434z = x[y]
3435"#;
3436        parse_execute(ast).await.unwrap_err();
3437
3438        let ast = r#"x = [0, 0]
3439y = x[0mm + 1]
3440"#;
3441        parse_execute(ast).await.unwrap_err();
3442    }
3443
3444    #[tokio::test(flavor = "multi_thread")]
3445    async fn getting_property_of_plane() {
3446        let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
3447        parse_execute(&ast).await.unwrap();
3448    }
3449
3450    #[cfg(feature = "artifact-graph")]
3451    #[tokio::test(flavor = "multi_thread")]
3452    async fn no_artifacts_from_within_hole_call() {
3453        // Test that executing stdlib KCL, like the `hole` function
3454        // (which is actually implemented in KCL not Rust)
3455        // does not generate artifacts from within the stdlib code,
3456        // only from the user code.
3457        let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
3458        let out = parse_execute(&ast).await.unwrap();
3459
3460        // Get all the operations that occurred.
3461        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3462
3463        // There should be 5, for sketching the cube and applying the hole.
3464        // If the stdlib internal calls are being tracked, that's a bug,
3465        // and the actual number of operations will be something like 35.
3466        let expected = 5;
3467        assert_eq!(
3468            actual_operations.len(),
3469            expected,
3470            "expected {expected} operations, received {}:\n{actual_operations:#?}",
3471            actual_operations.len(),
3472        );
3473    }
3474
3475    #[cfg(feature = "artifact-graph")]
3476    #[tokio::test(flavor = "multi_thread")]
3477    async fn feature_tree_annotation_on_user_defined_kcl() {
3478        // The call to foo() should not generate an operation,
3479        // because its 'feature_tree' attribute has been set to false.
3480        let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
3481        let out = parse_execute(&ast).await.unwrap();
3482
3483        // Get all the operations that occurred.
3484        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3485
3486        let expected = 0;
3487        assert_eq!(
3488            actual_operations.len(),
3489            expected,
3490            "expected {expected} operations, received {}:\n{actual_operations:#?}",
3491            actual_operations.len(),
3492        );
3493    }
3494
3495    #[cfg(feature = "artifact-graph")]
3496    #[tokio::test(flavor = "multi_thread")]
3497    async fn no_feature_tree_annotation_on_user_defined_kcl() {
3498        // The call to foo() should generate an operation,
3499        // because @(feature_tree) defaults to true.
3500        let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
3501        let out = parse_execute(&ast).await.unwrap();
3502
3503        // Get all the operations that occurred.
3504        let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3505
3506        let expected = 2;
3507        assert_eq!(
3508            actual_operations.len(),
3509            expected,
3510            "expected {expected} operations, received {}:\n{actual_operations:#?}",
3511            actual_operations.len(),
3512        );
3513        assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
3514        assert!(matches!(actual_operations[1], Operation::GroupEnd));
3515    }
3516
3517    #[tokio::test(flavor = "multi_thread")]
3518    async fn custom_warning() {
3519        let warn = r#"
3520a = PI * 2
3521"#;
3522        let result = parse_execute(warn).await.unwrap();
3523        assert_eq!(result.exec_state.errors().len(), 1);
3524        assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
3525
3526        let allow = r#"
3527@warnings(allow = unknownUnits)
3528a = PI * 2
3529"#;
3530        let result = parse_execute(allow).await.unwrap();
3531        assert_eq!(result.exec_state.errors().len(), 0);
3532
3533        let deny = r#"
3534@warnings(deny = [unknownUnits])
3535a = PI * 2
3536"#;
3537        let result = parse_execute(deny).await.unwrap();
3538        assert_eq!(result.exec_state.errors().len(), 1);
3539        assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
3540    }
3541
3542    #[tokio::test(flavor = "multi_thread")]
3543    async fn cannot_solid_extrude_an_open_profile() {
3544        // This should fail during mock execution, because KCL should catch
3545        // that the profile is not closed.
3546        let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
3547        let program = crate::Program::parse_no_errs(&code).expect("should parse");
3548        let exec_ctxt = ExecutorContext::new_mock(None).await;
3549        let mut exec_state = ExecState::new(&exec_ctxt);
3550
3551        let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
3552        assert!(matches!(err, KclError::Semantic { .. }));
3553        exec_ctxt.close().await;
3554    }
3555}