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