1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4
5use crate::{
6 CompilationError, NodePath,
7 errors::{KclError, KclErrorDetails},
8 execution::{
9 BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, ModelingCmdMeta, ModuleArtifactState,
10 Operation, PlaneType, StatementKind, TagIdentifier, annotations,
11 cad_op::OpKclValue,
12 fn_call::Args,
13 kcl_value::{FunctionSource, TypeDef},
14 memory,
15 state::ModuleState,
16 types::{NumericType, PrimitiveType, RuntimeType},
17 },
18 fmt,
19 modules::{ModuleId, ModulePath, ModuleRepr},
20 parsing::ast::types::{
21 Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
22 BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, MemberExpression, Name,
23 Node, NodeRef, ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
24 },
25 source_range::SourceRange,
26 std::args::TyF64,
27};
28
29impl<'a> StatementKind<'a> {
30 fn expect_name(&self) -> &'a str {
31 match self {
32 StatementKind::Declaration { name } => name,
33 StatementKind::Expression => unreachable!(),
34 }
35 }
36}
37
38impl ExecutorContext {
39 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 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 #[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 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 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 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 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 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 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 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 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 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 exec_state
516 .flush_batch(
517 ModelingCmdMeta::new(self, SourceRange::new(program.end, program.end, program.module_id)),
518 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 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 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 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 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 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 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 result.map_err(|(err, _, _)| {
684 if let KclError::ImportCycle { .. } = err {
685 err.override_source_ranges(vec![source_range])
687 } else {
688 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 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 result
819 }
820 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
821 };
822 Ok(item)
823 }
824}
825
826fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
829 let KclError::UndefinedValue { name, mut details } = e else {
830 return e;
831 };
832 if let (Some(name0), Some(name1)) = (&being_declared, &name)
836 && name0 == name1
837 {
838 details.message = format!(
839 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
840 );
841 }
842 KclError::UndefinedValue { details, name }
843}
844
845impl Node<AscribedExpression> {
846 #[async_recursion]
847 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
848 let metadata = Metadata {
849 source_range: SourceRange::from(self),
850 };
851 let result = ctx
852 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
853 .await?;
854 apply_ascription(&result, &self.ty, exec_state, self.into())
855 }
856}
857
858fn apply_ascription(
859 value: &KclValue,
860 ty: &Node<Type>,
861 exec_state: &mut ExecState,
862 source_range: SourceRange,
863) -> Result<KclValue, KclError> {
864 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into())
865 .map_err(|e| KclError::new_semantic(e.into()))?;
866
867 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
868 exec_state.clear_units_warnings(&source_range);
869 }
870
871 value.coerce(&ty, false, exec_state).map_err(|_| {
872 let suggestion = if ty == RuntimeType::length() {
873 ", you might try coercing to a fully specified numeric type such as `mm`"
874 } else if ty == RuntimeType::angle() {
875 ", you might try coercing to a fully specified numeric type such as `deg`"
876 } else {
877 ""
878 };
879 let ty_str = if let Some(ty) = value.principal_type() {
880 format!("(with type `{ty}`) ")
881 } else {
882 String::new()
883 };
884 KclError::new_semantic(KclErrorDetails::new(
885 format!(
886 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
887 value.human_friendly_type()
888 ),
889 vec![source_range],
890 ))
891 })
892}
893
894impl BinaryPart {
895 #[async_recursion]
896 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
897 match self {
898 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state)),
899 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned(),
900 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
901 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
902 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
903 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
904 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
905 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
906 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
907 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
908 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
909 }
910 }
911}
912
913impl Node<Name> {
914 pub(super) async fn get_result<'a>(
915 &self,
916 exec_state: &'a mut ExecState,
917 ctx: &ExecutorContext,
918 ) -> Result<&'a KclValue, KclError> {
919 let being_declared = exec_state.mod_local.being_declared.clone();
920 self.get_result_inner(exec_state, ctx)
921 .await
922 .map_err(|e| var_in_own_ref_err(e, &being_declared))
923 }
924
925 async fn get_result_inner<'a>(
926 &self,
927 exec_state: &'a mut ExecState,
928 ctx: &ExecutorContext,
929 ) -> Result<&'a KclValue, KclError> {
930 if self.abs_path {
931 return Err(KclError::new_semantic(KclErrorDetails::new(
932 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
933 self.as_source_ranges(),
934 )));
935 }
936
937 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
938
939 if self.path.is_empty() {
940 let item_value = exec_state.stack().get(&self.name.name, self.into());
941 if item_value.is_ok() {
942 return item_value;
943 }
944 return exec_state.stack().get(&mod_name, self.into());
945 }
946
947 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
948 for p in &self.path {
949 let value = match mem_spec {
950 Some((env, exports)) => {
951 if !exports.contains(&p.name) {
952 return Err(KclError::new_semantic(KclErrorDetails::new(
953 format!("Item {} not found in module's exported items", p.name),
954 p.as_source_ranges(),
955 )));
956 }
957
958 exec_state
959 .stack()
960 .memory
961 .get_from(&p.name, env, p.as_source_range(), 0)?
962 }
963 None => exec_state
964 .stack()
965 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
966 };
967
968 let KclValue::Module { value: module_id, .. } = value else {
969 return Err(KclError::new_semantic(KclErrorDetails::new(
970 format!(
971 "Identifier in path must refer to a module, found {}",
972 value.human_friendly_type()
973 ),
974 p.as_source_ranges(),
975 )));
976 };
977
978 mem_spec = Some(
979 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
980 .await?,
981 );
982 }
983
984 let (env, exports) = mem_spec.unwrap();
985
986 let item_exported = exports.contains(&self.name.name);
987 let item_value = exec_state
988 .stack()
989 .memory
990 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
991
992 if item_exported && item_value.is_ok() {
994 return item_value;
995 }
996
997 let mod_exported = exports.contains(&mod_name);
998 let mod_value = exec_state
999 .stack()
1000 .memory
1001 .get_from(&mod_name, env, self.name.as_source_range(), 0);
1002
1003 if mod_exported && mod_value.is_ok() {
1005 return mod_value;
1006 }
1007
1008 if item_value.is_err() && mod_value.is_err() {
1010 return item_value;
1011 }
1012
1013 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1015 Err(KclError::new_semantic(KclErrorDetails::new(
1016 format!("Item {} not found in module's exported items", self.name.name),
1017 self.name.as_source_ranges(),
1018 )))
1019 }
1020}
1021
1022impl Node<MemberExpression> {
1023 async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1024 let meta = Metadata {
1025 source_range: SourceRange::from(self),
1026 };
1027 let property = Property::try_from(
1028 self.computed,
1029 self.property.clone(),
1030 exec_state,
1031 self.into(),
1032 ctx,
1033 &meta,
1034 &[],
1035 StatementKind::Expression,
1036 )
1037 .await?;
1038 let object = ctx
1039 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1040 .await?;
1041
1042 match (object, property, self.computed) {
1044 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
1045 "zAxis" => {
1046 let (p, u) = plane.info.z_axis.as_3_dims();
1047 Ok(KclValue::array_from_point3d(
1048 p,
1049 NumericType::Known(crate::exec::UnitType::Length(u)),
1050 vec![meta],
1051 ))
1052 }
1053 "yAxis" => {
1054 let (p, u) = plane.info.y_axis.as_3_dims();
1055 Ok(KclValue::array_from_point3d(
1056 p,
1057 NumericType::Known(crate::exec::UnitType::Length(u)),
1058 vec![meta],
1059 ))
1060 }
1061 "xAxis" => {
1062 let (p, u) = plane.info.x_axis.as_3_dims();
1063 Ok(KclValue::array_from_point3d(
1064 p,
1065 NumericType::Known(crate::exec::UnitType::Length(u)),
1066 vec![meta],
1067 ))
1068 }
1069 "origin" => {
1070 let (p, u) = plane.info.origin.as_3_dims();
1071 Ok(KclValue::array_from_point3d(
1072 p,
1073 NumericType::Known(crate::exec::UnitType::Length(u)),
1074 vec![meta],
1075 ))
1076 }
1077 other => Err(KclError::new_undefined_value(
1078 KclErrorDetails::new(
1079 format!("Property '{other}' not found in plane"),
1080 vec![self.clone().into()],
1081 ),
1082 None,
1083 )),
1084 },
1085 (KclValue::Object { value: map, meta: _ }, Property::String(property), false) => {
1086 if let Some(value) = map.get(&property) {
1087 Ok(value.to_owned())
1088 } else {
1089 Err(KclError::new_undefined_value(
1090 KclErrorDetails::new(
1091 format!("Property '{property}' not found in object"),
1092 vec![self.clone().into()],
1093 ),
1094 None,
1095 ))
1096 }
1097 }
1098 (KclValue::Object { .. }, Property::String(property), true) => {
1099 Err(KclError::new_semantic(KclErrorDetails::new(
1100 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
1101 vec![self.clone().into()],
1102 )))
1103 }
1104 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
1105 if i == 0
1106 && let Some(value) = map.get("x")
1107 {
1108 return Ok(value.to_owned());
1109 }
1110 if i == 1
1111 && let Some(value) = map.get("y")
1112 {
1113 return Ok(value.to_owned());
1114 }
1115 if i == 2
1116 && let Some(value) = map.get("z")
1117 {
1118 return Ok(value.to_owned());
1119 }
1120 let t = p.type_name();
1121 let article = article_for(t);
1122 Err(KclError::new_semantic(KclErrorDetails::new(
1123 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
1124 vec![self.clone().into()],
1125 )))
1126 }
1127 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
1128 let value_of_arr = arr.get(index);
1129 if let Some(value) = value_of_arr {
1130 Ok(value.to_owned())
1131 } else {
1132 Err(KclError::new_undefined_value(
1133 KclErrorDetails::new(
1134 format!("The array doesn't have any item at index {index}"),
1135 vec![self.clone().into()],
1136 ),
1137 None,
1138 ))
1139 }
1140 }
1141 (obj, Property::UInt(0), _) => Ok(obj),
1144 (KclValue::HomArray { .. }, p, _) => {
1145 let t = p.type_name();
1146 let article = article_for(t);
1147 Err(KclError::new_semantic(KclErrorDetails::new(
1148 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
1149 vec![self.clone().into()],
1150 )))
1151 }
1152 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
1153 value: Box::new(value.sketch),
1154 }),
1155 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
1156 Err(KclError::new_semantic(KclErrorDetails::new(
1158 format!(
1159 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
1160 geometry.human_friendly_type()
1161 ),
1162 vec![self.clone().into()],
1163 )))
1164 }
1165 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
1166 meta: vec![Metadata {
1167 source_range: SourceRange::from(self.clone()),
1168 }],
1169 value: sk
1170 .tags
1171 .iter()
1172 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
1173 .collect(),
1174 }),
1175 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
1176 Err(KclError::new_semantic(KclErrorDetails::new(
1177 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
1178 vec![self.clone().into()],
1179 )))
1180 }
1181 (being_indexed, _, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1182 format!(
1183 "Only arrays can be indexed, but you're trying to index {}",
1184 being_indexed.human_friendly_type()
1185 ),
1186 vec![self.clone().into()],
1187 ))),
1188 }
1189 }
1190}
1191
1192impl Node<BinaryExpression> {
1193 #[async_recursion]
1194 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1195 let left_value = self.left.get_result(exec_state, ctx).await?;
1196 let right_value = self.right.get_result(exec_state, ctx).await?;
1197 let mut meta = left_value.metadata();
1198 meta.extend(right_value.metadata());
1199
1200 if self.operator == BinaryOperator::Add
1202 && let (KclValue::String { value: left, meta: _ }, KclValue::String { value: right, meta: _ }) =
1203 (&left_value, &right_value)
1204 {
1205 return Ok(KclValue::String {
1206 value: format!("{left}{right}"),
1207 meta,
1208 });
1209 }
1210
1211 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
1213 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1214 let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
1215 let result = crate::std::csg::inner_union(
1216 vec![*left.clone(), *right.clone()],
1217 Default::default(),
1218 exec_state,
1219 args,
1220 )
1221 .await?;
1222 return Ok(result.into());
1223 }
1224 } else if self.operator == BinaryOperator::Sub {
1225 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1227 let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
1228 let result = crate::std::csg::inner_subtract(
1229 vec![*left.clone()],
1230 vec![*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::And {
1239 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1241 let args = Args::new(Default::default(), self.into(), ctx.clone(), None);
1242 let result = crate::std::csg::inner_intersect(
1243 vec![*left.clone(), *right.clone()],
1244 Default::default(),
1245 exec_state,
1246 args,
1247 )
1248 .await?;
1249 return Ok(result.into());
1250 }
1251 }
1252
1253 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
1255 let KclValue::Bool {
1256 value: left_value,
1257 meta: _,
1258 } = left_value
1259 else {
1260 return Err(KclError::new_semantic(KclErrorDetails::new(
1261 format!(
1262 "Cannot apply logical operator to non-boolean value: {}",
1263 left_value.human_friendly_type()
1264 ),
1265 vec![self.left.clone().into()],
1266 )));
1267 };
1268 let KclValue::Bool {
1269 value: right_value,
1270 meta: _,
1271 } = right_value
1272 else {
1273 return Err(KclError::new_semantic(KclErrorDetails::new(
1274 format!(
1275 "Cannot apply logical operator to non-boolean value: {}",
1276 right_value.human_friendly_type()
1277 ),
1278 vec![self.right.clone().into()],
1279 )));
1280 };
1281 let raw_value = match self.operator {
1282 BinaryOperator::Or => left_value || right_value,
1283 BinaryOperator::And => left_value && right_value,
1284 _ => unreachable!(),
1285 };
1286 return Ok(KclValue::Bool { value: raw_value, meta });
1287 }
1288
1289 let left = number_as_f64(&left_value, self.left.clone().into())?;
1290 let right = number_as_f64(&right_value, self.right.clone().into())?;
1291
1292 let value = match self.operator {
1293 BinaryOperator::Add => {
1294 let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1295 self.warn_on_unknown(&ty, "Adding", exec_state);
1296 KclValue::Number { value: l + r, meta, ty }
1297 }
1298 BinaryOperator::Sub => {
1299 let (l, r, ty) = NumericType::combine_eq_coerce(left, right);
1300 self.warn_on_unknown(&ty, "Subtracting", exec_state);
1301 KclValue::Number { value: l - r, meta, ty }
1302 }
1303 BinaryOperator::Mul => {
1304 let (l, r, ty) = NumericType::combine_mul(left, right);
1305 self.warn_on_unknown(&ty, "Multiplying", exec_state);
1306 KclValue::Number { value: l * r, meta, ty }
1307 }
1308 BinaryOperator::Div => {
1309 let (l, r, ty) = NumericType::combine_div(left, right);
1310 self.warn_on_unknown(&ty, "Dividing", exec_state);
1311 KclValue::Number { value: l / r, meta, ty }
1312 }
1313 BinaryOperator::Mod => {
1314 let (l, r, ty) = NumericType::combine_mod(left, right);
1315 self.warn_on_unknown(&ty, "Modulo of", exec_state);
1316 KclValue::Number { value: l % r, meta, ty }
1317 }
1318 BinaryOperator::Pow => KclValue::Number {
1319 value: left.n.powf(right.n),
1320 meta,
1321 ty: exec_state.current_default_units(),
1322 },
1323 BinaryOperator::Neq => {
1324 let (l, r, ty) = NumericType::combine_eq(left, right);
1325 self.warn_on_unknown(&ty, "Comparing", exec_state);
1326 KclValue::Bool { value: l != r, meta }
1327 }
1328 BinaryOperator::Gt => {
1329 let (l, r, ty) = NumericType::combine_eq(left, right);
1330 self.warn_on_unknown(&ty, "Comparing", exec_state);
1331 KclValue::Bool { value: l > r, meta }
1332 }
1333 BinaryOperator::Gte => {
1334 let (l, r, ty) = NumericType::combine_eq(left, right);
1335 self.warn_on_unknown(&ty, "Comparing", exec_state);
1336 KclValue::Bool { value: l >= r, meta }
1337 }
1338 BinaryOperator::Lt => {
1339 let (l, r, ty) = NumericType::combine_eq(left, right);
1340 self.warn_on_unknown(&ty, "Comparing", exec_state);
1341 KclValue::Bool { value: l < r, meta }
1342 }
1343 BinaryOperator::Lte => {
1344 let (l, r, ty) = NumericType::combine_eq(left, right);
1345 self.warn_on_unknown(&ty, "Comparing", exec_state);
1346 KclValue::Bool { value: l <= r, meta }
1347 }
1348 BinaryOperator::Eq => {
1349 let (l, r, ty) = NumericType::combine_eq(left, right);
1350 self.warn_on_unknown(&ty, "Comparing", exec_state);
1351 KclValue::Bool { value: l == r, meta }
1352 }
1353 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
1354 };
1355
1356 Ok(value)
1357 }
1358
1359 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
1360 if ty == &NumericType::Unknown {
1361 let sr = self.as_source_range();
1362 exec_state.clear_units_warnings(&sr);
1363 let mut err = CompilationError::err(
1364 sr,
1365 format!(
1366 "{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)`."
1367 ),
1368 );
1369 err.tag = crate::errors::Tag::UnknownNumericUnits;
1370 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
1371 }
1372 }
1373}
1374
1375impl Node<UnaryExpression> {
1376 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1377 if self.operator == UnaryOperator::Not {
1378 let value = self.argument.get_result(exec_state, ctx).await?;
1379 let KclValue::Bool {
1380 value: bool_value,
1381 meta: _,
1382 } = value
1383 else {
1384 return Err(KclError::new_semantic(KclErrorDetails::new(
1385 format!(
1386 "Cannot apply unary operator ! to non-boolean value: {}",
1387 value.human_friendly_type()
1388 ),
1389 vec![self.into()],
1390 )));
1391 };
1392 let meta = vec![Metadata {
1393 source_range: self.into(),
1394 }];
1395 let negated = KclValue::Bool {
1396 value: !bool_value,
1397 meta,
1398 };
1399
1400 return Ok(negated);
1401 }
1402
1403 let value = &self.argument.get_result(exec_state, ctx).await?;
1404 let err = || {
1405 KclError::new_semantic(KclErrorDetails::new(
1406 format!(
1407 "You can only negate numbers, planes, or lines, but this is a {}",
1408 value.human_friendly_type()
1409 ),
1410 vec![self.into()],
1411 ))
1412 };
1413 match value {
1414 KclValue::Number { value, ty, .. } => {
1415 let meta = vec![Metadata {
1416 source_range: self.into(),
1417 }];
1418 Ok(KclValue::Number {
1419 value: -value,
1420 meta,
1421 ty: *ty,
1422 })
1423 }
1424 KclValue::Plane { value } => {
1425 let mut plane = value.clone();
1426 if plane.info.x_axis.x != 0.0 {
1427 plane.info.x_axis.x *= -1.0;
1428 }
1429 if plane.info.x_axis.y != 0.0 {
1430 plane.info.x_axis.y *= -1.0;
1431 }
1432 if plane.info.x_axis.z != 0.0 {
1433 plane.info.x_axis.z *= -1.0;
1434 }
1435
1436 plane.value = PlaneType::Uninit;
1437 plane.id = exec_state.next_uuid();
1438 Ok(KclValue::Plane { value: plane })
1439 }
1440 KclValue::Object { value: values, meta } => {
1441 let Some(direction) = values.get("direction") else {
1443 return Err(err());
1444 };
1445
1446 let direction = match direction {
1447 KclValue::Tuple { value: values, meta } => {
1448 let values = values
1449 .iter()
1450 .map(|v| match v {
1451 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1452 value: *value * -1.0,
1453 ty: *ty,
1454 meta: meta.clone(),
1455 }),
1456 _ => Err(err()),
1457 })
1458 .collect::<Result<Vec<_>, _>>()?;
1459
1460 KclValue::Tuple {
1461 value: values,
1462 meta: meta.clone(),
1463 }
1464 }
1465 KclValue::HomArray {
1466 value: values,
1467 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
1468 } => {
1469 let values = values
1470 .iter()
1471 .map(|v| match v {
1472 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1473 value: *value * -1.0,
1474 ty: *ty,
1475 meta: meta.clone(),
1476 }),
1477 _ => Err(err()),
1478 })
1479 .collect::<Result<Vec<_>, _>>()?;
1480
1481 KclValue::HomArray {
1482 value: values,
1483 ty: ty.clone(),
1484 }
1485 }
1486 _ => return Err(err()),
1487 };
1488
1489 let mut value = values.clone();
1490 value.insert("direction".to_owned(), direction);
1491 Ok(KclValue::Object {
1492 value,
1493 meta: meta.clone(),
1494 })
1495 }
1496 _ => Err(err()),
1497 }
1498 }
1499}
1500
1501pub(crate) async fn execute_pipe_body(
1502 exec_state: &mut ExecState,
1503 body: &[Expr],
1504 source_range: SourceRange,
1505 ctx: &ExecutorContext,
1506) -> Result<KclValue, KclError> {
1507 let Some((first, body)) = body.split_first() else {
1508 return Err(KclError::new_semantic(KclErrorDetails::new(
1509 "Pipe expressions cannot be empty".to_owned(),
1510 vec![source_range],
1511 )));
1512 };
1513 let meta = Metadata {
1518 source_range: SourceRange::from(first),
1519 };
1520 let output = ctx
1521 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
1522 .await?;
1523
1524 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
1528 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
1530 exec_state.mod_local.pipe_value = previous_pipe_value;
1532
1533 result
1534}
1535
1536#[async_recursion]
1539async fn inner_execute_pipe_body(
1540 exec_state: &mut ExecState,
1541 body: &[Expr],
1542 ctx: &ExecutorContext,
1543) -> Result<KclValue, KclError> {
1544 for expression in body {
1545 if let Expr::TagDeclarator(_) = expression {
1546 return Err(KclError::new_semantic(KclErrorDetails::new(
1547 format!("This cannot be in a PipeExpression: {expression:?}"),
1548 vec![expression.into()],
1549 )));
1550 }
1551 let metadata = Metadata {
1552 source_range: SourceRange::from(expression),
1553 };
1554 let output = ctx
1555 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
1556 .await?;
1557 exec_state.mod_local.pipe_value = Some(output);
1558 }
1559 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
1561 Ok(final_output)
1562}
1563
1564impl Node<TagDeclarator> {
1565 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
1566 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
1567 value: self.name.clone(),
1568 info: Vec::new(),
1569 meta: vec![Metadata {
1570 source_range: self.into(),
1571 }],
1572 }));
1573
1574 exec_state
1575 .mut_stack()
1576 .add(self.name.clone(), memory_item.clone(), self.into())?;
1577
1578 Ok(self.into())
1579 }
1580}
1581
1582impl Node<ArrayExpression> {
1583 #[async_recursion]
1584 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1585 let mut results = Vec::with_capacity(self.elements.len());
1586
1587 for element in &self.elements {
1588 let metadata = Metadata::from(element);
1589 let value = ctx
1592 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
1593 .await?;
1594
1595 results.push(value);
1596 }
1597
1598 Ok(KclValue::HomArray {
1599 value: results,
1600 ty: RuntimeType::Primitive(PrimitiveType::Any),
1601 })
1602 }
1603}
1604
1605impl Node<ArrayRangeExpression> {
1606 #[async_recursion]
1607 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1608 let metadata = Metadata::from(&self.start_element);
1609 let start_val = ctx
1610 .execute_expr(
1611 &self.start_element,
1612 exec_state,
1613 &metadata,
1614 &[],
1615 StatementKind::Expression,
1616 )
1617 .await?;
1618 let (start, start_ty) = start_val
1619 .as_int_with_ty()
1620 .ok_or(KclError::new_semantic(KclErrorDetails::new(
1621 format!("Expected int but found {}", start_val.human_friendly_type()),
1622 vec![self.into()],
1623 )))?;
1624 let metadata = Metadata::from(&self.end_element);
1625 let end_val = ctx
1626 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
1627 .await?;
1628 let (end, end_ty) = end_val
1629 .as_int_with_ty()
1630 .ok_or(KclError::new_semantic(KclErrorDetails::new(
1631 format!("Expected int but found {}", end_val.human_friendly_type()),
1632 vec![self.into()],
1633 )))?;
1634
1635 if start_ty != end_ty {
1636 let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
1637 let start = fmt::human_display_number(start.n, start.ty);
1638 let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
1639 let end = fmt::human_display_number(end.n, end.ty);
1640 return Err(KclError::new_semantic(KclErrorDetails::new(
1641 format!("Range start and end must be of the same type, but found {start} and {end}"),
1642 vec![self.into()],
1643 )));
1644 }
1645
1646 if end < start {
1647 return Err(KclError::new_semantic(KclErrorDetails::new(
1648 format!("Range start is greater than range end: {start} .. {end}"),
1649 vec![self.into()],
1650 )));
1651 }
1652
1653 let range: Vec<_> = if self.end_inclusive {
1654 (start..=end).collect()
1655 } else {
1656 (start..end).collect()
1657 };
1658
1659 let meta = vec![Metadata {
1660 source_range: self.into(),
1661 }];
1662
1663 Ok(KclValue::HomArray {
1664 value: range
1665 .into_iter()
1666 .map(|num| KclValue::Number {
1667 value: num as f64,
1668 ty: start_ty,
1669 meta: meta.clone(),
1670 })
1671 .collect(),
1672 ty: RuntimeType::Primitive(PrimitiveType::Number(start_ty)),
1673 })
1674 }
1675}
1676
1677impl Node<ObjectExpression> {
1678 #[async_recursion]
1679 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1680 let mut object = HashMap::with_capacity(self.properties.len());
1681 for property in &self.properties {
1682 let metadata = Metadata::from(&property.value);
1683 let result = ctx
1684 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
1685 .await?;
1686
1687 object.insert(property.key.name.clone(), result);
1688 }
1689
1690 Ok(KclValue::Object {
1691 value: object,
1692 meta: vec![Metadata {
1693 source_range: self.into(),
1694 }],
1695 })
1696 }
1697}
1698
1699fn article_for<S: AsRef<str>>(s: S) -> &'static str {
1700 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
1702 "an"
1703 } else {
1704 "a"
1705 }
1706}
1707
1708fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
1709 v.as_ty_f64().ok_or_else(|| {
1710 let actual_type = v.human_friendly_type();
1711 KclError::new_semantic(KclErrorDetails::new(
1712 format!("Expected a number, but found {actual_type}",),
1713 vec![source_range],
1714 ))
1715 })
1716}
1717
1718impl Node<IfExpression> {
1719 #[async_recursion]
1720 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1721 let cond = ctx
1723 .execute_expr(
1724 &self.cond,
1725 exec_state,
1726 &Metadata::from(self),
1727 &[],
1728 StatementKind::Expression,
1729 )
1730 .await?
1731 .get_bool()?;
1732 if cond {
1733 let block_result = ctx.exec_block(&self.then_val, exec_state, BodyType::Block).await?;
1734 return Ok(block_result.unwrap());
1738 }
1739
1740 for else_if in &self.else_ifs {
1742 let cond = ctx
1743 .execute_expr(
1744 &else_if.cond,
1745 exec_state,
1746 &Metadata::from(self),
1747 &[],
1748 StatementKind::Expression,
1749 )
1750 .await?
1751 .get_bool()?;
1752 if cond {
1753 let block_result = ctx.exec_block(&else_if.then_val, exec_state, BodyType::Block).await?;
1754 return Ok(block_result.unwrap());
1758 }
1759 }
1760
1761 ctx.exec_block(&self.final_else, exec_state, BodyType::Block)
1763 .await
1764 .map(|expr| expr.unwrap())
1765 }
1766}
1767
1768#[derive(Debug)]
1769enum Property {
1770 UInt(usize),
1771 String(String),
1772}
1773
1774impl Property {
1775 #[allow(clippy::too_many_arguments)]
1776 async fn try_from<'a>(
1777 computed: bool,
1778 value: Expr,
1779 exec_state: &mut ExecState,
1780 sr: SourceRange,
1781 ctx: &ExecutorContext,
1782 metadata: &Metadata,
1783 annotations: &[Node<Annotation>],
1784 statement_kind: StatementKind<'a>,
1785 ) -> Result<Self, KclError> {
1786 let property_sr = vec![sr];
1787 if !computed {
1788 let Expr::Name(identifier) = value else {
1789 return Err(KclError::new_semantic(KclErrorDetails::new(
1791 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
1792 .to_owned(),
1793 property_sr,
1794 )));
1795 };
1796 return Ok(Property::String(identifier.to_string()));
1797 }
1798
1799 let prop_value = ctx
1800 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
1801 .await?;
1802 match prop_value {
1803 KclValue::Number { value, ty, meta: _ } => {
1804 if !matches!(
1805 ty,
1806 NumericType::Unknown
1807 | NumericType::Default { .. }
1808 | NumericType::Known(crate::exec::UnitType::Count)
1809 ) {
1810 return Err(KclError::new_semantic(KclErrorDetails::new(
1811 format!(
1812 "{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"
1813 ),
1814 property_sr,
1815 )));
1816 }
1817 if let Some(x) = crate::try_f64_to_usize(value) {
1818 Ok(Property::UInt(x))
1819 } else {
1820 Err(KclError::new_semantic(KclErrorDetails::new(
1821 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
1822 property_sr,
1823 )))
1824 }
1825 }
1826 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1827 "Only numbers (>= 0) can be indexes".to_owned(),
1828 vec![sr],
1829 ))),
1830 }
1831 }
1832}
1833
1834impl Property {
1835 fn type_name(&self) -> &'static str {
1836 match self {
1837 Property::UInt(_) => "number",
1838 Property::String(_) => "string",
1839 }
1840 }
1841}
1842
1843impl Node<PipeExpression> {
1844 #[async_recursion]
1845 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1846 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
1847 }
1848}
1849
1850#[cfg(test)]
1851mod test {
1852 use std::sync::Arc;
1853
1854 use tokio::io::AsyncWriteExt;
1855
1856 use super::*;
1857 use crate::{
1858 ExecutorSettings, UnitLen,
1859 errors::Severity,
1860 exec::UnitType,
1861 execution::{ContextType, parse_execute},
1862 };
1863
1864 #[tokio::test(flavor = "multi_thread")]
1865 async fn ascription() {
1866 let program = r#"
1867a = 42: number
1868b = a: number
1869p = {
1870 origin = { x = 0, y = 0, z = 0 },
1871 xAxis = { x = 1, y = 0, z = 0 },
1872 yAxis = { x = 0, y = 1, z = 0 },
1873 zAxis = { x = 0, y = 0, z = 1 }
1874}: Plane
1875arr1 = [42]: [number(cm)]
1876"#;
1877
1878 let result = parse_execute(program).await.unwrap();
1879 let mem = result.exec_state.stack();
1880 assert!(matches!(
1881 mem.memory
1882 .get_from("p", result.mem_env, SourceRange::default(), 0)
1883 .unwrap(),
1884 KclValue::Plane { .. }
1885 ));
1886 let arr1 = mem
1887 .memory
1888 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
1889 .unwrap();
1890 if let KclValue::HomArray { value, ty } = arr1 {
1891 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
1892 assert_eq!(*ty, RuntimeType::known_length(UnitLen::Cm));
1893 if let KclValue::Number { value, ty, .. } = &value[0] {
1895 assert_eq!(*value, 42.0);
1897 assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLen::Cm)));
1898 } else {
1899 panic!("Expected a number; found {:?}", value[0]);
1900 }
1901 } else {
1902 panic!("Expected HomArray; found {arr1:?}");
1903 }
1904
1905 let program = r#"
1906a = 42: string
1907"#;
1908 let result = parse_execute(program).await;
1909 let err = result.unwrap_err();
1910 assert!(
1911 err.to_string()
1912 .contains("could not coerce a number (with type `number`) to type `string`"),
1913 "Expected error but found {err:?}"
1914 );
1915
1916 let program = r#"
1917a = 42: Plane
1918"#;
1919 let result = parse_execute(program).await;
1920 let err = result.unwrap_err();
1921 assert!(
1922 err.to_string()
1923 .contains("could not coerce a number (with type `number`) to type `Plane`"),
1924 "Expected error but found {err:?}"
1925 );
1926
1927 let program = r#"
1928arr = [0]: [string]
1929"#;
1930 let result = parse_execute(program).await;
1931 let err = result.unwrap_err();
1932 assert!(
1933 err.to_string().contains(
1934 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
1935 ),
1936 "Expected error but found {err:?}"
1937 );
1938
1939 let program = r#"
1940mixedArr = [0, "a"]: [number(mm)]
1941"#;
1942 let result = parse_execute(program).await;
1943 let err = result.unwrap_err();
1944 assert!(
1945 err.to_string().contains(
1946 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
1947 ),
1948 "Expected error but found {err:?}"
1949 );
1950
1951 let program = r#"
1952mixedArr = [0, "a"]: [mm]
1953"#;
1954 let result = parse_execute(program).await;
1955 let err = result.unwrap_err();
1956 assert!(
1957 err.to_string().contains(
1958 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
1959 ),
1960 "Expected error but found {err:?}"
1961 );
1962 }
1963
1964 #[tokio::test(flavor = "multi_thread")]
1965 async fn neg_plane() {
1966 let program = r#"
1967p = {
1968 origin = { x = 0, y = 0, z = 0 },
1969 xAxis = { x = 1, y = 0, z = 0 },
1970 yAxis = { x = 0, y = 1, z = 0 },
1971}: Plane
1972p2 = -p
1973"#;
1974
1975 let result = parse_execute(program).await.unwrap();
1976 let mem = result.exec_state.stack();
1977 match mem
1978 .memory
1979 .get_from("p2", result.mem_env, SourceRange::default(), 0)
1980 .unwrap()
1981 {
1982 KclValue::Plane { value } => {
1983 assert_eq!(value.info.x_axis.x, -1.0);
1984 assert_eq!(value.info.x_axis.y, 0.0);
1985 assert_eq!(value.info.x_axis.z, 0.0);
1986 }
1987 _ => unreachable!(),
1988 }
1989 }
1990
1991 #[tokio::test(flavor = "multi_thread")]
1992 async fn multiple_returns() {
1993 let program = r#"fn foo() {
1994 return 0
1995 return 42
1996}
1997
1998a = foo()
1999"#;
2000
2001 let result = parse_execute(program).await;
2002 assert!(result.unwrap_err().to_string().contains("return"));
2003 }
2004
2005 #[tokio::test(flavor = "multi_thread")]
2006 async fn load_all_modules() {
2007 let program_a_kcl = r#"
2009export a = 1
2010"#;
2011 let program_b_kcl = r#"
2013import a from 'a.kcl'
2014
2015export b = a + 1
2016"#;
2017 let program_c_kcl = r#"
2019import a from 'a.kcl'
2020
2021export c = a + 2
2022"#;
2023
2024 let main_kcl = r#"
2026import b from 'b.kcl'
2027import c from 'c.kcl'
2028
2029d = b + c
2030"#;
2031
2032 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
2033 .parse_errs_as_err()
2034 .unwrap();
2035
2036 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
2037
2038 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
2039 .await
2040 .unwrap()
2041 .write_all(main_kcl.as_bytes())
2042 .await
2043 .unwrap();
2044
2045 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
2046 .await
2047 .unwrap()
2048 .write_all(program_a_kcl.as_bytes())
2049 .await
2050 .unwrap();
2051
2052 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
2053 .await
2054 .unwrap()
2055 .write_all(program_b_kcl.as_bytes())
2056 .await
2057 .unwrap();
2058
2059 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
2060 .await
2061 .unwrap()
2062 .write_all(program_c_kcl.as_bytes())
2063 .await
2064 .unwrap();
2065
2066 let exec_ctxt = ExecutorContext {
2067 engine: Arc::new(Box::new(
2068 crate::engine::conn_mock::EngineConnection::new()
2069 .await
2070 .map_err(|err| {
2071 KclError::new_internal(KclErrorDetails::new(
2072 format!("Failed to create mock engine connection: {err}"),
2073 vec![SourceRange::default()],
2074 ))
2075 })
2076 .unwrap(),
2077 )),
2078 fs: Arc::new(crate::fs::FileManager::new()),
2079 settings: ExecutorSettings {
2080 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
2081 ..Default::default()
2082 },
2083 context_type: ContextType::Mock,
2084 };
2085 let mut exec_state = ExecState::new(&exec_ctxt);
2086
2087 exec_ctxt
2088 .run(
2089 &crate::Program {
2090 ast: main.clone(),
2091 original_file_contents: "".to_owned(),
2092 },
2093 &mut exec_state,
2094 )
2095 .await
2096 .unwrap();
2097 }
2098
2099 #[tokio::test(flavor = "multi_thread")]
2100 async fn user_coercion() {
2101 let program = r#"fn foo(x: Axis2d) {
2102 return 0
2103}
2104
2105foo(x = { direction = [0, 0], origin = [0, 0]})
2106"#;
2107
2108 parse_execute(program).await.unwrap();
2109
2110 let program = r#"fn foo(x: Axis3d) {
2111 return 0
2112}
2113
2114foo(x = { direction = [0, 0], origin = [0, 0]})
2115"#;
2116
2117 parse_execute(program).await.unwrap_err();
2118 }
2119
2120 #[tokio::test(flavor = "multi_thread")]
2121 async fn coerce_return() {
2122 let program = r#"fn foo(): number(mm) {
2123 return 42
2124}
2125
2126a = foo()
2127"#;
2128
2129 parse_execute(program).await.unwrap();
2130
2131 let program = r#"fn foo(): mm {
2132 return 42
2133}
2134
2135a = foo()
2136"#;
2137
2138 parse_execute(program).await.unwrap();
2139
2140 let program = r#"fn foo(): number(mm) {
2141 return { bar: 42 }
2142}
2143
2144a = foo()
2145"#;
2146
2147 parse_execute(program).await.unwrap_err();
2148
2149 let program = r#"fn foo(): mm {
2150 return { bar: 42 }
2151}
2152
2153a = foo()
2154"#;
2155
2156 parse_execute(program).await.unwrap_err();
2157 }
2158
2159 #[tokio::test(flavor = "multi_thread")]
2160 async fn test_sensible_error_when_missing_equals_in_kwarg() {
2161 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)"]
2162 .into_iter()
2163 .enumerate()
2164 {
2165 let program = format!(
2166 "fn foo() {{ return 0 }}
2167z = 0
2168fn f(x, y, z) {{ return 0 }}
2169{call}"
2170 );
2171 let err = parse_execute(&program).await.unwrap_err();
2172 let msg = err.message();
2173 assert!(
2174 msg.contains("This argument needs a label, but it doesn't have one"),
2175 "failed test {i}: {msg}"
2176 );
2177 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
2178 if i == 0 {
2179 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
2180 }
2181 }
2182 }
2183
2184 #[tokio::test(flavor = "multi_thread")]
2185 async fn default_param_for_unlabeled() {
2186 let ast = r#"fn myExtrude(@sk, length) {
2189 return extrude(sk, length)
2190}
2191sketch001 = startSketchOn(XY)
2192 |> circle(center = [0, 0], radius = 93.75)
2193 |> myExtrude(length = 40)
2194"#;
2195
2196 parse_execute(ast).await.unwrap();
2197 }
2198
2199 #[tokio::test(flavor = "multi_thread")]
2200 async fn dont_use_unlabelled_as_input() {
2201 let ast = r#"length = 10
2203startSketchOn(XY)
2204 |> circle(center = [0, 0], radius = 93.75)
2205 |> extrude(length)
2206"#;
2207
2208 parse_execute(ast).await.unwrap();
2209 }
2210
2211 #[tokio::test(flavor = "multi_thread")]
2212 async fn ascription_in_binop() {
2213 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
2214 parse_execute(ast).await.unwrap();
2215
2216 let ast = r#"foo = tan(0): rad - 4deg"#;
2217 parse_execute(ast).await.unwrap();
2218 }
2219
2220 #[tokio::test(flavor = "multi_thread")]
2221 async fn neg_sqrt() {
2222 let ast = r#"bad = sqrt(-2)"#;
2223
2224 let e = parse_execute(ast).await.unwrap_err();
2225 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
2227 }
2228
2229 #[tokio::test(flavor = "multi_thread")]
2230 async fn non_array_fns() {
2231 let ast = r#"push(1, item = 2)
2232pop(1)
2233map(1, f = fn(@x) { return x + 1 })
2234reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
2235
2236 parse_execute(ast).await.unwrap();
2237 }
2238
2239 #[tokio::test(flavor = "multi_thread")]
2240 async fn non_array_indexing() {
2241 let good = r#"a = 42
2242good = a[0]
2243"#;
2244 let result = parse_execute(good).await.unwrap();
2245 let mem = result.exec_state.stack();
2246 let num = mem
2247 .memory
2248 .get_from("good", result.mem_env, SourceRange::default(), 0)
2249 .unwrap()
2250 .as_ty_f64()
2251 .unwrap();
2252 assert_eq!(num.n, 42.0);
2253
2254 let bad = r#"a = 42
2255bad = a[1]
2256"#;
2257
2258 parse_execute(bad).await.unwrap_err();
2259 }
2260
2261 #[tokio::test(flavor = "multi_thread")]
2262 async fn coerce_unknown_to_length() {
2263 let ast = r#"x = 2mm * 2mm
2264y = x: number(Length)"#;
2265 let e = parse_execute(ast).await.unwrap_err();
2266 assert!(
2267 e.message().contains("could not coerce"),
2268 "Error message: '{}'",
2269 e.message()
2270 );
2271
2272 let ast = r#"x = 2mm
2273y = x: number(Length)"#;
2274 let result = parse_execute(ast).await.unwrap();
2275 let mem = result.exec_state.stack();
2276 let num = mem
2277 .memory
2278 .get_from("y", result.mem_env, SourceRange::default(), 0)
2279 .unwrap()
2280 .as_ty_f64()
2281 .unwrap();
2282 assert_eq!(num.n, 2.0);
2283 assert_eq!(num.ty, NumericType::mm());
2284 }
2285
2286 #[tokio::test(flavor = "multi_thread")]
2287 async fn one_warning_unknown() {
2288 let ast = r#"
2289// Should warn once
2290a = PI * 2
2291// Should warn once
2292b = (PI * 2) / 3
2293// Should not warn
2294c = ((PI * 2) / 3): number(deg)
2295"#;
2296
2297 let result = parse_execute(ast).await.unwrap();
2298 assert_eq!(result.exec_state.errors().len(), 2);
2299 }
2300
2301 #[tokio::test(flavor = "multi_thread")]
2302 async fn non_count_indexing() {
2303 let ast = r#"x = [0, 0]
2304y = x[1mm]
2305"#;
2306 parse_execute(ast).await.unwrap_err();
2307
2308 let ast = r#"x = [0, 0]
2309y = 1deg
2310z = x[y]
2311"#;
2312 parse_execute(ast).await.unwrap_err();
2313
2314 let ast = r#"x = [0, 0]
2315y = x[0mm + 1]
2316"#;
2317 parse_execute(ast).await.unwrap_err();
2318 }
2319
2320 #[tokio::test(flavor = "multi_thread")]
2321 async fn getting_property_of_plane() {
2322 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
2324
2325 parse_execute(&ast).await.unwrap();
2326 }
2327
2328 #[tokio::test(flavor = "multi_thread")]
2329 async fn custom_warning() {
2330 let warn = r#"
2331a = PI * 2
2332"#;
2333 let result = parse_execute(warn).await.unwrap();
2334 assert_eq!(result.exec_state.errors().len(), 1);
2335 assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
2336
2337 let allow = r#"
2338@warnings(allow = unknownUnits)
2339a = PI * 2
2340"#;
2341 let result = parse_execute(allow).await.unwrap();
2342 assert_eq!(result.exec_state.errors().len(), 0);
2343
2344 let deny = r#"
2345@warnings(deny = [unknownUnits])
2346a = PI * 2
2347"#;
2348 let result = parse_execute(deny).await.unwrap();
2349 assert_eq!(result.exec_state.errors().len(), 1);
2350 assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
2351 }
2352}