kcl_lib/execution/
exec_ast.rs

1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4
5use crate::{
6    CompilationError, NodePath, SourceRange,
7    errors::{KclError, KclErrorDetails},
8    execution::{
9        BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, ModelingCmdMeta, ModuleArtifactState,
10        Operation, PlaneType, StatementKind, TagIdentifier, annotations,
11        cad_op::OpKclValue,
12        fn_call::Args,
13        kcl_value::{FunctionSource, TypeDef},
14        memory,
15        state::ModuleState,
16        types::{NumericType, PrimitiveType, RuntimeType},
17    },
18    fmt,
19    modules::{ModuleId, ModulePath, ModuleRepr},
20    parsing::ast::types::{
21        Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
22        BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, MemberExpression, Name,
23        Node, NodeRef, ObjectExpression, PipeExpression, Program, SketchBlock, SketchVar, TagDeclarator, Type,
24        UnaryExpression, UnaryOperator,
25    },
26    std::args::TyF64,
27};
28
29impl<'a> StatementKind<'a> {
30    fn expect_name(&self) -> &'a str {
31        match self {
32            StatementKind::Declaration { name } => name,
33            StatementKind::Expression => unreachable!(),
34        }
35    }
36}
37
38impl ExecutorContext {
39    /// Returns true if importing the prelude should be skipped.
40    async fn handle_annotations(
41        &self,
42        annotations: impl Iterator<Item = &Node<Annotation>>,
43        body_type: BodyType,
44        exec_state: &mut ExecState,
45    ) -> Result<bool, KclError> {
46        let mut no_prelude = false;
47        for annotation in annotations {
48            if annotation.name() == Some(annotations::SETTINGS) {
49                if matches!(body_type, BodyType::Root) {
50                    if exec_state.mod_local.settings.update_from_annotation(annotation)? {
51                        exec_state.mod_local.explicit_length_units = true;
52                    }
53                } else {
54                    exec_state.err(CompilationError::err(
55                        annotation.as_source_range(),
56                        "Settings can only be modified at the top level scope of a file",
57                    ));
58                }
59            } else if annotation.name() == Some(annotations::NO_PRELUDE) {
60                if matches!(body_type, BodyType::Root) {
61                    no_prelude = true;
62                } else {
63                    exec_state.err(CompilationError::err(
64                        annotation.as_source_range(),
65                        "The standard library can only be skipped at the top level scope of a file",
66                    ));
67                }
68            } else if annotation.name() == Some(annotations::WARNINGS) {
69                // TODO we should support setting warnings for the whole project, not just one file
70                if matches!(body_type, BodyType::Root) {
71                    let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
72                    for p in props {
73                        match &*p.inner.key.name {
74                            annotations::WARN_ALLOW => {
75                                let allowed = annotations::many_of(
76                                    &p.inner.value,
77                                    &annotations::WARN_VALUES,
78                                    annotation.as_source_range(),
79                                )?;
80                                exec_state.mod_local.allowed_warnings = allowed;
81                            }
82                            annotations::WARN_DENY => {
83                                let denied = annotations::many_of(
84                                    &p.inner.value,
85                                    &annotations::WARN_VALUES,
86                                    annotation.as_source_range(),
87                                )?;
88                                exec_state.mod_local.denied_warnings = denied;
89                            }
90                            name => {
91                                return Err(KclError::new_semantic(KclErrorDetails::new(
92                                    format!(
93                                        "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
94                                        annotations::WARN_ALLOW,
95                                        annotations::WARN_DENY,
96                                    ),
97                                    vec![annotation.as_source_range()],
98                                )));
99                            }
100                        }
101                    }
102                } else {
103                    exec_state.err(CompilationError::err(
104                        annotation.as_source_range(),
105                        "Warnings can only be customized at the top level scope of a file",
106                    ));
107                }
108            } else {
109                exec_state.warn(
110                    CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
111                    annotations::WARN_UNKNOWN_ATTR,
112                );
113            }
114        }
115        Ok(no_prelude)
116    }
117
118    pub(super) async fn exec_module_body(
119        &self,
120        program: &Node<Program>,
121        exec_state: &mut ExecState,
122        preserve_mem: bool,
123        module_id: ModuleId,
124        path: &ModulePath,
125    ) -> Result<
126        (Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState),
127        (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>),
128    > {
129        crate::log::log(format!("enter module {path} {}", exec_state.stack()));
130
131        let mut local_state = ModuleState::new(path.clone(), exec_state.stack().memory.clone(), Some(module_id));
132        if !preserve_mem {
133            std::mem::swap(&mut exec_state.mod_local, &mut local_state);
134        }
135
136        let no_prelude = self
137            .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
138            .await
139            .map_err(|err| (err, None, None))?;
140
141        if !preserve_mem {
142            exec_state.mut_stack().push_new_root_env(!no_prelude);
143        }
144
145        let result = self
146            .exec_block(program, exec_state, crate::execution::BodyType::Root)
147            .await;
148
149        let env_ref = if preserve_mem {
150            exec_state.mut_stack().pop_and_preserve_env()
151        } else {
152            exec_state.mut_stack().pop_env()
153        };
154        let module_artifacts = if !preserve_mem {
155            std::mem::swap(&mut exec_state.mod_local, &mut local_state);
156            local_state.artifacts
157        } else {
158            std::mem::take(&mut exec_state.mod_local.artifacts)
159        };
160
161        crate::log::log(format!("leave {path}"));
162
163        result
164            .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
165            .map(|result| (result, env_ref, local_state.module_exports, module_artifacts))
166    }
167
168    /// Execute an AST's program.
169    #[async_recursion]
170    pub(super) async fn exec_block<'a>(
171        &'a self,
172        program: NodeRef<'a, Program>,
173        exec_state: &mut ExecState,
174        body_type: BodyType,
175    ) -> Result<Option<KclValue>, KclError> {
176        let mut last_expr = None;
177        // Iterate over the body of the program.
178        for statement in &program.body {
179            match statement {
180                BodyItem::ImportStatement(import_stmt) => {
181                    if !matches!(body_type, BodyType::Root) {
182                        return Err(KclError::new_semantic(KclErrorDetails::new(
183                            "Imports are only supported at the top-level of a file.".to_owned(),
184                            vec![import_stmt.into()],
185                        )));
186                    }
187
188                    let source_range = SourceRange::from(import_stmt);
189                    let attrs = &import_stmt.outer_attrs;
190                    let module_path = ModulePath::from_import_path(
191                        &import_stmt.path,
192                        &self.settings.project_directory,
193                        &exec_state.mod_local.path,
194                    )?;
195                    let module_id = self
196                        .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
197                        .await?;
198
199                    match &import_stmt.selector {
200                        ImportSelector::List { items } => {
201                            let (env_ref, module_exports) =
202                                self.exec_module_for_items(module_id, exec_state, source_range).await?;
203                            for import_item in items {
204                                // Extract the item from the module.
205                                let mem = &exec_state.stack().memory;
206                                let mut value = mem
207                                    .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
208                                    .cloned();
209                                let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
210                                let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
211                                let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
212                                let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
213
214                                if value.is_err() && ty.is_err() && mod_value.is_err() {
215                                    return Err(KclError::new_undefined_value(
216                                        KclErrorDetails::new(
217                                            format!("{} is not defined in module", import_item.name.name),
218                                            vec![SourceRange::from(&import_item.name)],
219                                        ),
220                                        None,
221                                    ));
222                                }
223
224                                // Check that the item is allowed to be imported (in at least one namespace).
225                                if value.is_ok() && !module_exports.contains(&import_item.name.name) {
226                                    value = Err(KclError::new_semantic(KclErrorDetails::new(
227                                        format!(
228                                            "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
229                                            import_item.name.name
230                                        ),
231                                        vec![SourceRange::from(&import_item.name)],
232                                    )));
233                                }
234
235                                if ty.is_ok() && !module_exports.contains(&ty_name) {
236                                    ty = Err(KclError::new_semantic(KclErrorDetails::new(
237                                        format!(
238                                            "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
239                                            import_item.name.name
240                                        ),
241                                        vec![SourceRange::from(&import_item.name)],
242                                    )));
243                                }
244
245                                if mod_value.is_ok() && !module_exports.contains(&mod_name) {
246                                    mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
247                                        format!(
248                                            "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
249                                            import_item.name.name
250                                        ),
251                                        vec![SourceRange::from(&import_item.name)],
252                                    )));
253                                }
254
255                                if value.is_err() && ty.is_err() && mod_value.is_err() {
256                                    return value.map(Option::Some);
257                                }
258
259                                // Add the item to the current module.
260                                if let Ok(value) = value {
261                                    exec_state.mut_stack().add(
262                                        import_item.identifier().to_owned(),
263                                        value,
264                                        SourceRange::from(&import_item.name),
265                                    )?;
266
267                                    if let ItemVisibility::Export = import_stmt.visibility {
268                                        exec_state
269                                            .mod_local
270                                            .module_exports
271                                            .push(import_item.identifier().to_owned());
272                                    }
273                                }
274
275                                if let Ok(ty) = ty {
276                                    let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
277                                    exec_state.mut_stack().add(
278                                        ty_name.clone(),
279                                        ty,
280                                        SourceRange::from(&import_item.name),
281                                    )?;
282
283                                    if let ItemVisibility::Export = import_stmt.visibility {
284                                        exec_state.mod_local.module_exports.push(ty_name);
285                                    }
286                                }
287
288                                if let Ok(mod_value) = mod_value {
289                                    let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
290                                    exec_state.mut_stack().add(
291                                        mod_name.clone(),
292                                        mod_value,
293                                        SourceRange::from(&import_item.name),
294                                    )?;
295
296                                    if let ItemVisibility::Export = import_stmt.visibility {
297                                        exec_state.mod_local.module_exports.push(mod_name);
298                                    }
299                                }
300                            }
301                        }
302                        ImportSelector::Glob(_) => {
303                            let (env_ref, module_exports) =
304                                self.exec_module_for_items(module_id, exec_state, source_range).await?;
305                            for name in module_exports.iter() {
306                                let item = exec_state
307                                    .stack()
308                                    .memory
309                                    .get_from(name, env_ref, source_range, 0)
310                                    .map_err(|_err| {
311                                        KclError::new_internal(KclErrorDetails::new(
312                                            format!("{name} is not defined in module (but was exported?)"),
313                                            vec![source_range],
314                                        ))
315                                    })?
316                                    .clone();
317                                exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
318
319                                if let ItemVisibility::Export = import_stmt.visibility {
320                                    exec_state.mod_local.module_exports.push(name.clone());
321                                }
322                            }
323                        }
324                        ImportSelector::None { .. } => {
325                            let name = import_stmt.module_name().unwrap();
326                            let item = KclValue::Module {
327                                value: module_id,
328                                meta: vec![source_range.into()],
329                            };
330                            exec_state.mut_stack().add(
331                                format!("{}{}", memory::MODULE_PREFIX, name),
332                                item,
333                                source_range,
334                            )?;
335                        }
336                    }
337                    last_expr = None;
338                }
339                BodyItem::ExpressionStatement(expression_statement) => {
340                    let metadata = Metadata::from(expression_statement);
341                    last_expr = Some(
342                        self.execute_expr(
343                            &expression_statement.expression,
344                            exec_state,
345                            &metadata,
346                            &[],
347                            StatementKind::Expression,
348                        )
349                        .await?,
350                    );
351                }
352                BodyItem::VariableDeclaration(variable_declaration) => {
353                    let var_name = variable_declaration.declaration.id.name.to_string();
354                    let source_range = SourceRange::from(&variable_declaration.declaration.init);
355                    let metadata = Metadata { source_range };
356
357                    let annotations = &variable_declaration.outer_attrs;
358
359                    // During the evaluation of the variable's RHS, set context that this is all happening inside a variable
360                    // declaration, for the given name. This helps improve user-facing error messages.
361                    let lhs = variable_declaration.inner.name().to_owned();
362                    let prev_being_declared = exec_state.mod_local.being_declared.take();
363                    exec_state.mod_local.being_declared = Some(lhs);
364                    let rhs_result = self
365                        .execute_expr(
366                            &variable_declaration.declaration.init,
367                            exec_state,
368                            &metadata,
369                            annotations,
370                            StatementKind::Declaration { name: &var_name },
371                        )
372                        .await;
373                    // Declaration over, so unset this context.
374                    exec_state.mod_local.being_declared = prev_being_declared;
375                    let rhs = rhs_result?;
376
377                    exec_state
378                        .mut_stack()
379                        .add(var_name.clone(), rhs.clone(), source_range)?;
380
381                    if rhs.show_variable_in_feature_tree() {
382                        exec_state.push_op(Operation::VariableDeclaration {
383                            name: var_name.clone(),
384                            value: OpKclValue::from(&rhs),
385                            visibility: variable_declaration.visibility,
386                            node_path: NodePath::placeholder(),
387                            source_range,
388                        });
389                    }
390
391                    // Track exports.
392                    if let ItemVisibility::Export = variable_declaration.visibility {
393                        if matches!(body_type, BodyType::Root) {
394                            exec_state.mod_local.module_exports.push(var_name);
395                        } else {
396                            exec_state.err(CompilationError::err(
397                                variable_declaration.as_source_range(),
398                                "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
399                            ));
400                        }
401                    }
402                    // Variable declaration can be the return value of a module.
403                    last_expr = matches!(body_type, BodyType::Root).then_some(rhs);
404                }
405                BodyItem::TypeDeclaration(ty) => {
406                    let metadata = Metadata::from(&**ty);
407                    let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
408                    match attrs.impl_ {
409                        annotations::Impl::Rust | annotations::Impl::RustConstraint => {
410                            let std_path = match &exec_state.mod_local.path {
411                                ModulePath::Std { value } => value,
412                                ModulePath::Local { .. } | ModulePath::Main => {
413                                    return Err(KclError::new_semantic(KclErrorDetails::new(
414                                        "User-defined types are not yet supported.".to_owned(),
415                                        vec![metadata.source_range],
416                                    )));
417                                }
418                            };
419                            let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
420                            let value = KclValue::Type {
421                                value: TypeDef::RustRepr(t, props),
422                                meta: vec![metadata],
423                                experimental: attrs.experimental,
424                            };
425                            let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
426                            exec_state
427                                .mut_stack()
428                                .add(name_in_mem.clone(), value, metadata.source_range)
429                                .map_err(|_| {
430                                    KclError::new_semantic(KclErrorDetails::new(
431                                        format!("Redefinition of type {}.", ty.name.name),
432                                        vec![metadata.source_range],
433                                    ))
434                                })?;
435
436                            if let ItemVisibility::Export = ty.visibility {
437                                exec_state.mod_local.module_exports.push(name_in_mem);
438                            }
439                        }
440                        // Do nothing for primitive types, they get special treatment and their declarations are just for documentation.
441                        annotations::Impl::Primitive => {}
442                        annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
443                            Some(alias) => {
444                                let value = KclValue::Type {
445                                    value: TypeDef::Alias(
446                                        RuntimeType::from_parsed(
447                                            alias.inner.clone(),
448                                            exec_state,
449                                            metadata.source_range,
450                                            attrs.impl_ == annotations::Impl::KclConstrainable,
451                                        )
452                                        .map_err(|e| KclError::new_semantic(e.into()))?,
453                                    ),
454                                    meta: vec![metadata],
455                                    experimental: attrs.experimental,
456                                };
457                                let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
458                                exec_state
459                                    .mut_stack()
460                                    .add(name_in_mem.clone(), value, metadata.source_range)
461                                    .map_err(|_| {
462                                        KclError::new_semantic(KclErrorDetails::new(
463                                            format!("Redefinition of type {}.", ty.name.name),
464                                            vec![metadata.source_range],
465                                        ))
466                                    })?;
467
468                                if let ItemVisibility::Export = ty.visibility {
469                                    exec_state.mod_local.module_exports.push(name_in_mem);
470                                }
471                            }
472                            None => {
473                                return Err(KclError::new_semantic(KclErrorDetails::new(
474                                    "User-defined types are not yet supported.".to_owned(),
475                                    vec![metadata.source_range],
476                                )));
477                            }
478                        },
479                    }
480
481                    last_expr = None;
482                }
483                BodyItem::ReturnStatement(return_statement) => {
484                    let metadata = Metadata::from(return_statement);
485
486                    if matches!(body_type, BodyType::Root) {
487                        return Err(KclError::new_semantic(KclErrorDetails::new(
488                            "Cannot return from outside a function.".to_owned(),
489                            vec![metadata.source_range],
490                        )));
491                    }
492
493                    let value = self
494                        .execute_expr(
495                            &return_statement.argument,
496                            exec_state,
497                            &metadata,
498                            &[],
499                            StatementKind::Expression,
500                        )
501                        .await?;
502                    exec_state
503                        .mut_stack()
504                        .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
505                        .map_err(|_| {
506                            KclError::new_semantic(KclErrorDetails::new(
507                                "Multiple returns from a single function.".to_owned(),
508                                vec![metadata.source_range],
509                            ))
510                        })?;
511                    last_expr = None;
512                }
513            }
514        }
515
516        if matches!(body_type, BodyType::Root) {
517            // Flush the batch queue.
518            exec_state
519                .flush_batch(
520                    ModelingCmdMeta::new(self, SourceRange::new(program.end, program.end, program.module_id)),
521                    // True here tells the engine to flush all the end commands as well like fillets
522                    // and chamfers where the engine would otherwise eat the ID of the segments.
523                    true,
524                )
525                .await?;
526        }
527
528        Ok(last_expr)
529    }
530
531    pub async fn open_module(
532        &self,
533        path: &ImportPath,
534        attrs: &[Node<Annotation>],
535        resolved_path: &ModulePath,
536        exec_state: &mut ExecState,
537        source_range: SourceRange,
538    ) -> Result<ModuleId, KclError> {
539        match path {
540            ImportPath::Kcl { .. } => {
541                exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
542
543                if let Some(id) = exec_state.id_for_module(resolved_path) {
544                    return Ok(id);
545                }
546
547                let id = exec_state.next_module_id();
548                // Add file path string to global state even if it fails to import
549                exec_state.add_path_to_source_id(resolved_path.clone(), id);
550                let source = resolved_path.source(&self.fs, source_range).await?;
551                exec_state.add_id_to_source(id, source.clone());
552                // TODO handle parsing errors properly
553                let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
554                exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
555
556                Ok(id)
557            }
558            ImportPath::Foreign { .. } => {
559                if let Some(id) = exec_state.id_for_module(resolved_path) {
560                    return Ok(id);
561                }
562
563                let id = exec_state.next_module_id();
564                let path = resolved_path.expect_path();
565                // Add file path string to global state even if it fails to import
566                exec_state.add_path_to_source_id(resolved_path.clone(), id);
567                let format = super::import::format_from_annotations(attrs, path, source_range)?;
568                let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
569                exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
570                Ok(id)
571            }
572            ImportPath::Std { .. } => {
573                if let Some(id) = exec_state.id_for_module(resolved_path) {
574                    return Ok(id);
575                }
576
577                let id = exec_state.next_module_id();
578                // Add file path string to global state even if it fails to import
579                exec_state.add_path_to_source_id(resolved_path.clone(), id);
580                let source = resolved_path.source(&self.fs, source_range).await?;
581                exec_state.add_id_to_source(id, source.clone());
582                let parsed = crate::parsing::parse_str(&source.source, id)
583                    .parse_errs_as_err()
584                    .unwrap();
585                exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
586                Ok(id)
587            }
588        }
589    }
590
591    pub(super) async fn exec_module_for_items(
592        &self,
593        module_id: ModuleId,
594        exec_state: &mut ExecState,
595        source_range: SourceRange,
596    ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
597        let path = exec_state.global.module_infos[&module_id].path.clone();
598        let mut repr = exec_state.global.module_infos[&module_id].take_repr();
599        // DON'T EARLY RETURN! We need to restore the module repr
600
601        let result = match &mut repr {
602            ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
603            ModuleRepr::Kcl(_, Some((_, env_ref, items, _))) => Ok((*env_ref, items.clone())),
604            ModuleRepr::Kcl(program, cache) => self
605                .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
606                .await
607                .map(|(val, er, items, module_artifacts)| {
608                    *cache = Some((val, er, items.clone(), module_artifacts.clone()));
609                    (er, items)
610                }),
611            ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
612                "Cannot import items from foreign modules".to_owned(),
613                vec![geom.source_range],
614            ))),
615            ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
616        };
617
618        exec_state.global.module_infos[&module_id].restore_repr(repr);
619        result
620    }
621
622    async fn exec_module_for_result(
623        &self,
624        module_id: ModuleId,
625        exec_state: &mut ExecState,
626        source_range: SourceRange,
627    ) -> Result<Option<KclValue>, KclError> {
628        let path = exec_state.global.module_infos[&module_id].path.clone();
629        let mut repr = exec_state.global.module_infos[&module_id].take_repr();
630        // DON'T EARLY RETURN! We need to restore the module repr
631
632        let result = match &mut repr {
633            ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
634            ModuleRepr::Kcl(_, Some((val, _, _, _))) => Ok(val.clone()),
635            ModuleRepr::Kcl(program, cached_items) => {
636                let result = self
637                    .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
638                    .await;
639                match result {
640                    Ok((val, env, items, module_artifacts)) => {
641                        *cached_items = Some((val.clone(), env, items, module_artifacts));
642                        Ok(val)
643                    }
644                    Err(e) => Err(e),
645                }
646            }
647            ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
648            ModuleRepr::Foreign(geom, cached) => {
649                let result = super::import::send_to_engine(geom.clone(), exec_state, self)
650                    .await
651                    .map(|geom| Some(KclValue::ImportedGeometry(geom)));
652
653                match result {
654                    Ok(val) => {
655                        *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
656                        Ok(val)
657                    }
658                    Err(e) => Err(e),
659                }
660            }
661            ModuleRepr::Dummy => unreachable!(),
662        };
663
664        exec_state.global.module_infos[&module_id].restore_repr(repr);
665
666        result
667    }
668
669    pub async fn exec_module_from_ast(
670        &self,
671        program: &Node<Program>,
672        module_id: ModuleId,
673        path: &ModulePath,
674        exec_state: &mut ExecState,
675        source_range: SourceRange,
676        preserve_mem: bool,
677    ) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>, ModuleArtifactState), KclError> {
678        exec_state.global.mod_loader.enter_module(path);
679        let result = self
680            .exec_module_body(program, exec_state, preserve_mem, module_id, path)
681            .await;
682        exec_state.global.mod_loader.leave_module(path);
683
684        // TODO: ModuleArtifactState is getting dropped here when there's an
685        // error.  Should we propagate it for non-root modules?
686        result.map_err(|(err, _, _)| {
687            if let KclError::ImportCycle { .. } = err {
688                // It was an import cycle.  Keep the original message.
689                err.override_source_ranges(vec![source_range])
690            } else {
691                // TODO would be great to have line/column for the underlying error here
692                KclError::new_semantic(KclErrorDetails::new(
693                    format!(
694                        "Error loading imported file ({path}). Open it to view more details.\n  {}",
695                        err.message()
696                    ),
697                    vec![source_range],
698                ))
699            }
700        })
701    }
702
703    #[async_recursion]
704    pub(super) async fn execute_expr<'a: 'async_recursion>(
705        &self,
706        init: &Expr,
707        exec_state: &mut ExecState,
708        metadata: &Metadata,
709        annotations: &[Node<Annotation>],
710        statement_kind: StatementKind<'a>,
711    ) -> Result<KclValue, KclError> {
712        let item = match init {
713            Expr::None(none) => KclValue::from(none),
714            Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
715            Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
716            Expr::Name(name) => {
717                let being_declared = exec_state.mod_local.being_declared.clone();
718                let value = name
719                    .get_result(exec_state, self)
720                    .await
721                    .map_err(|e| var_in_own_ref_err(e, &being_declared))?
722                    .clone();
723                if let KclValue::Module { value: module_id, meta } = value {
724                    self.exec_module_for_result(
725                        module_id,
726                        exec_state,
727                        metadata.source_range
728                        ).await?
729                        .unwrap_or_else(|| {
730                            exec_state.warn(CompilationError::err(
731                                metadata.source_range,
732                                "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
733                            ),
734                        annotations::WARN_MOD_RETURN_VALUE);
735
736                            let mut new_meta = vec![metadata.to_owned()];
737                            new_meta.extend(meta);
738                            KclValue::KclNone {
739                                value: Default::default(),
740                                meta: new_meta,
741                            }
742                        })
743                } else {
744                    value
745                }
746            }
747            Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
748            Expr::FunctionExpression(function_expression) => {
749                let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
750                if let Some(attrs) = attrs
751                    && (attrs.impl_ == annotations::Impl::Rust || attrs.impl_ == annotations::Impl::RustConstraint)
752                {
753                    if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
754                        let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
755                        KclValue::Function {
756                            value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
757                            meta: vec![metadata.to_owned()],
758                        }
759                    } else {
760                        return Err(KclError::new_semantic(KclErrorDetails::new(
761                            "Rust implementation of functions is restricted to the standard library".to_owned(),
762                            vec![metadata.source_range],
763                        )));
764                    }
765                } else {
766                    // Snapshotting memory here is crucial for semantics so that we close
767                    // over variables. Variables defined lexically later shouldn't
768                    // be available to the function body.
769                    KclValue::Function {
770                        value: Box::new(FunctionSource::kcl(
771                            function_expression.clone(),
772                            exec_state.mut_stack().snapshot(),
773                            matches!(&exec_state.mod_local.path, ModulePath::Std { .. }),
774                        )),
775                        meta: vec![metadata.to_owned()],
776                    }
777                }
778            }
779            Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
780            Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
781            Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
782                StatementKind::Declaration { name } => {
783                    let message = format!(
784                        "you cannot declare variable {name} as %, because % can only be used in function calls"
785                    );
786
787                    return Err(KclError::new_semantic(KclErrorDetails::new(
788                        message,
789                        vec![pipe_substitution.into()],
790                    )));
791                }
792                StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
793                    Some(x) => x,
794                    None => {
795                        return Err(KclError::new_semantic(KclErrorDetails::new(
796                            "cannot use % outside a pipe expression".to_owned(),
797                            vec![pipe_substitution.into()],
798                        )));
799                    }
800                },
801            },
802            Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
803            Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
804            Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
805            Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
806            Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
807            Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
808            Expr::LabelledExpression(expr) => {
809                let result = self
810                    .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
811                    .await?;
812                exec_state
813                    .mut_stack()
814                    .add(expr.label.name.clone(), result.clone(), init.into())?;
815                // TODO this lets us use the label as a variable name, but not as a tag in most cases
816                result
817            }
818            Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
819            Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
820            Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?,
821        };
822        Ok(item)
823    }
824}
825
826/// If the error is about an undefined name, and that name matches the name being defined,
827/// make the error message more specific.
828fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
829    let KclError::UndefinedValue { name, mut details } = e else {
830        return e;
831    };
832    // TODO after June 26th: replace this with a let-chain,
833    // which will be available in Rust 1.88
834    // https://rust-lang.github.io/rfcs/2497-if-let-chains.html
835    if let (Some(name0), Some(name1)) = (&being_declared, &name)
836        && name0 == name1
837    {
838        details.message = format!(
839            "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
840        );
841    }
842    KclError::UndefinedValue { details, name }
843}
844
845impl Node<AscribedExpression> {
846    #[async_recursion]
847    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
848        let metadata = Metadata {
849            source_range: SourceRange::from(self),
850        };
851        let result = ctx
852            .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
853            .await?;
854        apply_ascription(&result, &self.ty, exec_state, self.into())
855    }
856}
857
858impl Node<SketchBlock> {
859    pub async fn get_result(&self, _exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
860        let metadata = Metadata {
861            source_range: SourceRange::from(self),
862        };
863        // TODO: sketch-api: Implement sketch block execution
864        Ok(KclValue::Bool {
865            value: false,
866            meta: vec![metadata],
867        })
868    }
869}
870
871impl Node<SketchVar> {
872    pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
873        // TODO: sketch-api: Implement sketch variable execution
874        if let Some(initial) = &self.initial {
875            Ok(KclValue::from_numeric_literal(initial, exec_state))
876        } else {
877            let metadata = Metadata {
878                source_range: SourceRange::from(self),
879            };
880
881            Ok(KclValue::Number {
882                value: 0.0,
883                ty: NumericType::default(),
884                meta: vec![metadata],
885            })
886        }
887    }
888}
889
890fn apply_ascription(
891    value: &KclValue,
892    ty: &Node<Type>,
893    exec_state: &mut ExecState,
894    source_range: SourceRange,
895) -> Result<KclValue, KclError> {
896    let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false)
897        .map_err(|e| KclError::new_semantic(e.into()))?;
898
899    if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
900        exec_state.clear_units_warnings(&source_range);
901    }
902
903    value.coerce(&ty, false, exec_state).map_err(|_| {
904        let suggestion = if ty == RuntimeType::length() {
905            ", you might try coercing to a fully specified numeric type such as `mm`"
906        } else if ty == RuntimeType::angle() {
907            ", you might try coercing to a fully specified numeric type such as `deg`"
908        } else {
909            ""
910        };
911        let ty_str = if let Some(ty) = value.principal_type() {
912            format!("(with type `{ty}`) ")
913        } else {
914            String::new()
915        };
916        KclError::new_semantic(KclErrorDetails::new(
917            format!(
918                "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
919                value.human_friendly_type()
920            ),
921            vec![source_range],
922        ))
923    })
924}
925
926impl BinaryPart {
927    #[async_recursion]
928    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
929        match self {
930            BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state)),
931            BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned(),
932            BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
933            BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
934            BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
935            BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
936            BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
937            BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
938            BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
939            BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
940            BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
941            BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await,
942        }
943    }
944}
945
946impl Node<Name> {
947    pub(super) async fn get_result<'a>(
948        &self,
949        exec_state: &'a mut ExecState,
950        ctx: &ExecutorContext,
951    ) -> Result<&'a KclValue, KclError> {
952        let being_declared = exec_state.mod_local.being_declared.clone();
953        self.get_result_inner(exec_state, ctx)
954            .await
955            .map_err(|e| var_in_own_ref_err(e, &being_declared))
956    }
957
958    async fn get_result_inner<'a>(
959        &self,
960        exec_state: &'a mut ExecState,
961        ctx: &ExecutorContext,
962    ) -> Result<&'a KclValue, KclError> {
963        if self.abs_path {
964            return Err(KclError::new_semantic(KclErrorDetails::new(
965                "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
966                self.as_source_ranges(),
967            )));
968        }
969
970        let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
971
972        if self.path.is_empty() {
973            let item_value = exec_state.stack().get(&self.name.name, self.into());
974            if item_value.is_ok() {
975                return item_value;
976            }
977            return exec_state.stack().get(&mod_name, self.into());
978        }
979
980        let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
981        for p in &self.path {
982            let value = match mem_spec {
983                Some((env, exports)) => {
984                    if !exports.contains(&p.name) {
985                        return Err(KclError::new_semantic(KclErrorDetails::new(
986                            format!("Item {} not found in module's exported items", p.name),
987                            p.as_source_ranges(),
988                        )));
989                    }
990
991                    exec_state
992                        .stack()
993                        .memory
994                        .get_from(&p.name, env, p.as_source_range(), 0)?
995                }
996                None => exec_state
997                    .stack()
998                    .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
999            };
1000
1001            let KclValue::Module { value: module_id, .. } = value else {
1002                return Err(KclError::new_semantic(KclErrorDetails::new(
1003                    format!(
1004                        "Identifier in path must refer to a module, found {}",
1005                        value.human_friendly_type()
1006                    ),
1007                    p.as_source_ranges(),
1008                )));
1009            };
1010
1011            mem_spec = Some(
1012                ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1013                    .await?,
1014            );
1015        }
1016
1017        let (env, exports) = mem_spec.unwrap();
1018
1019        let item_exported = exports.contains(&self.name.name);
1020        let item_value = exec_state
1021            .stack()
1022            .memory
1023            .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1024
1025        // Item is defined and exported.
1026        if item_exported && item_value.is_ok() {
1027            return item_value;
1028        }
1029
1030        let mod_exported = exports.contains(&mod_name);
1031        let mod_value = exec_state
1032            .stack()
1033            .memory
1034            .get_from(&mod_name, env, self.name.as_source_range(), 0);
1035
1036        // Module is defined and exported.
1037        if mod_exported && mod_value.is_ok() {
1038            return mod_value;
1039        }
1040
1041        // Neither item or module is defined.
1042        if item_value.is_err() && mod_value.is_err() {
1043            return item_value;
1044        }
1045
1046        // Either item or module is defined, but not exported.
1047        debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1048        Err(KclError::new_semantic(KclErrorDetails::new(
1049            format!("Item {} not found in module's exported items", self.name.name),
1050            self.name.as_source_ranges(),
1051        )))
1052    }
1053}
1054
1055impl Node<MemberExpression> {
1056    async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1057        let meta = Metadata {
1058            source_range: SourceRange::from(self),
1059        };
1060        let property = Property::try_from(
1061            self.computed,
1062            self.property.clone(),
1063            exec_state,
1064            self.into(),
1065            ctx,
1066            &meta,
1067            &[],
1068            StatementKind::Expression,
1069        )
1070        .await?;
1071        let object = ctx
1072            .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1073            .await?;
1074
1075        // Check the property and object match -- e.g. ints for arrays, strs for objects.
1076        match (object, property, self.computed) {
1077            (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
1078                "zAxis" => {
1079                    let (p, u) = plane.info.z_axis.as_3_dims();
1080                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1081                }
1082                "yAxis" => {
1083                    let (p, u) = plane.info.y_axis.as_3_dims();
1084                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1085                }
1086                "xAxis" => {
1087                    let (p, u) = plane.info.x_axis.as_3_dims();
1088                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1089                }
1090                "origin" => {
1091                    let (p, u) = plane.info.origin.as_3_dims();
1092                    Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1093                }
1094                other => Err(KclError::new_undefined_value(
1095                    KclErrorDetails::new(
1096                        format!("Property '{other}' not found in plane"),
1097                        vec![self.clone().into()],
1098                    ),
1099                    None,
1100                )),
1101            },
1102            (KclValue::Object { value: map, .. }, Property::String(property), false) => {
1103                if let Some(value) = map.get(&property) {
1104                    Ok(value.to_owned())
1105                } else {
1106                    Err(KclError::new_undefined_value(
1107                        KclErrorDetails::new(
1108                            format!("Property '{property}' not found in object"),
1109                            vec![self.clone().into()],
1110                        ),
1111                        None,
1112                    ))
1113                }
1114            }
1115            (KclValue::Object { .. }, Property::String(property), true) => {
1116                Err(KclError::new_semantic(KclErrorDetails::new(
1117                    format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
1118                    vec![self.clone().into()],
1119                )))
1120            }
1121            (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
1122                if i == 0
1123                    && let Some(value) = map.get("x")
1124                {
1125                    return Ok(value.to_owned());
1126                }
1127                if i == 1
1128                    && let Some(value) = map.get("y")
1129                {
1130                    return Ok(value.to_owned());
1131                }
1132                if i == 2
1133                    && let Some(value) = map.get("z")
1134                {
1135                    return Ok(value.to_owned());
1136                }
1137                let t = p.type_name();
1138                let article = article_for(t);
1139                Err(KclError::new_semantic(KclErrorDetails::new(
1140                    format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
1141                    vec![self.clone().into()],
1142                )))
1143            }
1144            (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
1145                let value_of_arr = arr.get(index);
1146                if let Some(value) = value_of_arr {
1147                    Ok(value.to_owned())
1148                } else {
1149                    Err(KclError::new_undefined_value(
1150                        KclErrorDetails::new(
1151                            format!("The array doesn't have any item at index {index}"),
1152                            vec![self.clone().into()],
1153                        ),
1154                        None,
1155                    ))
1156                }
1157            }
1158            // Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
1159            // This is kind of a silly property, but it's possible it occurs in generic code or something.
1160            (obj, Property::UInt(0), _) => Ok(obj),
1161            (KclValue::HomArray { .. }, p, _) => {
1162                let t = p.type_name();
1163                let article = article_for(t);
1164                Err(KclError::new_semantic(KclErrorDetails::new(
1165                    format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
1166                    vec![self.clone().into()],
1167                )))
1168            }
1169            (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
1170                value: Box::new(value.sketch),
1171            }),
1172            (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
1173                // This is a common mistake.
1174                Err(KclError::new_semantic(KclErrorDetails::new(
1175                    format!(
1176                        "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
1177                        geometry.human_friendly_type()
1178                    ),
1179                    vec![self.clone().into()],
1180                )))
1181            }
1182            (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
1183                meta: vec![Metadata {
1184                    source_range: SourceRange::from(self.clone()),
1185                }],
1186                value: sk
1187                    .tags
1188                    .iter()
1189                    .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
1190                    .collect(),
1191                constrainable: false,
1192            }),
1193            (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
1194                Err(KclError::new_semantic(KclErrorDetails::new(
1195                    format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
1196                    vec![self.clone().into()],
1197                )))
1198            }
1199            (being_indexed, _, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1200                format!(
1201                    "Only arrays can be indexed, but you're trying to index {}",
1202                    being_indexed.human_friendly_type()
1203                ),
1204                vec![self.clone().into()],
1205            ))),
1206        }
1207    }
1208}
1209
1210impl Node<BinaryExpression> {
1211    #[async_recursion]
1212    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1213        let left_value = self.left.get_result(exec_state, ctx).await?;
1214        let right_value = self.right.get_result(exec_state, ctx).await?;
1215        let mut meta = left_value.metadata();
1216        meta.extend(right_value.metadata());
1217
1218        // First check if we are doing string concatenation.
1219        if self.operator == BinaryOperator::Add
1220            && let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
1221                (&left_value, &right_value)
1222        {
1223            return Ok(KclValue::String {
1224                value: format!("{left}{right}"),
1225                meta,
1226            });
1227        }
1228
1229        // Then check if we have solids.
1230        if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
1231            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1232                let args = Args::new_no_args(self.into(), ctx.clone());
1233                let result = crate::std::csg::inner_union(
1234                    vec![*left.clone(), *right.clone()],
1235                    Default::default(),
1236                    exec_state,
1237                    args,
1238                )
1239                .await?;
1240                return Ok(result.into());
1241            }
1242        } else if self.operator == BinaryOperator::Sub {
1243            // Check if we have solids.
1244            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1245                let args = Args::new_no_args(self.into(), ctx.clone());
1246                let result = crate::std::csg::inner_subtract(
1247                    vec![*left.clone()],
1248                    vec![*right.clone()],
1249                    Default::default(),
1250                    exec_state,
1251                    args,
1252                )
1253                .await?;
1254                return Ok(result.into());
1255            }
1256        } else if self.operator == BinaryOperator::And {
1257            // Check if we have solids.
1258            if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1259                let args = Args::new_no_args(self.into(), ctx.clone());
1260                let result = crate::std::csg::inner_intersect(
1261                    vec![*left.clone(), *right.clone()],
1262                    Default::default(),
1263                    exec_state,
1264                    args,
1265                )
1266                .await?;
1267                return Ok(result.into());
1268            }
1269        }
1270
1271        // Check if we are doing logical operations on booleans.
1272        if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
1273            let KclValue::Bool {
1274                value: left_value,
1275                meta: _,
1276            } = left_value
1277            else {
1278                return Err(KclError::new_semantic(KclErrorDetails::new(
1279                    format!(
1280                        "Cannot apply logical operator to non-boolean value: {}",
1281                        left_value.human_friendly_type()
1282                    ),
1283                    vec![self.left.clone().into()],
1284                )));
1285            };
1286            let KclValue::Bool {
1287                value: right_value,
1288                meta: _,
1289            } = right_value
1290            else {
1291                return Err(KclError::new_semantic(KclErrorDetails::new(
1292                    format!(
1293                        "Cannot apply logical operator to non-boolean value: {}",
1294                        right_value.human_friendly_type()
1295                    ),
1296                    vec![self.right.clone().into()],
1297                )));
1298            };
1299            let raw_value = match self.operator {
1300                BinaryOperator::Or => left_value || right_value,
1301                BinaryOperator::And => left_value && right_value,
1302                _ => unreachable!(),
1303            };
1304            return Ok(KclValue::Bool { value: raw_value, meta });
1305        }
1306
1307        let left = number_as_f64(&left_value, self.left.clone().into())?;
1308        let right = number_as_f64(&right_value, self.right.clone().into())?;
1309
1310        let value = match self.operator {
1311            BinaryOperator::Add => {
1312                let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1313                self.warn_on_unknown(&ty, "Adding", exec_state);
1314                KclValue::Number { value: l + r, meta, ty }
1315            }
1316            BinaryOperator::Sub => {
1317                let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1318                self.warn_on_unknown(&ty, "Subtracting", exec_state);
1319                KclValue::Number { value: l - r, meta, ty }
1320            }
1321            BinaryOperator::Mul => {
1322                let (l, r, ty) = NumericType::combine_mul(left, right);
1323                self.warn_on_unknown(&ty, "Multiplying", exec_state);
1324                KclValue::Number { value: l * r, meta, ty }
1325            }
1326            BinaryOperator::Div => {
1327                let (l, r, ty) = NumericType::combine_div(left, right);
1328                self.warn_on_unknown(&ty, "Dividing", exec_state);
1329                KclValue::Number { value: l / r, meta, ty }
1330            }
1331            BinaryOperator::Mod => {
1332                let (l, r, ty) = NumericType::combine_mod(left, right);
1333                self.warn_on_unknown(&ty, "Modulo of", exec_state);
1334                KclValue::Number { value: l % r, meta, ty }
1335            }
1336            BinaryOperator::Pow => KclValue::Number {
1337                value: left.n.powf(right.n),
1338                meta,
1339                ty: exec_state.current_default_units(),
1340            },
1341            BinaryOperator::Neq => {
1342                let (l, r, ty) = NumericType::combine_eq(left, right);
1343                self.warn_on_unknown(&ty, "Comparing", exec_state);
1344                KclValue::Bool { value: l != r, meta }
1345            }
1346            BinaryOperator::Gt => {
1347                let (l, r, ty) = NumericType::combine_eq(left, right);
1348                self.warn_on_unknown(&ty, "Comparing", exec_state);
1349                KclValue::Bool { value: l > r, meta }
1350            }
1351            BinaryOperator::Gte => {
1352                let (l, r, ty) = NumericType::combine_eq(left, right);
1353                self.warn_on_unknown(&ty, "Comparing", exec_state);
1354                KclValue::Bool { value: l >= r, meta }
1355            }
1356            BinaryOperator::Lt => {
1357                let (l, r, ty) = NumericType::combine_eq(left, right);
1358                self.warn_on_unknown(&ty, "Comparing", exec_state);
1359                KclValue::Bool { value: l < r, meta }
1360            }
1361            BinaryOperator::Lte => {
1362                let (l, r, ty) = NumericType::combine_eq(left, right);
1363                self.warn_on_unknown(&ty, "Comparing", exec_state);
1364                KclValue::Bool { value: l <= r, meta }
1365            }
1366            BinaryOperator::Eq => {
1367                let (l, r, ty) = NumericType::combine_eq(left, right);
1368                self.warn_on_unknown(&ty, "Comparing", exec_state);
1369                KclValue::Bool { value: l == r, meta }
1370            }
1371            BinaryOperator::And | BinaryOperator::Or => unreachable!(),
1372        };
1373
1374        Ok(value)
1375    }
1376
1377    fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
1378        if ty == &NumericType::Unknown {
1379            let sr = self.as_source_range();
1380            exec_state.clear_units_warnings(&sr);
1381            let mut err = CompilationError::err(
1382                sr,
1383                format!(
1384                    "{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)`."
1385                ),
1386            );
1387            err.tag = crate::errors::Tag::UnknownNumericUnits;
1388            exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
1389        }
1390    }
1391}
1392
1393impl Node<UnaryExpression> {
1394    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1395        if self.operator == UnaryOperator::Not {
1396            let value = self.argument.get_result(exec_state, ctx).await?;
1397            let KclValue::Bool {
1398                value: bool_value,
1399                meta: _,
1400            } = value
1401            else {
1402                return Err(KclError::new_semantic(KclErrorDetails::new(
1403                    format!(
1404                        "Cannot apply unary operator ! to non-boolean value: {}",
1405                        value.human_friendly_type()
1406                    ),
1407                    vec![self.into()],
1408                )));
1409            };
1410            let meta = vec![Metadata {
1411                source_range: self.into(),
1412            }];
1413            let negated = KclValue::Bool {
1414                value: !bool_value,
1415                meta,
1416            };
1417
1418            return Ok(negated);
1419        }
1420
1421        let value = &self.argument.get_result(exec_state, ctx).await?;
1422        let err = || {
1423            KclError::new_semantic(KclErrorDetails::new(
1424                format!(
1425                    "You can only negate numbers, planes, or lines, but this is a {}",
1426                    value.human_friendly_type()
1427                ),
1428                vec![self.into()],
1429            ))
1430        };
1431        match value {
1432            KclValue::Number { value, ty, .. } => {
1433                let meta = vec![Metadata {
1434                    source_range: self.into(),
1435                }];
1436                Ok(KclValue::Number {
1437                    value: -value,
1438                    meta,
1439                    ty: *ty,
1440                })
1441            }
1442            KclValue::Plane { value } => {
1443                let mut plane = value.clone();
1444                if plane.info.x_axis.x != 0.0 {
1445                    plane.info.x_axis.x *= -1.0;
1446                }
1447                if plane.info.x_axis.y != 0.0 {
1448                    plane.info.x_axis.y *= -1.0;
1449                }
1450                if plane.info.x_axis.z != 0.0 {
1451                    plane.info.x_axis.z *= -1.0;
1452                }
1453
1454                plane.value = PlaneType::Uninit;
1455                plane.id = exec_state.next_uuid();
1456                Ok(KclValue::Plane { value: plane })
1457            }
1458            KclValue::Object {
1459                value: values, meta, ..
1460            } => {
1461                // Special-case for negating line-like objects.
1462                let Some(direction) = values.get("direction") else {
1463                    return Err(err());
1464                };
1465
1466                let direction = match direction {
1467                    KclValue::Tuple { value: values, meta } => {
1468                        let values = values
1469                            .iter()
1470                            .map(|v| match v {
1471                                KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1472                                    value: *value * -1.0,
1473                                    ty: *ty,
1474                                    meta: meta.clone(),
1475                                }),
1476                                _ => Err(err()),
1477                            })
1478                            .collect::<Result<Vec<_>, _>>()?;
1479
1480                        KclValue::Tuple {
1481                            value: values,
1482                            meta: meta.clone(),
1483                        }
1484                    }
1485                    KclValue::HomArray {
1486                        value: values,
1487                        ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
1488                    } => {
1489                        let values = values
1490                            .iter()
1491                            .map(|v| match v {
1492                                KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1493                                    value: *value * -1.0,
1494                                    ty: *ty,
1495                                    meta: meta.clone(),
1496                                }),
1497                                _ => Err(err()),
1498                            })
1499                            .collect::<Result<Vec<_>, _>>()?;
1500
1501                        KclValue::HomArray {
1502                            value: values,
1503                            ty: ty.clone(),
1504                        }
1505                    }
1506                    _ => return Err(err()),
1507                };
1508
1509                let mut value = values.clone();
1510                value.insert("direction".to_owned(), direction);
1511                Ok(KclValue::Object {
1512                    value,
1513                    meta: meta.clone(),
1514                    constrainable: false,
1515                })
1516            }
1517            _ => Err(err()),
1518        }
1519    }
1520}
1521
1522pub(crate) async fn execute_pipe_body(
1523    exec_state: &mut ExecState,
1524    body: &[Expr],
1525    source_range: SourceRange,
1526    ctx: &ExecutorContext,
1527) -> Result<KclValue, KclError> {
1528    let Some((first, body)) = body.split_first() else {
1529        return Err(KclError::new_semantic(KclErrorDetails::new(
1530            "Pipe expressions cannot be empty".to_owned(),
1531            vec![source_range],
1532        )));
1533    };
1534    // Evaluate the first element in the pipeline.
1535    // They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
1536    // they use the % from the parent. After all, this pipe expression hasn't been executed yet, so it doesn't have any % value
1537    // of its own.
1538    let meta = Metadata {
1539        source_range: SourceRange::from(first),
1540    };
1541    let output = ctx
1542        .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
1543        .await?;
1544
1545    // Now that we've evaluated the first child expression in the pipeline, following child expressions
1546    // should use the previous child expression for %.
1547    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
1548    let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
1549    // Evaluate remaining elements.
1550    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
1551    // Restore the previous pipe value.
1552    exec_state.mod_local.pipe_value = previous_pipe_value;
1553
1554    result
1555}
1556
1557/// Execute the tail of a pipe expression.  exec_state.pipe_value must be set by
1558/// the caller.
1559#[async_recursion]
1560async fn inner_execute_pipe_body(
1561    exec_state: &mut ExecState,
1562    body: &[Expr],
1563    ctx: &ExecutorContext,
1564) -> Result<KclValue, KclError> {
1565    for expression in body {
1566        if let Expr::TagDeclarator(_) = expression {
1567            return Err(KclError::new_semantic(KclErrorDetails::new(
1568                format!("This cannot be in a PipeExpression: {expression:?}"),
1569                vec![expression.into()],
1570            )));
1571        }
1572        let metadata = Metadata {
1573            source_range: SourceRange::from(expression),
1574        };
1575        let output = ctx
1576            .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
1577            .await?;
1578        exec_state.mod_local.pipe_value = Some(output);
1579    }
1580    // Safe to unwrap here, because pipe_value always has something pushed in when the `match first` executes.
1581    let final_output = exec_state.mod_local.pipe_value.take().unwrap();
1582    Ok(final_output)
1583}
1584
1585impl Node<TagDeclarator> {
1586    pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
1587        let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
1588            value: self.name.clone(),
1589            info: Vec::new(),
1590            meta: vec![Metadata {
1591                source_range: self.into(),
1592            }],
1593        }));
1594
1595        exec_state
1596            .mut_stack()
1597            .add(self.name.clone(), memory_item.clone(), self.into())?;
1598
1599        Ok(self.into())
1600    }
1601}
1602
1603impl Node<ArrayExpression> {
1604    #[async_recursion]
1605    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1606        let mut results = Vec::with_capacity(self.elements.len());
1607
1608        for element in &self.elements {
1609            let metadata = Metadata::from(element);
1610            // TODO: Carry statement kind here so that we know if we're
1611            // inside a variable declaration.
1612            let value = ctx
1613                .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
1614                .await?;
1615
1616            results.push(value);
1617        }
1618
1619        Ok(KclValue::HomArray {
1620            value: results,
1621            ty: RuntimeType::Primitive(PrimitiveType::Any),
1622        })
1623    }
1624}
1625
1626impl Node<ArrayRangeExpression> {
1627    #[async_recursion]
1628    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1629        let metadata = Metadata::from(&self.start_element);
1630        let start_val = ctx
1631            .execute_expr(
1632                &self.start_element,
1633                exec_state,
1634                &metadata,
1635                &[],
1636                StatementKind::Expression,
1637            )
1638            .await?;
1639        let (start, start_ty) = start_val
1640            .as_int_with_ty()
1641            .ok_or(KclError::new_semantic(KclErrorDetails::new(
1642                format!("Expected int but found {}", start_val.human_friendly_type()),
1643                vec![self.into()],
1644            )))?;
1645        let metadata = Metadata::from(&self.end_element);
1646        let end_val = ctx
1647            .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
1648            .await?;
1649        let (end, end_ty) = end_val
1650            .as_int_with_ty()
1651            .ok_or(KclError::new_semantic(KclErrorDetails::new(
1652                format!("Expected int but found {}", end_val.human_friendly_type()),
1653                vec![self.into()],
1654            )))?;
1655
1656        if start_ty != end_ty {
1657            let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
1658            let start = fmt::human_display_number(start.n, start.ty);
1659            let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
1660            let end = fmt::human_display_number(end.n, end.ty);
1661            return Err(KclError::new_semantic(KclErrorDetails::new(
1662                format!("Range start and end must be of the same type, but found {start} and {end}"),
1663                vec![self.into()],
1664            )));
1665        }
1666
1667        if end < start {
1668            return Err(KclError::new_semantic(KclErrorDetails::new(
1669                format!("Range start is greater than range end: {start} .. {end}"),
1670                vec![self.into()],
1671            )));
1672        }
1673
1674        let range: Vec<_> = if self.end_inclusive {
1675            (start..=end).collect()
1676        } else {
1677            (start..end).collect()
1678        };
1679
1680        let meta = vec![Metadata {
1681            source_range: self.into(),
1682        }];
1683
1684        Ok(KclValue::HomArray {
1685            value: range
1686                .into_iter()
1687                .map(|num| KclValue::Number {
1688                    value: num as f64,
1689                    ty: start_ty,
1690                    meta: meta.clone(),
1691                })
1692                .collect(),
1693            ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)),
1694        })
1695    }
1696}
1697
1698impl Node<ObjectExpression> {
1699    #[async_recursion]
1700    pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1701        let mut object = HashMap::with_capacity(self.properties.len());
1702        for property in &self.properties {
1703            let metadata = Metadata::from(&property.value);
1704            let result = ctx
1705                .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
1706                .await?;
1707
1708            object.insert(property.key.name.clone(), result);
1709        }
1710
1711        Ok(KclValue::Object {
1712            value: object,
1713            meta: vec![Metadata {
1714                source_range: self.into(),
1715            }],
1716            constrainable: false,
1717        })
1718    }
1719}
1720
1721fn article_for<S: AsRef<str>>(s: S) -> &'static str {
1722    // '[' is included since it's an array.
1723    if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
1724        "an"
1725    } else {
1726        "a"
1727    }
1728}
1729
1730fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
1731    v.as_ty_f64().ok_or_else(|| {
1732        let actual_type = v.human_friendly_type();
1733        KclError::new_semantic(KclErrorDetails::new(
1734            format!("Expected a number, but found {actual_type}",),
1735            vec![source_range],
1736        ))
1737    })
1738}
1739
1740impl Node<IfExpression> {
1741    #[async_recursion]
1742    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1743        // Check the `if` branch.
1744        let cond = ctx
1745            .execute_expr(
1746                &self.cond,
1747                exec_state,
1748                &Metadata::from(self),
1749                &[],
1750                StatementKind::Expression,
1751            )
1752            .await?
1753            .get_bool()?;
1754        if cond {
1755            let block_result = ctx.exec_block(&self.then_val, exec_state, BodyType::Block).await?;
1756            // Block must end in an expression, so this has to be Some.
1757            // Enforced by the parser.
1758            // See https://github.com/KittyCAD/modeling-app/issues/4015
1759            return Ok(block_result.unwrap());
1760        }
1761
1762        // Check any `else if` branches.
1763        for else_if in &self.else_ifs {
1764            let cond = ctx
1765                .execute_expr(
1766                    &else_if.cond,
1767                    exec_state,
1768                    &Metadata::from(self),
1769                    &[],
1770                    StatementKind::Expression,
1771                )
1772                .await?
1773                .get_bool()?;
1774            if cond {
1775                let block_result = ctx.exec_block(&else_if.then_val, exec_state, BodyType::Block).await?;
1776                // Block must end in an expression, so this has to be Some.
1777                // Enforced by the parser.
1778                // See https://github.com/KittyCAD/modeling-app/issues/4015
1779                return Ok(block_result.unwrap());
1780            }
1781        }
1782
1783        // Run the final `else` branch.
1784        ctx.exec_block(&self.final_else, exec_state, BodyType::Block)
1785            .await
1786            .map(|expr| expr.unwrap())
1787    }
1788}
1789
1790#[derive(Debug)]
1791enum Property {
1792    UInt(usize),
1793    String(String),
1794}
1795
1796impl Property {
1797    #[allow(clippy::too_many_arguments)]
1798    async fn try_from<'a>(
1799        computed: bool,
1800        value: Expr,
1801        exec_state: &mut ExecState,
1802        sr: SourceRange,
1803        ctx: &ExecutorContext,
1804        metadata: &Metadata,
1805        annotations: &[Node<Annotation>],
1806        statement_kind: StatementKind<'a>,
1807    ) -> Result<Self, KclError> {
1808        let property_sr = vec![sr];
1809        if !computed {
1810            let Expr::Name(identifier) = value else {
1811                // Should actually be impossible because the parser would reject it.
1812                return Err(KclError::new_semantic(KclErrorDetails::new(
1813                    "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
1814                        .to_owned(),
1815                    property_sr,
1816                )));
1817            };
1818            return Ok(Property::String(identifier.to_string()));
1819        }
1820
1821        let prop_value = ctx
1822            .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
1823            .await?;
1824        match prop_value {
1825            KclValue::Number { value, ty, meta: _ } => {
1826                if !matches!(
1827                    ty,
1828                    NumericType::Unknown
1829                        | NumericType::Default { .. }
1830                        | NumericType::Known(crate::exec::UnitType::Count)
1831                ) {
1832                    return Err(KclError::new_semantic(KclErrorDetails::new(
1833                        format!(
1834                            "{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"
1835                        ),
1836                        property_sr,
1837                    )));
1838                }
1839                if let Some(x) = crate::try_f64_to_usize(value) {
1840                    Ok(Property::UInt(x))
1841                } else {
1842                    Err(KclError::new_semantic(KclErrorDetails::new(
1843                        format!("{value} is not a valid index, indices must be whole numbers >= 0"),
1844                        property_sr,
1845                    )))
1846                }
1847            }
1848            _ => Err(KclError::new_semantic(KclErrorDetails::new(
1849                "Only numbers (>= 0) can be indexes".to_owned(),
1850                vec![sr],
1851            ))),
1852        }
1853    }
1854}
1855
1856impl Property {
1857    fn type_name(&self) -> &'static str {
1858        match self {
1859            Property::UInt(_) => "number",
1860            Property::String(_) => "string",
1861        }
1862    }
1863}
1864
1865impl Node<PipeExpression> {
1866    #[async_recursion]
1867    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1868        execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
1869    }
1870}
1871
1872#[cfg(test)]
1873mod test {
1874    use std::sync::Arc;
1875
1876    use tokio::io::AsyncWriteExt;
1877
1878    use super::*;
1879    use crate::{
1880        ExecutorSettings,
1881        errors::Severity,
1882        exec::UnitType,
1883        execution::{ContextType, parse_execute},
1884    };
1885
1886    #[tokio::test(flavor = "multi_thread")]
1887    async fn ascription() {
1888        let program = r#"
1889a = 42: number
1890b = a: number
1891p = {
1892  origin = { x = 0, y = 0, z = 0 },
1893  xAxis = { x = 1, y = 0, z = 0 },
1894  yAxis = { x = 0, y = 1, z = 0 },
1895  zAxis = { x = 0, y = 0, z = 1 }
1896}: Plane
1897arr1 = [42]: [number(cm)]
1898"#;
1899
1900        let result = parse_execute(program).await.unwrap();
1901        let mem = result.exec_state.stack();
1902        assert!(matches!(
1903            mem.memory
1904                .get_from("p", result.mem_env, SourceRange::default(), 0)
1905                .unwrap(),
1906            KclValue::Plane { .. }
1907        ));
1908        let arr1 = mem
1909            .memory
1910            .get_from("arr1", result.mem_env, SourceRange::default(), 0)
1911            .unwrap();
1912        if let KclValue::HomArray { value, ty } = arr1 {
1913            assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
1914            assert_eq!(
1915                *ty,
1916                RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
1917            );
1918            // Compare, ignoring meta.
1919            if let KclValue::Number { value, ty, .. } = &value[0] {
1920                // It should not convert units.
1921                assert_eq!(*value, 42.0);
1922                assert_eq!(
1923                    *ty,
1924                    NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
1925                );
1926            } else {
1927                panic!("Expected a number; found {:?}", value[0]);
1928            }
1929        } else {
1930            panic!("Expected HomArray; found {arr1:?}");
1931        }
1932
1933        let program = r#"
1934a = 42: string
1935"#;
1936        let result = parse_execute(program).await;
1937        let err = result.unwrap_err();
1938        assert!(
1939            err.to_string()
1940                .contains("could not coerce a number (with type `number`) to type `string`"),
1941            "Expected error but found {err:?}"
1942        );
1943
1944        let program = r#"
1945a = 42: Plane
1946"#;
1947        let result = parse_execute(program).await;
1948        let err = result.unwrap_err();
1949        assert!(
1950            err.to_string()
1951                .contains("could not coerce a number (with type `number`) to type `Plane`"),
1952            "Expected error but found {err:?}"
1953        );
1954
1955        let program = r#"
1956arr = [0]: [string]
1957"#;
1958        let result = parse_execute(program).await;
1959        let err = result.unwrap_err();
1960        assert!(
1961            err.to_string().contains(
1962                "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
1963            ),
1964            "Expected error but found {err:?}"
1965        );
1966
1967        let program = r#"
1968mixedArr = [0, "a"]: [number(mm)]
1969"#;
1970        let result = parse_execute(program).await;
1971        let err = result.unwrap_err();
1972        assert!(
1973            err.to_string().contains(
1974                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
1975            ),
1976            "Expected error but found {err:?}"
1977        );
1978
1979        let program = r#"
1980mixedArr = [0, "a"]: [mm]
1981"#;
1982        let result = parse_execute(program).await;
1983        let err = result.unwrap_err();
1984        assert!(
1985            err.to_string().contains(
1986                "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
1987            ),
1988            "Expected error but found {err:?}"
1989        );
1990    }
1991
1992    #[tokio::test(flavor = "multi_thread")]
1993    async fn neg_plane() {
1994        let program = r#"
1995p = {
1996  origin = { x = 0, y = 0, z = 0 },
1997  xAxis = { x = 1, y = 0, z = 0 },
1998  yAxis = { x = 0, y = 1, z = 0 },
1999}: Plane
2000p2 = -p
2001"#;
2002
2003        let result = parse_execute(program).await.unwrap();
2004        let mem = result.exec_state.stack();
2005        match mem
2006            .memory
2007            .get_from("p2", result.mem_env, SourceRange::default(), 0)
2008            .unwrap()
2009        {
2010            KclValue::Plane { value } => {
2011                assert_eq!(value.info.x_axis.x, -1.0);
2012                assert_eq!(value.info.x_axis.y, 0.0);
2013                assert_eq!(value.info.x_axis.z, 0.0);
2014            }
2015            _ => unreachable!(),
2016        }
2017    }
2018
2019    #[tokio::test(flavor = "multi_thread")]
2020    async fn multiple_returns() {
2021        let program = r#"fn foo() {
2022  return 0
2023  return 42
2024}
2025
2026a = foo()
2027"#;
2028
2029        let result = parse_execute(program).await;
2030        assert!(result.unwrap_err().to_string().contains("return"));
2031    }
2032
2033    #[tokio::test(flavor = "multi_thread")]
2034    async fn load_all_modules() {
2035        // program a.kcl
2036        let program_a_kcl = r#"
2037export a = 1
2038"#;
2039        // program b.kcl
2040        let program_b_kcl = r#"
2041import a from 'a.kcl'
2042
2043export b = a + 1
2044"#;
2045        // program c.kcl
2046        let program_c_kcl = r#"
2047import a from 'a.kcl'
2048
2049export c = a + 2
2050"#;
2051
2052        // program main.kcl
2053        let main_kcl = r#"
2054import b from 'b.kcl'
2055import c from 'c.kcl'
2056
2057d = b + c
2058"#;
2059
2060        let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
2061            .parse_errs_as_err()
2062            .unwrap();
2063
2064        let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
2065
2066        tokio::fs::File::create(tmpdir.path().join("main.kcl"))
2067            .await
2068            .unwrap()
2069            .write_all(main_kcl.as_bytes())
2070            .await
2071            .unwrap();
2072
2073        tokio::fs::File::create(tmpdir.path().join("a.kcl"))
2074            .await
2075            .unwrap()
2076            .write_all(program_a_kcl.as_bytes())
2077            .await
2078            .unwrap();
2079
2080        tokio::fs::File::create(tmpdir.path().join("b.kcl"))
2081            .await
2082            .unwrap()
2083            .write_all(program_b_kcl.as_bytes())
2084            .await
2085            .unwrap();
2086
2087        tokio::fs::File::create(tmpdir.path().join("c.kcl"))
2088            .await
2089            .unwrap()
2090            .write_all(program_c_kcl.as_bytes())
2091            .await
2092            .unwrap();
2093
2094        let exec_ctxt = ExecutorContext {
2095            engine: Arc::new(Box::new(
2096                crate::engine::conn_mock::EngineConnection::new()
2097                    .await
2098                    .map_err(|err| {
2099                        KclError::new_internal(KclErrorDetails::new(
2100                            format!("Failed to create mock engine connection: {err}"),
2101                            vec![SourceRange::default()],
2102                        ))
2103                    })
2104                    .unwrap(),
2105            )),
2106            fs: Arc::new(crate::fs::FileManager::new()),
2107            settings: ExecutorSettings {
2108                project_directory: Some(crate::TypedPath(tmpdir.path().into())),
2109                ..Default::default()
2110            },
2111            context_type: ContextType::Mock,
2112        };
2113        let mut exec_state = ExecState::new(&exec_ctxt);
2114
2115        exec_ctxt
2116            .run(
2117                &crate::Program {
2118                    ast: main.clone(),
2119                    original_file_contents: "".to_owned(),
2120                },
2121                &mut exec_state,
2122            )
2123            .await
2124            .unwrap();
2125    }
2126
2127    #[tokio::test(flavor = "multi_thread")]
2128    async fn user_coercion() {
2129        let program = r#"fn foo(x: Axis2d) {
2130  return 0
2131}
2132
2133foo(x = { direction = [0, 0], origin = [0, 0]})
2134"#;
2135
2136        parse_execute(program).await.unwrap();
2137
2138        let program = r#"fn foo(x: Axis3d) {
2139  return 0
2140}
2141
2142foo(x = { direction = [0, 0], origin = [0, 0]})
2143"#;
2144
2145        parse_execute(program).await.unwrap_err();
2146    }
2147
2148    #[tokio::test(flavor = "multi_thread")]
2149    async fn coerce_return() {
2150        let program = r#"fn foo(): number(mm) {
2151  return 42
2152}
2153
2154a = foo()
2155"#;
2156
2157        parse_execute(program).await.unwrap();
2158
2159        let program = r#"fn foo(): mm {
2160  return 42
2161}
2162
2163a = foo()
2164"#;
2165
2166        parse_execute(program).await.unwrap();
2167
2168        let program = r#"fn foo(): number(mm) {
2169  return { bar: 42 }
2170}
2171
2172a = foo()
2173"#;
2174
2175        parse_execute(program).await.unwrap_err();
2176
2177        let program = r#"fn foo(): mm {
2178  return { bar: 42 }
2179}
2180
2181a = foo()
2182"#;
2183
2184        parse_execute(program).await.unwrap_err();
2185    }
2186
2187    #[tokio::test(flavor = "multi_thread")]
2188    async fn test_sensible_error_when_missing_equals_in_kwarg() {
2189        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)"]
2190            .into_iter()
2191            .enumerate()
2192        {
2193            let program = format!(
2194                "fn foo() {{ return 0 }}
2195z = 0
2196fn f(x, y, z) {{ return 0 }}
2197{call}"
2198            );
2199            let err = parse_execute(&program).await.unwrap_err();
2200            let msg = err.message();
2201            assert!(
2202                msg.contains("This argument needs a label, but it doesn't have one"),
2203                "failed test {i}: {msg}"
2204            );
2205            assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
2206            if i == 0 {
2207                assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
2208            }
2209        }
2210    }
2211
2212    #[tokio::test(flavor = "multi_thread")]
2213    async fn default_param_for_unlabeled() {
2214        // Tests that the input param for myExtrude is taken from the pipeline value and same-name
2215        // keyword args.
2216        let ast = r#"fn myExtrude(@sk, length) {
2217  return extrude(sk, length)
2218}
2219sketch001 = startSketchOn(XY)
2220  |> circle(center = [0, 0], radius = 93.75)
2221  |> myExtrude(length = 40)
2222"#;
2223
2224        parse_execute(ast).await.unwrap();
2225    }
2226
2227    #[tokio::test(flavor = "multi_thread")]
2228    async fn dont_use_unlabelled_as_input() {
2229        // `length` should be used as the `length` argument to extrude, not the unlabelled input
2230        let ast = r#"length = 10
2231startSketchOn(XY)
2232  |> circle(center = [0, 0], radius = 93.75)
2233  |> extrude(length)
2234"#;
2235
2236        parse_execute(ast).await.unwrap();
2237    }
2238
2239    #[tokio::test(flavor = "multi_thread")]
2240    async fn ascription_in_binop() {
2241        let ast = r#"foo = tan(0): number(rad) - 4deg"#;
2242        parse_execute(ast).await.unwrap();
2243
2244        let ast = r#"foo = tan(0): rad - 4deg"#;
2245        parse_execute(ast).await.unwrap();
2246    }
2247
2248    #[tokio::test(flavor = "multi_thread")]
2249    async fn neg_sqrt() {
2250        let ast = r#"bad = sqrt(-2)"#;
2251
2252        let e = parse_execute(ast).await.unwrap_err();
2253        // Make sure we get a useful error message and not an engine error.
2254        assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
2255    }
2256
2257    #[tokio::test(flavor = "multi_thread")]
2258    async fn non_array_fns() {
2259        let ast = r#"push(1, item = 2)
2260pop(1)
2261map(1, f = fn(@x) { return x + 1 })
2262reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
2263
2264        parse_execute(ast).await.unwrap();
2265    }
2266
2267    #[tokio::test(flavor = "multi_thread")]
2268    async fn non_array_indexing() {
2269        let good = r#"a = 42
2270good = a[0]
2271"#;
2272        let result = parse_execute(good).await.unwrap();
2273        let mem = result.exec_state.stack();
2274        let num = mem
2275            .memory
2276            .get_from("good", result.mem_env, SourceRange::default(), 0)
2277            .unwrap()
2278            .as_ty_f64()
2279            .unwrap();
2280        assert_eq!(num.n, 42.0);
2281
2282        let bad = r#"a = 42
2283bad = a[1]
2284"#;
2285
2286        parse_execute(bad).await.unwrap_err();
2287    }
2288
2289    #[tokio::test(flavor = "multi_thread")]
2290    async fn coerce_unknown_to_length() {
2291        let ast = r#"x = 2mm * 2mm
2292y = x: number(Length)"#;
2293        let e = parse_execute(ast).await.unwrap_err();
2294        assert!(
2295            e.message().contains("could not coerce"),
2296            "Error message: '{}'",
2297            e.message()
2298        );
2299
2300        let ast = r#"x = 2mm
2301y = x: number(Length)"#;
2302        let result = parse_execute(ast).await.unwrap();
2303        let mem = result.exec_state.stack();
2304        let num = mem
2305            .memory
2306            .get_from("y", result.mem_env, SourceRange::default(), 0)
2307            .unwrap()
2308            .as_ty_f64()
2309            .unwrap();
2310        assert_eq!(num.n, 2.0);
2311        assert_eq!(num.ty, NumericType::mm());
2312    }
2313
2314    #[tokio::test(flavor = "multi_thread")]
2315    async fn one_warning_unknown() {
2316        let ast = r#"
2317// Should warn once
2318a = PI * 2
2319// Should warn once
2320b = (PI * 2) / 3
2321// Should not warn
2322c = ((PI * 2) / 3): number(deg)
2323"#;
2324
2325        let result = parse_execute(ast).await.unwrap();
2326        assert_eq!(result.exec_state.errors().len(), 2);
2327    }
2328
2329    #[tokio::test(flavor = "multi_thread")]
2330    async fn non_count_indexing() {
2331        let ast = r#"x = [0, 0]
2332y = x[1mm]
2333"#;
2334        parse_execute(ast).await.unwrap_err();
2335
2336        let ast = r#"x = [0, 0]
2337y = 1deg
2338z = x[y]
2339"#;
2340        parse_execute(ast).await.unwrap_err();
2341
2342        let ast = r#"x = [0, 0]
2343y = x[0mm + 1]
2344"#;
2345        parse_execute(ast).await.unwrap_err();
2346    }
2347
2348    #[tokio::test(flavor = "multi_thread")]
2349    async fn getting_property_of_plane() {
2350        // let ast = include_str!("../../tests/inputs/planestuff.kcl");
2351        let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
2352
2353        parse_execute(&ast).await.unwrap();
2354    }
2355
2356    #[tokio::test(flavor = "multi_thread")]
2357    async fn custom_warning() {
2358        let warn = r#"
2359a = PI * 2
2360"#;
2361        let result = parse_execute(warn).await.unwrap();
2362        assert_eq!(result.exec_state.errors().len(), 1);
2363        assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
2364
2365        let allow = r#"
2366@warnings(allow = unknownUnits)
2367a = PI * 2
2368"#;
2369        let result = parse_execute(allow).await.unwrap();
2370        assert_eq!(result.exec_state.errors().len(), 0);
2371
2372        let deny = r#"
2373@warnings(deny = [unknownUnits])
2374a = PI * 2
2375"#;
2376        let result = parse_execute(deny).await.unwrap();
2377        assert_eq!(result.exec_state.errors().len(), 1);
2378        assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
2379    }
2380}