1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use indexmap::IndexMap;
5use kcl_ezpz::{Constraint, SolveOutcome};
6use kittycad_modeling_cmds::units::UnitLength;
7
8#[cfg(feature = "artifact-graph")]
9use crate::front::ObjectKind;
10use crate::{
11 CompilationError, NodePath, SourceRange,
12 errors::{KclError, KclErrorDetails},
13 exec::UnitType,
14 execution::{
15 AbstractSegment, BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, ModelingCmdMeta,
16 ModuleArtifactState, Operation, PlaneType, Segment, SegmentKind, SegmentRepr, SketchConstraintKind,
17 StatementKind, TagIdentifier, UnsolvedExpr, UnsolvedSegment, UnsolvedSegmentKind, annotations,
18 cad_op::OpKclValue,
19 fn_call::{Arg, Args},
20 kcl_value::{FunctionSource, KclFunctionSourceParams, TypeDef},
21 memory,
22 state::{ModuleState, SketchBlockState},
23 types::{NumericType, PrimitiveType, RuntimeType},
24 },
25 front::{Freedom, Object, PointCtor},
26 modules::{ModuleExecutionOutcome, ModuleId, ModulePath, ModuleRepr},
27 parsing::ast::types::{
28 Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
29 BinaryPart, BodyItem, CodeBlock, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility,
30 MemberExpression, Name, Node, ObjectExpression, PipeExpression, Program, SketchBlock, SketchVar, TagDeclarator,
31 Type, UnaryExpression, UnaryOperator,
32 },
33 std::args::TyF64,
34};
35
36impl<'a> StatementKind<'a> {
37 fn expect_name(&self) -> &'a str {
38 match self {
39 StatementKind::Declaration { name } => name,
40 StatementKind::Expression => unreachable!(),
41 }
42 }
43}
44
45impl ExecutorContext {
46 async fn handle_annotations(
48 &self,
49 annotations: impl Iterator<Item = &Node<Annotation>>,
50 body_type: BodyType,
51 exec_state: &mut ExecState,
52 ) -> Result<bool, KclError> {
53 let mut no_prelude = false;
54 for annotation in annotations {
55 if annotation.name() == Some(annotations::SETTINGS) {
56 if matches!(body_type, BodyType::Root) {
57 let (updated_len, updated_angle) =
58 exec_state.mod_local.settings.update_from_annotation(annotation)?;
59 if updated_len {
60 exec_state.mod_local.explicit_length_units = true;
61 }
62 if updated_angle {
63 exec_state.warn(
64 CompilationError::err(
65 annotation.as_source_range(),
66 "Prefer to use explicit units for angles",
67 ),
68 annotations::WARN_ANGLE_UNITS,
69 );
70 }
71 } else {
72 exec_state.err(CompilationError::err(
73 annotation.as_source_range(),
74 "Settings can only be modified at the top level scope of a file",
75 ));
76 }
77 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
78 if matches!(body_type, BodyType::Root) {
79 no_prelude = true;
80 } else {
81 exec_state.err(CompilationError::err(
82 annotation.as_source_range(),
83 "The standard library can only be skipped at the top level scope of a file",
84 ));
85 }
86 } else if annotation.name() == Some(annotations::WARNINGS) {
87 if matches!(body_type, BodyType::Root) {
89 let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
90 for p in props {
91 match &*p.inner.key.name {
92 annotations::WARN_ALLOW => {
93 let allowed = annotations::many_of(
94 &p.inner.value,
95 &annotations::WARN_VALUES,
96 annotation.as_source_range(),
97 )?;
98 exec_state.mod_local.allowed_warnings = allowed;
99 }
100 annotations::WARN_DENY => {
101 let denied = annotations::many_of(
102 &p.inner.value,
103 &annotations::WARN_VALUES,
104 annotation.as_source_range(),
105 )?;
106 exec_state.mod_local.denied_warnings = denied;
107 }
108 name => {
109 return Err(KclError::new_semantic(KclErrorDetails::new(
110 format!(
111 "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
112 annotations::WARN_ALLOW,
113 annotations::WARN_DENY,
114 ),
115 vec![annotation.as_source_range()],
116 )));
117 }
118 }
119 }
120 } else {
121 exec_state.err(CompilationError::err(
122 annotation.as_source_range(),
123 "Warnings can only be customized at the top level scope of a file",
124 ));
125 }
126 } else {
127 exec_state.warn(
128 CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
129 annotations::WARN_UNKNOWN_ATTR,
130 );
131 }
132 }
133 Ok(no_prelude)
134 }
135
136 pub(super) async fn exec_module_body(
137 &self,
138 program: &Node<Program>,
139 exec_state: &mut ExecState,
140 preserve_mem: bool,
141 module_id: ModuleId,
142 path: &ModulePath,
143 ) -> Result<ModuleExecutionOutcome, (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>)> {
144 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
145 #[cfg(not(feature = "artifact-graph"))]
146 let next_object_id = 0;
147 #[cfg(feature = "artifact-graph")]
148 let next_object_id = exec_state.global.root_module_artifacts.scene_objects.len();
149 let mut local_state = ModuleState::new(
150 path.clone(),
151 exec_state.stack().memory.clone(),
152 Some(module_id),
153 next_object_id,
154 exec_state.mod_local.freedom_analysis,
155 );
156 if !preserve_mem {
157 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
158 }
159
160 let no_prelude = self
161 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
162 .await
163 .map_err(|err| (err, None, None))?;
164
165 if !preserve_mem {
166 exec_state.mut_stack().push_new_root_env(!no_prelude);
167 }
168
169 let result = self
170 .exec_block(program, exec_state, crate::execution::BodyType::Root)
171 .await;
172
173 let env_ref = if preserve_mem {
174 exec_state.mut_stack().pop_and_preserve_env()
175 } else {
176 exec_state.mut_stack().pop_env()
177 };
178 let module_artifacts = if !preserve_mem {
179 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
180 local_state.artifacts
181 } else {
182 std::mem::take(&mut exec_state.mod_local.artifacts)
183 };
184
185 crate::log::log(format!("leave {path}"));
186
187 result
188 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
189 .map(|last_expr| ModuleExecutionOutcome {
190 last_expr,
191 environment: env_ref,
192 exports: local_state.module_exports,
193 artifacts: module_artifacts,
194 })
195 }
196
197 #[async_recursion]
199 pub(super) async fn exec_block<'a, B>(
200 &'a self,
201 block: &'a B,
202 exec_state: &mut ExecState,
203 body_type: BodyType,
204 ) -> Result<Option<KclValue>, KclError>
205 where
206 B: CodeBlock + Sync,
207 {
208 let mut last_expr = None;
209 for statement in block.body() {
211 match statement {
212 BodyItem::ImportStatement(import_stmt) => {
213 if !matches!(body_type, BodyType::Root) {
214 return Err(KclError::new_semantic(KclErrorDetails::new(
215 "Imports are only supported at the top-level of a file.".to_owned(),
216 vec![import_stmt.into()],
217 )));
218 }
219
220 let source_range = SourceRange::from(import_stmt);
221 let attrs = &import_stmt.outer_attrs;
222 let module_path = ModulePath::from_import_path(
223 &import_stmt.path,
224 &self.settings.project_directory,
225 &exec_state.mod_local.path,
226 )?;
227 let module_id = self
228 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
229 .await?;
230
231 match &import_stmt.selector {
232 ImportSelector::List { items } => {
233 let (env_ref, module_exports) =
234 self.exec_module_for_items(module_id, exec_state, source_range).await?;
235 for import_item in items {
236 let mem = &exec_state.stack().memory;
238 let mut value = mem
239 .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
240 .cloned();
241 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
242 let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
243 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
244 let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
245
246 if value.is_err() && ty.is_err() && mod_value.is_err() {
247 return Err(KclError::new_undefined_value(
248 KclErrorDetails::new(
249 format!("{} is not defined in module", import_item.name.name),
250 vec![SourceRange::from(&import_item.name)],
251 ),
252 None,
253 ));
254 }
255
256 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
258 value = Err(KclError::new_semantic(KclErrorDetails::new(
259 format!(
260 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
261 import_item.name.name
262 ),
263 vec![SourceRange::from(&import_item.name)],
264 )));
265 }
266
267 if ty.is_ok() && !module_exports.contains(&ty_name) {
268 ty = Err(KclError::new_semantic(KclErrorDetails::new(
269 format!(
270 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
271 import_item.name.name
272 ),
273 vec![SourceRange::from(&import_item.name)],
274 )));
275 }
276
277 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
278 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
279 format!(
280 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
281 import_item.name.name
282 ),
283 vec![SourceRange::from(&import_item.name)],
284 )));
285 }
286
287 if value.is_err() && ty.is_err() && mod_value.is_err() {
288 return value.map(Option::Some);
289 }
290
291 if let Ok(value) = value {
293 exec_state.mut_stack().add(
294 import_item.identifier().to_owned(),
295 value,
296 SourceRange::from(&import_item.name),
297 )?;
298
299 if let ItemVisibility::Export = import_stmt.visibility {
300 exec_state
301 .mod_local
302 .module_exports
303 .push(import_item.identifier().to_owned());
304 }
305 }
306
307 if let Ok(ty) = ty {
308 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
309 exec_state.mut_stack().add(
310 ty_name.clone(),
311 ty,
312 SourceRange::from(&import_item.name),
313 )?;
314
315 if let ItemVisibility::Export = import_stmt.visibility {
316 exec_state.mod_local.module_exports.push(ty_name);
317 }
318 }
319
320 if let Ok(mod_value) = mod_value {
321 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
322 exec_state.mut_stack().add(
323 mod_name.clone(),
324 mod_value,
325 SourceRange::from(&import_item.name),
326 )?;
327
328 if let ItemVisibility::Export = import_stmt.visibility {
329 exec_state.mod_local.module_exports.push(mod_name);
330 }
331 }
332 }
333 }
334 ImportSelector::Glob(_) => {
335 let (env_ref, module_exports) =
336 self.exec_module_for_items(module_id, exec_state, source_range).await?;
337 for name in module_exports.iter() {
338 let item = exec_state
339 .stack()
340 .memory
341 .get_from(name, env_ref, source_range, 0)
342 .map_err(|_err| {
343 KclError::new_internal(KclErrorDetails::new(
344 format!("{name} is not defined in module (but was exported?)"),
345 vec![source_range],
346 ))
347 })?
348 .clone();
349 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
350
351 if let ItemVisibility::Export = import_stmt.visibility {
352 exec_state.mod_local.module_exports.push(name.clone());
353 }
354 }
355 }
356 ImportSelector::None { .. } => {
357 let name = import_stmt.module_name().unwrap();
358 let item = KclValue::Module {
359 value: module_id,
360 meta: vec![source_range.into()],
361 };
362 exec_state.mut_stack().add(
363 format!("{}{}", memory::MODULE_PREFIX, name),
364 item,
365 source_range,
366 )?;
367 }
368 }
369 last_expr = None;
370 }
371 BodyItem::ExpressionStatement(expression_statement) => {
372 let metadata = Metadata::from(expression_statement);
373 last_expr = Some(
374 self.execute_expr(
375 &expression_statement.expression,
376 exec_state,
377 &metadata,
378 &[],
379 StatementKind::Expression,
380 )
381 .await?,
382 );
383 }
384 BodyItem::VariableDeclaration(variable_declaration) => {
385 let var_name = variable_declaration.declaration.id.name.to_string();
386 let source_range = SourceRange::from(&variable_declaration.declaration.init);
387 let metadata = Metadata { source_range };
388
389 let annotations = &variable_declaration.outer_attrs;
390
391 let lhs = variable_declaration.inner.name().to_owned();
394 let prev_being_declared = exec_state.mod_local.being_declared.take();
395 exec_state.mod_local.being_declared = Some(lhs);
396 let rhs_result = self
397 .execute_expr(
398 &variable_declaration.declaration.init,
399 exec_state,
400 &metadata,
401 annotations,
402 StatementKind::Declaration { name: &var_name },
403 )
404 .await;
405 exec_state.mod_local.being_declared = prev_being_declared;
407 let rhs = rhs_result?;
408
409 let should_bind_name =
410 if let Some(fn_name) = variable_declaration.declaration.init.fn_declaring_name() {
411 var_name != fn_name
415 } else {
416 true
419 };
420 if should_bind_name {
421 exec_state
422 .mut_stack()
423 .add(var_name.clone(), rhs.clone(), source_range)?;
424 }
425
426 let should_show_in_feature_tree =
430 !exec_state.mod_local.inside_stdlib && rhs.show_variable_in_feature_tree();
431 if should_show_in_feature_tree {
432 exec_state.push_op(Operation::VariableDeclaration {
433 name: var_name.clone(),
434 value: OpKclValue::from(&rhs),
435 visibility: variable_declaration.visibility,
436 node_path: NodePath::placeholder(),
437 source_range,
438 });
439 }
440
441 if let ItemVisibility::Export = variable_declaration.visibility {
443 if matches!(body_type, BodyType::Root) {
444 exec_state.mod_local.module_exports.push(var_name);
445 } else {
446 exec_state.err(CompilationError::err(
447 variable_declaration.as_source_range(),
448 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
449 ));
450 }
451 }
452 last_expr = matches!(body_type, BodyType::Root).then_some(rhs);
454 }
455 BodyItem::TypeDeclaration(ty) => {
456 let metadata = Metadata::from(&**ty);
457 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
458 match attrs.impl_ {
459 annotations::Impl::Rust
460 | annotations::Impl::RustConstrainable
461 | annotations::Impl::RustConstraint => {
462 let std_path = match &exec_state.mod_local.path {
463 ModulePath::Std { value } => value,
464 ModulePath::Local { .. } | ModulePath::Main => {
465 return Err(KclError::new_semantic(KclErrorDetails::new(
466 "User-defined types are not yet supported.".to_owned(),
467 vec![metadata.source_range],
468 )));
469 }
470 };
471 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
472 let value = KclValue::Type {
473 value: TypeDef::RustRepr(t, props),
474 meta: vec![metadata],
475 experimental: attrs.experimental,
476 };
477 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
478 exec_state
479 .mut_stack()
480 .add(name_in_mem.clone(), value, metadata.source_range)
481 .map_err(|_| {
482 KclError::new_semantic(KclErrorDetails::new(
483 format!("Redefinition of type {}.", ty.name.name),
484 vec![metadata.source_range],
485 ))
486 })?;
487
488 if let ItemVisibility::Export = ty.visibility {
489 exec_state.mod_local.module_exports.push(name_in_mem);
490 }
491 }
492 annotations::Impl::Primitive => {}
494 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
495 Some(alias) => {
496 let value = KclValue::Type {
497 value: TypeDef::Alias(
498 RuntimeType::from_parsed(
499 alias.inner.clone(),
500 exec_state,
501 metadata.source_range,
502 attrs.impl_ == annotations::Impl::KclConstrainable,
503 )
504 .map_err(|e| KclError::new_semantic(e.into()))?,
505 ),
506 meta: vec![metadata],
507 experimental: attrs.experimental,
508 };
509 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
510 exec_state
511 .mut_stack()
512 .add(name_in_mem.clone(), value, metadata.source_range)
513 .map_err(|_| {
514 KclError::new_semantic(KclErrorDetails::new(
515 format!("Redefinition of type {}.", ty.name.name),
516 vec![metadata.source_range],
517 ))
518 })?;
519
520 if let ItemVisibility::Export = ty.visibility {
521 exec_state.mod_local.module_exports.push(name_in_mem);
522 }
523 }
524 None => {
525 return Err(KclError::new_semantic(KclErrorDetails::new(
526 "User-defined types are not yet supported.".to_owned(),
527 vec![metadata.source_range],
528 )));
529 }
530 },
531 }
532
533 last_expr = None;
534 }
535 BodyItem::ReturnStatement(return_statement) => {
536 let metadata = Metadata::from(return_statement);
537
538 if matches!(body_type, BodyType::Root) {
539 return Err(KclError::new_semantic(KclErrorDetails::new(
540 "Cannot return from outside a function.".to_owned(),
541 vec![metadata.source_range],
542 )));
543 }
544
545 let value = self
546 .execute_expr(
547 &return_statement.argument,
548 exec_state,
549 &metadata,
550 &[],
551 StatementKind::Expression,
552 )
553 .await?;
554 exec_state
555 .mut_stack()
556 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
557 .map_err(|_| {
558 KclError::new_semantic(KclErrorDetails::new(
559 "Multiple returns from a single function.".to_owned(),
560 vec![metadata.source_range],
561 ))
562 })?;
563 last_expr = None;
564 }
565 }
566 }
567
568 if matches!(body_type, BodyType::Root) {
569 exec_state
571 .flush_batch(
572 ModelingCmdMeta::new(self, block.to_source_range()),
573 true,
576 )
577 .await?;
578 }
579
580 Ok(last_expr)
581 }
582
583 pub async fn open_module(
584 &self,
585 path: &ImportPath,
586 attrs: &[Node<Annotation>],
587 resolved_path: &ModulePath,
588 exec_state: &mut ExecState,
589 source_range: SourceRange,
590 ) -> Result<ModuleId, KclError> {
591 match path {
592 ImportPath::Kcl { .. } => {
593 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
594
595 if let Some(id) = exec_state.id_for_module(resolved_path) {
596 return Ok(id);
597 }
598
599 let id = exec_state.next_module_id();
600 exec_state.add_path_to_source_id(resolved_path.clone(), id);
602 let source = resolved_path.source(&self.fs, source_range).await?;
603 exec_state.add_id_to_source(id, source.clone());
604 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
606 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
607
608 Ok(id)
609 }
610 ImportPath::Foreign { .. } => {
611 if let Some(id) = exec_state.id_for_module(resolved_path) {
612 return Ok(id);
613 }
614
615 let id = exec_state.next_module_id();
616 let path = resolved_path.expect_path();
617 exec_state.add_path_to_source_id(resolved_path.clone(), id);
619 let format = super::import::format_from_annotations(attrs, path, source_range)?;
620 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
621 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
622 Ok(id)
623 }
624 ImportPath::Std { .. } => {
625 if let Some(id) = exec_state.id_for_module(resolved_path) {
626 return Ok(id);
627 }
628
629 let id = exec_state.next_module_id();
630 exec_state.add_path_to_source_id(resolved_path.clone(), id);
632 let source = resolved_path.source(&self.fs, source_range).await?;
633 exec_state.add_id_to_source(id, source.clone());
634 let parsed = crate::parsing::parse_str(&source.source, id)
635 .parse_errs_as_err()
636 .unwrap();
637 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
638 Ok(id)
639 }
640 }
641 }
642
643 pub(super) async fn exec_module_for_items(
644 &self,
645 module_id: ModuleId,
646 exec_state: &mut ExecState,
647 source_range: SourceRange,
648 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
649 let path = exec_state.global.module_infos[&module_id].path.clone();
650 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
651 let result = match &mut repr {
654 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
655 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
656 ModuleRepr::Kcl(program, cache) => self
657 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
658 .await
659 .map(|outcome| {
660 *cache = Some(outcome.clone());
661 (outcome.environment, outcome.exports)
662 }),
663 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
664 "Cannot import items from foreign modules".to_owned(),
665 vec![geom.source_range],
666 ))),
667 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
668 };
669
670 exec_state.global.module_infos[&module_id].restore_repr(repr);
671 result
672 }
673
674 async fn exec_module_for_result(
675 &self,
676 module_id: ModuleId,
677 exec_state: &mut ExecState,
678 source_range: SourceRange,
679 ) -> Result<Option<KclValue>, KclError> {
680 let path = exec_state.global.module_infos[&module_id].path.clone();
681 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
682 let result = match &mut repr {
685 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
686 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
687 ModuleRepr::Kcl(program, cached_items) => {
688 let result = self
689 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, false)
690 .await;
691 match result {
692 Ok(outcome) => {
693 let value = outcome.last_expr.clone();
694 *cached_items = Some(outcome);
695 Ok(value)
696 }
697 Err(e) => Err(e),
698 }
699 }
700 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
701 ModuleRepr::Foreign(geom, cached) => {
702 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
703 .await
704 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
705
706 match result {
707 Ok(val) => {
708 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
709 Ok(val)
710 }
711 Err(e) => Err(e),
712 }
713 }
714 ModuleRepr::Dummy => unreachable!(),
715 };
716
717 exec_state.global.module_infos[&module_id].restore_repr(repr);
718
719 result
720 }
721
722 pub async fn exec_module_from_ast(
723 &self,
724 program: &Node<Program>,
725 module_id: ModuleId,
726 path: &ModulePath,
727 exec_state: &mut ExecState,
728 source_range: SourceRange,
729 preserve_mem: bool,
730 ) -> Result<ModuleExecutionOutcome, KclError> {
731 exec_state.global.mod_loader.enter_module(path);
732 let result = self
733 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
734 .await;
735 exec_state.global.mod_loader.leave_module(path, source_range)?;
736
737 result.map_err(|(err, _, _)| {
740 if let KclError::ImportCycle { .. } = err {
741 err.override_source_ranges(vec![source_range])
743 } else {
744 KclError::new_semantic(KclErrorDetails::new(
746 format!(
747 "Error loading imported file ({path}). Open it to view more details.\n {}",
748 err.message()
749 ),
750 vec![source_range],
751 ))
752 }
753 })
754 }
755
756 #[async_recursion]
757 pub(crate) async fn execute_expr<'a: 'async_recursion>(
758 &self,
759 init: &Expr,
760 exec_state: &mut ExecState,
761 metadata: &Metadata,
762 annotations: &[Node<Annotation>],
763 statement_kind: StatementKind<'a>,
764 ) -> Result<KclValue, KclError> {
765 let item = match init {
766 Expr::None(none) => KclValue::from(none),
767 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
768 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
769 Expr::Name(name) => {
770 let being_declared = exec_state.mod_local.being_declared.clone();
771 let value = name
772 .get_result(exec_state, self)
773 .await
774 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
775 .clone();
776 if let KclValue::Module { value: module_id, meta } = value {
777 self.exec_module_for_result(
778 module_id,
779 exec_state,
780 metadata.source_range
781 ).await?
782 .unwrap_or_else(|| {
783 exec_state.warn(CompilationError::err(
784 metadata.source_range,
785 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
786 ),
787 annotations::WARN_MOD_RETURN_VALUE);
788
789 let mut new_meta = vec![metadata.to_owned()];
790 new_meta.extend(meta);
791 KclValue::KclNone {
792 value: Default::default(),
793 meta: new_meta,
794 }
795 })
796 } else {
797 value
798 }
799 }
800 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
801 Expr::FunctionExpression(function_expression) => {
802 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
803 let experimental = attrs.map(|a| a.experimental).unwrap_or_default();
804 let is_std = matches!(&exec_state.mod_local.path, ModulePath::Std { .. });
805
806 let include_in_feature_tree = attrs.unwrap_or_default().include_in_feature_tree;
808 let closure = if let Some(attrs) = attrs
809 && (attrs.impl_ == annotations::Impl::Rust
810 || attrs.impl_ == annotations::Impl::RustConstrainable
811 || attrs.impl_ == annotations::Impl::RustConstraint)
812 {
813 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
814 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
815 KclValue::Function {
816 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
817 meta: vec![metadata.to_owned()],
818 }
819 } else {
820 return Err(KclError::new_semantic(KclErrorDetails::new(
821 "Rust implementation of functions is restricted to the standard library".to_owned(),
822 vec![metadata.source_range],
823 )));
824 }
825 } else {
826 KclValue::Function {
830 value: Box::new(FunctionSource::kcl(
831 function_expression.clone(),
832 exec_state.mut_stack().snapshot(),
833 KclFunctionSourceParams {
834 is_std,
835 experimental,
836 include_in_feature_tree,
837 },
838 )),
839 meta: vec![metadata.to_owned()],
840 }
841 };
842
843 if let Some(fn_name) = &function_expression.name {
846 exec_state
847 .mut_stack()
848 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
849 }
850
851 closure
852 }
853 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
854 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
855 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
856 StatementKind::Declaration { name } => {
857 let message = format!(
858 "you cannot declare variable {name} as %, because % can only be used in function calls"
859 );
860
861 return Err(KclError::new_semantic(KclErrorDetails::new(
862 message,
863 vec![pipe_substitution.into()],
864 )));
865 }
866 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
867 Some(x) => x,
868 None => {
869 return Err(KclError::new_semantic(KclErrorDetails::new(
870 "cannot use % outside a pipe expression".to_owned(),
871 vec![pipe_substitution.into()],
872 )));
873 }
874 },
875 },
876 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
877 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
878 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
879 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
880 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
881 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
882 Expr::LabelledExpression(expr) => {
883 let result = self
884 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
885 .await?;
886 exec_state
887 .mut_stack()
888 .add(expr.label.name.clone(), result.clone(), init.into())?;
889 result
891 }
892 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
893 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
894 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?,
895 };
896 Ok(item)
897 }
898}
899
900fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
903 let KclError::UndefinedValue { name, mut details } = e else {
904 return e;
905 };
906 if let (Some(name0), Some(name1)) = (&being_declared, &name)
910 && name0 == name1
911 {
912 details.message = format!(
913 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
914 );
915 }
916 KclError::UndefinedValue { details, name }
917}
918
919impl Node<AscribedExpression> {
920 #[async_recursion]
921 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
922 let metadata = Metadata {
923 source_range: SourceRange::from(self),
924 };
925 let result = ctx
926 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
927 .await?;
928 apply_ascription(&result, &self.ty, exec_state, self.into())
929 }
930}
931
932impl Node<SketchBlock> {
933 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
934 if exec_state.mod_local.sketch_block.is_some() {
935 return Err(KclError::new_semantic(KclErrorDetails::new(
937 "Cannot execute a sketch block from within another sketch block".to_owned(),
938 vec![SourceRange::from(self)],
939 )));
940 }
941
942 let range = SourceRange::from(self);
943
944 let mut labeled = IndexMap::new();
946 for labeled_arg in &self.arguments {
947 let source_range = SourceRange::from(labeled_arg.arg.clone());
948 let metadata = Metadata { source_range };
949 let value = ctx
950 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
951 .await?;
952 let arg = Arg::new(value, source_range);
953 match &labeled_arg.label {
954 Some(label) => {
955 labeled.insert(label.name.clone(), arg);
956 }
957 None => {
958 let name = labeled_arg.arg.ident_name();
959 if let Some(name) = name {
960 labeled.insert(name.to_owned(), arg);
961 } else {
962 return Err(KclError::new_semantic(KclErrorDetails::new(
963 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
964 vec![SourceRange::from(&labeled_arg.arg)],
965 )));
966 }
967 }
968 }
969 }
970 let mut args = Args::new_no_args(range, ctx.clone(), Some("sketch block".to_owned()));
971 args.labeled = labeled;
972
973 #[cfg(feature = "artifact-graph")]
974 let sketch_id = {
975 use crate::{
980 engine::PlaneName,
981 execution::{Artifact, ArtifactId, CodeRef, SketchBlock},
982 };
983 let sketch_id = exec_state.next_object_id();
984 let arg_on: Option<crate::execution::Plane> =
985 args.get_kw_arg_opt("on", &RuntimeType::plane(), exec_state)?;
986 let on_object = arg_on.as_ref().and_then(|plane| plane.object_id);
987
988 let plane_artifact_id = arg_on.as_ref().map(|plane| plane.artifact_id);
990
991 let artifact_id = ArtifactId::from(exec_state.next_uuid());
992 let sketch_scene_object = Object {
993 id: sketch_id,
994 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
995 args: crate::front::SketchArgs {
996 on: on_object
997 .map(crate::front::Plane::Object)
998 .unwrap_or(crate::front::Plane::Default(PlaneName::Xy)),
999 },
1000 segments: Default::default(),
1001 constraints: Default::default(),
1002 is_underconstrained: None,
1003 }),
1004 label: Default::default(),
1005 comments: Default::default(),
1006 artifact_id,
1007 source: range.into(),
1008 };
1009 exec_state.add_scene_object(sketch_scene_object, range);
1010
1011 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
1013 id: artifact_id,
1014 plane_id: plane_artifact_id,
1015 code_ref: CodeRef::placeholder(range),
1016 sketch_id,
1017 }));
1018
1019 sketch_id
1020 };
1021
1022 let (return_result, variables, sketch_block_state) = {
1023 self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
1025
1026 let original_value = exec_state.mod_local.sketch_block.replace(SketchBlockState::default());
1028
1029 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1030
1031 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1032
1033 let block_variables = exec_state
1034 .stack()
1035 .find_all_in_current_env()
1036 .map(|(name, value)| (name.clone(), value.clone()))
1037 .collect::<IndexMap<_, _>>();
1038
1039 exec_state.mut_stack().pop_env();
1040
1041 (result, block_variables, sketch_block_state)
1042 };
1043
1044 return_result?;
1046 let Some(sketch_block_state) = sketch_block_state else {
1047 debug_assert!(false, "Sketch block state should still be set to Some from just above");
1048 return Err(KclError::new_internal(KclErrorDetails::new(
1049 "Sketch block state should still be set to Some from just above".to_owned(),
1050 vec![SourceRange::from(self)],
1051 )));
1052 };
1053
1054 let constraints = sketch_block_state
1056 .solver_constraints
1057 .iter()
1058 .cloned()
1059 .map(kcl_ezpz::ConstraintRequest::highest_priority)
1060 .chain(
1061 sketch_block_state
1063 .solver_optional_constraints
1064 .iter()
1065 .cloned()
1066 .map(|c| kcl_ezpz::ConstraintRequest::new(c, 1)),
1067 )
1068 .collect::<Vec<_>>();
1069 let initial_guesses = sketch_block_state
1070 .sketch_vars
1071 .iter()
1072 .map(|v| {
1073 let Some(sketch_var) = v.as_sketch_var() else {
1074 return Err(KclError::new_internal(KclErrorDetails::new(
1075 "Expected sketch variable".to_owned(),
1076 vec![SourceRange::from(self)],
1077 )));
1078 };
1079 let constraint_id = sketch_var.id.to_constraint_id(range)?;
1080 let number_value = KclValue::Number {
1082 value: sketch_var.initial_value,
1083 ty: sketch_var.ty,
1084 meta: sketch_var.meta.clone(),
1085 };
1086 let initial_guess_value =
1087 normalize_to_solver_unit(&number_value, v.into(), exec_state, "sketch variable initial value")?;
1088 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1089 n.n
1090 } else {
1091 let message = format!(
1092 "Expected number after coercion, but found {}",
1093 initial_guess_value.human_friendly_type()
1094 );
1095 debug_assert!(false, "{}", &message);
1096 return Err(KclError::new_internal(KclErrorDetails::new(
1097 message,
1098 vec![SourceRange::from(self)],
1099 )));
1100 };
1101 Ok((constraint_id, initial_guess))
1102 })
1103 .collect::<Result<Vec<_>, KclError>>()?;
1104 let config = kcl_ezpz::Config {
1106 max_iterations: 50,
1107 ..Default::default()
1108 };
1109 let solve_result = if exec_state.mod_local.freedom_analysis {
1110 kcl_ezpz::solve_with_priority_analysis(&constraints, initial_guesses.clone(), config)
1111 .map(|outcome| (outcome.outcome, Some(outcome.analysis)))
1112 } else {
1113 kcl_ezpz::solve_with_priority(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1114 };
1115 let (solve_outcome, solve_analysis) = match solve_result {
1116 Ok(o) => o,
1117 Err(failure) => {
1118 if let kcl_ezpz::Error::Solver(_) = &failure.error {
1119 exec_state.warn(
1122 CompilationError::err(range, "Constraint solver failed to find a solution".to_owned()),
1123 annotations::WARN_SOLVER,
1124 );
1125 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1126 (
1127 kcl_ezpz::SolveOutcome {
1128 final_values,
1129 iterations: Default::default(),
1130 warnings: failure.warnings,
1131 unsatisfied: Default::default(),
1132 priority_solved: Default::default(),
1133 },
1134 None,
1135 )
1136 } else {
1137 return Err(KclError::new_internal(KclErrorDetails::new(
1138 format!("Error from constraint solver: {}", &failure.error),
1139 vec![SourceRange::from(self)],
1140 )));
1141 }
1142 }
1143 };
1144 #[cfg(not(feature = "artifact-graph"))]
1145 let _ = solve_analysis;
1146 for warning in &solve_outcome.warnings {
1148 let message = if let Some(index) = warning.about_constraint.as_ref() {
1149 format!("{}; constraint index {}", &warning.content, index)
1150 } else {
1151 format!("{}", &warning.content)
1152 };
1153 exec_state.warn(CompilationError::err(range, message), annotations::WARN_SOLVER);
1154 }
1155 let solution_ty = solver_numeric_type(exec_state);
1157 let variables = substitute_sketch_vars(variables, &solve_outcome, solution_ty)?;
1158 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1159 for unsolved_segment in &sketch_block_state.needed_by_engine {
1160 solved_segments.push(substitute_sketch_var_in_segment(
1161 unsolved_segment.clone(),
1162 &solve_outcome,
1163 solver_numeric_type(exec_state),
1164 )?);
1165 }
1166 #[cfg(feature = "artifact-graph")]
1167 {
1168 if exec_state.mod_local.artifacts.var_solutions.is_empty() {
1174 exec_state.mod_local.artifacts.var_solutions =
1175 sketch_block_state.var_solutions(solve_outcome, solution_ty, SourceRange::from(self))?;
1176 }
1177 }
1178
1179 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1181
1182 #[cfg(not(feature = "artifact-graph"))]
1183 drop(scene_objects);
1184 #[cfg(feature = "artifact-graph")]
1185 {
1186 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1187 for scene_object in scene_objects {
1188 segment_object_ids.push(scene_object.id);
1189 exec_state.set_scene_object(scene_object);
1191 }
1192 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_objects.get_mut(sketch_id.0) else {
1194 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1195 debug_assert!(false, "{}", &message);
1196 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1197 };
1198 let ObjectKind::Sketch(sketch) = &mut sketch_object.kind else {
1199 let message = format!(
1200 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1201 sketch_id, sketch_object
1202 );
1203 debug_assert!(false, "{}", &message);
1204 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1205 };
1206 sketch.segments.extend(segment_object_ids);
1207 let mut sketch_block_state = sketch_block_state;
1209 sketch
1210 .constraints
1211 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1212 sketch.is_underconstrained = solve_analysis.map(|analysis| analysis.is_underconstrained);
1214
1215 exec_state.push_op(Operation::SketchSolve {
1217 sketch_id,
1218 node_path: NodePath::placeholder(),
1219 source_range: range,
1220 });
1221 }
1222
1223 let metadata = Metadata {
1226 source_range: SourceRange::from(self),
1227 };
1228 Ok(KclValue::Object {
1229 value: variables,
1230 constrainable: Default::default(),
1231 meta: vec![metadata],
1232 })
1233 }
1234}
1235
1236fn solver_unit(exec_state: &ExecState) -> UnitLength {
1237 exec_state.length_unit()
1238}
1239
1240fn solver_numeric_type(exec_state: &ExecState) -> NumericType {
1241 NumericType::Known(UnitType::Length(solver_unit(exec_state)))
1242}
1243
1244pub(crate) fn normalize_to_solver_unit(
1247 value: &KclValue,
1248 source_range: SourceRange,
1249 exec_state: &mut ExecState,
1250 description: &str,
1251) -> Result<KclValue, KclError> {
1252 let length_ty = RuntimeType::Primitive(PrimitiveType::Number(solver_numeric_type(exec_state)));
1253 value.coerce(&length_ty, true, exec_state).map_err(|_| {
1254 KclError::new_semantic(KclErrorDetails::new(
1255 format!(
1256 "{} must be a length coercible to the module length unit {}, but found {}",
1257 description,
1258 length_ty.human_friendly_type(),
1259 value.human_friendly_type(),
1260 ),
1261 vec![source_range],
1262 ))
1263 })
1264}
1265
1266fn substitute_sketch_vars(
1267 variables: IndexMap<String, KclValue>,
1268 solve_outcome: &SolveOutcome,
1269 solution_ty: NumericType,
1270) -> Result<HashMap<String, KclValue>, KclError> {
1271 let mut subbed = HashMap::with_capacity(variables.len());
1272 for (name, value) in variables {
1273 let subbed_value = substitute_sketch_var(value, solve_outcome, solution_ty)?;
1274 subbed.insert(name, subbed_value);
1275 }
1276 Ok(subbed)
1277}
1278
1279fn substitute_sketch_var(
1280 value: KclValue,
1281 solve_outcome: &SolveOutcome,
1282 solution_ty: NumericType,
1283) -> Result<KclValue, KclError> {
1284 match value {
1285 KclValue::Uuid { .. } => Ok(value),
1286 KclValue::Bool { .. } => Ok(value),
1287 KclValue::Number { .. } => Ok(value),
1288 KclValue::String { .. } => Ok(value),
1289 KclValue::SketchVar { value: var } => {
1290 let Some(solution) = solve_outcome.final_values.get(var.id.0) else {
1291 let message = format!("No solution for sketch variable with id {}", var.id.0);
1292 debug_assert!(false, "{}", &message);
1293 return Err(KclError::new_internal(KclErrorDetails::new(
1294 message,
1295 var.meta.into_iter().map(|m| m.source_range).collect(),
1296 )));
1297 };
1298 Ok(KclValue::Number {
1299 value: *solution,
1300 ty: solution_ty,
1301 meta: var.meta.clone(),
1302 })
1303 }
1304 KclValue::SketchConstraint { .. } => {
1305 debug_assert!(false, "Sketch constraints should not appear in substituted values");
1306 Ok(value)
1307 }
1308 KclValue::Tuple { value, meta } => {
1309 let subbed = value
1310 .into_iter()
1311 .map(|v| substitute_sketch_var(v, solve_outcome, solution_ty))
1312 .collect::<Result<Vec<_>, KclError>>()?;
1313 Ok(KclValue::Tuple { value: subbed, meta })
1314 }
1315 KclValue::HomArray { value, ty } => {
1316 let subbed = value
1317 .into_iter()
1318 .map(|v| substitute_sketch_var(v, solve_outcome, solution_ty))
1319 .collect::<Result<Vec<_>, KclError>>()?;
1320 Ok(KclValue::HomArray { value: subbed, ty })
1321 }
1322 KclValue::Object {
1323 value,
1324 constrainable,
1325 meta,
1326 } => {
1327 let subbed = value
1328 .into_iter()
1329 .map(|(k, v)| substitute_sketch_var(v, solve_outcome, solution_ty).map(|v| (k, v)))
1330 .collect::<Result<HashMap<_, _>, KclError>>()?;
1331 Ok(KclValue::Object {
1332 value: subbed,
1333 constrainable,
1334 meta,
1335 })
1336 }
1337 KclValue::TagIdentifier(_) => Ok(value),
1338 KclValue::TagDeclarator(_) => Ok(value),
1339 KclValue::GdtAnnotation { .. } => Ok(value),
1340 KclValue::Plane { .. } => Ok(value),
1341 KclValue::Face { .. } => Ok(value),
1342 KclValue::Segment {
1343 value: abstract_segment,
1344 } => match abstract_segment.repr {
1345 SegmentRepr::Unsolved { segment } => {
1346 let subbed = substitute_sketch_var_in_segment(segment, solve_outcome, solution_ty)?;
1347 Ok(KclValue::Segment {
1348 value: Box::new(AbstractSegment {
1349 repr: SegmentRepr::Solved { segment: subbed },
1350 meta: abstract_segment.meta,
1351 }),
1352 })
1353 }
1354 SegmentRepr::Solved { .. } => Ok(KclValue::Segment {
1355 value: abstract_segment,
1356 }),
1357 },
1358 KclValue::Sketch { .. } => Ok(value),
1359 KclValue::Solid { .. } => Ok(value),
1360 KclValue::Helix { .. } => Ok(value),
1361 KclValue::ImportedGeometry(_) => Ok(value),
1362 KclValue::Function { .. } => Ok(value),
1363 KclValue::Module { .. } => Ok(value),
1364 KclValue::Type { .. } => Ok(value),
1365 KclValue::KclNone { .. } => Ok(value),
1366 }
1367}
1368
1369fn substitute_sketch_var_in_segment(
1370 segment: UnsolvedSegment,
1371 solve_outcome: &SolveOutcome,
1372 solution_ty: NumericType,
1373) -> Result<Segment, KclError> {
1374 let srs = segment.meta.iter().map(|m| m.source_range).collect::<Vec<_>>();
1375 match &segment.kind {
1376 UnsolvedSegmentKind::Point { position, ctor } => {
1377 let (position_x, position_x_freedom) =
1378 substitute_sketch_var_in_unsolved_expr(&position[0], solve_outcome, solution_ty, &srs)?;
1379 let (position_y, position_y_freedom) =
1380 substitute_sketch_var_in_unsolved_expr(&position[1], solve_outcome, solution_ty, &srs)?;
1381 let position = [position_x, position_y];
1382 Ok(Segment {
1383 object_id: segment.object_id,
1384 kind: SegmentKind::Point {
1385 position,
1386 ctor: ctor.clone(),
1387 freedom: position_x_freedom.merge(position_y_freedom),
1388 },
1389 meta: segment.meta,
1390 })
1391 }
1392 UnsolvedSegmentKind::Line {
1393 start,
1394 end,
1395 ctor,
1396 start_object_id,
1397 end_object_id,
1398 } => {
1399 let (start_x, start_x_freedom) =
1400 substitute_sketch_var_in_unsolved_expr(&start[0], solve_outcome, solution_ty, &srs)?;
1401 let (start_y, start_y_freedom) =
1402 substitute_sketch_var_in_unsolved_expr(&start[1], solve_outcome, solution_ty, &srs)?;
1403 let (end_x, end_x_freedom) =
1404 substitute_sketch_var_in_unsolved_expr(&end[0], solve_outcome, solution_ty, &srs)?;
1405 let (end_y, end_y_freedom) =
1406 substitute_sketch_var_in_unsolved_expr(&end[1], solve_outcome, solution_ty, &srs)?;
1407 let start = [start_x, start_y];
1408 let end = [end_x, end_y];
1409 Ok(Segment {
1410 object_id: segment.object_id,
1411 kind: SegmentKind::Line {
1412 start,
1413 end,
1414 ctor: ctor.clone(),
1415 start_object_id: *start_object_id,
1416 end_object_id: *end_object_id,
1417 start_freedom: start_x_freedom.merge(start_y_freedom),
1418 end_freedom: end_x_freedom.merge(end_y_freedom),
1419 },
1420 meta: segment.meta,
1421 })
1422 }
1423 }
1424}
1425
1426fn substitute_sketch_var_in_unsolved_expr(
1427 unsolved_expr: &UnsolvedExpr,
1428 solve_outcome: &SolveOutcome,
1429 solution_ty: NumericType,
1430 source_ranges: &[SourceRange],
1431) -> Result<(TyF64, Freedom), KclError> {
1432 match unsolved_expr {
1433 UnsolvedExpr::Known(n) => Ok((n.clone(), Freedom::Fixed)),
1434 UnsolvedExpr::Unknown(var_id) => {
1435 let Some(solution) = solve_outcome.final_values.get(var_id.0) else {
1436 let message = format!("No solution for sketch variable with id {}", var_id.0);
1437 debug_assert!(false, "{}", &message);
1438 return Err(KclError::new_internal(KclErrorDetails::new(
1439 message,
1440 source_ranges.to_vec(),
1441 )));
1442 };
1443 let freedom = if solve_outcome.unsatisfied.contains(&var_id.0) {
1444 Freedom::Conflict
1445 } else {
1446 Freedom::Fixed
1448 };
1449 Ok((TyF64::new(*solution, solution_ty), freedom))
1450 }
1451 }
1452}
1453
1454#[cfg(not(feature = "artifact-graph"))]
1455fn create_segment_scene_objects(
1456 _segments: &[Segment],
1457 _sketch_block_range: SourceRange,
1458 _exec_state: &mut ExecState,
1459) -> Result<Vec<Object>, KclError> {
1460 Ok(Vec::new())
1461}
1462
1463#[cfg(feature = "artifact-graph")]
1464fn create_segment_scene_objects(
1465 segments: &[Segment],
1466 sketch_block_range: SourceRange,
1467 exec_state: &mut ExecState,
1468) -> Result<Vec<Object>, KclError> {
1469 let mut scene_objects = Vec::with_capacity(segments.len());
1470 for segment in segments {
1471 let source = Metadata::to_source_ref(&segment.meta);
1472
1473 match &segment.kind {
1474 SegmentKind::Point {
1475 position,
1476 ctor,
1477 freedom,
1478 } => {
1479 let point2d = TyF64::to_point2d(position).map_err(|_| {
1480 KclError::new_internal(KclErrorDetails::new(
1481 format!("Error converting start point runtime type to API value: {:?}", position),
1482 vec![sketch_block_range],
1483 ))
1484 })?;
1485 let artifact_id = exec_state.next_artifact_id();
1486 let point_object = Object {
1487 id: segment.object_id,
1488 kind: ObjectKind::Segment {
1489 segment: crate::front::Segment::Point(crate::front::Point {
1490 position: point2d.clone(),
1491 ctor: Some(crate::front::PointCtor {
1492 position: ctor.position.clone(),
1493 }),
1494 owner: None,
1495 freedom: *freedom,
1496 constraints: Vec::new(),
1497 }),
1498 },
1499 label: Default::default(),
1500 comments: Default::default(),
1501 artifact_id,
1502 source: source.clone(),
1503 };
1504 scene_objects.push(point_object);
1505 }
1506 SegmentKind::Line {
1507 start,
1508 end,
1509 ctor,
1510 start_object_id,
1511 end_object_id,
1512 start_freedom,
1513 end_freedom,
1514 } => {
1515 let start_point2d = TyF64::to_point2d(start).map_err(|_| {
1516 KclError::new_internal(KclErrorDetails::new(
1517 format!("Error converting start point runtime type to API value: {:?}", start),
1518 vec![sketch_block_range],
1519 ))
1520 })?;
1521 let start_artifact_id = exec_state.next_artifact_id();
1522 let start_point_object = Object {
1523 id: *start_object_id,
1524 kind: ObjectKind::Segment {
1525 segment: crate::front::Segment::Point(crate::front::Point {
1526 position: start_point2d.clone(),
1527 ctor: None,
1528 owner: Some(segment.object_id),
1529 freedom: *start_freedom,
1530 constraints: Vec::new(),
1531 }),
1532 },
1533 label: Default::default(),
1534 comments: Default::default(),
1535 artifact_id: start_artifact_id,
1536 source: source.clone(),
1537 };
1538 let start_point_object_id = start_point_object.id;
1539 scene_objects.push(start_point_object);
1540
1541 let end_point2d = TyF64::to_point2d(end).map_err(|_| {
1542 KclError::new_internal(KclErrorDetails::new(
1543 format!("Error converting end point runtime type to API value: {:?}", end),
1544 vec![sketch_block_range],
1545 ))
1546 })?;
1547 let end_artifact_id = exec_state.next_artifact_id();
1548 let end_point_object = Object {
1549 id: *end_object_id,
1550 kind: ObjectKind::Segment {
1551 segment: crate::front::Segment::Point(crate::front::Point {
1552 position: end_point2d.clone(),
1553 ctor: None,
1554 owner: Some(segment.object_id),
1555 freedom: *end_freedom,
1556 constraints: Vec::new(),
1557 }),
1558 },
1559 label: Default::default(),
1560 comments: Default::default(),
1561 artifact_id: end_artifact_id,
1562 source: source.clone(),
1563 };
1564 let end_point_object_id = end_point_object.id;
1565 scene_objects.push(end_point_object);
1566
1567 let line_artifact_id = exec_state.next_artifact_id();
1568 let segment_object = Object {
1569 id: segment.object_id,
1570 kind: ObjectKind::Segment {
1571 segment: crate::front::Segment::Line(crate::front::Line {
1572 start: start_point_object_id,
1573 end: end_point_object_id,
1574 ctor: crate::front::SegmentCtor::Line(ctor.as_ref().clone()),
1575 ctor_applicable: true,
1576 }),
1577 },
1578 label: Default::default(),
1579 comments: Default::default(),
1580 artifact_id: line_artifact_id,
1581 source,
1582 };
1583 scene_objects.push(segment_object);
1584 }
1585 }
1586 }
1587 Ok(scene_objects)
1588}
1589
1590impl SketchBlock {
1591 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1592 exec_state.mut_stack().push_new_env_for_call(parent);
1593 }
1594}
1595
1596impl Node<SketchVar> {
1597 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1598 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1599 return Err(KclError::new_semantic(KclErrorDetails::new(
1600 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1601 vec![SourceRange::from(self)],
1602 )));
1603 };
1604 let id = sketch_block_state.next_sketch_var_id();
1605 let sketch_var = if let Some(initial) = &self.initial {
1606 KclValue::from_sketch_var_literal(initial, id, exec_state)
1607 } else {
1608 let metadata = Metadata {
1609 source_range: SourceRange::from(self),
1610 };
1611
1612 KclValue::SketchVar {
1613 value: Box::new(super::SketchVar {
1614 id,
1615 initial_value: 0.0,
1616 ty: NumericType::default(),
1617 meta: vec![metadata],
1618 }),
1619 }
1620 };
1621
1622 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1623 return Err(KclError::new_semantic(KclErrorDetails::new(
1624 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1625 vec![SourceRange::from(self)],
1626 )));
1627 };
1628 sketch_block_state.sketch_vars.push(sketch_var.clone());
1629
1630 Ok(sketch_var)
1631 }
1632}
1633
1634fn apply_ascription(
1635 value: &KclValue,
1636 ty: &Node<Type>,
1637 exec_state: &mut ExecState,
1638 source_range: SourceRange,
1639) -> Result<KclValue, KclError> {
1640 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false)
1641 .map_err(|e| KclError::new_semantic(e.into()))?;
1642
1643 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1644 exec_state.clear_units_warnings(&source_range);
1645 }
1646
1647 value.coerce(&ty, false, exec_state).map_err(|_| {
1648 let suggestion = if ty == RuntimeType::length() {
1649 ", you might try coercing to a fully specified numeric type such as `mm`"
1650 } else if ty == RuntimeType::angle() {
1651 ", you might try coercing to a fully specified numeric type such as `deg`"
1652 } else {
1653 ""
1654 };
1655 let ty_str = if let Some(ty) = value.principal_type() {
1656 format!("(with type `{ty}`) ")
1657 } else {
1658 String::new()
1659 };
1660 KclError::new_semantic(KclErrorDetails::new(
1661 format!(
1662 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1663 value.human_friendly_type()
1664 ),
1665 vec![source_range],
1666 ))
1667 })
1668}
1669
1670impl BinaryPart {
1671 #[async_recursion]
1672 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1673 match self {
1674 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state)),
1675 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned(),
1676 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1677 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1678 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1679 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1680 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1681 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1682 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1683 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1684 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1685 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await,
1686 }
1687 }
1688}
1689
1690impl Node<Name> {
1691 pub(super) async fn get_result<'a>(
1692 &self,
1693 exec_state: &'a mut ExecState,
1694 ctx: &ExecutorContext,
1695 ) -> Result<&'a KclValue, KclError> {
1696 let being_declared = exec_state.mod_local.being_declared.clone();
1697 self.get_result_inner(exec_state, ctx)
1698 .await
1699 .map_err(|e| var_in_own_ref_err(e, &being_declared))
1700 }
1701
1702 async fn get_result_inner<'a>(
1703 &self,
1704 exec_state: &'a mut ExecState,
1705 ctx: &ExecutorContext,
1706 ) -> Result<&'a KclValue, KclError> {
1707 if self.abs_path {
1708 return Err(KclError::new_semantic(KclErrorDetails::new(
1709 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1710 self.as_source_ranges(),
1711 )));
1712 }
1713
1714 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1715
1716 if self.path.is_empty() {
1717 let item_value = exec_state.stack().get(&self.name.name, self.into());
1718 if item_value.is_ok() {
1719 return item_value;
1720 }
1721 return exec_state.stack().get(&mod_name, self.into());
1722 }
1723
1724 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1725 for p in &self.path {
1726 let value = match mem_spec {
1727 Some((env, exports)) => {
1728 if !exports.contains(&p.name) {
1729 return Err(KclError::new_semantic(KclErrorDetails::new(
1730 format!("Item {} not found in module's exported items", p.name),
1731 p.as_source_ranges(),
1732 )));
1733 }
1734
1735 exec_state
1736 .stack()
1737 .memory
1738 .get_from(&p.name, env, p.as_source_range(), 0)?
1739 }
1740 None => exec_state
1741 .stack()
1742 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
1743 };
1744
1745 let KclValue::Module { value: module_id, .. } = value else {
1746 return Err(KclError::new_semantic(KclErrorDetails::new(
1747 format!(
1748 "Identifier in path must refer to a module, found {}",
1749 value.human_friendly_type()
1750 ),
1751 p.as_source_ranges(),
1752 )));
1753 };
1754
1755 mem_spec = Some(
1756 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1757 .await?,
1758 );
1759 }
1760
1761 let (env, exports) = mem_spec.unwrap();
1762
1763 let item_exported = exports.contains(&self.name.name);
1764 let item_value = exec_state
1765 .stack()
1766 .memory
1767 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1768
1769 if item_exported && item_value.is_ok() {
1771 return item_value;
1772 }
1773
1774 let mod_exported = exports.contains(&mod_name);
1775 let mod_value = exec_state
1776 .stack()
1777 .memory
1778 .get_from(&mod_name, env, self.name.as_source_range(), 0);
1779
1780 if mod_exported && mod_value.is_ok() {
1782 return mod_value;
1783 }
1784
1785 if item_value.is_err() && mod_value.is_err() {
1787 return item_value;
1788 }
1789
1790 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1792 Err(KclError::new_semantic(KclErrorDetails::new(
1793 format!("Item {} not found in module's exported items", self.name.name),
1794 self.name.as_source_ranges(),
1795 )))
1796 }
1797}
1798
1799impl Node<MemberExpression> {
1800 async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1801 let meta = Metadata {
1802 source_range: SourceRange::from(self),
1803 };
1804 let property = Property::try_from(
1805 self.computed,
1806 self.property.clone(),
1807 exec_state,
1808 self.into(),
1809 ctx,
1810 &meta,
1811 &[],
1812 StatementKind::Expression,
1813 )
1814 .await?;
1815 let object = ctx
1816 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1817 .await?;
1818
1819 match (object, property, self.computed) {
1821 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
1822 "at" => match &segment.repr {
1823 SegmentRepr::Unsolved { segment } => {
1824 match &segment.kind {
1825 UnsolvedSegmentKind::Point { position, .. } => {
1826 Ok(KclValue::HomArray {
1828 value: vec![
1829 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
1830 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
1831 ],
1832 ty: RuntimeType::any(),
1833 })
1834 }
1835 UnsolvedSegmentKind::Line { .. } => Err(KclError::new_undefined_value(
1836 KclErrorDetails::new(
1837 format!("Property '{property}' not found in segment"),
1838 vec![self.clone().into()],
1839 ),
1840 None,
1841 )),
1842 }
1843 }
1844 SegmentRepr::Solved { segment } => {
1845 match &segment.kind {
1846 SegmentKind::Point { position, .. } => {
1847 Ok(KclValue::array_from_point2d(
1849 [position[0].n, position[1].n],
1850 position[0].ty,
1851 segment.meta.clone(),
1852 ))
1853 }
1854 SegmentKind::Line { .. } => Err(KclError::new_undefined_value(
1855 KclErrorDetails::new(
1856 format!("Property '{property}' not found in segment"),
1857 vec![self.clone().into()],
1858 ),
1859 None,
1860 )),
1861 }
1862 }
1863 },
1864 "start" => match &segment.repr {
1865 SegmentRepr::Unsolved { segment } => match &segment.kind {
1866 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1867 KclErrorDetails::new(
1868 format!("Property '{property}' not found in point segment"),
1869 vec![self.clone().into()],
1870 ),
1871 None,
1872 )),
1873 UnsolvedSegmentKind::Line {
1874 start,
1875 ctor,
1876 start_object_id,
1877 ..
1878 } => Ok(KclValue::Segment {
1879 value: Box::new(AbstractSegment {
1880 repr: SegmentRepr::Unsolved {
1881 segment: UnsolvedSegment {
1882 object_id: *start_object_id,
1883 kind: UnsolvedSegmentKind::Point {
1884 position: start.clone(),
1885 ctor: Box::new(PointCtor {
1886 position: ctor.start.clone(),
1887 }),
1888 },
1889 meta: segment.meta.clone(),
1890 },
1891 },
1892 meta: segment.meta.clone(),
1893 }),
1894 }),
1895 },
1896 SegmentRepr::Solved { segment } => match &segment.kind {
1897 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1898 KclErrorDetails::new(
1899 format!("Property '{property}' not found in point segment"),
1900 vec![self.clone().into()],
1901 ),
1902 None,
1903 )),
1904 SegmentKind::Line {
1905 start,
1906 ctor,
1907 start_object_id,
1908 start_freedom,
1909 ..
1910 } => Ok(KclValue::Segment {
1911 value: Box::new(AbstractSegment {
1912 repr: SegmentRepr::Solved {
1913 segment: Segment {
1914 object_id: *start_object_id,
1915 kind: SegmentKind::Point {
1916 position: start.clone(),
1917 ctor: Box::new(PointCtor {
1918 position: ctor.start.clone(),
1919 }),
1920 freedom: *start_freedom,
1921 },
1922 meta: segment.meta.clone(),
1923 },
1924 },
1925 meta: segment.meta.clone(),
1926 }),
1927 }),
1928 },
1929 },
1930 "end" => match &segment.repr {
1931 SegmentRepr::Unsolved { segment } => match &segment.kind {
1932 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1933 KclErrorDetails::new(
1934 format!("Property '{property}' not found in point segment"),
1935 vec![self.clone().into()],
1936 ),
1937 None,
1938 )),
1939 UnsolvedSegmentKind::Line {
1940 end,
1941 ctor,
1942 end_object_id,
1943 ..
1944 } => Ok(KclValue::Segment {
1945 value: Box::new(AbstractSegment {
1946 repr: SegmentRepr::Unsolved {
1947 segment: UnsolvedSegment {
1948 object_id: *end_object_id,
1949 kind: UnsolvedSegmentKind::Point {
1950 position: end.clone(),
1951 ctor: Box::new(PointCtor {
1952 position: ctor.end.clone(),
1953 }),
1954 },
1955 meta: segment.meta.clone(),
1956 },
1957 },
1958 meta: segment.meta.clone(),
1959 }),
1960 }),
1961 },
1962 SegmentRepr::Solved { segment } => match &segment.kind {
1963 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1964 KclErrorDetails::new(
1965 format!("Property '{property}' not found in point segment"),
1966 vec![self.clone().into()],
1967 ),
1968 None,
1969 )),
1970 SegmentKind::Line {
1971 end,
1972 ctor,
1973 end_object_id,
1974 end_freedom,
1975 ..
1976 } => Ok(KclValue::Segment {
1977 value: Box::new(AbstractSegment {
1978 repr: SegmentRepr::Solved {
1979 segment: Segment {
1980 object_id: *end_object_id,
1981 kind: SegmentKind::Point {
1982 position: end.clone(),
1983 ctor: Box::new(PointCtor {
1984 position: ctor.end.clone(),
1985 }),
1986 freedom: *end_freedom,
1987 },
1988 meta: segment.meta.clone(),
1989 },
1990 },
1991 meta: segment.meta.clone(),
1992 }),
1993 }),
1994 },
1995 },
1996 other => Err(KclError::new_undefined_value(
1997 KclErrorDetails::new(
1998 format!("Property '{other}' not found in segment"),
1999 vec![self.clone().into()],
2000 ),
2001 None,
2002 )),
2003 },
2004 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
2005 "zAxis" => {
2006 let (p, u) = plane.info.z_axis.as_3_dims();
2007 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
2008 }
2009 "yAxis" => {
2010 let (p, u) = plane.info.y_axis.as_3_dims();
2011 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
2012 }
2013 "xAxis" => {
2014 let (p, u) = plane.info.x_axis.as_3_dims();
2015 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
2016 }
2017 "origin" => {
2018 let (p, u) = plane.info.origin.as_3_dims();
2019 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]))
2020 }
2021 other => Err(KclError::new_undefined_value(
2022 KclErrorDetails::new(
2023 format!("Property '{other}' not found in plane"),
2024 vec![self.clone().into()],
2025 ),
2026 None,
2027 )),
2028 },
2029 (KclValue::Object { value: map, .. }, Property::String(property), false) => {
2030 if let Some(value) = map.get(&property) {
2031 Ok(value.to_owned())
2032 } else {
2033 Err(KclError::new_undefined_value(
2034 KclErrorDetails::new(
2035 format!("Property '{property}' not found in object"),
2036 vec![self.clone().into()],
2037 ),
2038 None,
2039 ))
2040 }
2041 }
2042 (KclValue::Object { .. }, Property::String(property), true) => {
2043 Err(KclError::new_semantic(KclErrorDetails::new(
2044 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
2045 vec![self.clone().into()],
2046 )))
2047 }
2048 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
2049 if i == 0
2050 && let Some(value) = map.get("x")
2051 {
2052 return Ok(value.to_owned());
2053 }
2054 if i == 1
2055 && let Some(value) = map.get("y")
2056 {
2057 return Ok(value.to_owned());
2058 }
2059 if i == 2
2060 && let Some(value) = map.get("z")
2061 {
2062 return Ok(value.to_owned());
2063 }
2064 let t = p.type_name();
2065 let article = article_for(t);
2066 Err(KclError::new_semantic(KclErrorDetails::new(
2067 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
2068 vec![self.clone().into()],
2069 )))
2070 }
2071 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
2072 let value_of_arr = arr.get(index);
2073 if let Some(value) = value_of_arr {
2074 Ok(value.to_owned())
2075 } else {
2076 Err(KclError::new_undefined_value(
2077 KclErrorDetails::new(
2078 format!("The array doesn't have any item at index {index}"),
2079 vec![self.clone().into()],
2080 ),
2081 None,
2082 ))
2083 }
2084 }
2085 (obj, Property::UInt(0), _) => Ok(obj),
2088 (KclValue::HomArray { .. }, p, _) => {
2089 let t = p.type_name();
2090 let article = article_for(t);
2091 Err(KclError::new_semantic(KclErrorDetails::new(
2092 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
2093 vec![self.clone().into()],
2094 )))
2095 }
2096 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
2097 value: Box::new(value.sketch),
2098 }),
2099 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
2100 Err(KclError::new_semantic(KclErrorDetails::new(
2102 format!(
2103 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
2104 geometry.human_friendly_type()
2105 ),
2106 vec![self.clone().into()],
2107 )))
2108 }
2109 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
2110 meta: vec![Metadata {
2111 source_range: SourceRange::from(self.clone()),
2112 }],
2113 value: sk
2114 .tags
2115 .iter()
2116 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
2117 .collect(),
2118 constrainable: false,
2119 }),
2120 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
2121 Err(KclError::new_semantic(KclErrorDetails::new(
2122 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
2123 vec![self.clone().into()],
2124 )))
2125 }
2126 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
2127 format!(
2128 "Only objects can have members accessed with dot notation, but you're trying to access {}",
2129 being_indexed.human_friendly_type()
2130 ),
2131 vec![self.clone().into()],
2132 ))),
2133 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
2134 format!(
2135 "Only arrays can be indexed, but you're trying to index {}",
2136 being_indexed.human_friendly_type()
2137 ),
2138 vec![self.clone().into()],
2139 ))),
2140 }
2141 }
2142}
2143
2144impl Node<BinaryExpression> {
2145 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2146 enum State {
2147 EvaluateLeft(Node<BinaryExpression>),
2148 FromLeft {
2149 node: Node<BinaryExpression>,
2150 },
2151 EvaluateRight {
2152 node: Node<BinaryExpression>,
2153 left: KclValue,
2154 },
2155 FromRight {
2156 node: Node<BinaryExpression>,
2157 left: KclValue,
2158 },
2159 }
2160
2161 let mut stack = vec![State::EvaluateLeft(self.clone())];
2162 let mut last_result: Option<KclValue> = None;
2163
2164 while let Some(state) = stack.pop() {
2165 match state {
2166 State::EvaluateLeft(node) => {
2167 let left_part = node.left.clone();
2168 match left_part {
2169 BinaryPart::BinaryExpression(child) => {
2170 stack.push(State::FromLeft { node });
2171 stack.push(State::EvaluateLeft(*child));
2172 }
2173 part => {
2174 let left_value = part.get_result(exec_state, ctx).await?;
2175 stack.push(State::EvaluateRight { node, left: left_value });
2176 }
2177 }
2178 }
2179 State::FromLeft { node } => {
2180 let Some(left_value) = last_result.take() else {
2181 return Err(Self::missing_result_error(&node));
2182 };
2183 stack.push(State::EvaluateRight { node, left: left_value });
2184 }
2185 State::EvaluateRight { node, left } => {
2186 let right_part = node.right.clone();
2187 match right_part {
2188 BinaryPart::BinaryExpression(child) => {
2189 stack.push(State::FromRight { node, left });
2190 stack.push(State::EvaluateLeft(*child));
2191 }
2192 part => {
2193 let right_value = part.get_result(exec_state, ctx).await?;
2194 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2195 last_result = Some(result);
2196 }
2197 }
2198 }
2199 State::FromRight { node, left } => {
2200 let Some(right_value) = last_result.take() else {
2201 return Err(Self::missing_result_error(&node));
2202 };
2203 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2204 last_result = Some(result);
2205 }
2206 }
2207 }
2208
2209 last_result.ok_or_else(|| Self::missing_result_error(self))
2210 }
2211
2212 async fn apply_operator(
2213 &self,
2214 exec_state: &mut ExecState,
2215 ctx: &ExecutorContext,
2216 left_value: KclValue,
2217 right_value: KclValue,
2218 ) -> Result<KclValue, KclError> {
2219 let mut meta = left_value.metadata();
2220 meta.extend(right_value.metadata());
2221
2222 if self.operator == BinaryOperator::Add
2224 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
2225 (&left_value, &right_value)
2226 {
2227 return Ok(KclValue::String {
2228 value: format!("{left}{right}"),
2229 meta,
2230 });
2231 }
2232
2233 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
2235 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2236 let args = Args::new_no_args(self.into(), ctx.clone(), Some("union".to_owned()));
2237 let result = crate::std::csg::inner_union(
2238 vec![*left.clone(), *right.clone()],
2239 Default::default(),
2240 exec_state,
2241 args,
2242 )
2243 .await?;
2244 return Ok(result.into());
2245 }
2246 } else if self.operator == BinaryOperator::Sub {
2247 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2249 let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
2250 let result = crate::std::csg::inner_subtract(
2251 vec![*left.clone()],
2252 vec![*right.clone()],
2253 Default::default(),
2254 exec_state,
2255 args,
2256 )
2257 .await?;
2258 return Ok(result.into());
2259 }
2260 } else if self.operator == BinaryOperator::And
2261 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2262 {
2263 let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
2265 let result = crate::std::csg::inner_intersect(
2266 vec![*left.clone(), *right.clone()],
2267 Default::default(),
2268 exec_state,
2269 args,
2270 )
2271 .await?;
2272 return Ok(result.into());
2273 }
2274
2275 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2277 let KclValue::Bool { value: left_value, .. } = left_value else {
2278 return Err(KclError::new_semantic(KclErrorDetails::new(
2279 format!(
2280 "Cannot apply logical operator to non-boolean value: {}",
2281 left_value.human_friendly_type()
2282 ),
2283 vec![self.left.clone().into()],
2284 )));
2285 };
2286 let KclValue::Bool { value: right_value, .. } = right_value else {
2287 return Err(KclError::new_semantic(KclErrorDetails::new(
2288 format!(
2289 "Cannot apply logical operator to non-boolean value: {}",
2290 right_value.human_friendly_type()
2291 ),
2292 vec![self.right.clone().into()],
2293 )));
2294 };
2295 let raw_value = match self.operator {
2296 BinaryOperator::Or => left_value || right_value,
2297 BinaryOperator::And => left_value && right_value,
2298 _ => unreachable!(),
2299 };
2300 return Ok(KclValue::Bool { value: raw_value, meta });
2301 }
2302
2303 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2305 match (&left_value, &right_value) {
2306 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2308 if left_value.id == right_value.id =>
2309 {
2310 return Ok(KclValue::Bool { value: true, meta });
2311 }
2312 (KclValue::SketchVar { .. }, KclValue::SketchVar { .. }) => {
2314 return Err(KclError::new_semantic(KclErrorDetails::new(
2317 "TODO: Different sketch variables".to_owned(),
2318 vec![self.into()],
2319 )));
2320 }
2321 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2323 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2324 let number_value = normalize_to_solver_unit(
2325 input_number,
2326 input_number.into(),
2327 exec_state,
2328 "fixed constraint value",
2329 )?;
2330 let Some(n) = number_value.as_ty_f64() else {
2331 let message = format!(
2332 "Expected number after coercion, but found {}",
2333 number_value.human_friendly_type()
2334 );
2335 debug_assert!(false, "{}", &message);
2336 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![self.into()])));
2337 };
2338 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2339 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2340 let message = "Being inside a sketch block should have already been checked above".to_owned();
2341 debug_assert!(false, "{}", &message);
2342 return Err(KclError::new_internal(KclErrorDetails::new(
2343 message,
2344 vec![SourceRange::from(self)],
2345 )));
2346 };
2347 sketch_block_state.solver_constraints.push(constraint);
2348 return Ok(KclValue::Bool { value: true, meta });
2349 }
2350 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
2352 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
2353 let number_value = normalize_to_solver_unit(
2354 input_number,
2355 input_number.into(),
2356 exec_state,
2357 "fixed constraint value",
2358 )?;
2359 let Some(n) = number_value.as_ty_f64() else {
2360 let message = format!(
2361 "Expected number after coercion, but found {}",
2362 number_value.human_friendly_type()
2363 );
2364 debug_assert!(false, "{}", &message);
2365 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![self.into()])));
2366 };
2367 match &constraint.kind {
2368 SketchConstraintKind::Distance { points } => {
2369 let range = self.as_source_range();
2370 let p0 = &points[0];
2371 let p1 = &points[1];
2372 let solver_pt0 = kcl_ezpz::datatypes::DatumPoint::new_xy(
2373 p0.vars.x.to_constraint_id(range)?,
2374 p0.vars.y.to_constraint_id(range)?,
2375 );
2376 let solver_pt1 = kcl_ezpz::datatypes::DatumPoint::new_xy(
2377 p1.vars.x.to_constraint_id(range)?,
2378 p1.vars.y.to_constraint_id(range)?,
2379 );
2380 let solver_constraint = Constraint::Distance(solver_pt0, solver_pt1, n.n);
2381
2382 #[cfg(feature = "artifact-graph")]
2383 let constraint_id = exec_state.next_object_id();
2384 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2385 let message =
2386 "Being inside a sketch block should have already been checked above".to_owned();
2387 debug_assert!(false, "{}", &message);
2388 return Err(KclError::new_internal(KclErrorDetails::new(
2389 message,
2390 vec![SourceRange::from(self)],
2391 )));
2392 };
2393 sketch_block_state.solver_constraints.push(solver_constraint);
2394 #[cfg(feature = "artifact-graph")]
2395 {
2396 use crate::{execution::ArtifactId, front::Distance};
2397
2398 let constraint = crate::front::Constraint::Distance(Distance {
2399 points: vec![p0.object_id, p1.object_id],
2400 distance: n.try_into().map_err(|_| {
2401 KclError::new_internal(KclErrorDetails::new(
2402 "Failed to convert distance units numeric suffix:".to_owned(),
2403 vec![range],
2404 ))
2405 })?,
2406 });
2407 sketch_block_state.sketch_constraints.push(constraint_id);
2408 exec_state.add_scene_object(
2409 Object {
2410 id: constraint_id,
2411 kind: ObjectKind::Constraint { constraint },
2412 label: Default::default(),
2413 comments: Default::default(),
2414 artifact_id: ArtifactId::constraint(),
2415 source: range.into(),
2416 },
2417 range,
2418 );
2419 }
2420 }
2421 }
2422 return Ok(KclValue::Bool { value: true, meta });
2423 }
2424 _ => {
2425 return Err(KclError::new_semantic(KclErrorDetails::new(
2426 format!(
2427 "Cannot create an equivalence constraint between values of these types: {} and {}",
2428 left_value.human_friendly_type(),
2429 right_value.human_friendly_type()
2430 ),
2431 vec![self.into()],
2432 )));
2433 }
2434 }
2435 }
2436
2437 let left = number_as_f64(&left_value, self.left.clone().into())?;
2438 let right = number_as_f64(&right_value, self.right.clone().into())?;
2439
2440 let value = match self.operator {
2441 BinaryOperator::Add => {
2442 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2443 self.warn_on_unknown(&ty, "Adding", exec_state);
2444 KclValue::Number { value: l + r, meta, ty }
2445 }
2446 BinaryOperator::Sub => {
2447 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2448 self.warn_on_unknown(&ty, "Subtracting", exec_state);
2449 KclValue::Number { value: l - r, meta, ty }
2450 }
2451 BinaryOperator::Mul => {
2452 let (l, r, ty) = NumericType::combine_mul(left, right);
2453 self.warn_on_unknown(&ty, "Multiplying", exec_state);
2454 KclValue::Number { value: l * r, meta, ty }
2455 }
2456 BinaryOperator::Div => {
2457 let (l, r, ty) = NumericType::combine_div(left, right);
2458 self.warn_on_unknown(&ty, "Dividing", exec_state);
2459 KclValue::Number { value: l / r, meta, ty }
2460 }
2461 BinaryOperator::Mod => {
2462 let (l, r, ty) = NumericType::combine_mod(left, right);
2463 self.warn_on_unknown(&ty, "Modulo of", exec_state);
2464 KclValue::Number { value: l % r, meta, ty }
2465 }
2466 BinaryOperator::Pow => KclValue::Number {
2467 value: left.n.powf(right.n),
2468 meta,
2469 ty: exec_state.current_default_units(),
2470 },
2471 BinaryOperator::Neq => {
2472 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2473 self.warn_on_unknown(&ty, "Comparing", exec_state);
2474 KclValue::Bool { value: l != r, meta }
2475 }
2476 BinaryOperator::Gt => {
2477 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2478 self.warn_on_unknown(&ty, "Comparing", exec_state);
2479 KclValue::Bool { value: l > r, meta }
2480 }
2481 BinaryOperator::Gte => {
2482 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2483 self.warn_on_unknown(&ty, "Comparing", exec_state);
2484 KclValue::Bool { value: l >= r, meta }
2485 }
2486 BinaryOperator::Lt => {
2487 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2488 self.warn_on_unknown(&ty, "Comparing", exec_state);
2489 KclValue::Bool { value: l < r, meta }
2490 }
2491 BinaryOperator::Lte => {
2492 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2493 self.warn_on_unknown(&ty, "Comparing", exec_state);
2494 KclValue::Bool { value: l <= r, meta }
2495 }
2496 BinaryOperator::Eq => {
2497 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2498 self.warn_on_unknown(&ty, "Comparing", exec_state);
2499 KclValue::Bool { value: l == r, meta }
2500 }
2501 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
2502 };
2503
2504 Ok(value)
2505 }
2506
2507 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
2508 KclError::new_internal(KclErrorDetails::new(
2509 "missing result while evaluating binary expression".to_owned(),
2510 vec![SourceRange::from(node)],
2511 ))
2512 }
2513
2514 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
2515 if ty == &NumericType::Unknown {
2516 let sr = self.as_source_range();
2517 exec_state.clear_units_warnings(&sr);
2518 let mut err = CompilationError::err(
2519 sr,
2520 format!(
2521 "{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)`."
2522 ),
2523 );
2524 err.tag = crate::errors::Tag::UnknownNumericUnits;
2525 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
2526 }
2527 }
2528}
2529
2530impl Node<UnaryExpression> {
2531 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2532 match self.operator {
2533 UnaryOperator::Not => {
2534 let value = self.argument.get_result(exec_state, ctx).await?;
2535 let KclValue::Bool {
2536 value: bool_value,
2537 meta: _,
2538 } = value
2539 else {
2540 return Err(KclError::new_semantic(KclErrorDetails::new(
2541 format!(
2542 "Cannot apply unary operator ! to non-boolean value: {}",
2543 value.human_friendly_type()
2544 ),
2545 vec![self.into()],
2546 )));
2547 };
2548 let meta = vec![Metadata {
2549 source_range: self.into(),
2550 }];
2551 let negated = KclValue::Bool {
2552 value: !bool_value,
2553 meta,
2554 };
2555
2556 Ok(negated)
2557 }
2558 UnaryOperator::Neg => {
2559 let value = &self.argument.get_result(exec_state, ctx).await?;
2560 let err = || {
2561 KclError::new_semantic(KclErrorDetails::new(
2562 format!(
2563 "You can only negate numbers, planes, or lines, but this is a {}",
2564 value.human_friendly_type()
2565 ),
2566 vec![self.into()],
2567 ))
2568 };
2569 match value {
2570 KclValue::Number { value, ty, .. } => {
2571 let meta = vec![Metadata {
2572 source_range: self.into(),
2573 }];
2574 Ok(KclValue::Number {
2575 value: -value,
2576 meta,
2577 ty: *ty,
2578 })
2579 }
2580 KclValue::Plane { value } => {
2581 let mut plane = value.clone();
2582 if plane.info.x_axis.x != 0.0 {
2583 plane.info.x_axis.x *= -1.0;
2584 }
2585 if plane.info.x_axis.y != 0.0 {
2586 plane.info.x_axis.y *= -1.0;
2587 }
2588 if plane.info.x_axis.z != 0.0 {
2589 plane.info.x_axis.z *= -1.0;
2590 }
2591
2592 plane.value = PlaneType::Uninit;
2593 plane.id = exec_state.next_uuid();
2594 Ok(KclValue::Plane { value: plane })
2595 }
2596 KclValue::Object {
2597 value: values, meta, ..
2598 } => {
2599 let Some(direction) = values.get("direction") else {
2601 return Err(err());
2602 };
2603
2604 let direction = match direction {
2605 KclValue::Tuple { value: values, meta } => {
2606 let values = values
2607 .iter()
2608 .map(|v| match v {
2609 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
2610 value: *value * -1.0,
2611 ty: *ty,
2612 meta: meta.clone(),
2613 }),
2614 _ => Err(err()),
2615 })
2616 .collect::<Result<Vec<_>, _>>()?;
2617
2618 KclValue::Tuple {
2619 value: values,
2620 meta: meta.clone(),
2621 }
2622 }
2623 KclValue::HomArray {
2624 value: values,
2625 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
2626 } => {
2627 let values = values
2628 .iter()
2629 .map(|v| match v {
2630 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
2631 value: *value * -1.0,
2632 ty: *ty,
2633 meta: meta.clone(),
2634 }),
2635 _ => Err(err()),
2636 })
2637 .collect::<Result<Vec<_>, _>>()?;
2638
2639 KclValue::HomArray {
2640 value: values,
2641 ty: ty.clone(),
2642 }
2643 }
2644 _ => return Err(err()),
2645 };
2646
2647 let mut value = values.clone();
2648 value.insert("direction".to_owned(), direction);
2649 Ok(KclValue::Object {
2650 value,
2651 meta: meta.clone(),
2652 constrainable: false,
2653 })
2654 }
2655 _ => Err(err()),
2656 }
2657 }
2658 UnaryOperator::Plus => {
2659 let operand = &self.argument.get_result(exec_state, ctx).await?;
2660 match operand {
2661 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.clone()),
2662 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2663 format!(
2664 "You can only apply unary + to numbers or planes, but this is a {}",
2665 operand.human_friendly_type()
2666 ),
2667 vec![self.into()],
2668 ))),
2669 }
2670 }
2671 }
2672 }
2673}
2674
2675pub(crate) async fn execute_pipe_body(
2676 exec_state: &mut ExecState,
2677 body: &[Expr],
2678 source_range: SourceRange,
2679 ctx: &ExecutorContext,
2680) -> Result<KclValue, KclError> {
2681 let Some((first, body)) = body.split_first() else {
2682 return Err(KclError::new_semantic(KclErrorDetails::new(
2683 "Pipe expressions cannot be empty".to_owned(),
2684 vec![source_range],
2685 )));
2686 };
2687 let meta = Metadata {
2692 source_range: SourceRange::from(first),
2693 };
2694 let output = ctx
2695 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
2696 .await?;
2697
2698 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
2702 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
2704 exec_state.mod_local.pipe_value = previous_pipe_value;
2706
2707 result
2708}
2709
2710#[async_recursion]
2713async fn inner_execute_pipe_body(
2714 exec_state: &mut ExecState,
2715 body: &[Expr],
2716 ctx: &ExecutorContext,
2717) -> Result<KclValue, KclError> {
2718 for expression in body {
2719 if let Expr::TagDeclarator(_) = expression {
2720 return Err(KclError::new_semantic(KclErrorDetails::new(
2721 format!("This cannot be in a PipeExpression: {expression:?}"),
2722 vec![expression.into()],
2723 )));
2724 }
2725 let metadata = Metadata {
2726 source_range: SourceRange::from(expression),
2727 };
2728 let output = ctx
2729 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
2730 .await?;
2731 exec_state.mod_local.pipe_value = Some(output);
2732 }
2733 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
2735 Ok(final_output)
2736}
2737
2738impl Node<TagDeclarator> {
2739 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
2740 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
2741 value: self.name.clone(),
2742 info: Vec::new(),
2743 meta: vec![Metadata {
2744 source_range: self.into(),
2745 }],
2746 }));
2747
2748 exec_state
2749 .mut_stack()
2750 .add(self.name.clone(), memory_item, self.into())?;
2751
2752 Ok(self.into())
2753 }
2754}
2755
2756impl Node<ArrayExpression> {
2757 #[async_recursion]
2758 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2759 let mut results = Vec::with_capacity(self.elements.len());
2760
2761 for element in &self.elements {
2762 let metadata = Metadata::from(element);
2763 let value = ctx
2766 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
2767 .await?;
2768
2769 results.push(value);
2770 }
2771
2772 Ok(KclValue::HomArray {
2773 value: results,
2774 ty: RuntimeType::Primitive(PrimitiveType::Any),
2775 })
2776 }
2777}
2778
2779impl Node<ArrayRangeExpression> {
2780 #[async_recursion]
2781 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2782 let metadata = Metadata::from(&self.start_element);
2783 let start_val = ctx
2784 .execute_expr(
2785 &self.start_element,
2786 exec_state,
2787 &metadata,
2788 &[],
2789 StatementKind::Expression,
2790 )
2791 .await?;
2792 let start = start_val
2793 .as_ty_f64()
2794 .ok_or(KclError::new_semantic(KclErrorDetails::new(
2795 format!(
2796 "Expected number for range start but found {}",
2797 start_val.human_friendly_type()
2798 ),
2799 vec![self.into()],
2800 )))?;
2801 let metadata = Metadata::from(&self.end_element);
2802 let end_val = ctx
2803 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
2804 .await?;
2805 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
2806 format!(
2807 "Expected number for range end but found {}",
2808 end_val.human_friendly_type()
2809 ),
2810 vec![self.into()],
2811 )))?;
2812
2813 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
2814 let Some(start) = crate::try_f64_to_i64(start) else {
2815 return Err(KclError::new_semantic(KclErrorDetails::new(
2816 format!("Range start must be an integer, but found {start}"),
2817 vec![self.into()],
2818 )));
2819 };
2820 let Some(end) = crate::try_f64_to_i64(end) else {
2821 return Err(KclError::new_semantic(KclErrorDetails::new(
2822 format!("Range end must be an integer, but found {end}"),
2823 vec![self.into()],
2824 )));
2825 };
2826
2827 if end < start {
2828 return Err(KclError::new_semantic(KclErrorDetails::new(
2829 format!("Range start is greater than range end: {start} .. {end}"),
2830 vec![self.into()],
2831 )));
2832 }
2833
2834 let range: Vec<_> = if self.end_inclusive {
2835 (start..=end).collect()
2836 } else {
2837 (start..end).collect()
2838 };
2839
2840 let meta = vec![Metadata {
2841 source_range: self.into(),
2842 }];
2843
2844 Ok(KclValue::HomArray {
2845 value: range
2846 .into_iter()
2847 .map(|num| KclValue::Number {
2848 value: num as f64,
2849 ty,
2850 meta: meta.clone(),
2851 })
2852 .collect(),
2853 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
2854 })
2855 }
2856}
2857
2858impl Node<ObjectExpression> {
2859 #[async_recursion]
2860 pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2861 let mut object = HashMap::with_capacity(self.properties.len());
2862 for property in &self.properties {
2863 let metadata = Metadata::from(&property.value);
2864 let result = ctx
2865 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
2866 .await?;
2867
2868 object.insert(property.key.name.clone(), result);
2869 }
2870
2871 Ok(KclValue::Object {
2872 value: object,
2873 meta: vec![Metadata {
2874 source_range: self.into(),
2875 }],
2876 constrainable: false,
2877 })
2878 }
2879}
2880
2881fn article_for<S: AsRef<str>>(s: S) -> &'static str {
2882 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
2884 "an"
2885 } else {
2886 "a"
2887 }
2888}
2889
2890fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
2891 v.as_ty_f64().ok_or_else(|| {
2892 let actual_type = v.human_friendly_type();
2893 KclError::new_semantic(KclErrorDetails::new(
2894 format!("Expected a number, but found {actual_type}",),
2895 vec![source_range],
2896 ))
2897 })
2898}
2899
2900impl Node<IfExpression> {
2901 #[async_recursion]
2902 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2903 let cond = ctx
2905 .execute_expr(
2906 &self.cond,
2907 exec_state,
2908 &Metadata::from(self),
2909 &[],
2910 StatementKind::Expression,
2911 )
2912 .await?
2913 .get_bool()?;
2914 if cond {
2915 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
2916 return Ok(block_result.unwrap());
2920 }
2921
2922 for else_if in &self.else_ifs {
2924 let cond = ctx
2925 .execute_expr(
2926 &else_if.cond,
2927 exec_state,
2928 &Metadata::from(self),
2929 &[],
2930 StatementKind::Expression,
2931 )
2932 .await?
2933 .get_bool()?;
2934 if cond {
2935 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
2936 return Ok(block_result.unwrap());
2940 }
2941 }
2942
2943 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
2945 .await
2946 .map(|expr| expr.unwrap())
2947 }
2948}
2949
2950#[derive(Debug)]
2951enum Property {
2952 UInt(usize),
2953 String(String),
2954}
2955
2956impl Property {
2957 #[allow(clippy::too_many_arguments)]
2958 async fn try_from<'a>(
2959 computed: bool,
2960 value: Expr,
2961 exec_state: &mut ExecState,
2962 sr: SourceRange,
2963 ctx: &ExecutorContext,
2964 metadata: &Metadata,
2965 annotations: &[Node<Annotation>],
2966 statement_kind: StatementKind<'a>,
2967 ) -> Result<Self, KclError> {
2968 let property_sr = vec![sr];
2969 if !computed {
2970 let Expr::Name(identifier) = value else {
2971 return Err(KclError::new_semantic(KclErrorDetails::new(
2973 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
2974 .to_owned(),
2975 property_sr,
2976 )));
2977 };
2978 return Ok(Property::String(identifier.to_string()));
2979 }
2980
2981 let prop_value = ctx
2982 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
2983 .await?;
2984 match prop_value {
2985 KclValue::Number { value, ty, meta: _ } => {
2986 if !matches!(
2987 ty,
2988 NumericType::Unknown
2989 | NumericType::Default { .. }
2990 | NumericType::Known(crate::exec::UnitType::Count)
2991 ) {
2992 return Err(KclError::new_semantic(KclErrorDetails::new(
2993 format!(
2994 "{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"
2995 ),
2996 property_sr,
2997 )));
2998 }
2999 if let Some(x) = crate::try_f64_to_usize(value) {
3000 Ok(Property::UInt(x))
3001 } else {
3002 Err(KclError::new_semantic(KclErrorDetails::new(
3003 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
3004 property_sr,
3005 )))
3006 }
3007 }
3008 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3009 "Only numbers (>= 0) can be indexes".to_owned(),
3010 vec![sr],
3011 ))),
3012 }
3013 }
3014}
3015
3016impl Property {
3017 fn type_name(&self) -> &'static str {
3018 match self {
3019 Property::UInt(_) => "number",
3020 Property::String(_) => "string",
3021 }
3022 }
3023}
3024
3025impl Node<PipeExpression> {
3026 #[async_recursion]
3027 pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
3028 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
3029 }
3030}
3031
3032#[cfg(test)]
3033mod test {
3034 use std::sync::Arc;
3035
3036 use tokio::io::AsyncWriteExt;
3037
3038 use super::*;
3039 use crate::{
3040 ExecutorSettings,
3041 errors::Severity,
3042 exec::UnitType,
3043 execution::{ContextType, parse_execute},
3044 };
3045
3046 #[tokio::test(flavor = "multi_thread")]
3047 async fn ascription() {
3048 let program = r#"
3049a = 42: number
3050b = a: number
3051p = {
3052 origin = { x = 0, y = 0, z = 0 },
3053 xAxis = { x = 1, y = 0, z = 0 },
3054 yAxis = { x = 0, y = 1, z = 0 },
3055 zAxis = { x = 0, y = 0, z = 1 }
3056}: Plane
3057arr1 = [42]: [number(cm)]
3058"#;
3059
3060 let result = parse_execute(program).await.unwrap();
3061 let mem = result.exec_state.stack();
3062 assert!(matches!(
3063 mem.memory
3064 .get_from("p", result.mem_env, SourceRange::default(), 0)
3065 .unwrap(),
3066 KclValue::Plane { .. }
3067 ));
3068 let arr1 = mem
3069 .memory
3070 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
3071 .unwrap();
3072 if let KclValue::HomArray { value, ty } = arr1 {
3073 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
3074 assert_eq!(
3075 *ty,
3076 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
3077 );
3078 if let KclValue::Number { value, ty, .. } = &value[0] {
3080 assert_eq!(*value, 42.0);
3082 assert_eq!(
3083 *ty,
3084 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
3085 );
3086 } else {
3087 panic!("Expected a number; found {:?}", value[0]);
3088 }
3089 } else {
3090 panic!("Expected HomArray; found {arr1:?}");
3091 }
3092
3093 let program = r#"
3094a = 42: string
3095"#;
3096 let result = parse_execute(program).await;
3097 let err = result.unwrap_err();
3098 assert!(
3099 err.to_string()
3100 .contains("could not coerce a number (with type `number`) to type `string`"),
3101 "Expected error but found {err:?}"
3102 );
3103
3104 let program = r#"
3105a = 42: Plane
3106"#;
3107 let result = parse_execute(program).await;
3108 let err = result.unwrap_err();
3109 assert!(
3110 err.to_string()
3111 .contains("could not coerce a number (with type `number`) to type `Plane`"),
3112 "Expected error but found {err:?}"
3113 );
3114
3115 let program = r#"
3116arr = [0]: [string]
3117"#;
3118 let result = parse_execute(program).await;
3119 let err = result.unwrap_err();
3120 assert!(
3121 err.to_string().contains(
3122 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
3123 ),
3124 "Expected error but found {err:?}"
3125 );
3126
3127 let program = r#"
3128mixedArr = [0, "a"]: [number(mm)]
3129"#;
3130 let result = parse_execute(program).await;
3131 let err = result.unwrap_err();
3132 assert!(
3133 err.to_string().contains(
3134 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3135 ),
3136 "Expected error but found {err:?}"
3137 );
3138
3139 let program = r#"
3140mixedArr = [0, "a"]: [mm]
3141"#;
3142 let result = parse_execute(program).await;
3143 let err = result.unwrap_err();
3144 assert!(
3145 err.to_string().contains(
3146 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3147 ),
3148 "Expected error but found {err:?}"
3149 );
3150 }
3151
3152 #[tokio::test(flavor = "multi_thread")]
3153 async fn neg_plane() {
3154 let program = r#"
3155p = {
3156 origin = { x = 0, y = 0, z = 0 },
3157 xAxis = { x = 1, y = 0, z = 0 },
3158 yAxis = { x = 0, y = 1, z = 0 },
3159}: Plane
3160p2 = -p
3161"#;
3162
3163 let result = parse_execute(program).await.unwrap();
3164 let mem = result.exec_state.stack();
3165 match mem
3166 .memory
3167 .get_from("p2", result.mem_env, SourceRange::default(), 0)
3168 .unwrap()
3169 {
3170 KclValue::Plane { value } => {
3171 assert_eq!(value.info.x_axis.x, -1.0);
3172 assert_eq!(value.info.x_axis.y, 0.0);
3173 assert_eq!(value.info.x_axis.z, 0.0);
3174 }
3175 _ => unreachable!(),
3176 }
3177 }
3178
3179 #[tokio::test(flavor = "multi_thread")]
3180 async fn multiple_returns() {
3181 let program = r#"fn foo() {
3182 return 0
3183 return 42
3184}
3185
3186a = foo()
3187"#;
3188
3189 let result = parse_execute(program).await;
3190 assert!(result.unwrap_err().to_string().contains("return"));
3191 }
3192
3193 #[tokio::test(flavor = "multi_thread")]
3194 async fn load_all_modules() {
3195 let program_a_kcl = r#"
3197export a = 1
3198"#;
3199 let program_b_kcl = r#"
3201import a from 'a.kcl'
3202
3203export b = a + 1
3204"#;
3205 let program_c_kcl = r#"
3207import a from 'a.kcl'
3208
3209export c = a + 2
3210"#;
3211
3212 let main_kcl = r#"
3214import b from 'b.kcl'
3215import c from 'c.kcl'
3216
3217d = b + c
3218"#;
3219
3220 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
3221 .parse_errs_as_err()
3222 .unwrap();
3223
3224 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
3225
3226 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
3227 .await
3228 .unwrap()
3229 .write_all(main_kcl.as_bytes())
3230 .await
3231 .unwrap();
3232
3233 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
3234 .await
3235 .unwrap()
3236 .write_all(program_a_kcl.as_bytes())
3237 .await
3238 .unwrap();
3239
3240 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
3241 .await
3242 .unwrap()
3243 .write_all(program_b_kcl.as_bytes())
3244 .await
3245 .unwrap();
3246
3247 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
3248 .await
3249 .unwrap()
3250 .write_all(program_c_kcl.as_bytes())
3251 .await
3252 .unwrap();
3253
3254 let exec_ctxt = ExecutorContext {
3255 engine: Arc::new(Box::new(
3256 crate::engine::conn_mock::EngineConnection::new()
3257 .map_err(|err| {
3258 KclError::new_internal(KclErrorDetails::new(
3259 format!("Failed to create mock engine connection: {err}"),
3260 vec![SourceRange::default()],
3261 ))
3262 })
3263 .unwrap(),
3264 )),
3265 fs: Arc::new(crate::fs::FileManager::new()),
3266 settings: ExecutorSettings {
3267 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
3268 ..Default::default()
3269 },
3270 context_type: ContextType::Mock,
3271 };
3272 let mut exec_state = ExecState::new(&exec_ctxt);
3273
3274 exec_ctxt
3275 .run(
3276 &crate::Program {
3277 ast: main.clone(),
3278 original_file_contents: "".to_owned(),
3279 },
3280 &mut exec_state,
3281 )
3282 .await
3283 .unwrap();
3284 }
3285
3286 #[tokio::test(flavor = "multi_thread")]
3287 async fn user_coercion() {
3288 let program = r#"fn foo(x: Axis2d) {
3289 return 0
3290}
3291
3292foo(x = { direction = [0, 0], origin = [0, 0]})
3293"#;
3294
3295 parse_execute(program).await.unwrap();
3296
3297 let program = r#"fn foo(x: Axis3d) {
3298 return 0
3299}
3300
3301foo(x = { direction = [0, 0], origin = [0, 0]})
3302"#;
3303
3304 parse_execute(program).await.unwrap_err();
3305 }
3306
3307 #[tokio::test(flavor = "multi_thread")]
3308 async fn coerce_return() {
3309 let program = r#"fn foo(): number(mm) {
3310 return 42
3311}
3312
3313a = foo()
3314"#;
3315
3316 parse_execute(program).await.unwrap();
3317
3318 let program = r#"fn foo(): mm {
3319 return 42
3320}
3321
3322a = foo()
3323"#;
3324
3325 parse_execute(program).await.unwrap();
3326
3327 let program = r#"fn foo(): number(mm) {
3328 return { bar: 42 }
3329}
3330
3331a = foo()
3332"#;
3333
3334 parse_execute(program).await.unwrap_err();
3335
3336 let program = r#"fn foo(): mm {
3337 return { bar: 42 }
3338}
3339
3340a = foo()
3341"#;
3342
3343 parse_execute(program).await.unwrap_err();
3344 }
3345
3346 #[tokio::test(flavor = "multi_thread")]
3347 async fn test_sensible_error_when_missing_equals_in_kwarg() {
3348 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)"]
3349 .into_iter()
3350 .enumerate()
3351 {
3352 let program = format!(
3353 "fn foo() {{ return 0 }}
3354z = 0
3355fn f(x, y, z) {{ return 0 }}
3356{call}"
3357 );
3358 let err = parse_execute(&program).await.unwrap_err();
3359 let msg = err.message();
3360 assert!(
3361 msg.contains("This argument needs a label, but it doesn't have one"),
3362 "failed test {i}: {msg}"
3363 );
3364 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
3365 if i == 0 {
3366 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
3367 }
3368 }
3369 }
3370
3371 #[tokio::test(flavor = "multi_thread")]
3372 async fn default_param_for_unlabeled() {
3373 let ast = r#"fn myExtrude(@sk, length) {
3376 return extrude(sk, length)
3377}
3378sketch001 = startSketchOn(XY)
3379 |> circle(center = [0, 0], radius = 93.75)
3380 |> myExtrude(length = 40)
3381"#;
3382
3383 parse_execute(ast).await.unwrap();
3384 }
3385
3386 #[tokio::test(flavor = "multi_thread")]
3387 async fn dont_use_unlabelled_as_input() {
3388 let ast = r#"length = 10
3390startSketchOn(XY)
3391 |> circle(center = [0, 0], radius = 93.75)
3392 |> extrude(length)
3393"#;
3394
3395 parse_execute(ast).await.unwrap();
3396 }
3397
3398 #[tokio::test(flavor = "multi_thread")]
3399 async fn ascription_in_binop() {
3400 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
3401 parse_execute(ast).await.unwrap();
3402
3403 let ast = r#"foo = tan(0): rad - 4deg"#;
3404 parse_execute(ast).await.unwrap();
3405 }
3406
3407 #[tokio::test(flavor = "multi_thread")]
3408 async fn neg_sqrt() {
3409 let ast = r#"bad = sqrt(-2)"#;
3410
3411 let e = parse_execute(ast).await.unwrap_err();
3412 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
3414 }
3415
3416 #[tokio::test(flavor = "multi_thread")]
3417 async fn non_array_fns() {
3418 let ast = r#"push(1, item = 2)
3419pop(1)
3420map(1, f = fn(@x) { return x + 1 })
3421reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
3422
3423 parse_execute(ast).await.unwrap();
3424 }
3425
3426 #[tokio::test(flavor = "multi_thread")]
3427 async fn non_array_indexing() {
3428 let good = r#"a = 42
3429good = a[0]
3430"#;
3431 let result = parse_execute(good).await.unwrap();
3432 let mem = result.exec_state.stack();
3433 let num = mem
3434 .memory
3435 .get_from("good", result.mem_env, SourceRange::default(), 0)
3436 .unwrap()
3437 .as_ty_f64()
3438 .unwrap();
3439 assert_eq!(num.n, 42.0);
3440
3441 let bad = r#"a = 42
3442bad = a[1]
3443"#;
3444
3445 parse_execute(bad).await.unwrap_err();
3446 }
3447
3448 #[tokio::test(flavor = "multi_thread")]
3449 async fn coerce_unknown_to_length() {
3450 let ast = r#"x = 2mm * 2mm
3451y = x: number(Length)"#;
3452 let e = parse_execute(ast).await.unwrap_err();
3453 assert!(
3454 e.message().contains("could not coerce"),
3455 "Error message: '{}'",
3456 e.message()
3457 );
3458
3459 let ast = r#"x = 2mm
3460y = x: number(Length)"#;
3461 let result = parse_execute(ast).await.unwrap();
3462 let mem = result.exec_state.stack();
3463 let num = mem
3464 .memory
3465 .get_from("y", result.mem_env, SourceRange::default(), 0)
3466 .unwrap()
3467 .as_ty_f64()
3468 .unwrap();
3469 assert_eq!(num.n, 2.0);
3470 assert_eq!(num.ty, NumericType::mm());
3471 }
3472
3473 #[tokio::test(flavor = "multi_thread")]
3474 async fn one_warning_unknown() {
3475 let ast = r#"
3476// Should warn once
3477a = PI * 2
3478// Should warn once
3479b = (PI * 2) / 3
3480// Should not warn
3481c = ((PI * 2) / 3): number(deg)
3482"#;
3483
3484 let result = parse_execute(ast).await.unwrap();
3485 assert_eq!(result.exec_state.errors().len(), 2);
3486 }
3487
3488 #[tokio::test(flavor = "multi_thread")]
3489 async fn non_count_indexing() {
3490 let ast = r#"x = [0, 0]
3491y = x[1mm]
3492"#;
3493 parse_execute(ast).await.unwrap_err();
3494
3495 let ast = r#"x = [0, 0]
3496y = 1deg
3497z = x[y]
3498"#;
3499 parse_execute(ast).await.unwrap_err();
3500
3501 let ast = r#"x = [0, 0]
3502y = x[0mm + 1]
3503"#;
3504 parse_execute(ast).await.unwrap_err();
3505 }
3506
3507 #[tokio::test(flavor = "multi_thread")]
3508 async fn getting_property_of_plane() {
3509 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
3510 parse_execute(&ast).await.unwrap();
3511 }
3512
3513 #[cfg(feature = "artifact-graph")]
3514 #[tokio::test(flavor = "multi_thread")]
3515 async fn no_artifacts_from_within_hole_call() {
3516 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
3521 let out = parse_execute(&ast).await.unwrap();
3522
3523 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3525
3526 let expected = 5;
3530 assert_eq!(
3531 actual_operations.len(),
3532 expected,
3533 "expected {expected} operations, received {}:\n{actual_operations:#?}",
3534 actual_operations.len(),
3535 );
3536 }
3537
3538 #[cfg(feature = "artifact-graph")]
3539 #[tokio::test(flavor = "multi_thread")]
3540 async fn feature_tree_annotation_on_user_defined_kcl() {
3541 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
3544 let out = parse_execute(&ast).await.unwrap();
3545
3546 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3548
3549 let expected = 0;
3550 assert_eq!(
3551 actual_operations.len(),
3552 expected,
3553 "expected {expected} operations, received {}:\n{actual_operations:#?}",
3554 actual_operations.len(),
3555 );
3556 }
3557
3558 #[cfg(feature = "artifact-graph")]
3559 #[tokio::test(flavor = "multi_thread")]
3560 async fn no_feature_tree_annotation_on_user_defined_kcl() {
3561 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
3564 let out = parse_execute(&ast).await.unwrap();
3565
3566 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3568
3569 let expected = 2;
3570 assert_eq!(
3571 actual_operations.len(),
3572 expected,
3573 "expected {expected} operations, received {}:\n{actual_operations:#?}",
3574 actual_operations.len(),
3575 );
3576 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
3577 assert!(matches!(actual_operations[1], Operation::GroupEnd));
3578 }
3579
3580 #[tokio::test(flavor = "multi_thread")]
3581 async fn custom_warning() {
3582 let warn = r#"
3583a = PI * 2
3584"#;
3585 let result = parse_execute(warn).await.unwrap();
3586 assert_eq!(result.exec_state.errors().len(), 1);
3587 assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
3588
3589 let allow = r#"
3590@warnings(allow = unknownUnits)
3591a = PI * 2
3592"#;
3593 let result = parse_execute(allow).await.unwrap();
3594 assert_eq!(result.exec_state.errors().len(), 0);
3595
3596 let deny = r#"
3597@warnings(deny = [unknownUnits])
3598a = PI * 2
3599"#;
3600 let result = parse_execute(deny).await.unwrap();
3601 assert_eq!(result.exec_state.errors().len(), 1);
3602 assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
3603 }
3604}