kcl_lib/execution/
exec_ast.rs

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