kcl_lib/execution/
exec_ast.rs

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