1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use indexmap::IndexMap;
5use kcl_ezpz::{Constraint, NonLinearSystemError};
6use kittycad_modeling_cmds as kcmc;
7
8#[cfg(feature = "artifact-graph")]
9use crate::front::{Object, ObjectKind};
10use crate::{
11 CompilationError, NodePath, SourceRange,
12 errors::{KclError, KclErrorDetails},
13 execution::{
14 AbstractSegment, BodyType, ControlFlowKind, EarlyReturn, EnvironmentRef, ExecState, ExecutorContext, KclValue,
15 KclValueControlFlow, Metadata, ModelingCmdMeta, ModuleArtifactState, Operation, PreserveMem,
16 SKETCH_BLOCK_PARAM_ON, Segment, SegmentKind, SegmentRepr, SketchConstraintKind, SketchSurface, StatementKind,
17 TagIdentifier, UnsolvedExpr, UnsolvedSegment, UnsolvedSegmentKind, annotations,
18 cad_op::OpKclValue,
19 control_continue, early_return,
20 fn_call::{Arg, Args},
21 kcl_value::{FunctionSource, KclFunctionSourceParams, TypeDef},
22 memory::{self, SKETCH_PREFIX},
23 sketch_solve::{
24 FreedomAnalysis, Solved, create_segment_scene_objects, normalize_to_solver_angle_unit,
25 normalize_to_solver_distance_unit, solver_numeric_type, substitute_sketch_var_in_segment,
26 substitute_sketch_vars,
27 },
28 state::{ModuleState, SketchBlockState},
29 types::{NumericType, PrimitiveType, RuntimeType},
30 },
31 front::{ObjectId, PointCtor},
32 modules::{ModuleExecutionOutcome, ModuleId, ModulePath, ModuleRepr},
33 parsing::ast::types::{
34 Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
35 BinaryPart, BodyItem, CodeBlock, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility,
36 MemberExpression, Name, Node, ObjectExpression, PipeExpression, Program, SketchBlock, SketchVar, TagDeclarator,
37 Type, UnaryExpression, UnaryOperator,
38 },
39 std::{
40 args::{FromKclValue, TyF64},
41 shapes::SketchOrSurface,
42 sketch::ensure_sketch_plane_in_engine,
43 sketch2::create_segments_in_engine,
44 },
45};
46
47fn internal_err(message: impl Into<String>, range: impl Into<SourceRange>) -> KclError {
48 KclError::new_internal(KclErrorDetails::new(message.into(), vec![range.into()]))
49}
50
51fn sketch_on_cache_name(sketch_id: ObjectId) -> String {
52 format!("{SKETCH_PREFIX}{}_on", sketch_id.0)
53}
54
55#[cfg(feature = "artifact-graph")]
56fn default_plane_name_from_expr(expr: &Expr) -> Option<crate::engine::PlaneName> {
57 fn parse_name(name: &str, negative: bool) -> Option<crate::engine::PlaneName> {
58 use crate::engine::PlaneName;
59
60 match (name, negative) {
61 ("XY", false) => Some(PlaneName::Xy),
62 ("XY", true) => Some(PlaneName::NegXy),
63 ("XZ", false) => Some(PlaneName::Xz),
64 ("XZ", true) => Some(PlaneName::NegXz),
65 ("YZ", false) => Some(PlaneName::Yz),
66 ("YZ", true) => Some(PlaneName::NegYz),
67 _ => None,
68 }
69 }
70
71 match expr {
72 Expr::Name(name) => {
73 if !name.path.is_empty() {
74 return None;
75 }
76 parse_name(&name.name.name, false)
77 }
78 Expr::UnaryExpression(unary) => {
79 if unary.operator != UnaryOperator::Neg {
80 return None;
81 }
82 let crate::parsing::ast::types::BinaryPart::Name(name) = &unary.argument else {
83 return None;
84 };
85 if !name.path.is_empty() {
86 return None;
87 }
88 parse_name(&name.name.name, true)
89 }
90 _ => None,
91 }
92}
93
94#[cfg(feature = "artifact-graph")]
95fn sketch_on_frontend_plane(
96 arguments: &[crate::parsing::ast::types::LabeledArg],
97 on_object_id: crate::front::ObjectId,
98) -> crate::front::Plane {
99 for arg in arguments {
100 let Some(label) = &arg.label else {
101 continue;
102 };
103 if label.name != SKETCH_BLOCK_PARAM_ON {
104 continue;
105 }
106 if let Some(name) = default_plane_name_from_expr(&arg.arg) {
107 return crate::front::Plane::Default(name);
108 }
109 break;
110 }
111
112 crate::front::Plane::Object(on_object_id)
113}
114
115impl<'a> StatementKind<'a> {
116 fn expect_name(&self) -> &'a str {
117 match self {
118 StatementKind::Declaration { name } => name,
119 StatementKind::Expression => unreachable!(),
120 }
121 }
122}
123
124impl ExecutorContext {
125 async fn handle_annotations(
127 &self,
128 annotations: impl Iterator<Item = &Node<Annotation>>,
129 body_type: BodyType,
130 exec_state: &mut ExecState,
131 ) -> Result<bool, KclError> {
132 let mut no_prelude = false;
133 for annotation in annotations {
134 if annotation.name() == Some(annotations::SETTINGS) {
135 if matches!(body_type, BodyType::Root) {
136 let (updated_len, updated_angle) =
137 exec_state.mod_local.settings.update_from_annotation(annotation)?;
138 if updated_len {
139 exec_state.mod_local.explicit_length_units = true;
140 }
141 if updated_angle {
142 exec_state.warn(
143 CompilationError::err(
144 annotation.as_source_range(),
145 "Prefer to use explicit units for angles",
146 ),
147 annotations::WARN_ANGLE_UNITS,
148 );
149 }
150 } else {
151 exec_state.err(CompilationError::err(
152 annotation.as_source_range(),
153 "Settings can only be modified at the top level scope of a file",
154 ));
155 }
156 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
157 if matches!(body_type, BodyType::Root) {
158 no_prelude = true;
159 } else {
160 exec_state.err(CompilationError::err(
161 annotation.as_source_range(),
162 "The standard library can only be skipped at the top level scope of a file",
163 ));
164 }
165 } else if annotation.name() == Some(annotations::WARNINGS) {
166 if matches!(body_type, BodyType::Root) {
168 let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
169 for p in props {
170 match &*p.inner.key.name {
171 annotations::WARN_ALLOW => {
172 let allowed = annotations::many_of(
173 &p.inner.value,
174 &annotations::WARN_VALUES,
175 annotation.as_source_range(),
176 )?;
177 exec_state.mod_local.allowed_warnings = allowed;
178 }
179 annotations::WARN_DENY => {
180 let denied = annotations::many_of(
181 &p.inner.value,
182 &annotations::WARN_VALUES,
183 annotation.as_source_range(),
184 )?;
185 exec_state.mod_local.denied_warnings = denied;
186 }
187 name => {
188 return Err(KclError::new_semantic(KclErrorDetails::new(
189 format!(
190 "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
191 annotations::WARN_ALLOW,
192 annotations::WARN_DENY,
193 ),
194 vec![annotation.as_source_range()],
195 )));
196 }
197 }
198 }
199 } else {
200 exec_state.err(CompilationError::err(
201 annotation.as_source_range(),
202 "Warnings can only be customized at the top level scope of a file",
203 ));
204 }
205 } else {
206 exec_state.warn(
207 CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
208 annotations::WARN_UNKNOWN_ATTR,
209 );
210 }
211 }
212 Ok(no_prelude)
213 }
214
215 pub(super) async fn exec_module_body(
216 &self,
217 program: &Node<Program>,
218 exec_state: &mut ExecState,
219 preserve_mem: PreserveMem,
220 module_id: ModuleId,
221 path: &ModulePath,
222 ) -> Result<ModuleExecutionOutcome, (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>)> {
223 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
224
225 let mut local_state = ModuleState::new(
234 path.clone(),
235 exec_state.stack().memory.clone(),
236 Some(module_id),
237 exec_state.mod_local.sketch_mode,
238 exec_state.mod_local.freedom_analysis,
239 );
240 match preserve_mem {
241 PreserveMem::Always => {
242 #[cfg(feature = "artifact-graph")]
243 {
244 use crate::id::IncIdGenerator;
245 exec_state
246 .mod_local
247 .artifacts
248 .scene_objects
249 .clone_from(&exec_state.global.root_module_artifacts.scene_objects);
250 exec_state.mod_local.artifacts.object_id_generator =
251 IncIdGenerator::new(exec_state.global.root_module_artifacts.scene_objects.len());
252 }
253 }
254 PreserveMem::Normal => {
255 #[cfg(feature = "artifact-graph")]
256 {
257 local_state
258 .artifacts
259 .scene_objects
260 .clone_from(&exec_state.mod_local.artifacts.scene_objects);
261 }
262 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
263 }
264 }
265
266 let no_prelude = self
267 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
268 .await
269 .map_err(|err| (err, None, None))?;
270
271 if preserve_mem.normal() {
272 exec_state.mut_stack().push_new_root_env(!no_prelude);
273 }
274
275 let result = self
276 .exec_block(program, exec_state, crate::execution::BodyType::Root)
277 .await;
278
279 let env_ref = match preserve_mem {
280 PreserveMem::Always => exec_state.mut_stack().pop_and_preserve_env(),
281 PreserveMem::Normal => exec_state.mut_stack().pop_env(),
282 };
283 let module_artifacts = match preserve_mem {
284 PreserveMem::Always => std::mem::take(&mut exec_state.mod_local.artifacts),
285 PreserveMem::Normal => {
286 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
287 local_state.artifacts
288 }
289 };
290
291 crate::log::log(format!("leave {path}"));
292
293 result
294 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
295 .map(|last_expr| ModuleExecutionOutcome {
296 last_expr: last_expr.map(|value_cf| value_cf.into_value()),
297 environment: env_ref,
298 exports: local_state.module_exports,
299 artifacts: module_artifacts,
300 })
301 }
302
303 #[async_recursion]
305 pub(super) async fn exec_block<'a, B>(
306 &'a self,
307 block: &'a B,
308 exec_state: &mut ExecState,
309 body_type: BodyType,
310 ) -> Result<Option<KclValueControlFlow>, KclError>
311 where
312 B: CodeBlock + Sync,
313 {
314 let mut last_expr = None;
315 for statement in block.body() {
317 match statement {
318 BodyItem::ImportStatement(import_stmt) => {
319 if exec_state.sketch_mode() {
320 continue;
321 }
322 if !matches!(body_type, BodyType::Root) {
323 return Err(KclError::new_semantic(KclErrorDetails::new(
324 "Imports are only supported at the top-level of a file.".to_owned(),
325 vec![import_stmt.into()],
326 )));
327 }
328
329 let source_range = SourceRange::from(import_stmt);
330 let attrs = &import_stmt.outer_attrs;
331 let module_path = ModulePath::from_import_path(
332 &import_stmt.path,
333 &self.settings.project_directory,
334 &exec_state.mod_local.path,
335 )?;
336 let module_id = self
337 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
338 .await?;
339
340 match &import_stmt.selector {
341 ImportSelector::List { items } => {
342 let (env_ref, module_exports) =
343 self.exec_module_for_items(module_id, exec_state, source_range).await?;
344 for import_item in items {
345 let mem = &exec_state.stack().memory;
347 let mut value = mem
348 .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
349 .cloned();
350 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
351 let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
352 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
353 let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
354
355 if value.is_err() && ty.is_err() && mod_value.is_err() {
356 return Err(KclError::new_undefined_value(
357 KclErrorDetails::new(
358 format!("{} is not defined in module", import_item.name.name),
359 vec![SourceRange::from(&import_item.name)],
360 ),
361 None,
362 ));
363 }
364
365 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
367 value = Err(KclError::new_semantic(KclErrorDetails::new(
368 format!(
369 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
370 import_item.name.name
371 ),
372 vec![SourceRange::from(&import_item.name)],
373 )));
374 }
375
376 if ty.is_ok() && !module_exports.contains(&ty_name) {
377 ty = Err(KclError::new_semantic(KclErrorDetails::new(
378 format!(
379 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
380 import_item.name.name
381 ),
382 vec![SourceRange::from(&import_item.name)],
383 )));
384 }
385
386 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
387 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
388 format!(
389 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
390 import_item.name.name
391 ),
392 vec![SourceRange::from(&import_item.name)],
393 )));
394 }
395
396 if value.is_err() && ty.is_err() && mod_value.is_err() {
397 return value.map(|v| Some(v.continue_()));
398 }
399
400 if let Ok(value) = value {
402 exec_state.mut_stack().add(
403 import_item.identifier().to_owned(),
404 value,
405 SourceRange::from(&import_item.name),
406 )?;
407
408 if let ItemVisibility::Export = import_stmt.visibility {
409 exec_state
410 .mod_local
411 .module_exports
412 .push(import_item.identifier().to_owned());
413 }
414 }
415
416 if let Ok(ty) = ty {
417 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
418 exec_state.mut_stack().add(
419 ty_name.clone(),
420 ty,
421 SourceRange::from(&import_item.name),
422 )?;
423
424 if let ItemVisibility::Export = import_stmt.visibility {
425 exec_state.mod_local.module_exports.push(ty_name);
426 }
427 }
428
429 if let Ok(mod_value) = mod_value {
430 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
431 exec_state.mut_stack().add(
432 mod_name.clone(),
433 mod_value,
434 SourceRange::from(&import_item.name),
435 )?;
436
437 if let ItemVisibility::Export = import_stmt.visibility {
438 exec_state.mod_local.module_exports.push(mod_name);
439 }
440 }
441 }
442 }
443 ImportSelector::Glob(_) => {
444 let (env_ref, module_exports) =
445 self.exec_module_for_items(module_id, exec_state, source_range).await?;
446 for name in module_exports.iter() {
447 let item = exec_state
448 .stack()
449 .memory
450 .get_from(name, env_ref, source_range, 0)
451 .map_err(|_err| {
452 internal_err(
453 format!("{name} is not defined in module (but was exported?)"),
454 source_range,
455 )
456 })?
457 .clone();
458 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
459
460 if let ItemVisibility::Export = import_stmt.visibility {
461 exec_state.mod_local.module_exports.push(name.clone());
462 }
463 }
464 }
465 ImportSelector::None { .. } => {
466 let name = import_stmt.module_name().unwrap();
467 let item = KclValue::Module {
468 value: module_id,
469 meta: vec![source_range.into()],
470 };
471 exec_state.mut_stack().add(
472 format!("{}{}", memory::MODULE_PREFIX, name),
473 item,
474 source_range,
475 )?;
476 }
477 }
478 last_expr = None;
479 }
480 BodyItem::ExpressionStatement(expression_statement) => {
481 if exec_state.sketch_mode() && sketch_mode_should_skip(&expression_statement.expression) {
482 continue;
483 }
484
485 let metadata = Metadata::from(expression_statement);
486 let value = self
487 .execute_expr(
488 &expression_statement.expression,
489 exec_state,
490 &metadata,
491 &[],
492 StatementKind::Expression,
493 )
494 .await?;
495
496 let is_return = value.is_some_return();
497 last_expr = Some(value);
498
499 if is_return {
500 break;
501 }
502 }
503 BodyItem::VariableDeclaration(variable_declaration) => {
504 if exec_state.sketch_mode() && sketch_mode_should_skip(&variable_declaration.declaration.init) {
505 continue;
506 }
507
508 let var_name = variable_declaration.declaration.id.name.to_string();
509 let source_range = SourceRange::from(&variable_declaration.declaration.init);
510 let metadata = Metadata { source_range };
511
512 let annotations = &variable_declaration.outer_attrs;
513
514 let lhs = variable_declaration.inner.name().to_owned();
517 let prev_being_declared = exec_state.mod_local.being_declared.take();
518 exec_state.mod_local.being_declared = Some(lhs);
519 let rhs_result = self
520 .execute_expr(
521 &variable_declaration.declaration.init,
522 exec_state,
523 &metadata,
524 annotations,
525 StatementKind::Declaration { name: &var_name },
526 )
527 .await;
528 exec_state.mod_local.being_declared = prev_being_declared;
530 let rhs = rhs_result?;
531
532 if rhs.is_some_return() {
533 last_expr = Some(rhs);
534 break;
535 }
536 let mut rhs = rhs.into_value();
537
538 if let KclValue::Segment { value } = &mut rhs
542 && let SegmentRepr::Unsolved { segment } = &mut value.repr
543 {
544 segment.tag = Some(TagIdentifier {
545 value: variable_declaration.declaration.id.name.clone(),
546 info: Default::default(),
547 meta: vec![SourceRange::from(&variable_declaration.declaration.id).into()],
548 });
549 }
550 let rhs = rhs; let should_bind_name =
553 if let Some(fn_name) = variable_declaration.declaration.init.fn_declaring_name() {
554 var_name != fn_name
558 } else {
559 true
562 };
563 if should_bind_name {
564 exec_state
565 .mut_stack()
566 .add(var_name.clone(), rhs.clone(), source_range)?;
567 }
568
569 if let Some(sketch_block_state) = exec_state.mod_local.sketch_block.as_mut()
570 && let KclValue::Segment { value } = &rhs
571 {
572 let segment_object_id = match &value.repr {
575 SegmentRepr::Unsolved { segment } => segment.object_id,
576 SegmentRepr::Solved { segment } => segment.object_id,
577 };
578 sketch_block_state
579 .segment_tags
580 .entry(segment_object_id)
581 .or_insert_with(|| {
582 let id_node = &variable_declaration.declaration.id;
583 Node::new(
584 TagDeclarator {
585 name: id_node.name.clone(),
586 digest: None,
587 },
588 id_node.start,
589 id_node.end,
590 id_node.module_id,
591 )
592 });
593 }
594
595 let should_show_in_feature_tree =
599 !exec_state.mod_local.inside_stdlib && rhs.show_variable_in_feature_tree();
600 if should_show_in_feature_tree {
601 exec_state.push_op(Operation::VariableDeclaration {
602 name: var_name.clone(),
603 value: OpKclValue::from(&rhs),
604 visibility: variable_declaration.visibility,
605 node_path: NodePath::placeholder(),
606 source_range,
607 });
608 }
609
610 if let ItemVisibility::Export = variable_declaration.visibility {
612 if matches!(body_type, BodyType::Root) {
613 exec_state.mod_local.module_exports.push(var_name);
614 } else {
615 exec_state.err(CompilationError::err(
616 variable_declaration.as_source_range(),
617 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
618 ));
619 }
620 }
621 last_expr = matches!(body_type, BodyType::Root).then_some(rhs.continue_());
623 }
624 BodyItem::TypeDeclaration(ty) => {
625 if exec_state.sketch_mode() {
626 continue;
627 }
628
629 let metadata = Metadata::from(&**ty);
630 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
631 match attrs.impl_ {
632 annotations::Impl::Rust
633 | annotations::Impl::RustConstrainable
634 | annotations::Impl::RustConstraint => {
635 let std_path = match &exec_state.mod_local.path {
636 ModulePath::Std { value } => value,
637 ModulePath::Local { .. } | ModulePath::Main => {
638 return Err(KclError::new_semantic(KclErrorDetails::new(
639 "User-defined types are not yet supported.".to_owned(),
640 vec![metadata.source_range],
641 )));
642 }
643 };
644 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
645 let value = KclValue::Type {
646 value: TypeDef::RustRepr(t, props),
647 meta: vec![metadata],
648 experimental: attrs.experimental,
649 };
650 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
651 exec_state
652 .mut_stack()
653 .add(name_in_mem.clone(), value, metadata.source_range)
654 .map_err(|_| {
655 KclError::new_semantic(KclErrorDetails::new(
656 format!("Redefinition of type {}.", ty.name.name),
657 vec![metadata.source_range],
658 ))
659 })?;
660
661 if let ItemVisibility::Export = ty.visibility {
662 exec_state.mod_local.module_exports.push(name_in_mem);
663 }
664 }
665 annotations::Impl::Primitive => {}
667 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
668 Some(alias) => {
669 let value = KclValue::Type {
670 value: TypeDef::Alias(
671 RuntimeType::from_parsed(
672 alias.inner.clone(),
673 exec_state,
674 metadata.source_range,
675 attrs.impl_ == annotations::Impl::KclConstrainable,
676 false,
677 )
678 .map_err(|e| KclError::new_semantic(e.into()))?,
679 ),
680 meta: vec![metadata],
681 experimental: attrs.experimental,
682 };
683 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
684 exec_state
685 .mut_stack()
686 .add(name_in_mem.clone(), value, metadata.source_range)
687 .map_err(|_| {
688 KclError::new_semantic(KclErrorDetails::new(
689 format!("Redefinition of type {}.", ty.name.name),
690 vec![metadata.source_range],
691 ))
692 })?;
693
694 if let ItemVisibility::Export = ty.visibility {
695 exec_state.mod_local.module_exports.push(name_in_mem);
696 }
697 }
698 None => {
699 return Err(KclError::new_semantic(KclErrorDetails::new(
700 "User-defined types are not yet supported.".to_owned(),
701 vec![metadata.source_range],
702 )));
703 }
704 },
705 }
706
707 last_expr = None;
708 }
709 BodyItem::ReturnStatement(return_statement) => {
710 if exec_state.sketch_mode() && sketch_mode_should_skip(&return_statement.argument) {
711 continue;
712 }
713
714 let metadata = Metadata::from(return_statement);
715
716 if matches!(body_type, BodyType::Root) {
717 return Err(KclError::new_semantic(KclErrorDetails::new(
718 "Cannot return from outside a function.".to_owned(),
719 vec![metadata.source_range],
720 )));
721 }
722
723 let value_cf = self
724 .execute_expr(
725 &return_statement.argument,
726 exec_state,
727 &metadata,
728 &[],
729 StatementKind::Expression,
730 )
731 .await?;
732 if value_cf.is_some_return() {
733 last_expr = Some(value_cf);
734 break;
735 }
736 let value = value_cf.into_value();
737 exec_state
738 .mut_stack()
739 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
740 .map_err(|_| {
741 KclError::new_semantic(KclErrorDetails::new(
742 "Multiple returns from a single function.".to_owned(),
743 vec![metadata.source_range],
744 ))
745 })?;
746 last_expr = None;
747 }
748 }
749 }
750
751 if matches!(body_type, BodyType::Root) {
752 exec_state
754 .flush_batch(
755 ModelingCmdMeta::new(exec_state, self, block.to_source_range()),
756 true,
759 )
760 .await?;
761 }
762
763 Ok(last_expr)
764 }
765
766 pub async fn open_module(
767 &self,
768 path: &ImportPath,
769 attrs: &[Node<Annotation>],
770 resolved_path: &ModulePath,
771 exec_state: &mut ExecState,
772 source_range: SourceRange,
773 ) -> Result<ModuleId, KclError> {
774 match path {
775 ImportPath::Kcl { .. } => {
776 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
777
778 if let Some(id) = exec_state.id_for_module(resolved_path) {
779 return Ok(id);
780 }
781
782 let id = exec_state.next_module_id();
783 exec_state.add_path_to_source_id(resolved_path.clone(), id);
785 let source = resolved_path.source(&self.fs, source_range).await?;
786 exec_state.add_id_to_source(id, source.clone());
787 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
789 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
790
791 Ok(id)
792 }
793 ImportPath::Foreign { .. } => {
794 if let Some(id) = exec_state.id_for_module(resolved_path) {
795 return Ok(id);
796 }
797
798 let id = exec_state.next_module_id();
799 let path = resolved_path.expect_path();
800 exec_state.add_path_to_source_id(resolved_path.clone(), id);
802 let format = super::import::format_from_annotations(attrs, path, source_range)?;
803 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
804 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
805 Ok(id)
806 }
807 ImportPath::Std { .. } => {
808 if let Some(id) = exec_state.id_for_module(resolved_path) {
809 return Ok(id);
810 }
811
812 let id = exec_state.next_module_id();
813 exec_state.add_path_to_source_id(resolved_path.clone(), id);
815 let source = resolved_path.source(&self.fs, source_range).await?;
816 exec_state.add_id_to_source(id, source.clone());
817 let parsed = crate::parsing::parse_str(&source.source, id)
818 .parse_errs_as_err()
819 .unwrap();
820 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
821 Ok(id)
822 }
823 }
824 }
825
826 pub(super) async fn exec_module_for_items(
827 &self,
828 module_id: ModuleId,
829 exec_state: &mut ExecState,
830 source_range: SourceRange,
831 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
832 let path = exec_state.global.module_infos[&module_id].path.clone();
833 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
834 let result = match &mut repr {
837 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
838 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
839 ModuleRepr::Kcl(program, cache) => self
840 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
841 .await
842 .map(|outcome| {
843 *cache = Some(outcome.clone());
844 (outcome.environment, outcome.exports)
845 }),
846 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
847 "Cannot import items from foreign modules".to_owned(),
848 vec![geom.source_range],
849 ))),
850 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
851 };
852
853 exec_state.global.module_infos[&module_id].restore_repr(repr);
854 result
855 }
856
857 async fn exec_module_for_result(
858 &self,
859 module_id: ModuleId,
860 exec_state: &mut ExecState,
861 source_range: SourceRange,
862 ) -> Result<Option<KclValue>, KclError> {
863 let path = exec_state.global.module_infos[&module_id].path.clone();
864 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
865 let result = match &mut repr {
868 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
869 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
870 ModuleRepr::Kcl(program, cached_items) => {
871 let result = self
872 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
873 .await;
874 match result {
875 Ok(outcome) => {
876 let value = outcome.last_expr.clone();
877 *cached_items = Some(outcome);
878 Ok(value)
879 }
880 Err(e) => Err(e),
881 }
882 }
883 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
884 ModuleRepr::Foreign(geom, cached) => {
885 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
886 .await
887 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
888
889 match result {
890 Ok(val) => {
891 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
892 Ok(val)
893 }
894 Err(e) => Err(e),
895 }
896 }
897 ModuleRepr::Dummy => unreachable!(),
898 };
899
900 exec_state.global.module_infos[&module_id].restore_repr(repr);
901
902 result
903 }
904
905 pub async fn exec_module_from_ast(
906 &self,
907 program: &Node<Program>,
908 module_id: ModuleId,
909 path: &ModulePath,
910 exec_state: &mut ExecState,
911 source_range: SourceRange,
912 preserve_mem: PreserveMem,
913 ) -> Result<ModuleExecutionOutcome, KclError> {
914 exec_state.global.mod_loader.enter_module(path);
915 let result = self
916 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
917 .await;
918 exec_state.global.mod_loader.leave_module(path, source_range)?;
919
920 result.map_err(|(err, _, _)| {
923 match err {
924 KclError::ImportCycle { .. } => {
925 err.override_source_ranges(vec![source_range])
927 }
928 KclError::EngineHangup { .. } | KclError::EngineInternal { .. } => {
929 err.override_source_ranges(vec![source_range])
932 }
933 _ => {
934 KclError::new_semantic(KclErrorDetails::new(
936 format!(
937 "Error loading imported file ({path}). Open it to view more details.\n {}",
938 err.message()
939 ),
940 vec![source_range],
941 ))
942 }
943 }
944 })
945 }
946
947 #[async_recursion]
948 pub(crate) async fn execute_expr<'a: 'async_recursion>(
949 &self,
950 init: &Expr,
951 exec_state: &mut ExecState,
952 metadata: &Metadata,
953 annotations: &[Node<Annotation>],
954 statement_kind: StatementKind<'a>,
955 ) -> Result<KclValueControlFlow, KclError> {
956 let item = match init {
957 Expr::None(none) => KclValue::from(none).continue_(),
958 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state).continue_(),
959 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?.continue_(),
960 Expr::Name(name) => {
961 let being_declared = exec_state.mod_local.being_declared.clone();
962 let value = name
963 .get_result(exec_state, self)
964 .await
965 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
966 .clone();
967 if let KclValue::Module { value: module_id, meta } = value {
968 self.exec_module_for_result(
969 module_id,
970 exec_state,
971 metadata.source_range
972 ).await?.map(|v| v.continue_())
973 .unwrap_or_else(|| {
974 exec_state.warn(CompilationError::err(
975 metadata.source_range,
976 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
977 ),
978 annotations::WARN_MOD_RETURN_VALUE);
979
980 let mut new_meta = vec![metadata.to_owned()];
981 new_meta.extend(meta);
982 KclValue::KclNone {
983 value: Default::default(),
984 meta: new_meta,
985 }.continue_()
986 })
987 } else {
988 value.continue_()
989 }
990 }
991 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
992 Expr::FunctionExpression(function_expression) => {
993 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
994 let experimental = attrs.map(|a| a.experimental).unwrap_or_default();
995 let is_std = matches!(&exec_state.mod_local.path, ModulePath::Std { .. });
996
997 let include_in_feature_tree = attrs.unwrap_or_default().include_in_feature_tree;
999 let (mut closure, placeholder_env_ref) = if let Some(attrs) = attrs
1000 && (attrs.impl_ == annotations::Impl::Rust
1001 || attrs.impl_ == annotations::Impl::RustConstrainable
1002 || attrs.impl_ == annotations::Impl::RustConstraint)
1003 {
1004 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
1005 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
1006 (
1007 KclValue::Function {
1008 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
1009 meta: vec![metadata.to_owned()],
1010 },
1011 None,
1012 )
1013 } else {
1014 return Err(KclError::new_semantic(KclErrorDetails::new(
1015 "Rust implementation of functions is restricted to the standard library".to_owned(),
1016 vec![metadata.source_range],
1017 )));
1018 }
1019 } else {
1020 let (env_ref, placeholder_env_ref) = if function_expression.name.is_some() {
1024 let dummy = EnvironmentRef::dummy();
1027 (dummy, Some(dummy))
1028 } else {
1029 (exec_state.mut_stack().snapshot(), None)
1030 };
1031 (
1032 KclValue::Function {
1033 value: Box::new(FunctionSource::kcl(
1034 function_expression.clone(),
1035 env_ref,
1036 KclFunctionSourceParams {
1037 is_std,
1038 experimental,
1039 include_in_feature_tree,
1040 },
1041 )),
1042 meta: vec![metadata.to_owned()],
1043 },
1044 placeholder_env_ref,
1045 )
1046 };
1047
1048 if let Some(fn_name) = &function_expression.name {
1051 if let Some(placeholder_env_ref) = placeholder_env_ref {
1055 closure = exec_state.mut_stack().add_recursive_closure(
1056 fn_name.name.to_owned(),
1057 closure,
1058 placeholder_env_ref,
1059 metadata.source_range,
1060 )?;
1061 } else {
1062 exec_state
1064 .mut_stack()
1065 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
1066 }
1067 }
1068
1069 closure.continue_()
1070 }
1071 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
1072 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
1073 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
1074 StatementKind::Declaration { name } => {
1075 let message = format!(
1076 "you cannot declare variable {name} as %, because % can only be used in function calls"
1077 );
1078
1079 return Err(KclError::new_semantic(KclErrorDetails::new(
1080 message,
1081 vec![pipe_substitution.into()],
1082 )));
1083 }
1084 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
1085 Some(x) => x.continue_(),
1086 None => {
1087 return Err(KclError::new_semantic(KclErrorDetails::new(
1088 "cannot use % outside a pipe expression".to_owned(),
1089 vec![pipe_substitution.into()],
1090 )));
1091 }
1092 },
1093 },
1094 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
1095 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
1096 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
1097 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
1098 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
1099 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
1100 Expr::LabelledExpression(expr) => {
1101 let value_cf = self
1102 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
1103 .await?;
1104 let value = control_continue!(value_cf);
1105 exec_state
1106 .mut_stack()
1107 .add(expr.label.name.clone(), value.clone(), init.into())?;
1108 value.continue_()
1110 }
1111 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
1112 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
1113 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
1114 };
1115 Ok(item)
1116 }
1117}
1118
1119fn sketch_mode_should_skip(expr: &Expr) -> bool {
1122 match expr {
1123 Expr::SketchBlock(sketch_block) => !sketch_block.is_being_edited,
1124 _ => true,
1125 }
1126}
1127
1128fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
1131 let KclError::UndefinedValue { name, mut details } = e else {
1132 return e;
1133 };
1134 if let (Some(name0), Some(name1)) = (&being_declared, &name)
1138 && name0 == name1
1139 {
1140 details.message = format!(
1141 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
1142 );
1143 }
1144 KclError::UndefinedValue { details, name }
1145}
1146
1147impl Node<AscribedExpression> {
1148 #[async_recursion]
1149 pub(super) async fn get_result(
1150 &self,
1151 exec_state: &mut ExecState,
1152 ctx: &ExecutorContext,
1153 ) -> Result<KclValueControlFlow, KclError> {
1154 let metadata = Metadata {
1155 source_range: SourceRange::from(self),
1156 };
1157 let result = ctx
1158 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
1159 .await?;
1160 let result = control_continue!(result);
1161 apply_ascription(&result, &self.ty, exec_state, self.into()).map(KclValue::continue_)
1162 }
1163}
1164
1165impl Node<SketchBlock> {
1166 pub(super) async fn get_result(
1167 &self,
1168 exec_state: &mut ExecState,
1169 ctx: &ExecutorContext,
1170 ) -> Result<KclValueControlFlow, KclError> {
1171 if exec_state.mod_local.sketch_block.is_some() {
1172 return Err(KclError::new_semantic(KclErrorDetails::new(
1174 "Cannot execute a sketch block from within another sketch block".to_owned(),
1175 vec![SourceRange::from(self)],
1176 )));
1177 }
1178
1179 let range = SourceRange::from(self);
1180
1181 let (sketch_id, sketch_surface) = match self.exec_arguments(exec_state, ctx).await {
1183 Ok(x) => x,
1184 Err(cf_error) => match cf_error {
1185 EarlyReturn::Value(cf_value) => return Ok(cf_value),
1187 EarlyReturn::Error(err) => return Err(err),
1188 },
1189 };
1190 let on_object_id = if let Some(object_id) = sketch_surface.object_id() {
1191 object_id
1192 } else {
1193 let message = "The `on` argument should have an object after ensure_sketch_plane_in_engine".to_owned();
1194 debug_assert!(false, "{message}");
1195 return Err(internal_err(message, range));
1196 };
1197 #[cfg(not(feature = "artifact-graph"))]
1198 let _ = on_object_id;
1199 #[cfg(not(feature = "artifact-graph"))]
1200 let _ = sketch_id;
1201 #[cfg(feature = "artifact-graph")]
1202 let sketch_ctor_on = sketch_on_frontend_plane(&self.arguments, on_object_id);
1203 #[cfg(feature = "artifact-graph")]
1204 {
1205 use crate::execution::{Artifact, ArtifactId, CodeRef, SketchBlock};
1206
1207 let on_object = exec_state.mod_local.artifacts.scene_object_by_id(on_object_id);
1208
1209 let plane_artifact_id = on_object.map(|object| object.artifact_id);
1211
1212 let artifact_id = ArtifactId::from(exec_state.next_uuid());
1213 let sketch_scene_object = Object {
1215 id: sketch_id,
1216 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
1217 args: crate::front::SketchCtor { on: sketch_ctor_on },
1218 plane: on_object_id,
1219 segments: Default::default(),
1220 constraints: Default::default(),
1221 }),
1222 label: Default::default(),
1223 comments: Default::default(),
1224 artifact_id,
1225 source: range.into(),
1226 };
1227 exec_state.set_scene_object(sketch_scene_object);
1228
1229 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
1231 id: artifact_id,
1232 plane_id: plane_artifact_id,
1233 code_ref: CodeRef::placeholder(range),
1234 sketch_id,
1235 }));
1236 }
1237
1238 let (return_result, variables, sketch_block_state) = {
1239 self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
1241
1242 #[cfg(feature = "artifact-graph")]
1244 let initial_sketch_block_state = {
1245 SketchBlockState {
1246 sketch_id: Some(sketch_id),
1247 ..Default::default()
1248 }
1249 };
1250 #[cfg(not(feature = "artifact-graph"))]
1251 let initial_sketch_block_state = SketchBlockState::default();
1252
1253 let original_value = exec_state.mod_local.sketch_block.replace(initial_sketch_block_state);
1254
1255 let original_sketch_mode = std::mem::replace(&mut exec_state.mod_local.sketch_mode, false);
1258
1259 let (result, block_variables) = match self.load_sketch2_into_current_scope(exec_state, ctx, range).await {
1264 Ok(()) => {
1265 let parent = exec_state.mut_stack().snapshot();
1266 exec_state.mut_stack().push_new_env_for_call(parent);
1267 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1268 let block_variables = exec_state
1269 .stack()
1270 .find_all_in_current_env()
1271 .map(|(name, value)| (name.clone(), value.clone()))
1272 .collect::<IndexMap<_, _>>();
1273 exec_state.mut_stack().pop_env();
1274 (result, block_variables)
1275 }
1276 Err(err) => (Err(err), IndexMap::new()),
1277 };
1278
1279 exec_state.mod_local.sketch_mode = original_sketch_mode;
1280
1281 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1282
1283 exec_state.mut_stack().pop_env();
1285
1286 (result, block_variables, sketch_block_state)
1287 };
1288
1289 return_result?;
1291 let Some(sketch_block_state) = sketch_block_state else {
1292 debug_assert!(false, "Sketch block state should still be set to Some from just above");
1293 return Err(internal_err(
1294 "Sketch block state should still be set to Some from just above",
1295 self,
1296 ));
1297 };
1298 #[cfg(feature = "artifact-graph")]
1299 let mut sketch_block_state = sketch_block_state;
1300
1301 let constraints = sketch_block_state
1303 .solver_constraints
1304 .iter()
1305 .cloned()
1306 .map(kcl_ezpz::ConstraintRequest::highest_priority)
1307 .chain(
1308 sketch_block_state
1310 .solver_optional_constraints
1311 .iter()
1312 .cloned()
1313 .map(|c| kcl_ezpz::ConstraintRequest::new(c, 1)),
1314 )
1315 .collect::<Vec<_>>();
1316 let initial_guesses = sketch_block_state
1317 .sketch_vars
1318 .iter()
1319 .map(|v| {
1320 let Some(sketch_var) = v.as_sketch_var() else {
1321 return Err(internal_err("Expected sketch variable", self));
1322 };
1323 let constraint_id = sketch_var.id.to_constraint_id(range)?;
1324 let number_value = KclValue::Number {
1326 value: sketch_var.initial_value,
1327 ty: sketch_var.ty,
1328 meta: sketch_var.meta.clone(),
1329 };
1330 let initial_guess_value = normalize_to_solver_distance_unit(
1331 &number_value,
1332 v.into(),
1333 exec_state,
1334 "sketch variable initial value",
1335 )?;
1336 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1337 n.n
1338 } else {
1339 let message = format!(
1340 "Expected number after coercion, but found {}",
1341 initial_guess_value.human_friendly_type()
1342 );
1343 debug_assert!(false, "{}", &message);
1344 return Err(internal_err(message, self));
1345 };
1346 Ok((constraint_id, initial_guess))
1347 })
1348 .collect::<Result<Vec<_>, KclError>>()?;
1349 let config = kcl_ezpz::Config::default().with_max_iterations(50);
1351 let solve_result = if exec_state.mod_local.freedom_analysis {
1352 kcl_ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
1353 let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
1354 (outcome.outcome, Some(freedom_analysis))
1355 })
1356 } else {
1357 kcl_ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1358 };
1359 let num_required_constraints = sketch_block_state.solver_constraints.len();
1361 let all_constraints: Vec<kcl_ezpz::Constraint> = sketch_block_state
1362 .solver_constraints
1363 .iter()
1364 .cloned()
1365 .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
1366 .collect();
1367
1368 let (solve_outcome, solve_analysis) = match solve_result {
1369 Ok((solved, freedom)) => {
1370 let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
1371 (outcome, freedom)
1372 }
1373 Err(failure) => {
1374 match &failure.error {
1375 NonLinearSystemError::FaerMatrix { .. }
1376 | NonLinearSystemError::Faer { .. }
1377 | NonLinearSystemError::FaerSolve { .. }
1378 | NonLinearSystemError::FaerSvd(..)
1379 | NonLinearSystemError::DidNotConverge => {
1380 exec_state.warn(
1383 CompilationError::err(range, "Constraint solver failed to find a solution".to_owned()),
1384 annotations::WARN_SOLVER,
1385 );
1386 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1387 (
1388 Solved {
1389 final_values,
1390 iterations: Default::default(),
1391 warnings: failure.warnings,
1392 priority_solved: Default::default(),
1393 variables_in_conflicts: Default::default(),
1394 },
1395 None,
1396 )
1397 }
1398 NonLinearSystemError::EmptySystemNotAllowed
1399 | NonLinearSystemError::WrongNumberGuesses { .. }
1400 | NonLinearSystemError::MissingGuess { .. }
1401 | NonLinearSystemError::NotFound(..) => {
1402 #[cfg(target_arch = "wasm32")]
1405 web_sys::console::error_1(
1406 &format!("Internal error from constraint solver: {}", &failure.error).into(),
1407 );
1408 return Err(internal_err(
1409 format!("Internal error from constraint solver: {}", &failure.error),
1410 self,
1411 ));
1412 }
1413 _ => {
1414 return Err(internal_err(
1416 format!("Error from constraint solver: {}", &failure.error),
1417 self,
1418 ));
1419 }
1420 }
1421 }
1422 };
1423 #[cfg(not(feature = "artifact-graph"))]
1424 let _ = solve_analysis;
1425 for warning in &solve_outcome.warnings {
1427 let message = if let Some(index) = warning.about_constraint.as_ref() {
1428 format!("{}; constraint index {}", &warning.content, index)
1429 } else {
1430 format!("{}", &warning.content)
1431 };
1432 exec_state.warn(CompilationError::err(range, message), annotations::WARN_SOLVER);
1433 }
1434 let sketch_engine_id = exec_state.next_uuid();
1436 let solution_ty = solver_numeric_type(exec_state);
1437 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1438 for unsolved_segment in &sketch_block_state.needed_by_engine {
1439 solved_segments.push(substitute_sketch_var_in_segment(
1440 unsolved_segment.clone(),
1441 &sketch_surface,
1442 sketch_engine_id,
1443 None,
1444 &solve_outcome,
1445 solver_numeric_type(exec_state),
1446 solve_analysis.as_ref(),
1447 )?);
1448 }
1449 #[cfg(feature = "artifact-graph")]
1450 {
1451 exec_state.mod_local.artifacts.var_solutions =
1457 sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
1458 }
1459
1460 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1462
1463 let sketch = create_segments_in_engine(
1465 &sketch_surface,
1466 sketch_engine_id,
1467 &mut solved_segments,
1468 &sketch_block_state.segment_tags,
1469 ctx,
1470 exec_state,
1471 range,
1472 )
1473 .await?;
1474
1475 let variables = substitute_sketch_vars(
1480 variables,
1481 &sketch_surface,
1482 sketch_engine_id,
1483 sketch,
1484 &solve_outcome,
1485 solution_ty,
1486 solve_analysis.as_ref(),
1487 )?;
1488
1489 #[cfg(not(feature = "artifact-graph"))]
1490 drop(scene_objects);
1491 #[cfg(feature = "artifact-graph")]
1492 {
1493 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1494 for scene_object in scene_objects {
1495 segment_object_ids.push(scene_object.id);
1496 exec_state.set_scene_object(scene_object);
1498 }
1499 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
1501 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1502 debug_assert!(false, "{}", &message);
1503 return Err(internal_err(message, range));
1504 };
1505 let ObjectKind::Sketch(sketch) = &mut sketch_object.kind else {
1506 let message = format!(
1507 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1508 sketch_id, sketch_object
1509 );
1510 debug_assert!(
1511 false,
1512 "{}; scene_objects={:#?}",
1513 &message, &exec_state.mod_local.artifacts.scene_objects
1514 );
1515 return Err(internal_err(message, range));
1516 };
1517 sketch.segments.extend(segment_object_ids);
1518 sketch
1520 .constraints
1521 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1522
1523 exec_state.push_op(Operation::SketchSolve {
1525 sketch_id,
1526 node_path: NodePath::placeholder(),
1527 source_range: range,
1528 });
1529 }
1530
1531 let metadata = Metadata {
1532 source_range: SourceRange::from(self),
1533 };
1534 let return_value = KclValue::Object {
1535 value: variables,
1536 constrainable: Default::default(),
1537 meta: vec![metadata],
1538 };
1539 Ok(if self.is_being_edited {
1540 return_value.exit()
1543 } else {
1544 return_value.continue_()
1545 })
1546 }
1547
1548 async fn exec_arguments(
1558 &self,
1559 exec_state: &mut ExecState,
1560 ctx: &ExecutorContext,
1561 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
1562 let range = SourceRange::from(self);
1563
1564 if !exec_state.sketch_mode() {
1565 let mut labeled = IndexMap::new();
1571 for labeled_arg in &self.arguments {
1572 let source_range = SourceRange::from(labeled_arg.arg.clone());
1573 let metadata = Metadata { source_range };
1574 let value_cf = ctx
1575 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
1576 .await?;
1577 let value = early_return!(value_cf);
1578 let arg = Arg::new(value, source_range);
1579 match &labeled_arg.label {
1580 Some(label) => {
1581 labeled.insert(label.name.clone(), arg);
1582 }
1583 None => {
1584 let name = labeled_arg.arg.ident_name();
1585 if let Some(name) = name {
1586 labeled.insert(name.to_owned(), arg);
1587 } else {
1588 return Err(KclError::new_semantic(KclErrorDetails::new(
1589 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
1590 vec![SourceRange::from(&labeled_arg.arg)],
1591 ))
1592 .into());
1593 }
1594 }
1595 }
1596 }
1597 let mut args = Args::new_no_args(range, ctx.clone(), Some("sketch block".to_owned()));
1598 args.labeled = labeled;
1599
1600 let arg_on_value: KclValue =
1601 args.get_kw_arg(SKETCH_BLOCK_PARAM_ON, &RuntimeType::sketch_or_surface(), exec_state)?;
1602
1603 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
1604 let message =
1605 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
1606 debug_assert!(false, "{message}");
1607 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
1608 };
1609 let mut sketch_surface = arg_on.into_sketch_surface();
1610
1611 match &mut sketch_surface {
1614 SketchSurface::Plane(plane) => {
1615 ensure_sketch_plane_in_engine(plane, exec_state, ctx, range).await?;
1617 }
1618 SketchSurface::Face(_) => {
1619 }
1621 }
1622
1623 let sketch_id = exec_state.next_object_id();
1629 #[cfg(feature = "artifact-graph")]
1630 exec_state.add_placeholder_scene_object(sketch_id, range);
1631 let on_cache_name = sketch_on_cache_name(sketch_id);
1632 exec_state.mut_stack().add(on_cache_name, arg_on_value, range)?;
1634
1635 Ok((sketch_id, sketch_surface))
1636 } else {
1637 let sketch_id = exec_state.next_object_id();
1644 #[cfg(feature = "artifact-graph")]
1645 exec_state.add_placeholder_scene_object(sketch_id, range);
1646 let on_cache_name = sketch_on_cache_name(sketch_id);
1647 let arg_on_value = exec_state.stack().get(&on_cache_name, range)?.clone();
1648
1649 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
1650 let message =
1651 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
1652 debug_assert!(false, "{message}");
1653 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
1654 };
1655 let mut sketch_surface = arg_on.into_sketch_surface();
1656
1657 if sketch_surface.object_id().is_none() {
1660 #[cfg(not(feature = "artifact-graph"))]
1661 {
1662 sketch_surface.set_object_id(exec_state.next_object_id());
1665 }
1666 #[cfg(feature = "artifact-graph")]
1667 {
1668 let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
1671 return Err(internal_err(
1672 "In sketch mode, the `on` plane argument must refer to an existing plane object.",
1673 range,
1674 )
1675 .into());
1676 };
1677 sketch_surface.set_object_id(last_object.id);
1678 }
1679 }
1680
1681 Ok((sketch_id, sketch_surface))
1682 }
1683 }
1684
1685 async fn load_sketch2_into_current_scope(
1686 &self,
1687 exec_state: &mut ExecState,
1688 ctx: &ExecutorContext,
1689 source_range: SourceRange,
1690 ) -> Result<(), KclError> {
1691 let path = vec!["std".to_owned(), "sketch2".to_owned()];
1692 let resolved_path = ModulePath::from_std_import_path(&path)?;
1693 let module_id = ctx
1694 .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
1695 .await?;
1696 let (env_ref, exports) = ctx.exec_module_for_items(module_id, exec_state, source_range).await?;
1697
1698 for name in exports {
1699 let value = exec_state
1700 .stack()
1701 .memory
1702 .get_from(&name, env_ref, source_range, 0)?
1703 .clone();
1704 exec_state.mut_stack().add(name, value, source_range)?;
1705 }
1706 Ok(())
1707 }
1708}
1709
1710impl SketchBlock {
1711 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1712 exec_state.mut_stack().push_new_env_for_call(parent);
1713 }
1714}
1715
1716impl Node<SketchVar> {
1717 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1718 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1719 return Err(KclError::new_semantic(KclErrorDetails::new(
1720 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1721 vec![SourceRange::from(self)],
1722 )));
1723 };
1724 let id = sketch_block_state.next_sketch_var_id();
1725 let sketch_var = if let Some(initial) = &self.initial {
1726 KclValue::from_sketch_var_literal(initial, id, exec_state)
1727 } else {
1728 let metadata = Metadata {
1729 source_range: SourceRange::from(self),
1730 };
1731
1732 KclValue::SketchVar {
1733 value: Box::new(super::SketchVar {
1734 id,
1735 initial_value: 0.0,
1736 ty: NumericType::default(),
1737 meta: vec![metadata],
1738 }),
1739 }
1740 };
1741
1742 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1743 return Err(KclError::new_semantic(KclErrorDetails::new(
1744 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1745 vec![SourceRange::from(self)],
1746 )));
1747 };
1748 sketch_block_state.sketch_vars.push(sketch_var.clone());
1749
1750 Ok(sketch_var)
1751 }
1752}
1753
1754fn apply_ascription(
1755 value: &KclValue,
1756 ty: &Node<Type>,
1757 exec_state: &mut ExecState,
1758 source_range: SourceRange,
1759) -> Result<KclValue, KclError> {
1760 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false, false)
1761 .map_err(|e| KclError::new_semantic(e.into()))?;
1762
1763 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1764 exec_state.clear_units_warnings(&source_range);
1765 }
1766
1767 value.coerce(&ty, false, exec_state).map_err(|_| {
1768 let suggestion = if ty == RuntimeType::length() {
1769 ", you might try coercing to a fully specified numeric type such as `mm`"
1770 } else if ty == RuntimeType::angle() {
1771 ", you might try coercing to a fully specified numeric type such as `deg`"
1772 } else {
1773 ""
1774 };
1775 let ty_str = if let Some(ty) = value.principal_type() {
1776 format!("(with type `{ty}`) ")
1777 } else {
1778 String::new()
1779 };
1780 KclError::new_semantic(KclErrorDetails::new(
1781 format!(
1782 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1783 value.human_friendly_type()
1784 ),
1785 vec![source_range],
1786 ))
1787 })
1788}
1789
1790impl BinaryPart {
1791 #[async_recursion]
1792 pub(super) async fn get_result(
1793 &self,
1794 exec_state: &mut ExecState,
1795 ctx: &ExecutorContext,
1796 ) -> Result<KclValueControlFlow, KclError> {
1797 match self {
1798 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
1799 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned().map(KclValue::continue_),
1800 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1801 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1802 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1803 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1804 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1805 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1806 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1807 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1808 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1809 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
1810 }
1811 }
1812}
1813
1814impl Node<Name> {
1815 pub(super) async fn get_result<'a>(
1816 &self,
1817 exec_state: &'a mut ExecState,
1818 ctx: &ExecutorContext,
1819 ) -> Result<&'a KclValue, KclError> {
1820 let being_declared = exec_state.mod_local.being_declared.clone();
1821 self.get_result_inner(exec_state, ctx)
1822 .await
1823 .map_err(|e| var_in_own_ref_err(e, &being_declared))
1824 }
1825
1826 async fn get_result_inner<'a>(
1827 &self,
1828 exec_state: &'a mut ExecState,
1829 ctx: &ExecutorContext,
1830 ) -> Result<&'a KclValue, KclError> {
1831 if self.abs_path {
1832 return Err(KclError::new_semantic(KclErrorDetails::new(
1833 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1834 self.as_source_ranges(),
1835 )));
1836 }
1837
1838 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1839
1840 if self.path.is_empty() {
1841 let item_value = exec_state.stack().get(&self.name.name, self.into());
1842 if item_value.is_ok() {
1843 return item_value;
1844 }
1845 return exec_state.stack().get(&mod_name, self.into());
1846 }
1847
1848 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1849 for p in &self.path {
1850 let value = match mem_spec {
1851 Some((env, exports)) => {
1852 if !exports.contains(&p.name) {
1853 return Err(KclError::new_semantic(KclErrorDetails::new(
1854 format!("Item {} not found in module's exported items", p.name),
1855 p.as_source_ranges(),
1856 )));
1857 }
1858
1859 exec_state
1860 .stack()
1861 .memory
1862 .get_from(&p.name, env, p.as_source_range(), 0)?
1863 }
1864 None => exec_state
1865 .stack()
1866 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
1867 };
1868
1869 let KclValue::Module { value: module_id, .. } = value else {
1870 return Err(KclError::new_semantic(KclErrorDetails::new(
1871 format!(
1872 "Identifier in path must refer to a module, found {}",
1873 value.human_friendly_type()
1874 ),
1875 p.as_source_ranges(),
1876 )));
1877 };
1878
1879 mem_spec = Some(
1880 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1881 .await?,
1882 );
1883 }
1884
1885 let (env, exports) = mem_spec.unwrap();
1886
1887 let item_exported = exports.contains(&self.name.name);
1888 let item_value = exec_state
1889 .stack()
1890 .memory
1891 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1892
1893 if item_exported && item_value.is_ok() {
1895 return item_value;
1896 }
1897
1898 let mod_exported = exports.contains(&mod_name);
1899 let mod_value = exec_state
1900 .stack()
1901 .memory
1902 .get_from(&mod_name, env, self.name.as_source_range(), 0);
1903
1904 if mod_exported && mod_value.is_ok() {
1906 return mod_value;
1907 }
1908
1909 if item_value.is_err() && mod_value.is_err() {
1911 return item_value;
1912 }
1913
1914 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1916 Err(KclError::new_semantic(KclErrorDetails::new(
1917 format!("Item {} not found in module's exported items", self.name.name),
1918 self.name.as_source_ranges(),
1919 )))
1920 }
1921}
1922
1923impl Node<MemberExpression> {
1924 async fn get_result(
1925 &self,
1926 exec_state: &mut ExecState,
1927 ctx: &ExecutorContext,
1928 ) -> Result<KclValueControlFlow, KclError> {
1929 let meta = Metadata {
1930 source_range: SourceRange::from(self),
1931 };
1932 let property = Property::try_from(
1935 self.computed,
1936 self.property.clone(),
1937 exec_state,
1938 self.into(),
1939 ctx,
1940 &meta,
1941 &[],
1942 StatementKind::Expression,
1943 )
1944 .await?;
1945 let object_cf = ctx
1946 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1947 .await?;
1948 let object = control_continue!(object_cf);
1949
1950 match (object, property, self.computed) {
1952 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
1953 "at" => match &segment.repr {
1954 SegmentRepr::Unsolved { segment } => {
1955 match &segment.kind {
1956 UnsolvedSegmentKind::Point { position, .. } => {
1957 Ok(KclValue::HomArray {
1959 value: vec![
1960 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
1961 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
1962 ],
1963 ty: RuntimeType::any(),
1964 }
1965 .continue_())
1966 }
1967 _ => Err(KclError::new_undefined_value(
1968 KclErrorDetails::new(
1969 format!("Property '{property}' not found in segment"),
1970 vec![self.clone().into()],
1971 ),
1972 None,
1973 )),
1974 }
1975 }
1976 SegmentRepr::Solved { segment } => {
1977 match &segment.kind {
1978 SegmentKind::Point { position, .. } => {
1979 Ok(KclValue::array_from_point2d(
1981 [position[0].n, position[1].n],
1982 position[0].ty,
1983 segment.meta.clone(),
1984 )
1985 .continue_())
1986 }
1987 _ => Err(KclError::new_undefined_value(
1988 KclErrorDetails::new(
1989 format!("Property '{property}' not found in segment"),
1990 vec![self.clone().into()],
1991 ),
1992 None,
1993 )),
1994 }
1995 }
1996 },
1997 "start" => match &segment.repr {
1998 SegmentRepr::Unsolved { segment } => match &segment.kind {
1999 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2000 KclErrorDetails::new(
2001 format!("Property '{property}' not found in point segment"),
2002 vec![self.clone().into()],
2003 ),
2004 None,
2005 )),
2006 UnsolvedSegmentKind::Line {
2007 start,
2008 ctor,
2009 start_object_id,
2010 ..
2011 } => Ok(KclValue::Segment {
2012 value: Box::new(AbstractSegment {
2013 repr: SegmentRepr::Unsolved {
2014 segment: Box::new(UnsolvedSegment {
2015 id: segment.id,
2016 object_id: *start_object_id,
2017 kind: UnsolvedSegmentKind::Point {
2018 position: start.clone(),
2019 ctor: Box::new(PointCtor {
2020 position: ctor.start.clone(),
2021 }),
2022 },
2023 tag: segment.tag.clone(),
2024 meta: segment.meta.clone(),
2025 }),
2026 },
2027 meta: segment.meta.clone(),
2028 }),
2029 }
2030 .continue_()),
2031 UnsolvedSegmentKind::Arc {
2032 start,
2033 ctor,
2034 start_object_id,
2035 ..
2036 } => Ok(KclValue::Segment {
2037 value: Box::new(AbstractSegment {
2038 repr: SegmentRepr::Unsolved {
2039 segment: Box::new(UnsolvedSegment {
2040 id: segment.id,
2041 object_id: *start_object_id,
2042 kind: UnsolvedSegmentKind::Point {
2043 position: start.clone(),
2044 ctor: Box::new(PointCtor {
2045 position: ctor.start.clone(),
2046 }),
2047 },
2048 tag: segment.tag.clone(),
2049 meta: segment.meta.clone(),
2050 }),
2051 },
2052 meta: segment.meta.clone(),
2053 }),
2054 }
2055 .continue_()),
2056 },
2057 SegmentRepr::Solved { segment } => match &segment.kind {
2058 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2059 KclErrorDetails::new(
2060 format!("Property '{property}' not found in point segment"),
2061 vec![self.clone().into()],
2062 ),
2063 None,
2064 )),
2065 SegmentKind::Line {
2066 start,
2067 ctor,
2068 start_object_id,
2069 start_freedom,
2070 ..
2071 } => Ok(KclValue::Segment {
2072 value: Box::new(AbstractSegment {
2073 repr: SegmentRepr::Solved {
2074 segment: Box::new(Segment {
2075 id: segment.id,
2076 object_id: *start_object_id,
2077 kind: SegmentKind::Point {
2078 position: start.clone(),
2079 ctor: Box::new(PointCtor {
2080 position: ctor.start.clone(),
2081 }),
2082 freedom: *start_freedom,
2083 },
2084 surface: segment.surface.clone(),
2085 sketch_id: segment.sketch_id,
2086 sketch: None,
2087 tag: segment.tag.clone(),
2088 meta: segment.meta.clone(),
2089 }),
2090 },
2091 meta: segment.meta.clone(),
2092 }),
2093 }
2094 .continue_()),
2095 SegmentKind::Arc {
2096 start,
2097 ctor,
2098 start_object_id,
2099 start_freedom,
2100 ..
2101 } => Ok(KclValue::Segment {
2102 value: Box::new(AbstractSegment {
2103 repr: SegmentRepr::Solved {
2104 segment: Box::new(Segment {
2105 id: segment.id,
2106 object_id: *start_object_id,
2107 kind: SegmentKind::Point {
2108 position: start.clone(),
2109 ctor: Box::new(PointCtor {
2110 position: ctor.start.clone(),
2111 }),
2112 freedom: *start_freedom,
2113 },
2114 surface: segment.surface.clone(),
2115 sketch_id: segment.sketch_id,
2116 sketch: None,
2117 tag: segment.tag.clone(),
2118 meta: segment.meta.clone(),
2119 }),
2120 },
2121 meta: segment.meta.clone(),
2122 }),
2123 }
2124 .continue_()),
2125 },
2126 },
2127 "end" => match &segment.repr {
2128 SegmentRepr::Unsolved { segment } => match &segment.kind {
2129 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2130 KclErrorDetails::new(
2131 format!("Property '{property}' not found in point segment"),
2132 vec![self.clone().into()],
2133 ),
2134 None,
2135 )),
2136 UnsolvedSegmentKind::Line {
2137 end,
2138 ctor,
2139 end_object_id,
2140 ..
2141 } => Ok(KclValue::Segment {
2142 value: Box::new(AbstractSegment {
2143 repr: SegmentRepr::Unsolved {
2144 segment: Box::new(UnsolvedSegment {
2145 id: segment.id,
2146 object_id: *end_object_id,
2147 kind: UnsolvedSegmentKind::Point {
2148 position: end.clone(),
2149 ctor: Box::new(PointCtor {
2150 position: ctor.end.clone(),
2151 }),
2152 },
2153 tag: segment.tag.clone(),
2154 meta: segment.meta.clone(),
2155 }),
2156 },
2157 meta: segment.meta.clone(),
2158 }),
2159 }
2160 .continue_()),
2161 UnsolvedSegmentKind::Arc {
2162 end,
2163 ctor,
2164 end_object_id,
2165 ..
2166 } => Ok(KclValue::Segment {
2167 value: Box::new(AbstractSegment {
2168 repr: SegmentRepr::Unsolved {
2169 segment: Box::new(UnsolvedSegment {
2170 id: segment.id,
2171 object_id: *end_object_id,
2172 kind: UnsolvedSegmentKind::Point {
2173 position: end.clone(),
2174 ctor: Box::new(PointCtor {
2175 position: ctor.end.clone(),
2176 }),
2177 },
2178 tag: segment.tag.clone(),
2179 meta: segment.meta.clone(),
2180 }),
2181 },
2182 meta: segment.meta.clone(),
2183 }),
2184 }
2185 .continue_()),
2186 },
2187 SegmentRepr::Solved { segment } => match &segment.kind {
2188 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2189 KclErrorDetails::new(
2190 format!("Property '{property}' not found in point segment"),
2191 vec![self.clone().into()],
2192 ),
2193 None,
2194 )),
2195 SegmentKind::Line {
2196 end,
2197 ctor,
2198 end_object_id,
2199 end_freedom,
2200 ..
2201 } => Ok(KclValue::Segment {
2202 value: Box::new(AbstractSegment {
2203 repr: SegmentRepr::Solved {
2204 segment: Box::new(Segment {
2205 id: segment.id,
2206 object_id: *end_object_id,
2207 kind: SegmentKind::Point {
2208 position: end.clone(),
2209 ctor: Box::new(PointCtor {
2210 position: ctor.end.clone(),
2211 }),
2212 freedom: *end_freedom,
2213 },
2214 surface: segment.surface.clone(),
2215 sketch_id: segment.sketch_id,
2216 sketch: None,
2217 tag: segment.tag.clone(),
2218 meta: segment.meta.clone(),
2219 }),
2220 },
2221 meta: segment.meta.clone(),
2222 }),
2223 }
2224 .continue_()),
2225 SegmentKind::Arc {
2226 end,
2227 ctor,
2228 end_object_id,
2229 end_freedom,
2230 ..
2231 } => Ok(KclValue::Segment {
2232 value: Box::new(AbstractSegment {
2233 repr: SegmentRepr::Solved {
2234 segment: Box::new(Segment {
2235 id: segment.id,
2236 object_id: *end_object_id,
2237 kind: SegmentKind::Point {
2238 position: end.clone(),
2239 ctor: Box::new(PointCtor {
2240 position: ctor.end.clone(),
2241 }),
2242 freedom: *end_freedom,
2243 },
2244 surface: segment.surface.clone(),
2245 sketch_id: segment.sketch_id,
2246 sketch: None,
2247 tag: segment.tag.clone(),
2248 meta: segment.meta.clone(),
2249 }),
2250 },
2251 meta: segment.meta.clone(),
2252 }),
2253 }
2254 .continue_()),
2255 },
2256 },
2257 "center" => match &segment.repr {
2258 SegmentRepr::Unsolved { segment } => match &segment.kind {
2259 UnsolvedSegmentKind::Arc {
2260 center,
2261 ctor,
2262 center_object_id,
2263 ..
2264 } => Ok(KclValue::Segment {
2265 value: Box::new(AbstractSegment {
2266 repr: SegmentRepr::Unsolved {
2267 segment: Box::new(UnsolvedSegment {
2268 id: segment.id,
2269 object_id: *center_object_id,
2270 kind: UnsolvedSegmentKind::Point {
2271 position: center.clone(),
2272 ctor: Box::new(PointCtor {
2273 position: ctor.center.clone(),
2274 }),
2275 },
2276 tag: segment.tag.clone(),
2277 meta: segment.meta.clone(),
2278 }),
2279 },
2280 meta: segment.meta.clone(),
2281 }),
2282 }
2283 .continue_()),
2284 _ => Err(KclError::new_undefined_value(
2285 KclErrorDetails::new(
2286 format!("Property '{property}' not found in segment"),
2287 vec![self.clone().into()],
2288 ),
2289 None,
2290 )),
2291 },
2292 SegmentRepr::Solved { segment } => match &segment.kind {
2293 SegmentKind::Arc {
2294 center,
2295 ctor,
2296 center_object_id,
2297 center_freedom,
2298 ..
2299 } => Ok(KclValue::Segment {
2300 value: Box::new(AbstractSegment {
2301 repr: SegmentRepr::Solved {
2302 segment: Box::new(Segment {
2303 id: segment.id,
2304 object_id: *center_object_id,
2305 kind: SegmentKind::Point {
2306 position: center.clone(),
2307 ctor: Box::new(PointCtor {
2308 position: ctor.center.clone(),
2309 }),
2310 freedom: *center_freedom,
2311 },
2312 surface: segment.surface.clone(),
2313 sketch_id: segment.sketch_id,
2314 sketch: None,
2315 tag: segment.tag.clone(),
2316 meta: segment.meta.clone(),
2317 }),
2318 },
2319 meta: segment.meta.clone(),
2320 }),
2321 }
2322 .continue_()),
2323 _ => Err(KclError::new_undefined_value(
2324 KclErrorDetails::new(
2325 format!("Property '{property}' not found in segment"),
2326 vec![self.clone().into()],
2327 ),
2328 None,
2329 )),
2330 },
2331 },
2332 other => Err(KclError::new_undefined_value(
2333 KclErrorDetails::new(
2334 format!("Property '{other}' not found in segment"),
2335 vec![self.clone().into()],
2336 ),
2337 None,
2338 )),
2339 },
2340 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
2341 "zAxis" => {
2342 let (p, u) = plane.info.z_axis.as_3_dims();
2343 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2344 }
2345 "yAxis" => {
2346 let (p, u) = plane.info.y_axis.as_3_dims();
2347 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2348 }
2349 "xAxis" => {
2350 let (p, u) = plane.info.x_axis.as_3_dims();
2351 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2352 }
2353 "origin" => {
2354 let (p, u) = plane.info.origin.as_3_dims();
2355 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2356 }
2357 other => Err(KclError::new_undefined_value(
2358 KclErrorDetails::new(
2359 format!("Property '{other}' not found in plane"),
2360 vec![self.clone().into()],
2361 ),
2362 None,
2363 )),
2364 },
2365 (KclValue::Object { value: map, .. }, Property::String(property), false) => {
2366 if let Some(value) = map.get(&property) {
2367 Ok(value.to_owned().continue_())
2368 } else {
2369 Err(KclError::new_undefined_value(
2370 KclErrorDetails::new(
2371 format!("Property '{property}' not found in object"),
2372 vec![self.clone().into()],
2373 ),
2374 None,
2375 ))
2376 }
2377 }
2378 (KclValue::Object { .. }, Property::String(property), true) => {
2379 Err(KclError::new_semantic(KclErrorDetails::new(
2380 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
2381 vec![self.clone().into()],
2382 )))
2383 }
2384 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
2385 if i == 0
2386 && let Some(value) = map.get("x")
2387 {
2388 return Ok(value.to_owned().continue_());
2389 }
2390 if i == 1
2391 && let Some(value) = map.get("y")
2392 {
2393 return Ok(value.to_owned().continue_());
2394 }
2395 if i == 2
2396 && let Some(value) = map.get("z")
2397 {
2398 return Ok(value.to_owned().continue_());
2399 }
2400 let t = p.type_name();
2401 let article = article_for(t);
2402 Err(KclError::new_semantic(KclErrorDetails::new(
2403 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
2404 vec![self.clone().into()],
2405 )))
2406 }
2407 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
2408 let value_of_arr = arr.get(index);
2409 if let Some(value) = value_of_arr {
2410 Ok(value.to_owned().continue_())
2411 } else {
2412 Err(KclError::new_undefined_value(
2413 KclErrorDetails::new(
2414 format!("The array doesn't have any item at index {index}"),
2415 vec![self.clone().into()],
2416 ),
2417 None,
2418 ))
2419 }
2420 }
2421 (obj, Property::UInt(0), _) => Ok(obj.continue_()),
2424 (KclValue::HomArray { .. }, p, _) => {
2425 let t = p.type_name();
2426 let article = article_for(t);
2427 Err(KclError::new_semantic(KclErrorDetails::new(
2428 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
2429 vec![self.clone().into()],
2430 )))
2431 }
2432 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
2433 let Some(sketch) = value.sketch() else {
2434 return Err(KclError::new_semantic(KclErrorDetails::new(
2435 "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
2436 vec![self.clone().into()],
2437 )));
2438 };
2439 Ok(KclValue::Sketch {
2440 value: Box::new(sketch.clone()),
2441 }
2442 .continue_())
2443 }
2444 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
2445 Err(KclError::new_semantic(KclErrorDetails::new(
2447 format!(
2448 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
2449 geometry.human_friendly_type()
2450 ),
2451 vec![self.clone().into()],
2452 )))
2453 }
2454 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
2455 meta: vec![Metadata {
2456 source_range: SourceRange::from(self.clone()),
2457 }],
2458 value: sk
2459 .tags
2460 .iter()
2461 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
2462 .collect(),
2463 constrainable: false,
2464 }
2465 .continue_()),
2466 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
2467 Err(KclError::new_semantic(KclErrorDetails::new(
2468 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
2469 vec![self.clone().into()],
2470 )))
2471 }
2472 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
2473 format!(
2474 "Only objects can have members accessed with dot notation, but you're trying to access {}",
2475 being_indexed.human_friendly_type()
2476 ),
2477 vec![self.clone().into()],
2478 ))),
2479 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
2480 format!(
2481 "Only arrays can be indexed, but you're trying to index {}",
2482 being_indexed.human_friendly_type()
2483 ),
2484 vec![self.clone().into()],
2485 ))),
2486 }
2487 }
2488}
2489
2490impl Node<BinaryExpression> {
2491 pub(super) async fn get_result(
2492 &self,
2493 exec_state: &mut ExecState,
2494 ctx: &ExecutorContext,
2495 ) -> Result<KclValueControlFlow, KclError> {
2496 enum State {
2497 EvaluateLeft(Node<BinaryExpression>),
2498 FromLeft {
2499 node: Node<BinaryExpression>,
2500 },
2501 EvaluateRight {
2502 node: Node<BinaryExpression>,
2503 left: KclValue,
2504 },
2505 FromRight {
2506 node: Node<BinaryExpression>,
2507 left: KclValue,
2508 },
2509 }
2510
2511 let mut stack = vec![State::EvaluateLeft(self.clone())];
2512 let mut last_result: Option<KclValue> = None;
2513
2514 while let Some(state) = stack.pop() {
2515 match state {
2516 State::EvaluateLeft(node) => {
2517 let left_part = node.left.clone();
2518 match left_part {
2519 BinaryPart::BinaryExpression(child) => {
2520 stack.push(State::FromLeft { node });
2521 stack.push(State::EvaluateLeft(*child));
2522 }
2523 part => {
2524 let left_value = part.get_result(exec_state, ctx).await?;
2525 let left_value = control_continue!(left_value);
2526 stack.push(State::EvaluateRight { node, left: left_value });
2527 }
2528 }
2529 }
2530 State::FromLeft { node } => {
2531 let Some(left_value) = last_result.take() else {
2532 return Err(Self::missing_result_error(&node));
2533 };
2534 stack.push(State::EvaluateRight { node, left: left_value });
2535 }
2536 State::EvaluateRight { node, left } => {
2537 let right_part = node.right.clone();
2538 match right_part {
2539 BinaryPart::BinaryExpression(child) => {
2540 stack.push(State::FromRight { node, left });
2541 stack.push(State::EvaluateLeft(*child));
2542 }
2543 part => {
2544 let right_value = part.get_result(exec_state, ctx).await?;
2545 let right_value = control_continue!(right_value);
2546 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2547 last_result = Some(result);
2548 }
2549 }
2550 }
2551 State::FromRight { node, left } => {
2552 let Some(right_value) = last_result.take() else {
2553 return Err(Self::missing_result_error(&node));
2554 };
2555 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2556 last_result = Some(result);
2557 }
2558 }
2559 }
2560
2561 last_result
2562 .map(KclValue::continue_)
2563 .ok_or_else(|| Self::missing_result_error(self))
2564 }
2565
2566 async fn apply_operator(
2567 &self,
2568 exec_state: &mut ExecState,
2569 ctx: &ExecutorContext,
2570 left_value: KclValue,
2571 right_value: KclValue,
2572 ) -> Result<KclValue, KclError> {
2573 let mut meta = left_value.metadata();
2574 meta.extend(right_value.metadata());
2575
2576 if self.operator == BinaryOperator::Add
2578 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
2579 (&left_value, &right_value)
2580 {
2581 return Ok(KclValue::String {
2582 value: format!("{left}{right}"),
2583 meta,
2584 });
2585 }
2586
2587 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
2589 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2590 let args = Args::new_no_args(self.into(), ctx.clone(), Some("union".to_owned()));
2591 let result = crate::std::csg::inner_union(
2592 vec![*left.clone(), *right.clone()],
2593 Default::default(),
2594 exec_state,
2595 args,
2596 )
2597 .await?;
2598 return Ok(result.into());
2599 }
2600 } else if self.operator == BinaryOperator::Sub {
2601 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2603 let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
2604 let result = crate::std::csg::inner_subtract(
2605 vec![*left.clone()],
2606 vec![*right.clone()],
2607 Default::default(),
2608 exec_state,
2609 args,
2610 )
2611 .await?;
2612 return Ok(result.into());
2613 }
2614 } else if self.operator == BinaryOperator::And
2615 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2616 {
2617 let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
2619 let result = crate::std::csg::inner_intersect(
2620 vec![*left.clone(), *right.clone()],
2621 Default::default(),
2622 exec_state,
2623 args,
2624 )
2625 .await?;
2626 return Ok(result.into());
2627 }
2628
2629 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2631 let KclValue::Bool { value: left_value, .. } = left_value else {
2632 return Err(KclError::new_semantic(KclErrorDetails::new(
2633 format!(
2634 "Cannot apply logical operator to non-boolean value: {}",
2635 left_value.human_friendly_type()
2636 ),
2637 vec![self.left.clone().into()],
2638 )));
2639 };
2640 let KclValue::Bool { value: right_value, .. } = right_value else {
2641 return Err(KclError::new_semantic(KclErrorDetails::new(
2642 format!(
2643 "Cannot apply logical operator to non-boolean value: {}",
2644 right_value.human_friendly_type()
2645 ),
2646 vec![self.right.clone().into()],
2647 )));
2648 };
2649 let raw_value = match self.operator {
2650 BinaryOperator::Or => left_value || right_value,
2651 BinaryOperator::And => left_value && right_value,
2652 _ => unreachable!(),
2653 };
2654 return Ok(KclValue::Bool { value: raw_value, meta });
2655 }
2656
2657 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2659 match (&left_value, &right_value) {
2660 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2662 if left_value.id == right_value.id =>
2663 {
2664 return Ok(KclValue::Bool { value: true, meta });
2665 }
2666 (KclValue::SketchVar { .. }, KclValue::SketchVar { .. }) => {
2668 return Err(KclError::new_semantic(KclErrorDetails::new(
2671 "TODO: Different sketch variables".to_owned(),
2672 vec![self.into()],
2673 )));
2674 }
2675 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2677 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2678 let number_value = normalize_to_solver_distance_unit(
2679 input_number,
2680 input_number.into(),
2681 exec_state,
2682 "fixed constraint value",
2683 )?;
2684 let Some(n) = number_value.as_ty_f64() else {
2685 let message = format!(
2686 "Expected number after coercion, but found {}",
2687 number_value.human_friendly_type()
2688 );
2689 debug_assert!(false, "{}", &message);
2690 return Err(internal_err(message, self));
2691 };
2692 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2693 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2694 let message = "Being inside a sketch block should have already been checked above".to_owned();
2695 debug_assert!(false, "{}", &message);
2696 return Err(internal_err(message, self));
2697 };
2698 sketch_block_state.solver_constraints.push(constraint);
2699 return Ok(KclValue::Bool { value: true, meta });
2700 }
2701 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
2703 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
2704 let number_value = match constraint.kind {
2705 SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
2707 input_number,
2708 input_number.into(),
2709 exec_state,
2710 "fixed constraint value",
2711 )?,
2712 SketchConstraintKind::Distance { .. }
2714 | SketchConstraintKind::Radius { .. }
2715 | SketchConstraintKind::Diameter { .. }
2716 | SketchConstraintKind::HorizontalDistance { .. }
2717 | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
2718 input_number,
2719 input_number.into(),
2720 exec_state,
2721 "fixed constraint value",
2722 )?,
2723 };
2724 let Some(n) = number_value.as_ty_f64() else {
2725 let message = format!(
2726 "Expected number after coercion, but found {}",
2727 number_value.human_friendly_type()
2728 );
2729 debug_assert!(false, "{}", &message);
2730 return Err(internal_err(message, self));
2731 };
2732 #[cfg(feature = "artifact-graph")]
2734 let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
2735 &self.right
2736 } else {
2737 &self.left
2738 };
2739 #[cfg(feature = "artifact-graph")]
2740 let source = {
2741 use crate::unparser::ExprContext;
2742 let mut buf = String::new();
2743 number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
2744 crate::frontend::sketch::ConstraintSource {
2745 expr: buf,
2746 is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
2747 }
2748 };
2749
2750 match &constraint.kind {
2751 SketchConstraintKind::Angle { line0, line1 } => {
2752 let range = self.as_source_range();
2753 let ax = line0.vars[0].x.to_constraint_id(range)?;
2756 let ay = line0.vars[0].y.to_constraint_id(range)?;
2757 let bx = line0.vars[1].x.to_constraint_id(range)?;
2758 let by = line0.vars[1].y.to_constraint_id(range)?;
2759 let cx = line1.vars[0].x.to_constraint_id(range)?;
2760 let cy = line1.vars[0].y.to_constraint_id(range)?;
2761 let dx = line1.vars[1].x.to_constraint_id(range)?;
2762 let dy = line1.vars[1].y.to_constraint_id(range)?;
2763 let solver_line0 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(
2764 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(ax, ay),
2765 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(bx, by),
2766 );
2767 let solver_line1 = kcl_ezpz::datatypes::inputs::DatumLineSegment::new(
2768 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(cx, cy),
2769 kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(dx, dy),
2770 );
2771 let desired_angle = match n.ty {
2772 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Degrees))
2773 | NumericType::Default {
2774 len: _,
2775 angle: kcmc::units::UnitAngle::Degrees,
2776 } => kcl_ezpz::datatypes::Angle::from_degrees(n.n),
2777 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Radians))
2778 | NumericType::Default {
2779 len: _,
2780 angle: kcmc::units::UnitAngle::Radians,
2781 } => kcl_ezpz::datatypes::Angle::from_radians(n.n),
2782 NumericType::Known(crate::exec::UnitType::Count)
2783 | NumericType::Known(crate::exec::UnitType::GenericLength)
2784 | NumericType::Known(crate::exec::UnitType::GenericAngle)
2785 | NumericType::Known(crate::exec::UnitType::Length(_))
2786 | NumericType::Unknown
2787 | NumericType::Any => {
2788 let message = format!("Expected angle but found {:?}", n);
2789 debug_assert!(false, "{}", &message);
2790 return Err(internal_err(message, self));
2791 }
2792 };
2793 let solver_constraint = Constraint::LinesAtAngle(
2794 solver_line0,
2795 solver_line1,
2796 kcl_ezpz::datatypes::AngleKind::Other(desired_angle),
2797 );
2798 #[cfg(feature = "artifact-graph")]
2799 let constraint_id = exec_state.next_object_id();
2800 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2801 let message =
2802 "Being inside a sketch block should have already been checked above".to_owned();
2803 debug_assert!(false, "{}", &message);
2804 return Err(internal_err(message, self));
2805 };
2806 sketch_block_state.solver_constraints.push(solver_constraint);
2807 #[cfg(feature = "artifact-graph")]
2808 {
2809 use crate::{
2810 execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
2811 front::Angle,
2812 };
2813
2814 let Some(sketch_id) = sketch_block_state.sketch_id else {
2815 let message = "Sketch id missing for constraint artifact".to_owned();
2816 debug_assert!(false, "{}", &message);
2817 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
2818 };
2819 let sketch_constraint = crate::front::Constraint::Angle(Angle {
2820 lines: vec![line0.object_id, line1.object_id],
2821 angle: n.try_into().map_err(|_| {
2822 internal_err("Failed to convert angle units numeric suffix:", range)
2823 })?,
2824 source,
2825 });
2826 sketch_block_state.sketch_constraints.push(constraint_id);
2827 let artifact_id = exec_state.next_artifact_id();
2828 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2829 id: artifact_id,
2830 sketch_id,
2831 constraint_id,
2832 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
2833 code_ref: CodeRef::placeholder(range),
2834 }));
2835 exec_state.add_scene_object(
2836 Object {
2837 id: constraint_id,
2838 kind: ObjectKind::Constraint {
2839 constraint: sketch_constraint,
2840 },
2841 label: Default::default(),
2842 comments: Default::default(),
2843 artifact_id,
2844 source: range.into(),
2845 },
2846 range,
2847 );
2848 }
2849 }
2850 SketchConstraintKind::Distance { points } => {
2851 let range = self.as_source_range();
2852 let p0 = &points[0];
2853 let p1 = &points[1];
2854 let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2855 p0.vars.x.to_constraint_id(range)?,
2856 p0.vars.y.to_constraint_id(range)?,
2857 );
2858 let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2859 p1.vars.x.to_constraint_id(range)?,
2860 p1.vars.y.to_constraint_id(range)?,
2861 );
2862 let solver_constraint = Constraint::Distance(solver_pt0, solver_pt1, n.n);
2863
2864 #[cfg(feature = "artifact-graph")]
2865 let constraint_id = exec_state.next_object_id();
2866 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2867 let message =
2868 "Being inside a sketch block should have already been checked above".to_owned();
2869 debug_assert!(false, "{}", &message);
2870 return Err(internal_err(message, self));
2871 };
2872 sketch_block_state.solver_constraints.push(solver_constraint);
2873 #[cfg(feature = "artifact-graph")]
2874 {
2875 use crate::{
2876 execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
2877 front::Distance,
2878 };
2879
2880 let Some(sketch_id) = sketch_block_state.sketch_id else {
2881 let message = "Sketch id missing for constraint artifact".to_owned();
2882 debug_assert!(false, "{}", &message);
2883 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
2884 };
2885 let sketch_constraint = crate::front::Constraint::Distance(Distance {
2886 points: vec![p0.object_id, p1.object_id],
2887 distance: n.try_into().map_err(|_| {
2888 internal_err("Failed to convert distance units numeric suffix:", range)
2889 })?,
2890 source,
2891 });
2892 sketch_block_state.sketch_constraints.push(constraint_id);
2893 let artifact_id = exec_state.next_artifact_id();
2894 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
2895 id: artifact_id,
2896 sketch_id,
2897 constraint_id,
2898 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
2899 code_ref: CodeRef::placeholder(range),
2900 }));
2901 exec_state.add_scene_object(
2902 Object {
2903 id: constraint_id,
2904 kind: ObjectKind::Constraint {
2905 constraint: sketch_constraint,
2906 },
2907 label: Default::default(),
2908 comments: Default::default(),
2909 artifact_id,
2910 source: range.into(),
2911 },
2912 range,
2913 );
2914 }
2915 }
2916 SketchConstraintKind::Radius { points } | SketchConstraintKind::Diameter { points } => {
2917 let range = self.as_source_range();
2918 let center = &points[0];
2919 let start = &points[1];
2920 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
2922 return Err(internal_err(
2923 "Being inside a sketch block should have already been checked above",
2924 self,
2925 ));
2926 };
2927 let (constraint_name, is_diameter) = match &constraint.kind {
2929 SketchConstraintKind::Radius { .. } => ("radius", false),
2930 SketchConstraintKind::Diameter { .. } => ("diameter", true),
2931 _ => unreachable!(),
2932 };
2933 let arc_segment = sketch_block_state
2934 .needed_by_engine
2935 .iter()
2936 .find(|seg| {
2937 matches!(&seg.kind, UnsolvedSegmentKind::Arc {
2938 center_object_id,
2939 start_object_id,
2940 ..
2941 } if *center_object_id == center.object_id && *start_object_id == start.object_id)
2942 })
2943 .ok_or_else(|| {
2944 internal_err(
2945 format!("Could not find arc segment for {} constraint", constraint_name),
2946 range,
2947 )
2948 })?;
2949 let UnsolvedSegmentKind::Arc { end, .. } = &arc_segment.kind else {
2950 return Err(internal_err("Expected arc segment", range));
2951 };
2952 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
2954 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => (*end_x, *end_y),
2955 _ => {
2956 return Err(internal_err(
2957 "Arc end point must have sketch vars in all coordinates",
2958 range,
2959 ));
2960 }
2961 };
2962 let solver_arc = kcl_ezpz::datatypes::inputs::DatumCircularArc {
2963 center: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2964 center.vars.x.to_constraint_id(range)?,
2965 center.vars.y.to_constraint_id(range)?,
2966 ),
2967 start: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2968 start.vars.x.to_constraint_id(range)?,
2969 start.vars.y.to_constraint_id(range)?,
2970 ),
2971 end: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2972 end_x_var.to_constraint_id(range)?,
2973 end_y_var.to_constraint_id(range)?,
2974 ),
2975 };
2976 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
2979 let solver_constraint = Constraint::ArcRadius(solver_arc, radius_value);
2980
2981 #[cfg(feature = "artifact-graph")]
2982 let constraint_id = exec_state.next_object_id();
2983 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2984 let message =
2985 "Being inside a sketch block should have already been checked above".to_owned();
2986 debug_assert!(false, "{}", &message);
2987 return Err(internal_err(message, self));
2988 };
2989 sketch_block_state.solver_constraints.push(solver_constraint);
2990 #[cfg(feature = "artifact-graph")]
2991 {
2992 use crate::execution::{
2993 Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType,
2994 };
2995 let arc_object_id = sketch_block_state
2998 .needed_by_engine
2999 .iter()
3000 .find(|seg| {
3001 matches!(&seg.kind, UnsolvedSegmentKind::Arc {
3002 center_object_id,
3003 start_object_id,
3004 ..
3005 } if *center_object_id == center.object_id && *start_object_id == start.object_id)
3006 })
3007 .map(|seg| seg.object_id)
3008 .ok_or_else(|| {
3009 internal_err(
3010 format!(
3011 "Could not find arc segment object ID for {} constraint",
3012 constraint_name
3013 ),
3014 range,
3015 )
3016 })?;
3017
3018 let constraint = if is_diameter {
3019 use crate::frontend::sketch::Diameter;
3020 crate::front::Constraint::Diameter(Diameter {
3021 arc: arc_object_id,
3022 diameter: n.try_into().map_err(|_| {
3023 internal_err("Failed to convert diameter units numeric suffix:", range)
3024 })?,
3025 source,
3026 })
3027 } else {
3028 use crate::frontend::sketch::Radius;
3029 crate::front::Constraint::Radius(Radius {
3030 arc: arc_object_id,
3031 radius: n.try_into().map_err(|_| {
3032 internal_err("Failed to convert radius units numeric suffix:", range)
3033 })?,
3034 source,
3035 })
3036 };
3037 sketch_block_state.sketch_constraints.push(constraint_id);
3038 let Some(sketch_id) = sketch_block_state.sketch_id else {
3039 let message = "Sketch id missing for constraint artifact".to_owned();
3040 debug_assert!(false, "{}", &message);
3041 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3042 };
3043 let artifact_id = exec_state.next_artifact_id();
3044 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3045 id: artifact_id,
3046 sketch_id,
3047 constraint_id,
3048 constraint_type: SketchBlockConstraintType::from(&constraint),
3049 code_ref: CodeRef::placeholder(range),
3050 }));
3051 exec_state.add_scene_object(
3052 Object {
3053 id: constraint_id,
3054 kind: ObjectKind::Constraint { constraint },
3055 label: Default::default(),
3056 comments: Default::default(),
3057 artifact_id,
3058 source: range.into(),
3059 },
3060 range,
3061 );
3062 }
3063 }
3064 SketchConstraintKind::HorizontalDistance { points } => {
3065 let range = self.as_source_range();
3066 let p0 = &points[0];
3067 let p1 = &points[1];
3068 let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
3069 p0.vars.x.to_constraint_id(range)?,
3070 p0.vars.y.to_constraint_id(range)?,
3071 );
3072 let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
3073 p1.vars.x.to_constraint_id(range)?,
3074 p1.vars.y.to_constraint_id(range)?,
3075 );
3076 let solver_constraint =
3080 kcl_ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n);
3081
3082 #[cfg(feature = "artifact-graph")]
3083 let constraint_id = exec_state.next_object_id();
3084 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3085 let message =
3086 "Being inside a sketch block should have already been checked above".to_owned();
3087 debug_assert!(false, "{}", &message);
3088 return Err(internal_err(message, self));
3089 };
3090 sketch_block_state.solver_constraints.push(solver_constraint);
3091 #[cfg(feature = "artifact-graph")]
3092 {
3093 use crate::{
3094 execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
3095 front::Distance,
3096 };
3097
3098 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
3099 points: vec![p0.object_id, p1.object_id],
3100 distance: n.try_into().map_err(|_| {
3101 internal_err("Failed to convert distance units numeric suffix:", range)
3102 })?,
3103 source,
3104 });
3105 sketch_block_state.sketch_constraints.push(constraint_id);
3106 let Some(sketch_id) = sketch_block_state.sketch_id else {
3107 let message = "Sketch id missing for constraint artifact".to_owned();
3108 debug_assert!(false, "{}", &message);
3109 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3110 };
3111 let artifact_id = exec_state.next_artifact_id();
3112 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3113 id: artifact_id,
3114 sketch_id,
3115 constraint_id,
3116 constraint_type: SketchBlockConstraintType::from(&constraint),
3117 code_ref: CodeRef::placeholder(range),
3118 }));
3119 exec_state.add_scene_object(
3120 Object {
3121 id: constraint_id,
3122 kind: ObjectKind::Constraint { constraint },
3123 label: Default::default(),
3124 comments: Default::default(),
3125 artifact_id,
3126 source: range.into(),
3127 },
3128 range,
3129 );
3130 }
3131 }
3132 SketchConstraintKind::VerticalDistance { points } => {
3133 let range = self.as_source_range();
3134 let p0 = &points[0];
3135 let p1 = &points[1];
3136 let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
3137 p0.vars.x.to_constraint_id(range)?,
3138 p0.vars.y.to_constraint_id(range)?,
3139 );
3140 let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
3141 p1.vars.x.to_constraint_id(range)?,
3142 p1.vars.y.to_constraint_id(range)?,
3143 );
3144 let solver_constraint = kcl_ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n);
3148
3149 #[cfg(feature = "artifact-graph")]
3150 let constraint_id = exec_state.next_object_id();
3151 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3152 let message =
3153 "Being inside a sketch block should have already been checked above".to_owned();
3154 debug_assert!(false, "{}", &message);
3155 return Err(internal_err(message, self));
3156 };
3157 sketch_block_state.solver_constraints.push(solver_constraint);
3158 #[cfg(feature = "artifact-graph")]
3159 {
3160 use crate::{
3161 execution::{Artifact, CodeRef, SketchBlockConstraint, SketchBlockConstraintType},
3162 front::Distance,
3163 };
3164
3165 let constraint = crate::front::Constraint::VerticalDistance(Distance {
3166 points: vec![p0.object_id, p1.object_id],
3167 distance: n.try_into().map_err(|_| {
3168 internal_err("Failed to convert distance units numeric suffix:", range)
3169 })?,
3170 source,
3171 });
3172 sketch_block_state.sketch_constraints.push(constraint_id);
3173 let Some(sketch_id) = sketch_block_state.sketch_id else {
3174 let message = "Sketch id missing for constraint artifact".to_owned();
3175 debug_assert!(false, "{}", &message);
3176 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3177 };
3178 let artifact_id = exec_state.next_artifact_id();
3179 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3180 id: artifact_id,
3181 sketch_id,
3182 constraint_id,
3183 constraint_type: SketchBlockConstraintType::from(&constraint),
3184 code_ref: CodeRef::placeholder(range),
3185 }));
3186 exec_state.add_scene_object(
3187 Object {
3188 id: constraint_id,
3189 kind: ObjectKind::Constraint { constraint },
3190 label: Default::default(),
3191 comments: Default::default(),
3192 artifact_id,
3193 source: range.into(),
3194 },
3195 range,
3196 );
3197 }
3198 }
3199 }
3200 return Ok(KclValue::Bool { value: true, meta });
3201 }
3202 _ => {
3203 return Err(KclError::new_semantic(KclErrorDetails::new(
3204 format!(
3205 "Cannot create an equivalence constraint between values of these types: {} and {}",
3206 left_value.human_friendly_type(),
3207 right_value.human_friendly_type()
3208 ),
3209 vec![self.into()],
3210 )));
3211 }
3212 }
3213 }
3214
3215 let left = number_as_f64(&left_value, self.left.clone().into())?;
3216 let right = number_as_f64(&right_value, self.right.clone().into())?;
3217
3218 let value = match self.operator {
3219 BinaryOperator::Add => {
3220 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3221 self.warn_on_unknown(&ty, "Adding", exec_state);
3222 KclValue::Number { value: l + r, meta, ty }
3223 }
3224 BinaryOperator::Sub => {
3225 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3226 self.warn_on_unknown(&ty, "Subtracting", exec_state);
3227 KclValue::Number { value: l - r, meta, ty }
3228 }
3229 BinaryOperator::Mul => {
3230 let (l, r, ty) = NumericType::combine_mul(left, right);
3231 self.warn_on_unknown(&ty, "Multiplying", exec_state);
3232 KclValue::Number { value: l * r, meta, ty }
3233 }
3234 BinaryOperator::Div => {
3235 let (l, r, ty) = NumericType::combine_div(left, right);
3236 self.warn_on_unknown(&ty, "Dividing", exec_state);
3237 KclValue::Number { value: l / r, meta, ty }
3238 }
3239 BinaryOperator::Mod => {
3240 let (l, r, ty) = NumericType::combine_mod(left, right);
3241 self.warn_on_unknown(&ty, "Modulo of", exec_state);
3242 KclValue::Number { value: l % r, meta, ty }
3243 }
3244 BinaryOperator::Pow => KclValue::Number {
3245 value: left.n.powf(right.n),
3246 meta,
3247 ty: exec_state.current_default_units(),
3248 },
3249 BinaryOperator::Neq => {
3250 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3251 self.warn_on_unknown(&ty, "Comparing", exec_state);
3252 KclValue::Bool { value: l != r, meta }
3253 }
3254 BinaryOperator::Gt => {
3255 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3256 self.warn_on_unknown(&ty, "Comparing", exec_state);
3257 KclValue::Bool { value: l > r, meta }
3258 }
3259 BinaryOperator::Gte => {
3260 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3261 self.warn_on_unknown(&ty, "Comparing", exec_state);
3262 KclValue::Bool { value: l >= r, meta }
3263 }
3264 BinaryOperator::Lt => {
3265 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3266 self.warn_on_unknown(&ty, "Comparing", exec_state);
3267 KclValue::Bool { value: l < r, meta }
3268 }
3269 BinaryOperator::Lte => {
3270 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3271 self.warn_on_unknown(&ty, "Comparing", exec_state);
3272 KclValue::Bool { value: l <= r, meta }
3273 }
3274 BinaryOperator::Eq => {
3275 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3276 self.warn_on_unknown(&ty, "Comparing", exec_state);
3277 KclValue::Bool { value: l == r, meta }
3278 }
3279 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
3280 };
3281
3282 Ok(value)
3283 }
3284
3285 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
3286 internal_err("missing result while evaluating binary expression", node)
3287 }
3288
3289 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
3290 if ty == &NumericType::Unknown {
3291 let sr = self.as_source_range();
3292 exec_state.clear_units_warnings(&sr);
3293 let mut err = CompilationError::err(
3294 sr,
3295 format!(
3296 "{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)`."
3297 ),
3298 );
3299 err.tag = crate::errors::Tag::UnknownNumericUnits;
3300 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
3301 }
3302 }
3303}
3304
3305impl Node<UnaryExpression> {
3306 pub(super) async fn get_result(
3307 &self,
3308 exec_state: &mut ExecState,
3309 ctx: &ExecutorContext,
3310 ) -> Result<KclValueControlFlow, KclError> {
3311 match self.operator {
3312 UnaryOperator::Not => {
3313 let value = self.argument.get_result(exec_state, ctx).await?;
3314 let value = control_continue!(value);
3315 let KclValue::Bool {
3316 value: bool_value,
3317 meta: _,
3318 } = value
3319 else {
3320 return Err(KclError::new_semantic(KclErrorDetails::new(
3321 format!(
3322 "Cannot apply unary operator ! to non-boolean value: {}",
3323 value.human_friendly_type()
3324 ),
3325 vec![self.into()],
3326 )));
3327 };
3328 let meta = vec![Metadata {
3329 source_range: self.into(),
3330 }];
3331 let negated = KclValue::Bool {
3332 value: !bool_value,
3333 meta,
3334 };
3335
3336 Ok(negated.continue_())
3337 }
3338 UnaryOperator::Neg => {
3339 let value = self.argument.get_result(exec_state, ctx).await?;
3340 let value = control_continue!(value);
3341 let err = || {
3342 KclError::new_semantic(KclErrorDetails::new(
3343 format!(
3344 "You can only negate numbers, planes, or lines, but this is a {}",
3345 value.human_friendly_type()
3346 ),
3347 vec![self.into()],
3348 ))
3349 };
3350 match &value {
3351 KclValue::Number { value, ty, .. } => {
3352 let meta = vec![Metadata {
3353 source_range: self.into(),
3354 }];
3355 Ok(KclValue::Number {
3356 value: -value,
3357 meta,
3358 ty: *ty,
3359 }
3360 .continue_())
3361 }
3362 KclValue::Plane { value } => {
3363 let mut plane = value.clone();
3364 if plane.info.x_axis.x != 0.0 {
3365 plane.info.x_axis.x *= -1.0;
3366 }
3367 if plane.info.x_axis.y != 0.0 {
3368 plane.info.x_axis.y *= -1.0;
3369 }
3370 if plane.info.x_axis.z != 0.0 {
3371 plane.info.x_axis.z *= -1.0;
3372 }
3373
3374 plane.id = exec_state.next_uuid();
3375 plane.object_id = None;
3376 Ok(KclValue::Plane { value: plane }.continue_())
3377 }
3378 KclValue::Object {
3379 value: values, meta, ..
3380 } => {
3381 let Some(direction) = values.get("direction") else {
3383 return Err(err());
3384 };
3385
3386 let direction = match direction {
3387 KclValue::Tuple { value: values, meta } => {
3388 let values = values
3389 .iter()
3390 .map(|v| match v {
3391 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3392 value: *value * -1.0,
3393 ty: *ty,
3394 meta: meta.clone(),
3395 }),
3396 _ => Err(err()),
3397 })
3398 .collect::<Result<Vec<_>, _>>()?;
3399
3400 KclValue::Tuple {
3401 value: values,
3402 meta: meta.clone(),
3403 }
3404 }
3405 KclValue::HomArray {
3406 value: values,
3407 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
3408 } => {
3409 let values = values
3410 .iter()
3411 .map(|v| match v {
3412 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3413 value: *value * -1.0,
3414 ty: *ty,
3415 meta: meta.clone(),
3416 }),
3417 _ => Err(err()),
3418 })
3419 .collect::<Result<Vec<_>, _>>()?;
3420
3421 KclValue::HomArray {
3422 value: values,
3423 ty: ty.clone(),
3424 }
3425 }
3426 _ => return Err(err()),
3427 };
3428
3429 let mut value = values.clone();
3430 value.insert("direction".to_owned(), direction);
3431 Ok(KclValue::Object {
3432 value,
3433 meta: meta.clone(),
3434 constrainable: false,
3435 }
3436 .continue_())
3437 }
3438 _ => Err(err()),
3439 }
3440 }
3441 UnaryOperator::Plus => {
3442 let operand = self.argument.get_result(exec_state, ctx).await?;
3443 let operand = control_continue!(operand);
3444 match operand {
3445 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
3446 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3447 format!(
3448 "You can only apply unary + to numbers or planes, but this is a {}",
3449 operand.human_friendly_type()
3450 ),
3451 vec![self.into()],
3452 ))),
3453 }
3454 }
3455 }
3456 }
3457}
3458
3459pub(crate) async fn execute_pipe_body(
3460 exec_state: &mut ExecState,
3461 body: &[Expr],
3462 source_range: SourceRange,
3463 ctx: &ExecutorContext,
3464) -> Result<KclValueControlFlow, KclError> {
3465 let Some((first, body)) = body.split_first() else {
3466 return Err(KclError::new_semantic(KclErrorDetails::new(
3467 "Pipe expressions cannot be empty".to_owned(),
3468 vec![source_range],
3469 )));
3470 };
3471 let meta = Metadata {
3476 source_range: SourceRange::from(first),
3477 };
3478 let output = ctx
3479 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
3480 .await?;
3481 let output = control_continue!(output);
3482
3483 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
3487 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
3489 exec_state.mod_local.pipe_value = previous_pipe_value;
3491
3492 result
3493}
3494
3495#[async_recursion]
3498async fn inner_execute_pipe_body(
3499 exec_state: &mut ExecState,
3500 body: &[Expr],
3501 ctx: &ExecutorContext,
3502) -> Result<KclValueControlFlow, KclError> {
3503 for expression in body {
3504 if let Expr::TagDeclarator(_) = expression {
3505 return Err(KclError::new_semantic(KclErrorDetails::new(
3506 format!("This cannot be in a PipeExpression: {expression:?}"),
3507 vec![expression.into()],
3508 )));
3509 }
3510 let metadata = Metadata {
3511 source_range: SourceRange::from(expression),
3512 };
3513 let output = ctx
3514 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
3515 .await?;
3516 let output = control_continue!(output);
3517 exec_state.mod_local.pipe_value = Some(output);
3518 }
3519 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
3521 Ok(final_output.continue_())
3522}
3523
3524impl Node<TagDeclarator> {
3525 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
3526 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
3527 value: self.name.clone(),
3528 info: Vec::new(),
3529 meta: vec![Metadata {
3530 source_range: self.into(),
3531 }],
3532 }));
3533
3534 exec_state
3535 .mut_stack()
3536 .add(self.name.clone(), memory_item, self.into())?;
3537
3538 Ok(self.into())
3539 }
3540}
3541
3542impl Node<ArrayExpression> {
3543 #[async_recursion]
3544 pub(super) async fn execute(
3545 &self,
3546 exec_state: &mut ExecState,
3547 ctx: &ExecutorContext,
3548 ) -> Result<KclValueControlFlow, KclError> {
3549 let mut results = Vec::with_capacity(self.elements.len());
3550
3551 for element in &self.elements {
3552 let metadata = Metadata::from(element);
3553 let value = ctx
3556 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
3557 .await?;
3558 let value = control_continue!(value);
3559
3560 results.push(value);
3561 }
3562
3563 Ok(KclValue::HomArray {
3564 value: results,
3565 ty: RuntimeType::Primitive(PrimitiveType::Any),
3566 }
3567 .continue_())
3568 }
3569}
3570
3571impl Node<ArrayRangeExpression> {
3572 #[async_recursion]
3573 pub(super) async fn execute(
3574 &self,
3575 exec_state: &mut ExecState,
3576 ctx: &ExecutorContext,
3577 ) -> Result<KclValueControlFlow, KclError> {
3578 let metadata = Metadata::from(&self.start_element);
3579 let start_val = ctx
3580 .execute_expr(
3581 &self.start_element,
3582 exec_state,
3583 &metadata,
3584 &[],
3585 StatementKind::Expression,
3586 )
3587 .await?;
3588 let start_val = control_continue!(start_val);
3589 let start = start_val
3590 .as_ty_f64()
3591 .ok_or(KclError::new_semantic(KclErrorDetails::new(
3592 format!(
3593 "Expected number for range start but found {}",
3594 start_val.human_friendly_type()
3595 ),
3596 vec![self.into()],
3597 )))?;
3598 let metadata = Metadata::from(&self.end_element);
3599 let end_val = ctx
3600 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
3601 .await?;
3602 let end_val = control_continue!(end_val);
3603 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
3604 format!(
3605 "Expected number for range end but found {}",
3606 end_val.human_friendly_type()
3607 ),
3608 vec![self.into()],
3609 )))?;
3610
3611 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
3612 let Some(start) = crate::try_f64_to_i64(start) else {
3613 return Err(KclError::new_semantic(KclErrorDetails::new(
3614 format!("Range start must be an integer, but found {start}"),
3615 vec![self.into()],
3616 )));
3617 };
3618 let Some(end) = crate::try_f64_to_i64(end) else {
3619 return Err(KclError::new_semantic(KclErrorDetails::new(
3620 format!("Range end must be an integer, but found {end}"),
3621 vec![self.into()],
3622 )));
3623 };
3624
3625 if end < start {
3626 return Err(KclError::new_semantic(KclErrorDetails::new(
3627 format!("Range start is greater than range end: {start} .. {end}"),
3628 vec![self.into()],
3629 )));
3630 }
3631
3632 let range: Vec<_> = if self.end_inclusive {
3633 (start..=end).collect()
3634 } else {
3635 (start..end).collect()
3636 };
3637
3638 let meta = vec![Metadata {
3639 source_range: self.into(),
3640 }];
3641
3642 Ok(KclValue::HomArray {
3643 value: range
3644 .into_iter()
3645 .map(|num| KclValue::Number {
3646 value: num as f64,
3647 ty,
3648 meta: meta.clone(),
3649 })
3650 .collect(),
3651 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
3652 }
3653 .continue_())
3654 }
3655}
3656
3657impl Node<ObjectExpression> {
3658 #[async_recursion]
3659 pub(super) async fn execute(
3660 &self,
3661 exec_state: &mut ExecState,
3662 ctx: &ExecutorContext,
3663 ) -> Result<KclValueControlFlow, KclError> {
3664 let mut object = HashMap::with_capacity(self.properties.len());
3665 for property in &self.properties {
3666 let metadata = Metadata::from(&property.value);
3667 let result = ctx
3668 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
3669 .await?;
3670 let result = control_continue!(result);
3671 object.insert(property.key.name.clone(), result);
3672 }
3673
3674 Ok(KclValue::Object {
3675 value: object,
3676 meta: vec![Metadata {
3677 source_range: self.into(),
3678 }],
3679 constrainable: false,
3680 }
3681 .continue_())
3682 }
3683}
3684
3685fn article_for<S: AsRef<str>>(s: S) -> &'static str {
3686 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
3688 "an"
3689 } else {
3690 "a"
3691 }
3692}
3693
3694fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
3695 v.as_ty_f64().ok_or_else(|| {
3696 let actual_type = v.human_friendly_type();
3697 KclError::new_semantic(KclErrorDetails::new(
3698 format!("Expected a number, but found {actual_type}",),
3699 vec![source_range],
3700 ))
3701 })
3702}
3703
3704impl Node<IfExpression> {
3705 #[async_recursion]
3706 pub(super) async fn get_result(
3707 &self,
3708 exec_state: &mut ExecState,
3709 ctx: &ExecutorContext,
3710 ) -> Result<KclValueControlFlow, KclError> {
3711 let cond_value = ctx
3713 .execute_expr(
3714 &self.cond,
3715 exec_state,
3716 &Metadata::from(self),
3717 &[],
3718 StatementKind::Expression,
3719 )
3720 .await?;
3721 let cond_value = control_continue!(cond_value);
3722 if cond_value.get_bool()? {
3723 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
3724 return Ok(block_result.unwrap());
3728 }
3729
3730 for else_if in &self.else_ifs {
3732 let cond_value = ctx
3733 .execute_expr(
3734 &else_if.cond,
3735 exec_state,
3736 &Metadata::from(self),
3737 &[],
3738 StatementKind::Expression,
3739 )
3740 .await?;
3741 let cond_value = control_continue!(cond_value);
3742 if cond_value.get_bool()? {
3743 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
3744 return Ok(block_result.unwrap());
3748 }
3749 }
3750
3751 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
3753 .await
3754 .map(|expr| expr.unwrap())
3755 }
3756}
3757
3758#[derive(Debug)]
3759enum Property {
3760 UInt(usize),
3761 String(String),
3762}
3763
3764impl Property {
3765 #[allow(clippy::too_many_arguments)]
3766 async fn try_from<'a>(
3767 computed: bool,
3768 value: Expr,
3769 exec_state: &mut ExecState,
3770 sr: SourceRange,
3771 ctx: &ExecutorContext,
3772 metadata: &Metadata,
3773 annotations: &[Node<Annotation>],
3774 statement_kind: StatementKind<'a>,
3775 ) -> Result<Self, KclError> {
3776 let property_sr = vec![sr];
3777 if !computed {
3778 let Expr::Name(identifier) = value else {
3779 return Err(KclError::new_semantic(KclErrorDetails::new(
3781 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
3782 .to_owned(),
3783 property_sr,
3784 )));
3785 };
3786 return Ok(Property::String(identifier.to_string()));
3787 }
3788
3789 let prop_value = ctx
3790 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
3791 .await?;
3792 let prop_value = match prop_value.control {
3793 ControlFlowKind::Continue => prop_value.into_value(),
3794 ControlFlowKind::Exit => {
3795 let message = "Early return inside array brackets is currently not supported".to_owned();
3796 debug_assert!(false, "{}", &message);
3797 return Err(internal_err(message, sr));
3798 }
3799 };
3800 match prop_value {
3801 KclValue::Number { value, ty, meta: _ } => {
3802 if !matches!(
3803 ty,
3804 NumericType::Unknown
3805 | NumericType::Default { .. }
3806 | NumericType::Known(crate::exec::UnitType::Count)
3807 ) {
3808 return Err(KclError::new_semantic(KclErrorDetails::new(
3809 format!(
3810 "{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"
3811 ),
3812 property_sr,
3813 )));
3814 }
3815 if let Some(x) = crate::try_f64_to_usize(value) {
3816 Ok(Property::UInt(x))
3817 } else {
3818 Err(KclError::new_semantic(KclErrorDetails::new(
3819 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
3820 property_sr,
3821 )))
3822 }
3823 }
3824 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3825 "Only numbers (>= 0) can be indexes".to_owned(),
3826 vec![sr],
3827 ))),
3828 }
3829 }
3830}
3831
3832impl Property {
3833 fn type_name(&self) -> &'static str {
3834 match self {
3835 Property::UInt(_) => "number",
3836 Property::String(_) => "string",
3837 }
3838 }
3839}
3840
3841impl Node<PipeExpression> {
3842 #[async_recursion]
3843 pub(super) async fn get_result(
3844 &self,
3845 exec_state: &mut ExecState,
3846 ctx: &ExecutorContext,
3847 ) -> Result<KclValueControlFlow, KclError> {
3848 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
3849 }
3850}
3851
3852#[cfg(test)]
3853mod test {
3854 use std::sync::Arc;
3855
3856 use tokio::io::AsyncWriteExt;
3857
3858 use super::*;
3859 use crate::{
3860 ExecutorSettings,
3861 errors::Severity,
3862 exec::UnitType,
3863 execution::{ContextType, parse_execute},
3864 };
3865
3866 #[tokio::test(flavor = "multi_thread")]
3867 async fn ascription() {
3868 let program = r#"
3869a = 42: number
3870b = a: number
3871p = {
3872 origin = { x = 0, y = 0, z = 0 },
3873 xAxis = { x = 1, y = 0, z = 0 },
3874 yAxis = { x = 0, y = 1, z = 0 },
3875 zAxis = { x = 0, y = 0, z = 1 }
3876}: Plane
3877arr1 = [42]: [number(cm)]
3878"#;
3879
3880 let result = parse_execute(program).await.unwrap();
3881 let mem = result.exec_state.stack();
3882 assert!(matches!(
3883 mem.memory
3884 .get_from("p", result.mem_env, SourceRange::default(), 0)
3885 .unwrap(),
3886 KclValue::Plane { .. }
3887 ));
3888 let arr1 = mem
3889 .memory
3890 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
3891 .unwrap();
3892 if let KclValue::HomArray { value, ty } = arr1 {
3893 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
3894 assert_eq!(
3895 *ty,
3896 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
3897 );
3898 if let KclValue::Number { value, ty, .. } = &value[0] {
3900 assert_eq!(*value, 42.0);
3902 assert_eq!(
3903 *ty,
3904 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
3905 );
3906 } else {
3907 panic!("Expected a number; found {:?}", value[0]);
3908 }
3909 } else {
3910 panic!("Expected HomArray; found {arr1:?}");
3911 }
3912
3913 let program = r#"
3914a = 42: string
3915"#;
3916 let result = parse_execute(program).await;
3917 let err = result.unwrap_err();
3918 assert!(
3919 err.to_string()
3920 .contains("could not coerce a number (with type `number`) to type `string`"),
3921 "Expected error but found {err:?}"
3922 );
3923
3924 let program = r#"
3925a = 42: Plane
3926"#;
3927 let result = parse_execute(program).await;
3928 let err = result.unwrap_err();
3929 assert!(
3930 err.to_string()
3931 .contains("could not coerce a number (with type `number`) to type `Plane`"),
3932 "Expected error but found {err:?}"
3933 );
3934
3935 let program = r#"
3936arr = [0]: [string]
3937"#;
3938 let result = parse_execute(program).await;
3939 let err = result.unwrap_err();
3940 assert!(
3941 err.to_string().contains(
3942 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
3943 ),
3944 "Expected error but found {err:?}"
3945 );
3946
3947 let program = r#"
3948mixedArr = [0, "a"]: [number(mm)]
3949"#;
3950 let result = parse_execute(program).await;
3951 let err = result.unwrap_err();
3952 assert!(
3953 err.to_string().contains(
3954 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3955 ),
3956 "Expected error but found {err:?}"
3957 );
3958
3959 let program = r#"
3960mixedArr = [0, "a"]: [mm]
3961"#;
3962 let result = parse_execute(program).await;
3963 let err = result.unwrap_err();
3964 assert!(
3965 err.to_string().contains(
3966 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3967 ),
3968 "Expected error but found {err:?}"
3969 );
3970 }
3971
3972 #[tokio::test(flavor = "multi_thread")]
3973 async fn neg_plane() {
3974 let program = r#"
3975p = {
3976 origin = { x = 0, y = 0, z = 0 },
3977 xAxis = { x = 1, y = 0, z = 0 },
3978 yAxis = { x = 0, y = 1, z = 0 },
3979}: Plane
3980p2 = -p
3981"#;
3982
3983 let result = parse_execute(program).await.unwrap();
3984 let mem = result.exec_state.stack();
3985 match mem
3986 .memory
3987 .get_from("p2", result.mem_env, SourceRange::default(), 0)
3988 .unwrap()
3989 {
3990 KclValue::Plane { value } => {
3991 assert_eq!(value.info.x_axis.x, -1.0);
3992 assert_eq!(value.info.x_axis.y, 0.0);
3993 assert_eq!(value.info.x_axis.z, 0.0);
3994 }
3995 _ => unreachable!(),
3996 }
3997 }
3998
3999 #[tokio::test(flavor = "multi_thread")]
4000 async fn multiple_returns() {
4001 let program = r#"fn foo() {
4002 return 0
4003 return 42
4004}
4005
4006a = foo()
4007"#;
4008
4009 let result = parse_execute(program).await;
4010 assert!(result.unwrap_err().to_string().contains("return"));
4011 }
4012
4013 #[tokio::test(flavor = "multi_thread")]
4014 async fn load_all_modules() {
4015 let program_a_kcl = r#"
4017export a = 1
4018"#;
4019 let program_b_kcl = r#"
4021import a from 'a.kcl'
4022
4023export b = a + 1
4024"#;
4025 let program_c_kcl = r#"
4027import a from 'a.kcl'
4028
4029export c = a + 2
4030"#;
4031
4032 let main_kcl = r#"
4034import b from 'b.kcl'
4035import c from 'c.kcl'
4036
4037d = b + c
4038"#;
4039
4040 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
4041 .parse_errs_as_err()
4042 .unwrap();
4043
4044 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
4045
4046 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
4047 .await
4048 .unwrap()
4049 .write_all(main_kcl.as_bytes())
4050 .await
4051 .unwrap();
4052
4053 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
4054 .await
4055 .unwrap()
4056 .write_all(program_a_kcl.as_bytes())
4057 .await
4058 .unwrap();
4059
4060 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
4061 .await
4062 .unwrap()
4063 .write_all(program_b_kcl.as_bytes())
4064 .await
4065 .unwrap();
4066
4067 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
4068 .await
4069 .unwrap()
4070 .write_all(program_c_kcl.as_bytes())
4071 .await
4072 .unwrap();
4073
4074 let exec_ctxt = ExecutorContext {
4075 engine: Arc::new(Box::new(
4076 crate::engine::conn_mock::EngineConnection::new()
4077 .map_err(|err| {
4078 internal_err(
4079 format!("Failed to create mock engine connection: {err}"),
4080 SourceRange::default(),
4081 )
4082 })
4083 .unwrap(),
4084 )),
4085 fs: Arc::new(crate::fs::FileManager::new()),
4086 settings: ExecutorSettings {
4087 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
4088 ..Default::default()
4089 },
4090 context_type: ContextType::Mock,
4091 };
4092 let mut exec_state = ExecState::new(&exec_ctxt);
4093
4094 exec_ctxt
4095 .run(
4096 &crate::Program {
4097 ast: main.clone(),
4098 original_file_contents: "".to_owned(),
4099 },
4100 &mut exec_state,
4101 )
4102 .await
4103 .unwrap();
4104 }
4105
4106 #[tokio::test(flavor = "multi_thread")]
4107 async fn user_coercion() {
4108 let program = r#"fn foo(x: Axis2d) {
4109 return 0
4110}
4111
4112foo(x = { direction = [0, 0], origin = [0, 0]})
4113"#;
4114
4115 parse_execute(program).await.unwrap();
4116
4117 let program = r#"fn foo(x: Axis3d) {
4118 return 0
4119}
4120
4121foo(x = { direction = [0, 0], origin = [0, 0]})
4122"#;
4123
4124 parse_execute(program).await.unwrap_err();
4125 }
4126
4127 #[tokio::test(flavor = "multi_thread")]
4128 async fn coerce_return() {
4129 let program = r#"fn foo(): number(mm) {
4130 return 42
4131}
4132
4133a = foo()
4134"#;
4135
4136 parse_execute(program).await.unwrap();
4137
4138 let program = r#"fn foo(): mm {
4139 return 42
4140}
4141
4142a = foo()
4143"#;
4144
4145 parse_execute(program).await.unwrap();
4146
4147 let program = r#"fn foo(): number(mm) {
4148 return { bar: 42 }
4149}
4150
4151a = foo()
4152"#;
4153
4154 parse_execute(program).await.unwrap_err();
4155
4156 let program = r#"fn foo(): mm {
4157 return { bar: 42 }
4158}
4159
4160a = foo()
4161"#;
4162
4163 parse_execute(program).await.unwrap_err();
4164 }
4165
4166 #[tokio::test(flavor = "multi_thread")]
4167 async fn test_sensible_error_when_missing_equals_in_kwarg() {
4168 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)"]
4169 .into_iter()
4170 .enumerate()
4171 {
4172 let program = format!(
4173 "fn foo() {{ return 0 }}
4174z = 0
4175fn f(x, y, z) {{ return 0 }}
4176{call}"
4177 );
4178 let err = parse_execute(&program).await.unwrap_err();
4179 let msg = err.message();
4180 assert!(
4181 msg.contains("This argument needs a label, but it doesn't have one"),
4182 "failed test {i}: {msg}"
4183 );
4184 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
4185 if i == 0 {
4186 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
4187 }
4188 }
4189 }
4190
4191 #[tokio::test(flavor = "multi_thread")]
4192 async fn default_param_for_unlabeled() {
4193 let ast = r#"fn myExtrude(@sk, length) {
4196 return extrude(sk, length)
4197}
4198sketch001 = startSketchOn(XY)
4199 |> circle(center = [0, 0], radius = 93.75)
4200 |> myExtrude(length = 40)
4201"#;
4202
4203 parse_execute(ast).await.unwrap();
4204 }
4205
4206 #[tokio::test(flavor = "multi_thread")]
4207 async fn dont_use_unlabelled_as_input() {
4208 let ast = r#"length = 10
4210startSketchOn(XY)
4211 |> circle(center = [0, 0], radius = 93.75)
4212 |> extrude(length)
4213"#;
4214
4215 parse_execute(ast).await.unwrap();
4216 }
4217
4218 #[tokio::test(flavor = "multi_thread")]
4219 async fn ascription_in_binop() {
4220 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
4221 parse_execute(ast).await.unwrap();
4222
4223 let ast = r#"foo = tan(0): rad - 4deg"#;
4224 parse_execute(ast).await.unwrap();
4225 }
4226
4227 #[tokio::test(flavor = "multi_thread")]
4228 async fn neg_sqrt() {
4229 let ast = r#"bad = sqrt(-2)"#;
4230
4231 let e = parse_execute(ast).await.unwrap_err();
4232 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
4234 }
4235
4236 #[tokio::test(flavor = "multi_thread")]
4237 async fn non_array_fns() {
4238 let ast = r#"push(1, item = 2)
4239pop(1)
4240map(1, f = fn(@x) { return x + 1 })
4241reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
4242
4243 parse_execute(ast).await.unwrap();
4244 }
4245
4246 #[tokio::test(flavor = "multi_thread")]
4247 async fn non_array_indexing() {
4248 let good = r#"a = 42
4249good = a[0]
4250"#;
4251 let result = parse_execute(good).await.unwrap();
4252 let mem = result.exec_state.stack();
4253 let num = mem
4254 .memory
4255 .get_from("good", result.mem_env, SourceRange::default(), 0)
4256 .unwrap()
4257 .as_ty_f64()
4258 .unwrap();
4259 assert_eq!(num.n, 42.0);
4260
4261 let bad = r#"a = 42
4262bad = a[1]
4263"#;
4264
4265 parse_execute(bad).await.unwrap_err();
4266 }
4267
4268 #[tokio::test(flavor = "multi_thread")]
4269 async fn coerce_unknown_to_length() {
4270 let ast = r#"x = 2mm * 2mm
4271y = x: number(Length)"#;
4272 let e = parse_execute(ast).await.unwrap_err();
4273 assert!(
4274 e.message().contains("could not coerce"),
4275 "Error message: '{}'",
4276 e.message()
4277 );
4278
4279 let ast = r#"x = 2mm
4280y = x: number(Length)"#;
4281 let result = parse_execute(ast).await.unwrap();
4282 let mem = result.exec_state.stack();
4283 let num = mem
4284 .memory
4285 .get_from("y", result.mem_env, SourceRange::default(), 0)
4286 .unwrap()
4287 .as_ty_f64()
4288 .unwrap();
4289 assert_eq!(num.n, 2.0);
4290 assert_eq!(num.ty, NumericType::mm());
4291 }
4292
4293 #[tokio::test(flavor = "multi_thread")]
4294 async fn one_warning_unknown() {
4295 let ast = r#"
4296// Should warn once
4297a = PI * 2
4298// Should warn once
4299b = (PI * 2) / 3
4300// Should not warn
4301c = ((PI * 2) / 3): number(deg)
4302"#;
4303
4304 let result = parse_execute(ast).await.unwrap();
4305 assert_eq!(result.exec_state.errors().len(), 2);
4306 }
4307
4308 #[tokio::test(flavor = "multi_thread")]
4309 async fn non_count_indexing() {
4310 let ast = r#"x = [0, 0]
4311y = x[1mm]
4312"#;
4313 parse_execute(ast).await.unwrap_err();
4314
4315 let ast = r#"x = [0, 0]
4316y = 1deg
4317z = x[y]
4318"#;
4319 parse_execute(ast).await.unwrap_err();
4320
4321 let ast = r#"x = [0, 0]
4322y = x[0mm + 1]
4323"#;
4324 parse_execute(ast).await.unwrap_err();
4325 }
4326
4327 #[tokio::test(flavor = "multi_thread")]
4328 async fn getting_property_of_plane() {
4329 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
4330 parse_execute(&ast).await.unwrap();
4331 }
4332
4333 #[cfg(feature = "artifact-graph")]
4334 #[tokio::test(flavor = "multi_thread")]
4335 async fn no_artifacts_from_within_hole_call() {
4336 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
4341 let out = parse_execute(&ast).await.unwrap();
4342
4343 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4345
4346 let expected = 5;
4350 assert_eq!(
4351 actual_operations.len(),
4352 expected,
4353 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4354 actual_operations.len(),
4355 );
4356 }
4357
4358 #[cfg(feature = "artifact-graph")]
4359 #[tokio::test(flavor = "multi_thread")]
4360 async fn feature_tree_annotation_on_user_defined_kcl() {
4361 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4364 let out = parse_execute(&ast).await.unwrap();
4365
4366 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4368
4369 let expected = 0;
4370 assert_eq!(
4371 actual_operations.len(),
4372 expected,
4373 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4374 actual_operations.len(),
4375 );
4376 }
4377
4378 #[cfg(feature = "artifact-graph")]
4379 #[tokio::test(flavor = "multi_thread")]
4380 async fn no_feature_tree_annotation_on_user_defined_kcl() {
4381 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4384 let out = parse_execute(&ast).await.unwrap();
4385
4386 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4388
4389 let expected = 2;
4390 assert_eq!(
4391 actual_operations.len(),
4392 expected,
4393 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4394 actual_operations.len(),
4395 );
4396 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
4397 assert!(matches!(actual_operations[1], Operation::GroupEnd));
4398 }
4399
4400 #[tokio::test(flavor = "multi_thread")]
4401 async fn custom_warning() {
4402 let warn = r#"
4403a = PI * 2
4404"#;
4405 let result = parse_execute(warn).await.unwrap();
4406 assert_eq!(result.exec_state.errors().len(), 1);
4407 assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
4408
4409 let allow = r#"
4410@warnings(allow = unknownUnits)
4411a = PI * 2
4412"#;
4413 let result = parse_execute(allow).await.unwrap();
4414 assert_eq!(result.exec_state.errors().len(), 0);
4415
4416 let deny = r#"
4417@warnings(deny = [unknownUnits])
4418a = PI * 2
4419"#;
4420 let result = parse_execute(deny).await.unwrap();
4421 assert_eq!(result.exec_state.errors().len(), 1);
4422 assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
4423 }
4424
4425 #[tokio::test(flavor = "multi_thread")]
4426 async fn sketch_block_unqualified_functions_use_sketch2() {
4427 let ast = r#"
4428@settings(experimentalFeatures = allow)
4429s = sketch(on = XY) {
4430 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
4431 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
4432 coincident([line1.end, line2.start])
4433}
4434"#;
4435 let result = parse_execute(ast).await.unwrap();
4436 let mem = result.exec_state.stack();
4437 let sketch_value = mem
4438 .memory
4439 .get_from("s", result.mem_env, SourceRange::default(), 0)
4440 .unwrap();
4441
4442 let KclValue::Object { value, .. } = sketch_value else {
4443 panic!("Expected sketch block to return an object, got {sketch_value:?}");
4444 };
4445
4446 assert!(value.contains_key("line1"));
4447 assert!(value.contains_key("line2"));
4448 assert!(!value.contains_key("line"));
4451 assert!(!value.contains_key("coincident"));
4452 }
4453
4454 #[tokio::test(flavor = "multi_thread")]
4455 async fn cannot_solid_extrude_an_open_profile() {
4456 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
4459 let program = crate::Program::parse_no_errs(&code).expect("should parse");
4460 let exec_ctxt = ExecutorContext::new_mock(None).await;
4461 let mut exec_state = ExecState::new(&exec_ctxt);
4462
4463 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
4464 assert!(matches!(err, KclError::Semantic { .. }));
4465 exec_ctxt.close().await;
4466 }
4467}