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