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