1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use indexmap::IndexMap;
5use kcl_ezpz::Constraint;
6use kittycad_modeling_cmds::units::UnitLength;
7
8use crate::{
9 CompilationError, NodePath, SourceRange,
10 errors::{KclError, KclErrorDetails},
11 exec::UnitType,
12 execution::{
13 BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, ModelingCmdMeta, ModuleArtifactState,
14 Operation, PlaneType, StatementKind, TagIdentifier, annotations,
15 cad_op::OpKclValue,
16 fn_call::Args,
17 kcl_value::{FunctionSource, KclFunctionSourceParams, TypeDef},
18 memory,
19 state::{ModuleState, SketchBlockState},
20 types::{NumericType, PrimitiveType, RuntimeType},
21 },
22 modules::{ModuleExecutionOutcome, ModuleId, ModulePath, ModuleRepr},
23 parsing::ast::types::{
24 Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
25 BinaryPart, BodyItem, CodeBlock, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility,
26 MemberExpression, Name, Node, ObjectExpression, PipeExpression, Program, SketchBlock, SketchVar, TagDeclarator,
27 Type, UnaryExpression, UnaryOperator,
28 },
29 std::args::TyF64,
30};
31
32impl<'a> StatementKind<'a> {
33 fn expect_name(&self) -> &'a str {
34 match self {
35 StatementKind::Declaration { name } => name,
36 StatementKind::Expression => unreachable!(),
37 }
38 }
39}
40
41impl ExecutorContext {
42 async fn handle_annotations(
44 &self,
45 annotations: impl Iterator<Item = &Node<Annotation>>,
46 body_type: BodyType,
47 exec_state: &mut ExecState,
48 ) -> Result<bool, KclError> {
49 let mut no_prelude = false;
50 for annotation in annotations {
51 if annotation.name() == Some(annotations::SETTINGS) {
52 if matches!(body_type, BodyType::Root) {
53 let (updated_len, updated_angle) =
54 exec_state.mod_local.settings.update_from_annotation(annotation)?;
55 if updated_len {
56 exec_state.mod_local.explicit_length_units = true;
57 }
58 if updated_angle {
59 exec_state.warn(
60 CompilationError::err(
61 annotation.as_source_range(),
62 "Prefer to use explicit units for angles",
63 ),
64 annotations::WARN_ANGLE_UNITS,
65 );
66 }
67 } else {
68 exec_state.err(CompilationError::err(
69 annotation.as_source_range(),
70 "Settings can only be modified at the top level scope of a file",
71 ));
72 }
73 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
74 if matches!(body_type, BodyType::Root) {
75 no_prelude = true;
76 } else {
77 exec_state.err(CompilationError::err(
78 annotation.as_source_range(),
79 "The standard library can only be skipped at the top level scope of a file",
80 ));
81 }
82 } else if annotation.name() == Some(annotations::WARNINGS) {
83 if matches!(body_type, BodyType::Root) {
85 let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
86 for p in props {
87 match &*p.inner.key.name {
88 annotations::WARN_ALLOW => {
89 let allowed = annotations::many_of(
90 &p.inner.value,
91 &annotations::WARN_VALUES,
92 annotation.as_source_range(),
93 )?;
94 exec_state.mod_local.allowed_warnings = allowed;
95 }
96 annotations::WARN_DENY => {
97 let denied = annotations::many_of(
98 &p.inner.value,
99 &annotations::WARN_VALUES,
100 annotation.as_source_range(),
101 )?;
102 exec_state.mod_local.denied_warnings = denied;
103 }
104 name => {
105 return Err(KclError::new_semantic(KclErrorDetails::new(
106 format!(
107 "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
108 annotations::WARN_ALLOW,
109 annotations::WARN_DENY,
110 ),
111 vec![annotation.as_source_range()],
112 )));
113 }
114 }
115 }
116 } else {
117 exec_state.err(CompilationError::err(
118 annotation.as_source_range(),
119 "Warnings can only be customized at the top level scope of a file",
120 ));
121 }
122 } else {
123 exec_state.warn(
124 CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
125 annotations::WARN_UNKNOWN_ATTR,
126 );
127 }
128 }
129 Ok(no_prelude)
130 }
131
132 pub(super) async fn exec_module_body(
133 &self,
134 program: &Node<Program>,
135 exec_state: &mut ExecState,
136 preserve_mem: bool,
137 module_id: ModuleId,
138 path: &ModulePath,
139 ) -> Result<ModuleExecutionOutcome, (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>)> {
140 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
141
142 let mut local_state = ModuleState::new(path.clone(), exec_state.stack().memory.clone(), Some(module_id));
143 if !preserve_mem {
144 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
145 }
146
147 let no_prelude = self
148 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
149 .await
150 .map_err(|err| (err, None, None))?;
151
152 if !preserve_mem {
153 exec_state.mut_stack().push_new_root_env(!no_prelude);
154 }
155
156 let result = self
157 .exec_block(program, exec_state, crate::execution::BodyType::Root)
158 .await;
159
160 let env_ref = if preserve_mem {
161 exec_state.mut_stack().pop_and_preserve_env()
162 } else {
163 exec_state.mut_stack().pop_env()
164 };
165 let module_artifacts = if !preserve_mem {
166 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
167 local_state.artifacts
168 } else {
169 std::mem::take(&mut exec_state.mod_local.artifacts)
170 };
171
172 crate::log::log(format!("leave {path}"));
173
174 result
175 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
176 .map(|last_expr| ModuleExecutionOutcome {
177 last_expr,
178 environment: env_ref,
179 exports: local_state.module_exports,
180 artifacts: module_artifacts,
181 })
182 }
183
184 #[async_recursion]
186 pub(super) async fn exec_block<'a, B>(
187 &'a self,
188 block: &'a B,
189 exec_state: &mut ExecState,
190 body_type: BodyType,
191 ) -> Result<Option<KclValue>, KclError>
192 where
193 B: CodeBlock + Sync,
194 {
195 let mut last_expr = None;
196 for statement in block.body() {
198 match statement {
199 BodyItem::ImportStatement(import_stmt) => {
200 if !matches!(body_type, BodyType::Root) {
201 return Err(KclError::new_semantic(KclErrorDetails::new(
202 "Imports are only supported at the top-level of a file.".to_owned(),
203 vec![import_stmt.into()],
204 )));
205 }
206
207 let source_range = SourceRange::from(import_stmt);
208 let attrs = &import_stmt.outer_attrs;
209 let module_path = ModulePath::from_import_path(
210 &import_stmt.path,
211 &self.settings.project_directory,
212 &exec_state.mod_local.path,
213 )?;
214 let module_id = self
215 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
216 .await?;
217
218 match &import_stmt.selector {
219 ImportSelector::List { items } => {
220 let (env_ref, module_exports) =
221 self.exec_module_for_items(module_id, exec_state, source_range).await?;
222 for import_item in items {
223 let mem = &exec_state.stack().memory;
225 let mut value = mem
226 .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
227 .cloned();
228 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
229 let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
230 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
231 let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
232
233 if value.is_err() && ty.is_err() && mod_value.is_err() {
234 return Err(KclError::new_undefined_value(
235 KclErrorDetails::new(
236 format!("{} is not defined in module", import_item.name.name),
237 vec![SourceRange::from(&import_item.name)],
238 ),
239 None,
240 ));
241 }
242
243 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
245 value = Err(KclError::new_semantic(KclErrorDetails::new(
246 format!(
247 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
248 import_item.name.name
249 ),
250 vec![SourceRange::from(&import_item.name)],
251 )));
252 }
253
254 if ty.is_ok() && !module_exports.contains(&ty_name) {
255 ty = Err(KclError::new_semantic(KclErrorDetails::new(
256 format!(
257 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
258 import_item.name.name
259 ),
260 vec![SourceRange::from(&import_item.name)],
261 )));
262 }
263
264 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
265 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
266 format!(
267 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
268 import_item.name.name
269 ),
270 vec![SourceRange::from(&import_item.name)],
271 )));
272 }
273
274 if value.is_err() && ty.is_err() && mod_value.is_err() {
275 return value.map(Option::Some);
276 }
277
278 if let Ok(value) = value {
280 exec_state.mut_stack().add(
281 import_item.identifier().to_owned(),
282 value,
283 SourceRange::from(&import_item.name),
284 )?;
285
286 if let ItemVisibility::Export = import_stmt.visibility {
287 exec_state
288 .mod_local
289 .module_exports
290 .push(import_item.identifier().to_owned());
291 }
292 }
293
294 if let Ok(ty) = ty {
295 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
296 exec_state.mut_stack().add(
297 ty_name.clone(),
298 ty,
299 SourceRange::from(&import_item.name),
300 )?;
301
302 if let ItemVisibility::Export = import_stmt.visibility {
303 exec_state.mod_local.module_exports.push(ty_name);
304 }
305 }
306
307 if let Ok(mod_value) = mod_value {
308 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
309 exec_state.mut_stack().add(
310 mod_name.clone(),
311 mod_value,
312 SourceRange::from(&import_item.name),
313 )?;
314
315 if let ItemVisibility::Export = import_stmt.visibility {
316 exec_state.mod_local.module_exports.push(mod_name);
317 }
318 }
319 }
320 }
321 ImportSelector::Glob(_) => {
322 let (env_ref, module_exports) =
323 self.exec_module_for_items(module_id, exec_state, source_range).await?;
324 for name in module_exports.iter() {
325 let item = exec_state
326 .stack()
327 .memory
328 .get_from(name, env_ref, source_range, 0)
329 .map_err(|_err| {
330 KclError::new_internal(KclErrorDetails::new(
331 format!("{name} is not defined in module (but was exported?)"),
332 vec![source_range],
333 ))
334 })?
335 .clone();
336 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
337
338 if let ItemVisibility::Export = import_stmt.visibility {
339 exec_state.mod_local.module_exports.push(name.clone());
340 }
341 }
342 }
343 ImportSelector::None { .. } => {
344 let name = import_stmt.module_name().unwrap();
345 let item = KclValue::Module {
346 value: module_id,
347 meta: vec![source_range.into()],
348 };
349 exec_state.mut_stack().add(
350 format!("{}{}", memory::MODULE_PREFIX, name),
351 item,
352 source_range,
353 )?;
354 }
355 }
356 last_expr = None;
357 }
358 BodyItem::ExpressionStatement(expression_statement) => {
359 let metadata = Metadata::from(expression_statement);
360 last_expr = Some(
361 self.execute_expr(
362 &expression_statement.expression,
363 exec_state,
364 &metadata,
365 &[],
366 StatementKind::Expression,
367 )
368 .await?,
369 );
370 }
371 BodyItem::VariableDeclaration(variable_declaration) => {
372 let var_name = variable_declaration.declaration.id.name.to_string();
373 let source_range = SourceRange::from(&variable_declaration.declaration.init);
374 let metadata = Metadata { source_range };
375
376 let annotations = &variable_declaration.outer_attrs;
377
378 let lhs = variable_declaration.inner.name().to_owned();
381 let prev_being_declared = exec_state.mod_local.being_declared.take();
382 exec_state.mod_local.being_declared = Some(lhs);
383 let rhs_result = self
384 .execute_expr(
385 &variable_declaration.declaration.init,
386 exec_state,
387 &metadata,
388 annotations,
389 StatementKind::Declaration { name: &var_name },
390 )
391 .await;
392 exec_state.mod_local.being_declared = prev_being_declared;
394 let rhs = rhs_result?;
395
396 let should_bind_name =
397 if let Some(fn_name) = variable_declaration.declaration.init.fn_declaring_name() {
398 var_name != fn_name
402 } else {
403 true
406 };
407 if should_bind_name {
408 exec_state
409 .mut_stack()
410 .add(var_name.clone(), rhs.clone(), source_range)?;
411 }
412
413 let should_show_in_feature_tree =
417 !exec_state.mod_local.inside_stdlib && rhs.show_variable_in_feature_tree();
418 if should_show_in_feature_tree {
419 exec_state.push_op(Operation::VariableDeclaration {
420 name: var_name.clone(),
421 value: OpKclValue::from(&rhs),
422 visibility: variable_declaration.visibility,
423 node_path: NodePath::placeholder(),
424 source_range,
425 });
426 }
427
428 if let ItemVisibility::Export = variable_declaration.visibility {
430 if matches!(body_type, BodyType::Root) {
431 exec_state.mod_local.module_exports.push(var_name);
432 } else {
433 exec_state.err(CompilationError::err(
434 variable_declaration.as_source_range(),
435 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
436 ));
437 }
438 }
439 last_expr = matches!(body_type, BodyType::Root).then_some(rhs);
441 }
442 BodyItem::TypeDeclaration(ty) => {
443 let metadata = Metadata::from(&**ty);
444 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
445 match attrs.impl_ {
446 annotations::Impl::Rust | annotations::Impl::RustConstraint => {
447 let std_path = match &exec_state.mod_local.path {
448 ModulePath::Std { value } => value,
449 ModulePath::Local { .. } | ModulePath::Main => {
450 return Err(KclError::new_semantic(KclErrorDetails::new(
451 "User-defined types are not yet supported.".to_owned(),
452 vec![metadata.source_range],
453 )));
454 }
455 };
456 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
457 let value = KclValue::Type {
458 value: TypeDef::RustRepr(t, props),
459 meta: vec![metadata],
460 experimental: attrs.experimental,
461 };
462 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
463 exec_state
464 .mut_stack()
465 .add(name_in_mem.clone(), value, metadata.source_range)
466 .map_err(|_| {
467 KclError::new_semantic(KclErrorDetails::new(
468 format!("Redefinition of type {}.", ty.name.name),
469 vec![metadata.source_range],
470 ))
471 })?;
472
473 if let ItemVisibility::Export = ty.visibility {
474 exec_state.mod_local.module_exports.push(name_in_mem);
475 }
476 }
477 annotations::Impl::Primitive => {}
479 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
480 Some(alias) => {
481 let value = KclValue::Type {
482 value: TypeDef::Alias(
483 RuntimeType::from_parsed(
484 alias.inner.clone(),
485 exec_state,
486 metadata.source_range,
487 attrs.impl_ == annotations::Impl::KclConstrainable,
488 )
489 .map_err(|e| KclError::new_semantic(e.into()))?,
490 ),
491 meta: vec![metadata],
492 experimental: attrs.experimental,
493 };
494 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
495 exec_state
496 .mut_stack()
497 .add(name_in_mem.clone(), value, metadata.source_range)
498 .map_err(|_| {
499 KclError::new_semantic(KclErrorDetails::new(
500 format!("Redefinition of type {}.", ty.name.name),
501 vec![metadata.source_range],
502 ))
503 })?;
504
505 if let ItemVisibility::Export = ty.visibility {
506 exec_state.mod_local.module_exports.push(name_in_mem);
507 }
508 }
509 None => {
510 return Err(KclError::new_semantic(KclErrorDetails::new(
511 "User-defined types are not yet supported.".to_owned(),
512 vec![metadata.source_range],
513 )));
514 }
515 },
516 }
517
518 last_expr = None;
519 }
520 BodyItem::ReturnStatement(return_statement) => {
521 let metadata = Metadata::from(return_statement);
522
523 if matches!(body_type, BodyType::Root) {
524 return Err(KclError::new_semantic(KclErrorDetails::new(
525 "Cannot return from outside a function.".to_owned(),
526 vec![metadata.source_range],
527 )));
528 }
529
530 let value = self
531 .execute_expr(
532 &return_statement.argument,
533 exec_state,
534 &metadata,
535 &[],
536 StatementKind::Expression,
537 )
538 .await?;
539 exec_state
540 .mut_stack()
541 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
542 .map_err(|_| {
543 KclError::new_semantic(KclErrorDetails::new(
544 "Multiple returns from a single function.".to_owned(),
545 vec![metadata.source_range],
546 ))
547 })?;
548 last_expr = None;
549 }
550 }
551 }
552
553 if matches!(body_type, BodyType::Root) {
554 exec_state
556 .flush_batch(
557 ModelingCmdMeta::new(self, block.to_source_range()),
558 true,
561 )
562 .await?;
563 }
564
565 Ok(last_expr)
566 }
567
568 pub async fn open_module(
569 &self,
570 path: &ImportPath,
571 attrs: &[Node<Annotation>],
572 resolved_path: &ModulePath,
573 exec_state: &mut ExecState,
574 source_range: SourceRange,
575 ) -> Result<ModuleId, KclError> {
576 match path {
577 ImportPath::Kcl { .. } => {
578 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
579
580 if let Some(id) = exec_state.id_for_module(resolved_path) {
581 return Ok(id);
582 }
583
584 let id = exec_state.next_module_id();
585 exec_state.add_path_to_source_id(resolved_path.clone(), id);
587 let source = resolved_path.source(&self.fs, source_range).await?;
588 exec_state.add_id_to_source(id, source.clone());
589 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
591 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
592
593 Ok(id)
594 }
595 ImportPath::Foreign { .. } => {
596 if let Some(id) = exec_state.id_for_module(resolved_path) {
597 return Ok(id);
598 }
599
600 let id = exec_state.next_module_id();
601 let path = resolved_path.expect_path();
602 exec_state.add_path_to_source_id(resolved_path.clone(), id);
604 let format = super::import::format_from_annotations(attrs, path, source_range)?;
605 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
606 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
607 Ok(id)
608 }
609 ImportPath::Std { .. } => {
610 if let Some(id) = exec_state.id_for_module(resolved_path) {
611 return Ok(id);
612 }
613
614 let id = exec_state.next_module_id();
615 exec_state.add_path_to_source_id(resolved_path.clone(), id);
617 let source = resolved_path.source(&self.fs, source_range).await?;
618 exec_state.add_id_to_source(id, source.clone());
619 let parsed = crate::parsing::parse_str(&source.source, id)
620 .parse_errs_as_err()
621 .unwrap();
622 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
623 Ok(id)
624 }
625 }
626 }
627
628 pub(super) async fn exec_module_for_items(
629 &self,
630 module_id: ModuleId,
631 exec_state: &mut ExecState,
632 source_range: SourceRange,
633 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
634 let path = exec_state.global.module_infos[&module_id].path.clone();
635 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
636 let result = match &mut repr {
639 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
640 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
641 ModuleRepr::Kcl(program, cache) => self
642 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
643 .await
644 .map(|outcome| {
645 *cache = Some(outcome.clone());
646 (outcome.environment, outcome.exports)
647 }),
648 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
649 "Cannot import items from foreign modules".to_owned(),
650 vec![geom.source_range],
651 ))),
652 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
653 };
654
655 exec_state.global.module_infos[&module_id].restore_repr(repr);
656 result
657 }
658
659 async fn exec_module_for_result(
660 &self,
661 module_id: ModuleId,
662 exec_state: &mut ExecState,
663 source_range: SourceRange,
664 ) -> Result<Option<KclValue>, KclError> {
665 let path = exec_state.global.module_infos[&module_id].path.clone();
666 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
667 let result = match &mut repr {
670 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
671 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
672 ModuleRepr::Kcl(program, cached_items) => {
673 let result = self
674 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
675 .await;
676 match result {
677 Ok(outcome) => {
678 let value = outcome.last_expr.clone();
679 *cached_items = Some(outcome);
680 Ok(value)
681 }
682 Err(e) => Err(e),
683 }
684 }
685 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
686 ModuleRepr::Foreign(geom, cached) => {
687 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
688 .await
689 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
690
691 match result {
692 Ok(val) => {
693 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
694 Ok(val)
695 }
696 Err(e) => Err(e),
697 }
698 }
699 ModuleRepr::Dummy => unreachable!(),
700 };
701
702 exec_state.global.module_infos[&module_id].restore_repr(repr);
703
704 result
705 }
706
707 pub async fn exec_module_from_ast(
708 &self,
709 program: &Node<Program>,
710 module_id: ModuleId,
711 path: &ModulePath,
712 exec_state: &mut ExecState,
713 source_range: SourceRange,
714 preserve_mem: bool,
715 ) -> Result<ModuleExecutionOutcome, KclError> {
716 exec_state.global.mod_loader.enter_module(path);
717 let result = self
718 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
719 .await;
720 exec_state.global.mod_loader.leave_module(path, source_range)?;
721
722 result.map_err(|(err, _, _)| {
725 if let KclError::ImportCycle { .. } = err {
726 err.override_source_ranges(vec![source_range])
728 } else {
729 KclError::new_semantic(KclErrorDetails::new(
731 format!(
732 "Error loading imported file ({path}). Open it to view more details.\n {}",
733 err.message()
734 ),
735 vec![source_range],
736 ))
737 }
738 })
739 }
740
741 #[async_recursion]
742 pub(crate) async fn execute_expr<'a: 'async_recursion>(
743 &self,
744 init: &Expr,
745 exec_state: &mut ExecState,
746 metadata: &Metadata,
747 annotations: &[Node<Annotation>],
748 statement_kind: StatementKind<'a>,
749 ) -> Result<KclValue, KclError> {
750 let item = match init {
751 Expr::None(none) => KclValue::from(none),
752 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
753 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
754 Expr::Name(name) => {
755 let being_declared = exec_state.mod_local.being_declared.clone();
756 let value = name
757 .get_result(exec_state, self)
758 .await
759 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
760 .clone();
761 if let KclValue::Module { value: module_id, meta } = value {
762 self.exec_module_for_result(
763 module_id,
764 exec_state,
765 metadata.source_range
766 ).await?
767 .unwrap_or_else(|| {
768 exec_state.warn(CompilationError::err(
769 metadata.source_range,
770 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
771 ),
772 annotations::WARN_MOD_RETURN_VALUE);
773
774 let mut new_meta = vec![metadata.to_owned()];
775 new_meta.extend(meta);
776 KclValue::KclNone {
777 value: Default::default(),
778 meta: new_meta,
779 }
780 })
781 } else {
782 value
783 }
784 }
785 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
786 Expr::FunctionExpression(function_expression) => {
787 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
788 let experimental = attrs.map(|a| a.experimental).unwrap_or_default();
789 let is_std = matches!(&exec_state.mod_local.path, ModulePath::Std { .. });
790
791 let include_in_feature_tree = attrs.unwrap_or_default().include_in_feature_tree;
793 let closure = if let Some(attrs) = attrs
794 && (attrs.impl_ == annotations::Impl::Rust || attrs.impl_ == annotations::Impl::RustConstraint)
795 {
796 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
797 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
798 KclValue::Function {
799 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
800 meta: vec![metadata.to_owned()],
801 }
802 } else {
803 return Err(KclError::new_semantic(KclErrorDetails::new(
804 "Rust implementation of functions is restricted to the standard library".to_owned(),
805 vec![metadata.source_range],
806 )));
807 }
808 } else {
809 KclValue::Function {
813 value: Box::new(FunctionSource::kcl(
814 function_expression.clone(),
815 exec_state.mut_stack().snapshot(),
816 KclFunctionSourceParams {
817 is_std,
818 experimental,
819 include_in_feature_tree,
820 },
821 )),
822 meta: vec![metadata.to_owned()],
823 }
824 };
825
826 if let Some(fn_name) = &function_expression.name {
829 exec_state
830 .mut_stack()
831 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
832 }
833
834 closure
835 }
836 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
837 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
838 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
839 StatementKind::Declaration { name } => {
840 let message = format!(
841 "you cannot declare variable {name} as %, because % can only be used in function calls"
842 );
843
844 return Err(KclError::new_semantic(KclErrorDetails::new(
845 message,
846 vec![pipe_substitution.into()],
847 )));
848 }
849 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
850 Some(x) => x,
851 None => {
852 return Err(KclError::new_semantic(KclErrorDetails::new(
853 "cannot use % outside a pipe expression".to_owned(),
854 vec![pipe_substitution.into()],
855 )));
856 }
857 },
858 },
859 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
860 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
861 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
862 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
863 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
864 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
865 Expr::LabelledExpression(expr) => {
866 let result = self
867 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
868 .await?;
869 exec_state
870 .mut_stack()
871 .add(expr.label.name.clone(), result.clone(), init.into())?;
872 result
874 }
875 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
876 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
877 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?,
878 };
879 Ok(item)
880 }
881}
882
883fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
886 let KclError::UndefinedValue { name, mut details } = e else {
887 return e;
888 };
889 if let (Some(name0), Some(name1)) = (&being_declared, &name)
893 && name0 == name1
894 {
895 details.message = format!(
896 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
897 );
898 }
899 KclError::UndefinedValue { details, name }
900}
901
902impl Node<AscribedExpression> {
903 #[async_recursion]
904 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
905 let metadata = Metadata {
906 source_range: SourceRange::from(self),
907 };
908 let result = ctx
909 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
910 .await?;
911 apply_ascription(&result, &self.ty, exec_state, self.into())
912 }
913}
914
915impl Node<SketchBlock> {
916 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
917 if exec_state.mod_local.sketch_block.is_some() {
918 return Err(KclError::new_semantic(KclErrorDetails::new(
920 "Cannot execute a sketch block from within another sketch block".to_owned(),
921 vec![SourceRange::from(self)],
922 )));
923 }
924
925 let (return_result, variables, sketch_block_state) = {
926 self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
928
929 let original_value = exec_state.mod_local.sketch_block.replace(SketchBlockState::default());
931
932 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
933
934 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
935
936 let block_variables = exec_state
937 .stack()
938 .find_all_in_current_env()
939 .map(|(name, value)| (name.clone(), value.clone()))
940 .collect::<IndexMap<_, _>>();
941
942 exec_state.mut_stack().pop_env();
943
944 (result, block_variables, sketch_block_state)
945 };
946
947 return_result?;
949 let Some(sketch_block_state) = sketch_block_state else {
950 debug_assert!(false, "Sketch block state should still be set to Some from just above");
951 return Err(KclError::new_internal(KclErrorDetails::new(
952 "Sketch block state should still be set to Some from just above".to_owned(),
953 vec![SourceRange::from(self)],
954 )));
955 };
956
957 let range = SourceRange::from(self);
959 let constraints = &sketch_block_state.constraints;
960 let initial_guesses = sketch_block_state
961 .sketch_vars
962 .iter()
963 .map(|v| {
964 let Some(sketch_var) = v.as_sketch_var() else {
965 return Err(KclError::new_internal(KclErrorDetails::new(
966 "Expected sketch variable".to_owned(),
967 vec![SourceRange::from(self)],
968 )));
969 };
970 let constraint_id = sketch_var.id.to_constraint_id(range)?;
971 let number_value = KclValue::Number {
973 value: sketch_var.initial_value,
974 ty: sketch_var.ty,
975 meta: sketch_var.meta.clone(),
976 };
977 let initial_guess_value =
978 normalize_to_solver_unit(&number_value, v.into(), exec_state, "sketch variable initial value")?;
979 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
980 n.n
981 } else {
982 let message = format!(
983 "Expected number after coercion, but found {}",
984 initial_guess_value.human_friendly_type()
985 );
986 debug_assert!(false, "{}", &message);
987 return Err(KclError::new_internal(KclErrorDetails::new(
988 message,
989 vec![SourceRange::from(self)],
990 )));
991 };
992 Ok((constraint_id, initial_guess))
993 })
994 .collect::<Result<Vec<_>, KclError>>()?;
995 let config = kcl_ezpz::Config::default();
997 let solve_outcome = kcl_ezpz::solve(constraints, initial_guesses, config).map_err(|e| {
998 KclError::new_internal(KclErrorDetails::new(
999 format!("Error from constraint solver: {}", e.error),
1000 vec![SourceRange::from(self)],
1001 ))
1002 })?;
1003 for warning in &solve_outcome.warnings {
1005 let message = if let Some(index) = warning.about_constraint.as_ref() {
1006 format!("{}; constraint index {}", &warning.content, index)
1007 } else {
1008 format!("{}", &warning.content)
1009 };
1010 exec_state.warn(CompilationError::err(range, message), annotations::WARN_SOLVER);
1011 }
1012 let variables =
1014 substitute_sketch_vars(variables, &solve_outcome.final_values, solver_numeric_type(exec_state))?;
1015
1016 let metadata = Metadata {
1017 source_range: SourceRange::from(self),
1018 };
1019 Ok(KclValue::Object {
1020 value: variables,
1021 constrainable: Default::default(),
1022 meta: vec![metadata],
1023 })
1024 }
1025}
1026
1027fn solver_unit(exec_state: &ExecState) -> UnitLength {
1028 exec_state.length_unit()
1029}
1030
1031fn solver_numeric_type(exec_state: &ExecState) -> NumericType {
1032 NumericType::Known(UnitType::Length(solver_unit(exec_state)))
1033}
1034
1035fn normalize_to_solver_unit(
1038 value: &KclValue,
1039 source_range: SourceRange,
1040 exec_state: &mut ExecState,
1041 description: &str,
1042) -> Result<KclValue, KclError> {
1043 let length_ty = RuntimeType::Primitive(PrimitiveType::Number(solver_numeric_type(exec_state)));
1044 value.coerce(&length_ty, true, exec_state).map_err(|_| {
1045 KclError::new_semantic(KclErrorDetails::new(
1046 format!(
1047 "{} must be a length coercible to the module length unit {}, but found {}",
1048 description,
1049 length_ty.human_friendly_type(),
1050 value.human_friendly_type(),
1051 ),
1052 vec![source_range],
1053 ))
1054 })
1055}
1056
1057fn substitute_sketch_vars(
1058 variables: IndexMap<String, KclValue>,
1059 solutions: &[f64],
1060 solution_ty: NumericType,
1061) -> Result<HashMap<String, KclValue>, KclError> {
1062 let mut subbed = HashMap::with_capacity(variables.len());
1063 for (name, value) in variables {
1064 let subbed_value = substitute_sketch_var(value, solutions, solution_ty)?;
1065 subbed.insert(name, subbed_value);
1066 }
1067 Ok(subbed)
1068}
1069
1070fn substitute_sketch_var(value: KclValue, solutions: &[f64], solution_ty: NumericType) -> Result<KclValue, KclError> {
1071 match value {
1072 KclValue::Uuid { .. } => Ok(value),
1073 KclValue::Bool { .. } => Ok(value),
1074 KclValue::Number { .. } => Ok(value),
1075 KclValue::String { .. } => Ok(value),
1076 KclValue::SketchVar { value: var } => {
1077 let Some(solution) = solutions.get(var.id.0) else {
1078 let message = format!("No solution for sketch variable with id {}", var.id.0);
1079 debug_assert!(false, "{}", &message);
1080 return Err(KclError::new_internal(KclErrorDetails::new(
1081 message,
1082 var.meta.into_iter().map(|m| m.source_range).collect(),
1083 )));
1084 };
1085 Ok(KclValue::Number {
1086 value: *solution,
1087 ty: solution_ty,
1088 meta: var.meta.clone(),
1089 })
1090 }
1091 KclValue::Tuple { value, meta } => {
1092 let subbed = value
1093 .into_iter()
1094 .map(|v| substitute_sketch_var(v, solutions, solution_ty))
1095 .collect::<Result<Vec<_>, KclError>>()?;
1096 Ok(KclValue::Tuple { value: subbed, meta })
1097 }
1098 KclValue::HomArray { value, ty } => {
1099 let subbed = value
1100 .into_iter()
1101 .map(|v| substitute_sketch_var(v, solutions, solution_ty))
1102 .collect::<Result<Vec<_>, KclError>>()?;
1103 Ok(KclValue::HomArray { value: subbed, ty })
1104 }
1105 KclValue::Object {
1106 value,
1107 constrainable,
1108 meta,
1109 } => {
1110 let subbed = value
1111 .into_iter()
1112 .map(|(k, v)| substitute_sketch_var(v, solutions, solution_ty).map(|v| (k, v)))
1113 .collect::<Result<HashMap<_, _>, KclError>>()?;
1114 Ok(KclValue::Object {
1115 value: subbed,
1116 constrainable,
1117 meta,
1118 })
1119 }
1120 KclValue::TagIdentifier(_) => Ok(value),
1121 KclValue::TagDeclarator(_) => Ok(value),
1122 KclValue::GdtAnnotation { .. } => Ok(value),
1123 KclValue::Plane { .. } => Ok(value),
1124 KclValue::Face { .. } => Ok(value),
1125 KclValue::Sketch { .. } => Ok(value),
1126 KclValue::Solid { .. } => Ok(value),
1127 KclValue::Helix { .. } => Ok(value),
1128 KclValue::ImportedGeometry(_) => Ok(value),
1129 KclValue::Function { .. } => Ok(value),
1130 KclValue::Module { .. } => Ok(value),
1131 KclValue::Type { .. } => Ok(value),
1132 KclValue::KclNone { .. } => Ok(value),
1133 }
1134}
1135
1136impl SketchBlock {
1137 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1138 exec_state.mut_stack().push_new_env_for_call(parent);
1139 }
1140}
1141
1142impl Node<SketchVar> {
1143 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1144 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1145 return Err(KclError::new_semantic(KclErrorDetails::new(
1146 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1147 vec![SourceRange::from(self)],
1148 )));
1149 };
1150 let id = sketch_block_state.next_sketch_var_id();
1151 let sketch_var = if let Some(initial) = &self.initial {
1152 KclValue::from_sketch_var_literal(initial, id, exec_state)
1153 } else {
1154 let metadata = Metadata {
1155 source_range: SourceRange::from(self),
1156 };
1157
1158 KclValue::SketchVar {
1159 value: Box::new(super::SketchVar {
1160 id,
1161 initial_value: 0.0,
1162 ty: NumericType::default(),
1163 meta: vec![metadata],
1164 }),
1165 }
1166 };
1167
1168 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1169 return Err(KclError::new_semantic(KclErrorDetails::new(
1170 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1171 vec![SourceRange::from(self)],
1172 )));
1173 };
1174 sketch_block_state.sketch_vars.push(sketch_var.clone());
1175
1176 Ok(sketch_var)
1177 }
1178}
1179
1180fn apply_ascription(
1181 value: &KclValue,
1182 ty: &Node<Type>,
1183 exec_state: &mut ExecState,
1184 source_range: SourceRange,
1185) -> Result<KclValue, KclError> {
1186 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false)
1187 .map_err(|e| KclError::new_semantic(e.into()))?;
1188
1189 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1190 exec_state.clear_units_warnings(&source_range);
1191 }
1192
1193 value.coerce(&ty, false, exec_state).map_err(|_| {
1194 let suggestion = if ty == RuntimeType::length() {
1195 ", you might try coercing to a fully specified numeric type such as `mm`"
1196 } else if ty == RuntimeType::angle() {
1197 ", you might try coercing to a fully specified numeric type such as `deg`"
1198 } else {
1199 ""
1200 };
1201 let ty_str = if let Some(ty) = value.principal_type() {
1202 format!("(with type `{ty}`) ")
1203 } else {
1204 String::new()
1205 };
1206 KclError::new_semantic(KclErrorDetails::new(
1207 format!(
1208 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1209 value.human_friendly_type()
1210 ),
1211 vec![source_range],
1212 ))
1213 })
1214}
1215
1216impl BinaryPart {
1217 #[async_recursion]
1218 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1219 match self {
1220 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state)),
1221 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned(),
1222 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1223 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1224 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1225 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1226 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1227 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1228 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1229 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1230 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1231 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await,
1232 }
1233 }
1234}
1235
1236impl Node<Name> {
1237 pub(super) async fn get_result<'a>(
1238 &self,
1239 exec_state: &'a mut ExecState,
1240 ctx: &ExecutorContext,
1241 ) -> Result<&'a KclValue, KclError> {
1242 let being_declared = exec_state.mod_local.being_declared.clone();
1243 self.get_result_inner(exec_state, ctx)
1244 .await
1245 .map_err(|e| var_in_own_ref_err(e, &being_declared))
1246 }
1247
1248 async fn get_result_inner<'a>(
1249 &self,
1250 exec_state: &'a mut ExecState,
1251 ctx: &ExecutorContext,
1252 ) -> Result<&'a KclValue, KclError> {
1253 if self.abs_path {
1254 return Err(KclError::new_semantic(KclErrorDetails::new(
1255 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1256 self.as_source_ranges(),
1257 )));
1258 }
1259
1260 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1261
1262 if self.path.is_empty() {
1263 let item_value = exec_state.stack().get(&self.name.name, self.into());
1264 if item_value.is_ok() {
1265 return item_value;
1266 }
1267 return exec_state.stack().get(&mod_name, self.into());
1268 }
1269
1270 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1271 for p in &self.path {
1272 let value = match mem_spec {
1273 Some((env, exports)) => {
1274 if !exports.contains(&p.name) {
1275 return Err(KclError::new_semantic(KclErrorDetails::new(
1276 format!("Item {} not found in module's exported items", p.name),
1277 p.as_source_ranges(),
1278 )));
1279 }
1280
1281 exec_state
1282 .stack()
1283 .memory
1284 .get_from(&p.name, env, p.as_source_range(), 0)?
1285 }
1286 None => exec_state
1287 .stack()
1288 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
1289 };
1290
1291 let KclValue::Module { value: module_id, .. } = value else {
1292 return Err(KclError::new_semantic(KclErrorDetails::new(
1293 format!(
1294 "Identifier in path must refer to a module, found {}",
1295 value.human_friendly_type()
1296 ),
1297 p.as_source_ranges(),
1298 )));
1299 };
1300
1301 mem_spec = Some(
1302 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1303 .await?,
1304 );
1305 }
1306
1307 let (env, exports) = mem_spec.unwrap();
1308
1309 let item_exported = exports.contains(&self.name.name);
1310 let item_value = exec_state
1311 .stack()
1312 .memory
1313 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1314
1315 if item_exported && item_value.is_ok() {
1317 return item_value;
1318 }
1319
1320 let mod_exported = exports.contains(&mod_name);
1321 let mod_value = exec_state
1322 .stack()
1323 .memory
1324 .get_from(&mod_name, env, self.name.as_source_range(), 0);
1325
1326 if mod_exported && mod_value.is_ok() {
1328 return mod_value;
1329 }
1330
1331 if item_value.is_err() && mod_value.is_err() {
1333 return item_value;
1334 }
1335
1336 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1338 Err(KclError::new_semantic(KclErrorDetails::new(
1339 format!("Item {} not found in module's exported items", self.name.name),
1340 self.name.as_source_ranges(),
1341 )))
1342 }
1343}
1344
1345impl Node<MemberExpression> {
1346 async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1347 let meta = Metadata {
1348 source_range: SourceRange::from(self),
1349 };
1350 let property = Property::try_from(
1351 self.computed,
1352 self.property.clone(),
1353 exec_state,
1354 self.into(),
1355 ctx,
1356 &meta,
1357 &[],
1358 StatementKind::Expression,
1359 )
1360 .await?;
1361 let object = ctx
1362 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1363 .await?;
1364
1365 match (object, property, self.computed) {
1367 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
1368 "zAxis" => {
1369 let (p, u) = plane.info.z_axis.as_3_dims();
1370 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1371 }
1372 "yAxis" => {
1373 let (p, u) = plane.info.y_axis.as_3_dims();
1374 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1375 }
1376 "xAxis" => {
1377 let (p, u) = plane.info.x_axis.as_3_dims();
1378 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1379 }
1380 "origin" => {
1381 let (p, u) = plane.info.origin.as_3_dims();
1382 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
1383 }
1384 other => Err(KclError::new_undefined_value(
1385 KclErrorDetails::new(
1386 format!("Property '{other}' not found in plane"),
1387 vec![self.clone().into()],
1388 ),
1389 None,
1390 )),
1391 },
1392 (KclValue::Object { value: map, .. }, Property::String(property), false) => {
1393 if let Some(value) = map.get(&property) {
1394 Ok(value.to_owned())
1395 } else {
1396 Err(KclError::new_undefined_value(
1397 KclErrorDetails::new(
1398 format!("Property '{property}' not found in object"),
1399 vec![self.clone().into()],
1400 ),
1401 None,
1402 ))
1403 }
1404 }
1405 (KclValue::Object { .. }, Property::String(property), true) => {
1406 Err(KclError::new_semantic(KclErrorDetails::new(
1407 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
1408 vec![self.clone().into()],
1409 )))
1410 }
1411 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
1412 if i == 0
1413 && let Some(value) = map.get("x")
1414 {
1415 return Ok(value.to_owned());
1416 }
1417 if i == 1
1418 && let Some(value) = map.get("y")
1419 {
1420 return Ok(value.to_owned());
1421 }
1422 if i == 2
1423 && let Some(value) = map.get("z")
1424 {
1425 return Ok(value.to_owned());
1426 }
1427 let t = p.type_name();
1428 let article = article_for(t);
1429 Err(KclError::new_semantic(KclErrorDetails::new(
1430 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
1431 vec![self.clone().into()],
1432 )))
1433 }
1434 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
1435 let value_of_arr = arr.get(index);
1436 if let Some(value) = value_of_arr {
1437 Ok(value.to_owned())
1438 } else {
1439 Err(KclError::new_undefined_value(
1440 KclErrorDetails::new(
1441 format!("The array doesn't have any item at index {index}"),
1442 vec![self.clone().into()],
1443 ),
1444 None,
1445 ))
1446 }
1447 }
1448 (obj, Property::UInt(0), _) => Ok(obj),
1451 (KclValue::HomArray { .. }, p, _) => {
1452 let t = p.type_name();
1453 let article = article_for(t);
1454 Err(KclError::new_semantic(KclErrorDetails::new(
1455 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
1456 vec![self.clone().into()],
1457 )))
1458 }
1459 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
1460 value: Box::new(value.sketch),
1461 }),
1462 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
1463 Err(KclError::new_semantic(KclErrorDetails::new(
1465 format!(
1466 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
1467 geometry.human_friendly_type()
1468 ),
1469 vec![self.clone().into()],
1470 )))
1471 }
1472 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
1473 meta: vec![Metadata {
1474 source_range: SourceRange::from(self.clone()),
1475 }],
1476 value: sk
1477 .tags
1478 .iter()
1479 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
1480 .collect(),
1481 constrainable: false,
1482 }),
1483 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
1484 Err(KclError::new_semantic(KclErrorDetails::new(
1485 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
1486 vec![self.clone().into()],
1487 )))
1488 }
1489 (being_indexed, _, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1490 format!(
1491 "Only arrays can be indexed, but you're trying to index {}",
1492 being_indexed.human_friendly_type()
1493 ),
1494 vec![self.clone().into()],
1495 ))),
1496 }
1497 }
1498}
1499
1500impl Node<BinaryExpression> {
1501 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1502 enum State {
1503 EvaluateLeft(Node<BinaryExpression>),
1504 FromLeft {
1505 node: Node<BinaryExpression>,
1506 },
1507 EvaluateRight {
1508 node: Node<BinaryExpression>,
1509 left: KclValue,
1510 },
1511 FromRight {
1512 node: Node<BinaryExpression>,
1513 left: KclValue,
1514 },
1515 }
1516
1517 let mut stack = vec![State::EvaluateLeft(self.clone())];
1518 let mut last_result: Option<KclValue> = None;
1519
1520 while let Some(state) = stack.pop() {
1521 match state {
1522 State::EvaluateLeft(node) => {
1523 let left_part = node.left.clone();
1524 match left_part {
1525 BinaryPart::BinaryExpression(child) => {
1526 stack.push(State::FromLeft { node });
1527 stack.push(State::EvaluateLeft(*child));
1528 }
1529 part => {
1530 let left_value = part.get_result(exec_state, ctx).await?;
1531 stack.push(State::EvaluateRight { node, left: left_value });
1532 }
1533 }
1534 }
1535 State::FromLeft { node } => {
1536 let Some(left_value) = last_result.take() else {
1537 return Err(Self::missing_result_error(&node));
1538 };
1539 stack.push(State::EvaluateRight { node, left: left_value });
1540 }
1541 State::EvaluateRight { node, left } => {
1542 let right_part = node.right.clone();
1543 match right_part {
1544 BinaryPart::BinaryExpression(child) => {
1545 stack.push(State::FromRight { node, left });
1546 stack.push(State::EvaluateLeft(*child));
1547 }
1548 part => {
1549 let right_value = part.get_result(exec_state, ctx).await?;
1550 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
1551 last_result = Some(result);
1552 }
1553 }
1554 }
1555 State::FromRight { node, left } => {
1556 let Some(right_value) = last_result.take() else {
1557 return Err(Self::missing_result_error(&node));
1558 };
1559 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
1560 last_result = Some(result);
1561 }
1562 }
1563 }
1564
1565 last_result.ok_or_else(|| Self::missing_result_error(self))
1566 }
1567
1568 async fn apply_operator(
1569 &self,
1570 exec_state: &mut ExecState,
1571 ctx: &ExecutorContext,
1572 left_value: KclValue,
1573 right_value: KclValue,
1574 ) -> Result<KclValue, KclError> {
1575 let mut meta = left_value.metadata();
1576 meta.extend(right_value.metadata());
1577
1578 if self.operator == BinaryOperator::Add
1580 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
1581 (&left_value, &right_value)
1582 {
1583 return Ok(KclValue::String {
1584 value: format!("{left}{right}"),
1585 meta,
1586 });
1587 }
1588
1589 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
1591 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1592 let args = Args::new_no_args(self.into(), ctx.clone(), Some("union".to_owned()));
1593 let result = crate::std::csg::inner_union(
1594 vec![*left.clone(), *right.clone()],
1595 Default::default(),
1596 exec_state,
1597 args,
1598 )
1599 .await?;
1600 return Ok(result.into());
1601 }
1602 } else if self.operator == BinaryOperator::Sub {
1603 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
1605 let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
1606 let result = crate::std::csg::inner_subtract(
1607 vec![*left.clone()],
1608 vec![*right.clone()],
1609 Default::default(),
1610 exec_state,
1611 args,
1612 )
1613 .await?;
1614 return Ok(result.into());
1615 }
1616 } else if self.operator == BinaryOperator::And
1617 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
1618 {
1619 let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
1621 let result = crate::std::csg::inner_intersect(
1622 vec![*left.clone(), *right.clone()],
1623 Default::default(),
1624 exec_state,
1625 args,
1626 )
1627 .await?;
1628 return Ok(result.into());
1629 }
1630
1631 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
1633 let KclValue::Bool { value: left_value, .. } = left_value else {
1634 return Err(KclError::new_semantic(KclErrorDetails::new(
1635 format!(
1636 "Cannot apply logical operator to non-boolean value: {}",
1637 left_value.human_friendly_type()
1638 ),
1639 vec![self.left.clone().into()],
1640 )));
1641 };
1642 let KclValue::Bool { value: right_value, .. } = right_value else {
1643 return Err(KclError::new_semantic(KclErrorDetails::new(
1644 format!(
1645 "Cannot apply logical operator to non-boolean value: {}",
1646 right_value.human_friendly_type()
1647 ),
1648 vec![self.right.clone().into()],
1649 )));
1650 };
1651 let raw_value = match self.operator {
1652 BinaryOperator::Or => left_value || right_value,
1653 BinaryOperator::And => left_value && right_value,
1654 _ => unreachable!(),
1655 };
1656 return Ok(KclValue::Bool { value: raw_value, meta });
1657 }
1658
1659 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
1661 match (&left_value, &right_value) {
1662 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
1664 if left_value.id == right_value.id =>
1665 {
1666 return Ok(KclValue::Bool { value: true, meta });
1667 }
1668 (KclValue::SketchVar { .. }, KclValue::SketchVar { .. }) => {
1670 return Err(KclError::new_semantic(KclErrorDetails::new(
1673 "TODO: Different sketch variables".to_owned(),
1674 vec![self.into()],
1675 )));
1676 }
1677 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
1679 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
1680 let number_value = normalize_to_solver_unit(
1681 input_number,
1682 input_number.into(),
1683 exec_state,
1684 "fixed constraint value",
1685 )?;
1686 let Some(n) = number_value.as_ty_f64() else {
1687 let message = format!(
1688 "Expected number after coercion, but found {}",
1689 number_value.human_friendly_type()
1690 );
1691 debug_assert!(false, "{}", &message);
1692 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![self.into()])));
1693 };
1694 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
1695 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1696 let message = "Being inside a sketch block should have already been checked above".to_owned();
1697 debug_assert!(false, "{}", &message);
1698 return Err(KclError::new_internal(KclErrorDetails::new(
1699 message,
1700 vec![SourceRange::from(self)],
1701 )));
1702 };
1703 sketch_block_state.constraints.push(constraint);
1704 return Ok(KclValue::Bool { value: true, meta });
1705 }
1706 _ => {
1707 return Err(KclError::new_semantic(KclErrorDetails::new(
1708 format!(
1709 "Cannot create an equivalence constraint between values of these types: {} and {}",
1710 left_value.human_friendly_type(),
1711 right_value.human_friendly_type()
1712 ),
1713 vec![self.into()],
1714 )));
1715 }
1716 }
1717 }
1718
1719 let left = number_as_f64(&left_value, self.left.clone().into())?;
1720 let right = number_as_f64(&right_value, self.right.clone().into())?;
1721
1722 let value = match self.operator {
1723 BinaryOperator::Add => {
1724 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
1725 self.warn_on_unknown(&ty, "Adding", exec_state);
1726 KclValue::Number { value: l + r, meta, ty }
1727 }
1728 BinaryOperator::Sub => {
1729 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
1730 self.warn_on_unknown(&ty, "Subtracting", exec_state);
1731 KclValue::Number { value: l - r, meta, ty }
1732 }
1733 BinaryOperator::Mul => {
1734 let (l, r, ty) = NumericType::combine_mul(left, right);
1735 self.warn_on_unknown(&ty, "Multiplying", exec_state);
1736 KclValue::Number { value: l * r, meta, ty }
1737 }
1738 BinaryOperator::Div => {
1739 let (l, r, ty) = NumericType::combine_div(left, right);
1740 self.warn_on_unknown(&ty, "Dividing", exec_state);
1741 KclValue::Number { value: l / r, meta, ty }
1742 }
1743 BinaryOperator::Mod => {
1744 let (l, r, ty) = NumericType::combine_mod(left, right);
1745 self.warn_on_unknown(&ty, "Modulo of", exec_state);
1746 KclValue::Number { value: l % r, meta, ty }
1747 }
1748 BinaryOperator::Pow => KclValue::Number {
1749 value: left.n.powf(right.n),
1750 meta,
1751 ty: exec_state.current_default_units(),
1752 },
1753 BinaryOperator::Neq => {
1754 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1755 self.warn_on_unknown(&ty, "Comparing", exec_state);
1756 KclValue::Bool { value: l != r, meta }
1757 }
1758 BinaryOperator::Gt => {
1759 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1760 self.warn_on_unknown(&ty, "Comparing", exec_state);
1761 KclValue::Bool { value: l > r, meta }
1762 }
1763 BinaryOperator::Gte => {
1764 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1765 self.warn_on_unknown(&ty, "Comparing", exec_state);
1766 KclValue::Bool { value: l >= r, meta }
1767 }
1768 BinaryOperator::Lt => {
1769 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1770 self.warn_on_unknown(&ty, "Comparing", exec_state);
1771 KclValue::Bool { value: l < r, meta }
1772 }
1773 BinaryOperator::Lte => {
1774 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1775 self.warn_on_unknown(&ty, "Comparing", exec_state);
1776 KclValue::Bool { value: l <= r, meta }
1777 }
1778 BinaryOperator::Eq => {
1779 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
1780 self.warn_on_unknown(&ty, "Comparing", exec_state);
1781 KclValue::Bool { value: l == r, meta }
1782 }
1783 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
1784 };
1785
1786 Ok(value)
1787 }
1788
1789 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
1790 KclError::new_internal(KclErrorDetails::new(
1791 "missing result while evaluating binary expression".to_owned(),
1792 vec![SourceRange::from(node)],
1793 ))
1794 }
1795
1796 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
1797 if ty == &NumericType::Unknown {
1798 let sr = self.as_source_range();
1799 exec_state.clear_units_warnings(&sr);
1800 let mut err = CompilationError::err(
1801 sr,
1802 format!(
1803 "{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)`."
1804 ),
1805 );
1806 err.tag = crate::errors::Tag::UnknownNumericUnits;
1807 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
1808 }
1809 }
1810}
1811
1812impl Node<UnaryExpression> {
1813 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1814 match self.operator {
1815 UnaryOperator::Not => {
1816 let value = self.argument.get_result(exec_state, ctx).await?;
1817 let KclValue::Bool {
1818 value: bool_value,
1819 meta: _,
1820 } = value
1821 else {
1822 return Err(KclError::new_semantic(KclErrorDetails::new(
1823 format!(
1824 "Cannot apply unary operator ! to non-boolean value: {}",
1825 value.human_friendly_type()
1826 ),
1827 vec![self.into()],
1828 )));
1829 };
1830 let meta = vec![Metadata {
1831 source_range: self.into(),
1832 }];
1833 let negated = KclValue::Bool {
1834 value: !bool_value,
1835 meta,
1836 };
1837
1838 Ok(negated)
1839 }
1840 UnaryOperator::Neg => {
1841 let value = &self.argument.get_result(exec_state, ctx).await?;
1842 let err = || {
1843 KclError::new_semantic(KclErrorDetails::new(
1844 format!(
1845 "You can only negate numbers, planes, or lines, but this is a {}",
1846 value.human_friendly_type()
1847 ),
1848 vec![self.into()],
1849 ))
1850 };
1851 match value {
1852 KclValue::Number { value, ty, .. } => {
1853 let meta = vec![Metadata {
1854 source_range: self.into(),
1855 }];
1856 Ok(KclValue::Number {
1857 value: -value,
1858 meta,
1859 ty: *ty,
1860 })
1861 }
1862 KclValue::Plane { value } => {
1863 let mut plane = value.clone();
1864 if plane.info.x_axis.x != 0.0 {
1865 plane.info.x_axis.x *= -1.0;
1866 }
1867 if plane.info.x_axis.y != 0.0 {
1868 plane.info.x_axis.y *= -1.0;
1869 }
1870 if plane.info.x_axis.z != 0.0 {
1871 plane.info.x_axis.z *= -1.0;
1872 }
1873
1874 plane.value = PlaneType::Uninit;
1875 plane.id = exec_state.next_uuid();
1876 Ok(KclValue::Plane { value: plane })
1877 }
1878 KclValue::Object {
1879 value: values, meta, ..
1880 } => {
1881 let Some(direction) = values.get("direction") else {
1883 return Err(err());
1884 };
1885
1886 let direction = match direction {
1887 KclValue::Tuple { value: values, meta } => {
1888 let values = values
1889 .iter()
1890 .map(|v| match v {
1891 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1892 value: *value * -1.0,
1893 ty: *ty,
1894 meta: meta.clone(),
1895 }),
1896 _ => Err(err()),
1897 })
1898 .collect::<Result<Vec<_>, _>>()?;
1899
1900 KclValue::Tuple {
1901 value: values,
1902 meta: meta.clone(),
1903 }
1904 }
1905 KclValue::HomArray {
1906 value: values,
1907 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
1908 } => {
1909 let values = values
1910 .iter()
1911 .map(|v| match v {
1912 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
1913 value: *value * -1.0,
1914 ty: *ty,
1915 meta: meta.clone(),
1916 }),
1917 _ => Err(err()),
1918 })
1919 .collect::<Result<Vec<_>, _>>()?;
1920
1921 KclValue::HomArray {
1922 value: values,
1923 ty: ty.clone(),
1924 }
1925 }
1926 _ => return Err(err()),
1927 };
1928
1929 let mut value = values.clone();
1930 value.insert("direction".to_owned(), direction);
1931 Ok(KclValue::Object {
1932 value,
1933 meta: meta.clone(),
1934 constrainable: false,
1935 })
1936 }
1937 _ => Err(err()),
1938 }
1939 }
1940 UnaryOperator::Plus => {
1941 let operand = &self.argument.get_result(exec_state, ctx).await?;
1942 match operand {
1943 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.clone()),
1944 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1945 format!(
1946 "You can only apply unary + to numbers or planes, but this is a {}",
1947 operand.human_friendly_type()
1948 ),
1949 vec![self.into()],
1950 ))),
1951 }
1952 }
1953 }
1954 }
1955}
1956
1957pub(crate) async fn execute_pipe_body(
1958 exec_state: &mut ExecState,
1959 body: &[Expr],
1960 source_range: SourceRange,
1961 ctx: &ExecutorContext,
1962) -> Result<KclValue, KclError> {
1963 let Some((first, body)) = body.split_first() else {
1964 return Err(KclError::new_semantic(KclErrorDetails::new(
1965 "Pipe expressions cannot be empty".to_owned(),
1966 vec![source_range],
1967 )));
1968 };
1969 let meta = Metadata {
1974 source_range: SourceRange::from(first),
1975 };
1976 let output = ctx
1977 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
1978 .await?;
1979
1980 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
1984 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
1986 exec_state.mod_local.pipe_value = previous_pipe_value;
1988
1989 result
1990}
1991
1992#[async_recursion]
1995async fn inner_execute_pipe_body(
1996 exec_state: &mut ExecState,
1997 body: &[Expr],
1998 ctx: &ExecutorContext,
1999) -> Result<KclValue, KclError> {
2000 for expression in body {
2001 if let Expr::TagDeclarator(_) = expression {
2002 return Err(KclError::new_semantic(KclErrorDetails::new(
2003 format!("This cannot be in a PipeExpression: {expression:?}"),
2004 vec![expression.into()],
2005 )));
2006 }
2007 let metadata = Metadata {
2008 source_range: SourceRange::from(expression),
2009 };
2010 let output = ctx
2011 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
2012 .await?;
2013 exec_state.mod_local.pipe_value = Some(output);
2014 }
2015 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
2017 Ok(final_output)
2018}
2019
2020impl Node<TagDeclarator> {
2021 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
2022 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
2023 value: self.name.clone(),
2024 info: Vec::new(),
2025 meta: vec![Metadata {
2026 source_range: self.into(),
2027 }],
2028 }));
2029
2030 exec_state
2031 .mut_stack()
2032 .add(self.name.clone(), memory_item, self.into())?;
2033
2034 Ok(self.into())
2035 }
2036}
2037
2038impl Node<ArrayExpression> {
2039 #[async_recursion]
2040 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2041 let mut results = Vec::with_capacity(self.elements.len());
2042
2043 for element in &self.elements {
2044 let metadata = Metadata::from(element);
2045 let value = ctx
2048 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
2049 .await?;
2050
2051 results.push(value);
2052 }
2053
2054 Ok(KclValue::HomArray {
2055 value: results,
2056 ty: RuntimeType::Primitive(PrimitiveType::Any),
2057 })
2058 }
2059}
2060
2061impl Node<ArrayRangeExpression> {
2062 #[async_recursion]
2063 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2064 let metadata = Metadata::from(&self.start_element);
2065 let start_val = ctx
2066 .execute_expr(
2067 &self.start_element,
2068 exec_state,
2069 &metadata,
2070 &[],
2071 StatementKind::Expression,
2072 )
2073 .await?;
2074 let start = start_val
2075 .as_ty_f64()
2076 .ok_or(KclError::new_semantic(KclErrorDetails::new(
2077 format!(
2078 "Expected number for range start but found {}",
2079 start_val.human_friendly_type()
2080 ),
2081 vec![self.into()],
2082 )))?;
2083 let metadata = Metadata::from(&self.end_element);
2084 let end_val = ctx
2085 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
2086 .await?;
2087 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
2088 format!(
2089 "Expected number for range end but found {}",
2090 end_val.human_friendly_type()
2091 ),
2092 vec![self.into()],
2093 )))?;
2094
2095 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
2096 let Some(start) = crate::try_f64_to_i64(start) else {
2097 return Err(KclError::new_semantic(KclErrorDetails::new(
2098 format!("Range start must be an integer, but found {start}"),
2099 vec![self.into()],
2100 )));
2101 };
2102 let Some(end) = crate::try_f64_to_i64(end) else {
2103 return Err(KclError::new_semantic(KclErrorDetails::new(
2104 format!("Range end must be an integer, but found {end}"),
2105 vec![self.into()],
2106 )));
2107 };
2108
2109 if end < start {
2110 return Err(KclError::new_semantic(KclErrorDetails::new(
2111 format!("Range start is greater than range end: {start} .. {end}"),
2112 vec![self.into()],
2113 )));
2114 }
2115
2116 let range: Vec<_> = if self.end_inclusive {
2117 (start..=end).collect()
2118 } else {
2119 (start..end).collect()
2120 };
2121
2122 let meta = vec![Metadata {
2123 source_range: self.into(),
2124 }];
2125
2126 Ok(KclValue::HomArray {
2127 value: range
2128 .into_iter()
2129 .map(|num| KclValue::Number {
2130 value: num as f64,
2131 ty,
2132 meta: meta.clone(),
2133 })
2134 .collect(),
2135 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
2136 })
2137 }
2138}
2139
2140impl Node<ObjectExpression> {
2141 #[async_recursion]
2142 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2143 let mut object = HashMap::with_capacity(self.properties.len());
2144 for property in &self.properties {
2145 let metadata = Metadata::from(&property.value);
2146 let result = ctx
2147 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
2148 .await?;
2149
2150 object.insert(property.key.name.clone(), result);
2151 }
2152
2153 Ok(KclValue::Object {
2154 value: object,
2155 meta: vec![Metadata {
2156 source_range: self.into(),
2157 }],
2158 constrainable: false,
2159 })
2160 }
2161}
2162
2163fn article_for<S: AsRef<str>>(s: S) -> &'static str {
2164 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
2166 "an"
2167 } else {
2168 "a"
2169 }
2170}
2171
2172fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
2173 v.as_ty_f64().ok_or_else(|| {
2174 let actual_type = v.human_friendly_type();
2175 KclError::new_semantic(KclErrorDetails::new(
2176 format!("Expected a number, but found {actual_type}",),
2177 vec![source_range],
2178 ))
2179 })
2180}
2181
2182impl Node<IfExpression> {
2183 #[async_recursion]
2184 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2185 let cond = ctx
2187 .execute_expr(
2188 &self.cond,
2189 exec_state,
2190 &Metadata::from(self),
2191 &[],
2192 StatementKind::Expression,
2193 )
2194 .await?
2195 .get_bool()?;
2196 if cond {
2197 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
2198 return Ok(block_result.unwrap());
2202 }
2203
2204 for else_if in &self.else_ifs {
2206 let cond = ctx
2207 .execute_expr(
2208 &else_if.cond,
2209 exec_state,
2210 &Metadata::from(self),
2211 &[],
2212 StatementKind::Expression,
2213 )
2214 .await?
2215 .get_bool()?;
2216 if cond {
2217 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
2218 return Ok(block_result.unwrap());
2222 }
2223 }
2224
2225 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
2227 .await
2228 .map(|expr| expr.unwrap())
2229 }
2230}
2231
2232#[derive(Debug)]
2233enum Property {
2234 UInt(usize),
2235 String(String),
2236}
2237
2238impl Property {
2239 #[allow(clippy::too_many_arguments)]
2240 async fn try_from<'a>(
2241 computed: bool,
2242 value: Expr,
2243 exec_state: &mut ExecState,
2244 sr: SourceRange,
2245 ctx: &ExecutorContext,
2246 metadata: &Metadata,
2247 annotations: &[Node<Annotation>],
2248 statement_kind: StatementKind<'a>,
2249 ) -> Result<Self, KclError> {
2250 let property_sr = vec![sr];
2251 if !computed {
2252 let Expr::Name(identifier) = value else {
2253 return Err(KclError::new_semantic(KclErrorDetails::new(
2255 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
2256 .to_owned(),
2257 property_sr,
2258 )));
2259 };
2260 return Ok(Property::String(identifier.to_string()));
2261 }
2262
2263 let prop_value = ctx
2264 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
2265 .await?;
2266 match prop_value {
2267 KclValue::Number { value, ty, meta: _ } => {
2268 if !matches!(
2269 ty,
2270 NumericType::Unknown
2271 | NumericType::Default { .. }
2272 | NumericType::Known(crate::exec::UnitType::Count)
2273 ) {
2274 return Err(KclError::new_semantic(KclErrorDetails::new(
2275 format!(
2276 "{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"
2277 ),
2278 property_sr,
2279 )));
2280 }
2281 if let Some(x) = crate::try_f64_to_usize(value) {
2282 Ok(Property::UInt(x))
2283 } else {
2284 Err(KclError::new_semantic(KclErrorDetails::new(
2285 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
2286 property_sr,
2287 )))
2288 }
2289 }
2290 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2291 "Only numbers (>= 0) can be indexes".to_owned(),
2292 vec![sr],
2293 ))),
2294 }
2295 }
2296}
2297
2298impl Property {
2299 fn type_name(&self) -> &'static str {
2300 match self {
2301 Property::UInt(_) => "number",
2302 Property::String(_) => "string",
2303 }
2304 }
2305}
2306
2307impl Node<PipeExpression> {
2308 #[async_recursion]
2309 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2310 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
2311 }
2312}
2313
2314#[cfg(test)]
2315mod test {
2316 use std::sync::Arc;
2317
2318 use tokio::io::AsyncWriteExt;
2319
2320 use super::*;
2321 use crate::{
2322 ExecutorSettings,
2323 errors::Severity,
2324 exec::UnitType,
2325 execution::{ContextType, parse_execute},
2326 };
2327
2328 #[tokio::test(flavor = "multi_thread")]
2329 async fn ascription() {
2330 let program = r#"
2331a = 42: number
2332b = a: number
2333p = {
2334 origin = { x = 0, y = 0, z = 0 },
2335 xAxis = { x = 1, y = 0, z = 0 },
2336 yAxis = { x = 0, y = 1, z = 0 },
2337 zAxis = { x = 0, y = 0, z = 1 }
2338}: Plane
2339arr1 = [42]: [number(cm)]
2340"#;
2341
2342 let result = parse_execute(program).await.unwrap();
2343 let mem = result.exec_state.stack();
2344 assert!(matches!(
2345 mem.memory
2346 .get_from("p", result.mem_env, SourceRange::default(), 0)
2347 .unwrap(),
2348 KclValue::Plane { .. }
2349 ));
2350 let arr1 = mem
2351 .memory
2352 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
2353 .unwrap();
2354 if let KclValue::HomArray { value, ty } = arr1 {
2355 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
2356 assert_eq!(
2357 *ty,
2358 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
2359 );
2360 if let KclValue::Number { value, ty, .. } = &value[0] {
2362 assert_eq!(*value, 42.0);
2364 assert_eq!(
2365 *ty,
2366 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
2367 );
2368 } else {
2369 panic!("Expected a number; found {:?}", value[0]);
2370 }
2371 } else {
2372 panic!("Expected HomArray; found {arr1:?}");
2373 }
2374
2375 let program = r#"
2376a = 42: string
2377"#;
2378 let result = parse_execute(program).await;
2379 let err = result.unwrap_err();
2380 assert!(
2381 err.to_string()
2382 .contains("could not coerce a number (with type `number`) to type `string`"),
2383 "Expected error but found {err:?}"
2384 );
2385
2386 let program = r#"
2387a = 42: Plane
2388"#;
2389 let result = parse_execute(program).await;
2390 let err = result.unwrap_err();
2391 assert!(
2392 err.to_string()
2393 .contains("could not coerce a number (with type `number`) to type `Plane`"),
2394 "Expected error but found {err:?}"
2395 );
2396
2397 let program = r#"
2398arr = [0]: [string]
2399"#;
2400 let result = parse_execute(program).await;
2401 let err = result.unwrap_err();
2402 assert!(
2403 err.to_string().contains(
2404 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
2405 ),
2406 "Expected error but found {err:?}"
2407 );
2408
2409 let program = r#"
2410mixedArr = [0, "a"]: [number(mm)]
2411"#;
2412 let result = parse_execute(program).await;
2413 let err = result.unwrap_err();
2414 assert!(
2415 err.to_string().contains(
2416 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
2417 ),
2418 "Expected error but found {err:?}"
2419 );
2420
2421 let program = r#"
2422mixedArr = [0, "a"]: [mm]
2423"#;
2424 let result = parse_execute(program).await;
2425 let err = result.unwrap_err();
2426 assert!(
2427 err.to_string().contains(
2428 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
2429 ),
2430 "Expected error but found {err:?}"
2431 );
2432 }
2433
2434 #[tokio::test(flavor = "multi_thread")]
2435 async fn neg_plane() {
2436 let program = r#"
2437p = {
2438 origin = { x = 0, y = 0, z = 0 },
2439 xAxis = { x = 1, y = 0, z = 0 },
2440 yAxis = { x = 0, y = 1, z = 0 },
2441}: Plane
2442p2 = -p
2443"#;
2444
2445 let result = parse_execute(program).await.unwrap();
2446 let mem = result.exec_state.stack();
2447 match mem
2448 .memory
2449 .get_from("p2", result.mem_env, SourceRange::default(), 0)
2450 .unwrap()
2451 {
2452 KclValue::Plane { value } => {
2453 assert_eq!(value.info.x_axis.x, -1.0);
2454 assert_eq!(value.info.x_axis.y, 0.0);
2455 assert_eq!(value.info.x_axis.z, 0.0);
2456 }
2457 _ => unreachable!(),
2458 }
2459 }
2460
2461 #[tokio::test(flavor = "multi_thread")]
2462 async fn multiple_returns() {
2463 let program = r#"fn foo() {
2464 return 0
2465 return 42
2466}
2467
2468a = foo()
2469"#;
2470
2471 let result = parse_execute(program).await;
2472 assert!(result.unwrap_err().to_string().contains("return"));
2473 }
2474
2475 #[tokio::test(flavor = "multi_thread")]
2476 async fn load_all_modules() {
2477 let program_a_kcl = r#"
2479export a = 1
2480"#;
2481 let program_b_kcl = r#"
2483import a from 'a.kcl'
2484
2485export b = a + 1
2486"#;
2487 let program_c_kcl = r#"
2489import a from 'a.kcl'
2490
2491export c = a + 2
2492"#;
2493
2494 let main_kcl = r#"
2496import b from 'b.kcl'
2497import c from 'c.kcl'
2498
2499d = b + c
2500"#;
2501
2502 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
2503 .parse_errs_as_err()
2504 .unwrap();
2505
2506 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
2507
2508 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
2509 .await
2510 .unwrap()
2511 .write_all(main_kcl.as_bytes())
2512 .await
2513 .unwrap();
2514
2515 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
2516 .await
2517 .unwrap()
2518 .write_all(program_a_kcl.as_bytes())
2519 .await
2520 .unwrap();
2521
2522 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
2523 .await
2524 .unwrap()
2525 .write_all(program_b_kcl.as_bytes())
2526 .await
2527 .unwrap();
2528
2529 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
2530 .await
2531 .unwrap()
2532 .write_all(program_c_kcl.as_bytes())
2533 .await
2534 .unwrap();
2535
2536 let exec_ctxt = ExecutorContext {
2537 engine: Arc::new(Box::new(
2538 crate::engine::conn_mock::EngineConnection::new()
2539 .map_err(|err| {
2540 KclError::new_internal(KclErrorDetails::new(
2541 format!("Failed to create mock engine connection: {err}"),
2542 vec![SourceRange::default()],
2543 ))
2544 })
2545 .unwrap(),
2546 )),
2547 fs: Arc::new(crate::fs::FileManager::new()),
2548 settings: ExecutorSettings {
2549 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
2550 ..Default::default()
2551 },
2552 context_type: ContextType::Mock,
2553 };
2554 let mut exec_state = ExecState::new(&exec_ctxt);
2555
2556 exec_ctxt
2557 .run(
2558 &crate::Program {
2559 ast: main.clone(),
2560 original_file_contents: "".to_owned(),
2561 },
2562 &mut exec_state,
2563 )
2564 .await
2565 .unwrap();
2566 }
2567
2568 #[tokio::test(flavor = "multi_thread")]
2569 async fn user_coercion() {
2570 let program = r#"fn foo(x: Axis2d) {
2571 return 0
2572}
2573
2574foo(x = { direction = [0, 0], origin = [0, 0]})
2575"#;
2576
2577 parse_execute(program).await.unwrap();
2578
2579 let program = r#"fn foo(x: Axis3d) {
2580 return 0
2581}
2582
2583foo(x = { direction = [0, 0], origin = [0, 0]})
2584"#;
2585
2586 parse_execute(program).await.unwrap_err();
2587 }
2588
2589 #[tokio::test(flavor = "multi_thread")]
2590 async fn coerce_return() {
2591 let program = r#"fn foo(): number(mm) {
2592 return 42
2593}
2594
2595a = foo()
2596"#;
2597
2598 parse_execute(program).await.unwrap();
2599
2600 let program = r#"fn foo(): mm {
2601 return 42
2602}
2603
2604a = foo()
2605"#;
2606
2607 parse_execute(program).await.unwrap();
2608
2609 let program = r#"fn foo(): number(mm) {
2610 return { bar: 42 }
2611}
2612
2613a = foo()
2614"#;
2615
2616 parse_execute(program).await.unwrap_err();
2617
2618 let program = r#"fn foo(): mm {
2619 return { bar: 42 }
2620}
2621
2622a = foo()
2623"#;
2624
2625 parse_execute(program).await.unwrap_err();
2626 }
2627
2628 #[tokio::test(flavor = "multi_thread")]
2629 async fn test_sensible_error_when_missing_equals_in_kwarg() {
2630 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)"]
2631 .into_iter()
2632 .enumerate()
2633 {
2634 let program = format!(
2635 "fn foo() {{ return 0 }}
2636z = 0
2637fn f(x, y, z) {{ return 0 }}
2638{call}"
2639 );
2640 let err = parse_execute(&program).await.unwrap_err();
2641 let msg = err.message();
2642 assert!(
2643 msg.contains("This argument needs a label, but it doesn't have one"),
2644 "failed test {i}: {msg}"
2645 );
2646 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
2647 if i == 0 {
2648 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
2649 }
2650 }
2651 }
2652
2653 #[tokio::test(flavor = "multi_thread")]
2654 async fn default_param_for_unlabeled() {
2655 let ast = r#"fn myExtrude(@sk, length) {
2658 return extrude(sk, length)
2659}
2660sketch001 = startSketchOn(XY)
2661 |> circle(center = [0, 0], radius = 93.75)
2662 |> myExtrude(length = 40)
2663"#;
2664
2665 parse_execute(ast).await.unwrap();
2666 }
2667
2668 #[tokio::test(flavor = "multi_thread")]
2669 async fn dont_use_unlabelled_as_input() {
2670 let ast = r#"length = 10
2672startSketchOn(XY)
2673 |> circle(center = [0, 0], radius = 93.75)
2674 |> extrude(length)
2675"#;
2676
2677 parse_execute(ast).await.unwrap();
2678 }
2679
2680 #[tokio::test(flavor = "multi_thread")]
2681 async fn ascription_in_binop() {
2682 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
2683 parse_execute(ast).await.unwrap();
2684
2685 let ast = r#"foo = tan(0): rad - 4deg"#;
2686 parse_execute(ast).await.unwrap();
2687 }
2688
2689 #[tokio::test(flavor = "multi_thread")]
2690 async fn neg_sqrt() {
2691 let ast = r#"bad = sqrt(-2)"#;
2692
2693 let e = parse_execute(ast).await.unwrap_err();
2694 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
2696 }
2697
2698 #[tokio::test(flavor = "multi_thread")]
2699 async fn non_array_fns() {
2700 let ast = r#"push(1, item = 2)
2701pop(1)
2702map(1, f = fn(@x) { return x + 1 })
2703reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
2704
2705 parse_execute(ast).await.unwrap();
2706 }
2707
2708 #[tokio::test(flavor = "multi_thread")]
2709 async fn non_array_indexing() {
2710 let good = r#"a = 42
2711good = a[0]
2712"#;
2713 let result = parse_execute(good).await.unwrap();
2714 let mem = result.exec_state.stack();
2715 let num = mem
2716 .memory
2717 .get_from("good", result.mem_env, SourceRange::default(), 0)
2718 .unwrap()
2719 .as_ty_f64()
2720 .unwrap();
2721 assert_eq!(num.n, 42.0);
2722
2723 let bad = r#"a = 42
2724bad = a[1]
2725"#;
2726
2727 parse_execute(bad).await.unwrap_err();
2728 }
2729
2730 #[tokio::test(flavor = "multi_thread")]
2731 async fn coerce_unknown_to_length() {
2732 let ast = r#"x = 2mm * 2mm
2733y = x: number(Length)"#;
2734 let e = parse_execute(ast).await.unwrap_err();
2735 assert!(
2736 e.message().contains("could not coerce"),
2737 "Error message: '{}'",
2738 e.message()
2739 );
2740
2741 let ast = r#"x = 2mm
2742y = x: number(Length)"#;
2743 let result = parse_execute(ast).await.unwrap();
2744 let mem = result.exec_state.stack();
2745 let num = mem
2746 .memory
2747 .get_from("y", result.mem_env, SourceRange::default(), 0)
2748 .unwrap()
2749 .as_ty_f64()
2750 .unwrap();
2751 assert_eq!(num.n, 2.0);
2752 assert_eq!(num.ty, NumericType::mm());
2753 }
2754
2755 #[tokio::test(flavor = "multi_thread")]
2756 async fn one_warning_unknown() {
2757 let ast = r#"
2758// Should warn once
2759a = PI * 2
2760// Should warn once
2761b = (PI * 2) / 3
2762// Should not warn
2763c = ((PI * 2) / 3): number(deg)
2764"#;
2765
2766 let result = parse_execute(ast).await.unwrap();
2767 assert_eq!(result.exec_state.errors().len(), 2);
2768 }
2769
2770 #[tokio::test(flavor = "multi_thread")]
2771 async fn non_count_indexing() {
2772 let ast = r#"x = [0, 0]
2773y = x[1mm]
2774"#;
2775 parse_execute(ast).await.unwrap_err();
2776
2777 let ast = r#"x = [0, 0]
2778y = 1deg
2779z = x[y]
2780"#;
2781 parse_execute(ast).await.unwrap_err();
2782
2783 let ast = r#"x = [0, 0]
2784y = x[0mm + 1]
2785"#;
2786 parse_execute(ast).await.unwrap_err();
2787 }
2788
2789 #[tokio::test(flavor = "multi_thread")]
2790 async fn getting_property_of_plane() {
2791 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
2792 parse_execute(&ast).await.unwrap();
2793 }
2794
2795 #[cfg(feature = "artifact-graph")]
2796 #[tokio::test(flavor = "multi_thread")]
2797 async fn no_artifacts_from_within_hole_call() {
2798 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
2803 let out = parse_execute(&ast).await.unwrap();
2804
2805 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
2807
2808 let expected = 5;
2812 assert_eq!(
2813 actual_operations.len(),
2814 expected,
2815 "expected {expected} operations, received {}:\n{actual_operations:#?}",
2816 actual_operations.len(),
2817 );
2818 }
2819
2820 #[cfg(feature = "artifact-graph")]
2821 #[tokio::test(flavor = "multi_thread")]
2822 async fn feature_tree_annotation_on_user_defined_kcl() {
2823 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
2826 let out = parse_execute(&ast).await.unwrap();
2827
2828 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
2830
2831 let expected = 0;
2832 assert_eq!(
2833 actual_operations.len(),
2834 expected,
2835 "expected {expected} operations, received {}:\n{actual_operations:#?}",
2836 actual_operations.len(),
2837 );
2838 }
2839
2840 #[cfg(feature = "artifact-graph")]
2841 #[tokio::test(flavor = "multi_thread")]
2842 async fn no_feature_tree_annotation_on_user_defined_kcl() {
2843 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
2846 let out = parse_execute(&ast).await.unwrap();
2847
2848 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
2850
2851 let expected = 2;
2852 assert_eq!(
2853 actual_operations.len(),
2854 expected,
2855 "expected {expected} operations, received {}:\n{actual_operations:#?}",
2856 actual_operations.len(),
2857 );
2858 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
2859 assert!(matches!(actual_operations[1], Operation::GroupEnd));
2860 }
2861
2862 #[tokio::test(flavor = "multi_thread")]
2863 async fn custom_warning() {
2864 let warn = r#"
2865a = PI * 2
2866"#;
2867 let result = parse_execute(warn).await.unwrap();
2868 assert_eq!(result.exec_state.errors().len(), 1);
2869 assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
2870
2871 let allow = r#"
2872@warnings(allow = unknownUnits)
2873a = PI * 2
2874"#;
2875 let result = parse_execute(allow).await.unwrap();
2876 assert_eq!(result.exec_state.errors().len(), 0);
2877
2878 let deny = r#"
2879@warnings(deny = [unknownUnits])
2880a = PI * 2
2881"#;
2882 let result = parse_execute(deny).await.unwrap();
2883 assert_eq!(result.exec_state.errors().len(), 1);
2884 assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
2885 }
2886}