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