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