kcl_lib/execution/
exec_ast.rs

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