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 { .. } | SketchConstraintKind::Diameter { .. } => {
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 (points, label_position) = match &constraint.kind {
3354 SketchConstraintKind::Radius { points, label_position } => {
3355 (points, label_position.clone())
3356 }
3357 SketchConstraintKind::Diameter { points, label_position } => {
3358 (points, label_position.clone())
3359 }
3360 _ => unreachable!(),
3361 };
3362 #[cfg(not(feature = "artifact-graph"))]
3363 let _ = &label_position;
3364 let range = self.as_source_range();
3365 let center = &points[0];
3366 let start = &points[1];
3367 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
3368 return Err(internal_err(
3369 "Being inside a sketch block should have already been checked above",
3370 self,
3371 ));
3372 };
3373 let (constraint_name, is_diameter) = match &constraint.kind {
3374 SketchConstraintKind::Radius { .. } => ("radius", false),
3375 SketchConstraintKind::Diameter { .. } => ("diameter", true),
3376 _ => unreachable!(),
3377 };
3378 let sketch_vars = sketch_block_state.sketch_vars.clone();
3379 let target_segment = sketch_block_state
3380 .needed_by_engine
3381 .iter()
3382 .find_map(|seg| match &seg.kind {
3383 UnsolvedSegmentKind::Arc {
3384 center_object_id,
3385 start_object_id,
3386 end,
3387 ..
3388 } if *center_object_id == center.object_id
3389 && *start_object_id == start.object_id =>
3390 {
3391 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
3392 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
3393 (*end_x, *end_y)
3394 }
3395 _ => return None,
3396 };
3397 Some(CircularSegmentConstraintTarget::Arc {
3398 object_id: seg.object_id,
3399 end: [end_x_var, end_y_var],
3400 })
3401 }
3402 UnsolvedSegmentKind::Circle {
3403 center_object_id,
3404 start_object_id,
3405 ..
3406 } if *center_object_id == center.object_id
3407 && *start_object_id == start.object_id =>
3408 {
3409 Some(CircularSegmentConstraintTarget::Circle {
3410 object_id: seg.object_id,
3411 })
3412 }
3413 _ => None,
3414 })
3415 .ok_or_else(|| {
3416 internal_err(
3417 format!("Could not find circular segment for {} constraint", constraint_name),
3418 range,
3419 )
3420 })?;
3421 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
3422 let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3423 center.vars.x.to_constraint_id(range)?,
3424 center.vars.y.to_constraint_id(range)?,
3425 );
3426 let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3427 start.vars.x.to_constraint_id(range)?,
3428 start.vars.y.to_constraint_id(range)?,
3429 );
3430 let solver_constraint = match target_segment {
3431 CircularSegmentConstraintTarget::Arc { end, .. } => {
3432 let solver_arc = ezpz::datatypes::inputs::DatumCircularArc {
3433 center: center_point,
3434 start: start_point,
3435 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
3436 end[0].to_constraint_id(range)?,
3437 end[1].to_constraint_id(range)?,
3438 ),
3439 };
3440 Constraint::ArcRadius(solver_arc, radius_value)
3441 }
3442 CircularSegmentConstraintTarget::Circle { .. } => {
3443 let sketch_var_ty = solver_numeric_type(exec_state);
3444 let start_x =
3445 sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
3446 let start_y =
3447 sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
3448 let center_x =
3449 sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
3450 let center_y =
3451 sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
3452
3453 let radius_initial_value = libm::hypot(start_x - center_x, start_y - center_y);
3455
3456 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3457 let message =
3458 "Being inside a sketch block should have already been checked above"
3459 .to_owned();
3460 debug_assert!(false, "{}", &message);
3461 return Err(internal_err(message, self));
3462 };
3463 let radius_id = sketch_block_state.next_sketch_var_id();
3464 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3465 value: Box::new(crate::execution::SketchVar {
3466 id: radius_id,
3467 initial_value: radius_initial_value,
3468 ty: sketch_var_ty,
3469 meta: vec![],
3470 }),
3471 });
3472 let radius =
3473 ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
3474 let solver_circle = ezpz::datatypes::inputs::DatumCircle {
3475 center: center_point,
3476 radius,
3477 };
3478 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
3479 start_point,
3480 center_point,
3481 radius,
3482 ));
3483 Constraint::CircleRadius(solver_circle, radius_value)
3484 }
3485 };
3486
3487 #[cfg(feature = "artifact-graph")]
3488 let constraint_id = exec_state.next_object_id();
3489 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3490 let message =
3491 "Being inside a sketch block should have already been checked above".to_owned();
3492 debug_assert!(false, "{}", &message);
3493 return Err(internal_err(message, self));
3494 };
3495 sketch_block_state.solver_constraints.push(solver_constraint);
3496 #[cfg(feature = "artifact-graph")]
3497 {
3498 use crate::execution::Artifact;
3499 use crate::execution::CodeRef;
3500 use crate::execution::SketchBlockConstraint;
3501 use crate::execution::SketchBlockConstraintType;
3502 use crate::front::SourceRef;
3503 let segment_object_id = match target_segment {
3504 CircularSegmentConstraintTarget::Arc { object_id, .. }
3505 | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
3506 };
3507
3508 let constraint = if is_diameter {
3509 use crate::frontend::sketch::Diameter;
3510 crate::front::Constraint::Diameter(Diameter {
3511 arc: segment_object_id,
3512 diameter: n.try_into().map_err(|_| {
3513 internal_err("Failed to convert diameter units numeric suffix:", range)
3514 })?,
3515 label_position,
3516 source,
3517 })
3518 } else {
3519 use crate::frontend::sketch::Radius;
3520 crate::front::Constraint::Radius(Radius {
3521 arc: segment_object_id,
3522 radius: n.try_into().map_err(|_| {
3523 internal_err("Failed to convert radius units numeric suffix:", range)
3524 })?,
3525 label_position,
3526 source,
3527 })
3528 };
3529 sketch_block_state.sketch_constraints.push(constraint_id);
3530 let Some(sketch_id) = sketch_block_state.sketch_id else {
3531 let message = "Sketch id missing for constraint artifact".to_owned();
3532 debug_assert!(false, "{}", &message);
3533 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3534 };
3535 let artifact_id = exec_state.next_artifact_id();
3536 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3537 id: artifact_id,
3538 sketch_id,
3539 constraint_id,
3540 constraint_type: SketchBlockConstraintType::from(&constraint),
3541 code_ref: CodeRef::placeholder(range),
3542 }));
3543 exec_state.add_scene_object(
3544 Object {
3545 id: constraint_id,
3546 kind: ObjectKind::Constraint { constraint },
3547 label: Default::default(),
3548 comments: Default::default(),
3549 artifact_id,
3550 source: SourceRef::new(range, self.node_path.clone()),
3551 },
3552 range,
3553 );
3554 }
3555 }
3556 SketchConstraintKind::HorizontalDistance { points, label_position } => {
3557 #[cfg(not(feature = "artifact-graph"))]
3558 let _ = label_position;
3559 let range = self.as_source_range();
3560 let p0 = &points[0];
3561 let p1 = &points[1];
3562 #[cfg(feature = "artifact-graph")]
3563 let constraint_id = exec_state.next_object_id();
3564 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3565 let message =
3566 "Being inside a sketch block should have already been checked above".to_owned();
3567 debug_assert!(false, "{}", &message);
3568 return Err(internal_err(message, self));
3569 };
3570 match (p0, p1) {
3571 (
3572 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3573 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3574 ) => {
3575 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3576 p0.vars.x.to_constraint_id(range)?,
3577 p0.vars.y.to_constraint_id(range)?,
3578 );
3579 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3580 p1.vars.x.to_constraint_id(range)?,
3581 p1.vars.y.to_constraint_id(range)?,
3582 );
3583 sketch_block_state
3584 .solver_constraints
3585 .push(ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n));
3586 }
3587 (
3588 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3589 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3590 ) => {
3591 sketch_block_state
3593 .solver_constraints
3594 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, -n.n));
3595 }
3596 (
3597 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3598 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3599 ) => {
3600 sketch_block_state
3602 .solver_constraints
3603 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, n.n));
3604 }
3605 (
3606 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3607 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3608 ) => {
3609 return Err(internal_err(
3610 "horizontalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3611 range,
3612 ));
3613 }
3614 }
3615 #[cfg(feature = "artifact-graph")]
3616 {
3617 use crate::execution::Artifact;
3618 use crate::execution::CodeRef;
3619 use crate::execution::SketchBlockConstraint;
3620 use crate::execution::SketchBlockConstraintType;
3621 use crate::front::Distance;
3622 use crate::front::SourceRef;
3623 use crate::frontend::sketch::ConstraintSegment;
3624
3625 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
3626 points: vec![
3627 match p0 {
3628 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3629 ConstraintSegment::from(point.object_id)
3630 }
3631 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3632 ConstraintSegment::ORIGIN
3633 }
3634 },
3635 match p1 {
3636 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3637 ConstraintSegment::from(point.object_id)
3638 }
3639 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3640 ConstraintSegment::ORIGIN
3641 }
3642 },
3643 ],
3644 distance: n.try_into().map_err(|_| {
3645 internal_err("Failed to convert distance units numeric suffix:", range)
3646 })?,
3647 label_position: label_position.clone(),
3648 source,
3649 });
3650 sketch_block_state.sketch_constraints.push(constraint_id);
3651 let Some(sketch_id) = sketch_block_state.sketch_id else {
3652 let message = "Sketch id missing for constraint artifact".to_owned();
3653 debug_assert!(false, "{}", &message);
3654 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3655 };
3656 let artifact_id = exec_state.next_artifact_id();
3657 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3658 id: artifact_id,
3659 sketch_id,
3660 constraint_id,
3661 constraint_type: SketchBlockConstraintType::from(&constraint),
3662 code_ref: CodeRef::placeholder(range),
3663 }));
3664 exec_state.add_scene_object(
3665 Object {
3666 id: constraint_id,
3667 kind: ObjectKind::Constraint { constraint },
3668 label: Default::default(),
3669 comments: Default::default(),
3670 artifact_id,
3671 source: SourceRef::new(range, self.node_path.clone()),
3672 },
3673 range,
3674 );
3675 }
3676 }
3677 SketchConstraintKind::VerticalDistance { points, label_position } => {
3678 #[cfg(not(feature = "artifact-graph"))]
3679 let _ = label_position;
3680 let range = self.as_source_range();
3681 let p0 = &points[0];
3682 let p1 = &points[1];
3683 #[cfg(feature = "artifact-graph")]
3684 let constraint_id = exec_state.next_object_id();
3685 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3686 let message =
3687 "Being inside a sketch block should have already been checked above".to_owned();
3688 debug_assert!(false, "{}", &message);
3689 return Err(internal_err(message, self));
3690 };
3691 match (p0, p1) {
3692 (
3693 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3694 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3695 ) => {
3696 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3697 p0.vars.x.to_constraint_id(range)?,
3698 p0.vars.y.to_constraint_id(range)?,
3699 );
3700 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3701 p1.vars.x.to_constraint_id(range)?,
3702 p1.vars.y.to_constraint_id(range)?,
3703 );
3704 sketch_block_state
3705 .solver_constraints
3706 .push(ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n));
3707 }
3708 (
3709 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3710 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3711 ) => {
3712 sketch_block_state
3713 .solver_constraints
3714 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, -n.n));
3715 }
3716 (
3717 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3718 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3719 ) => {
3720 sketch_block_state
3721 .solver_constraints
3722 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, n.n));
3723 }
3724 (
3725 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3726 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3727 ) => {
3728 return Err(internal_err(
3729 "verticalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3730 range,
3731 ));
3732 }
3733 }
3734 #[cfg(feature = "artifact-graph")]
3735 {
3736 use crate::execution::Artifact;
3737 use crate::execution::CodeRef;
3738 use crate::execution::SketchBlockConstraint;
3739 use crate::execution::SketchBlockConstraintType;
3740 use crate::front::Distance;
3741 use crate::front::SourceRef;
3742 use crate::frontend::sketch::ConstraintSegment;
3743
3744 let constraint = crate::front::Constraint::VerticalDistance(Distance {
3745 points: vec![
3746 match p0 {
3747 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3748 ConstraintSegment::from(point.object_id)
3749 }
3750 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3751 ConstraintSegment::ORIGIN
3752 }
3753 },
3754 match p1 {
3755 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3756 ConstraintSegment::from(point.object_id)
3757 }
3758 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3759 ConstraintSegment::ORIGIN
3760 }
3761 },
3762 ],
3763 distance: n.try_into().map_err(|_| {
3764 internal_err("Failed to convert distance units numeric suffix:", range)
3765 })?,
3766 label_position: label_position.clone(),
3767 source,
3768 });
3769 sketch_block_state.sketch_constraints.push(constraint_id);
3770 let Some(sketch_id) = sketch_block_state.sketch_id else {
3771 let message = "Sketch id missing for constraint artifact".to_owned();
3772 debug_assert!(false, "{}", &message);
3773 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3774 };
3775 let artifact_id = exec_state.next_artifact_id();
3776 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3777 id: artifact_id,
3778 sketch_id,
3779 constraint_id,
3780 constraint_type: SketchBlockConstraintType::from(&constraint),
3781 code_ref: CodeRef::placeholder(range),
3782 }));
3783 exec_state.add_scene_object(
3784 Object {
3785 id: constraint_id,
3786 kind: ObjectKind::Constraint { constraint },
3787 label: Default::default(),
3788 comments: Default::default(),
3789 artifact_id,
3790 source: SourceRef::new(range, self.node_path.clone()),
3791 },
3792 range,
3793 );
3794 }
3795 }
3796 }
3797 return Ok(KclValue::none());
3798 }
3799 _ => {
3800 return Err(KclError::new_semantic(KclErrorDetails::new(
3801 format!(
3802 "Cannot create an equivalence constraint between values of these types: {} and {}",
3803 left_value.human_friendly_type(),
3804 right_value.human_friendly_type()
3805 ),
3806 vec![self.into()],
3807 )));
3808 }
3809 }
3810 }
3811
3812 let left = number_as_f64(&left_value, self.left.clone().into())?;
3813 let right = number_as_f64(&right_value, self.right.clone().into())?;
3814
3815 let value = match self.operator {
3816 BinaryOperator::Add => {
3817 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3818 self.warn_on_unknown(&ty, "Adding", exec_state);
3819 KclValue::Number { value: l + r, meta, ty }
3820 }
3821 BinaryOperator::Sub => {
3822 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3823 self.warn_on_unknown(&ty, "Subtracting", exec_state);
3824 KclValue::Number { value: l - r, meta, ty }
3825 }
3826 BinaryOperator::Mul => {
3827 let (l, r, ty) = NumericType::combine_mul(left, right);
3828 self.warn_on_unknown(&ty, "Multiplying", exec_state);
3829 KclValue::Number { value: l * r, meta, ty }
3830 }
3831 BinaryOperator::Div => {
3832 let (l, r, ty) = NumericType::combine_div(left, right);
3833 self.warn_on_unknown(&ty, "Dividing", exec_state);
3834 KclValue::Number { value: l / r, meta, ty }
3835 }
3836 BinaryOperator::Mod => {
3837 let (l, r, ty) = NumericType::combine_mod(left, right);
3838 self.warn_on_unknown(&ty, "Modulo of", exec_state);
3839 KclValue::Number { value: l % r, meta, ty }
3840 }
3841 BinaryOperator::Pow => KclValue::Number {
3842 value: left.n.powf(right.n),
3843 meta,
3844 ty: exec_state.current_default_units(),
3845 },
3846 BinaryOperator::Neq => {
3847 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3848 self.warn_on_unknown(&ty, "Comparing", exec_state);
3849 KclValue::Bool { value: l != r, meta }
3850 }
3851 BinaryOperator::Gt => {
3852 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3853 self.warn_on_unknown(&ty, "Comparing", exec_state);
3854 KclValue::Bool { value: l > r, meta }
3855 }
3856 BinaryOperator::Gte => {
3857 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3858 self.warn_on_unknown(&ty, "Comparing", exec_state);
3859 KclValue::Bool { value: l >= r, meta }
3860 }
3861 BinaryOperator::Lt => {
3862 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3863 self.warn_on_unknown(&ty, "Comparing", exec_state);
3864 KclValue::Bool { value: l < r, meta }
3865 }
3866 BinaryOperator::Lte => {
3867 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3868 self.warn_on_unknown(&ty, "Comparing", exec_state);
3869 KclValue::Bool { value: l <= r, meta }
3870 }
3871 BinaryOperator::Eq => {
3872 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3873 self.warn_on_unknown(&ty, "Comparing", exec_state);
3874 KclValue::Bool { value: l == r, meta }
3875 }
3876 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
3877 };
3878
3879 Ok(value)
3880 }
3881
3882 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
3883 internal_err("missing result while evaluating binary expression", node)
3884 }
3885
3886 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
3887 if ty == &NumericType::Unknown {
3888 let sr = self.as_source_range();
3889 exec_state.clear_units_warnings(&sr);
3890 let mut err = CompilationIssue::err(
3891 sr,
3892 format!(
3893 "{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)`."
3894 ),
3895 );
3896 err.tag = crate::errors::Tag::UnknownNumericUnits;
3897 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
3898 }
3899 }
3900}
3901
3902impl Node<UnaryExpression> {
3903 pub(super) async fn get_result(
3904 &self,
3905 exec_state: &mut ExecState,
3906 ctx: &ExecutorContext,
3907 ) -> Result<KclValueControlFlow, KclError> {
3908 match self.operator {
3909 UnaryOperator::Not => {
3910 let value = self.argument.get_result(exec_state, ctx).await?;
3911 let value = control_continue!(value);
3912 let KclValue::Bool {
3913 value: bool_value,
3914 meta: _,
3915 } = value
3916 else {
3917 return Err(KclError::new_semantic(KclErrorDetails::new(
3918 format!(
3919 "Cannot apply unary operator ! to non-boolean value: {}",
3920 value.human_friendly_type()
3921 ),
3922 vec![self.into()],
3923 )));
3924 };
3925 let meta = vec![Metadata {
3926 source_range: self.into(),
3927 }];
3928 let negated = KclValue::Bool {
3929 value: !bool_value,
3930 meta,
3931 };
3932
3933 Ok(negated.continue_())
3934 }
3935 UnaryOperator::Neg => {
3936 let value = self.argument.get_result(exec_state, ctx).await?;
3937 let value = control_continue!(value);
3938 let err = || {
3939 KclError::new_semantic(KclErrorDetails::new(
3940 format!(
3941 "You can only negate numbers, planes, or lines, but this is a {}",
3942 value.human_friendly_type()
3943 ),
3944 vec![self.into()],
3945 ))
3946 };
3947 match &value {
3948 KclValue::Number { value, ty, .. } => {
3949 let meta = vec![Metadata {
3950 source_range: self.into(),
3951 }];
3952 Ok(KclValue::Number {
3953 value: -value,
3954 meta,
3955 ty: *ty,
3956 }
3957 .continue_())
3958 }
3959 KclValue::Plane { value } => {
3960 let mut plane = value.clone();
3961 if plane.info.x_axis.x != 0.0 {
3962 plane.info.x_axis.x *= -1.0;
3963 }
3964 if plane.info.x_axis.y != 0.0 {
3965 plane.info.x_axis.y *= -1.0;
3966 }
3967 if plane.info.x_axis.z != 0.0 {
3968 plane.info.x_axis.z *= -1.0;
3969 }
3970
3971 plane.id = exec_state.next_uuid();
3972 plane.object_id = None;
3973 Ok(KclValue::Plane { value: plane }.continue_())
3974 }
3975 KclValue::Object {
3976 value: values, meta, ..
3977 } => {
3978 let Some(direction) = values.get("direction") else {
3980 return Err(err());
3981 };
3982
3983 let direction = match direction {
3984 KclValue::Tuple { value: values, meta } => {
3985 let values = values
3986 .iter()
3987 .map(|v| match v {
3988 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3989 value: *value * -1.0,
3990 ty: *ty,
3991 meta: meta.clone(),
3992 }),
3993 _ => Err(err()),
3994 })
3995 .collect::<Result<Vec<_>, _>>()?;
3996
3997 KclValue::Tuple {
3998 value: values,
3999 meta: meta.clone(),
4000 }
4001 }
4002 KclValue::HomArray {
4003 value: values,
4004 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
4005 } => {
4006 let values = values
4007 .iter()
4008 .map(|v| match v {
4009 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
4010 value: *value * -1.0,
4011 ty: *ty,
4012 meta: meta.clone(),
4013 }),
4014 _ => Err(err()),
4015 })
4016 .collect::<Result<Vec<_>, _>>()?;
4017
4018 KclValue::HomArray {
4019 value: values,
4020 ty: ty.clone(),
4021 }
4022 }
4023 _ => return Err(err()),
4024 };
4025
4026 let mut value = values.clone();
4027 value.insert("direction".to_owned(), direction);
4028 Ok(KclValue::Object {
4029 value,
4030 meta: meta.clone(),
4031 constrainable: false,
4032 }
4033 .continue_())
4034 }
4035 _ => Err(err()),
4036 }
4037 }
4038 UnaryOperator::Plus => {
4039 let operand = self.argument.get_result(exec_state, ctx).await?;
4040 let operand = control_continue!(operand);
4041 match operand {
4042 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
4043 _ => Err(KclError::new_semantic(KclErrorDetails::new(
4044 format!(
4045 "You can only apply unary + to numbers or planes, but this is a {}",
4046 operand.human_friendly_type()
4047 ),
4048 vec![self.into()],
4049 ))),
4050 }
4051 }
4052 }
4053 }
4054}
4055
4056pub(crate) async fn execute_pipe_body(
4057 exec_state: &mut ExecState,
4058 body: &[Expr],
4059 source_range: SourceRange,
4060 ctx: &ExecutorContext,
4061) -> Result<KclValueControlFlow, KclError> {
4062 let Some((first, body)) = body.split_first() else {
4063 return Err(KclError::new_semantic(KclErrorDetails::new(
4064 "Pipe expressions cannot be empty".to_owned(),
4065 vec![source_range],
4066 )));
4067 };
4068 let meta = Metadata {
4073 source_range: SourceRange::from(first),
4074 };
4075 let output = ctx
4076 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
4077 .await?;
4078 let output = control_continue!(output);
4079
4080 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
4084 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
4086 exec_state.mod_local.pipe_value = previous_pipe_value;
4088
4089 result
4090}
4091
4092#[async_recursion]
4095async fn inner_execute_pipe_body(
4096 exec_state: &mut ExecState,
4097 body: &[Expr],
4098 ctx: &ExecutorContext,
4099) -> Result<KclValueControlFlow, KclError> {
4100 for expression in body {
4101 if let Expr::TagDeclarator(_) = expression {
4102 return Err(KclError::new_semantic(KclErrorDetails::new(
4103 format!("This cannot be in a PipeExpression: {expression:?}"),
4104 vec![expression.into()],
4105 )));
4106 }
4107 let metadata = Metadata {
4108 source_range: SourceRange::from(expression),
4109 };
4110 let output = ctx
4111 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
4112 .await?;
4113 let output = control_continue!(output);
4114 exec_state.mod_local.pipe_value = Some(output);
4115 }
4116 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
4118 Ok(final_output.continue_())
4119}
4120
4121impl Node<TagDeclarator> {
4122 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
4123 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
4124 value: self.name.clone(),
4125 info: Vec::new(),
4126 meta: vec![Metadata {
4127 source_range: self.into(),
4128 }],
4129 }));
4130
4131 exec_state
4132 .mut_stack()
4133 .add(self.name.clone(), memory_item, self.into())?;
4134
4135 Ok(self.into())
4136 }
4137}
4138
4139impl Node<ArrayExpression> {
4140 #[async_recursion]
4141 pub(super) async fn execute(
4142 &self,
4143 exec_state: &mut ExecState,
4144 ctx: &ExecutorContext,
4145 ) -> Result<KclValueControlFlow, KclError> {
4146 let mut results = Vec::with_capacity(self.elements.len());
4147
4148 for element in &self.elements {
4149 let metadata = Metadata::from(element);
4150 let value = ctx
4153 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
4154 .await?;
4155 let value = control_continue!(value);
4156
4157 results.push(value);
4158 }
4159
4160 Ok(KclValue::HomArray {
4161 value: results,
4162 ty: RuntimeType::Primitive(PrimitiveType::Any),
4163 }
4164 .continue_())
4165 }
4166}
4167
4168impl Node<ArrayRangeExpression> {
4169 #[async_recursion]
4170 pub(super) async fn execute(
4171 &self,
4172 exec_state: &mut ExecState,
4173 ctx: &ExecutorContext,
4174 ) -> Result<KclValueControlFlow, KclError> {
4175 let metadata = Metadata::from(&self.start_element);
4176 let start_val = ctx
4177 .execute_expr(
4178 &self.start_element,
4179 exec_state,
4180 &metadata,
4181 &[],
4182 StatementKind::Expression,
4183 )
4184 .await?;
4185 let start_val = control_continue!(start_val);
4186 let start = start_val
4187 .as_ty_f64()
4188 .ok_or(KclError::new_semantic(KclErrorDetails::new(
4189 format!(
4190 "Expected number for range start but found {}",
4191 start_val.human_friendly_type()
4192 ),
4193 vec![self.into()],
4194 )))?;
4195 let metadata = Metadata::from(&self.end_element);
4196 let end_val = ctx
4197 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
4198 .await?;
4199 let end_val = control_continue!(end_val);
4200 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
4201 format!(
4202 "Expected number for range end but found {}",
4203 end_val.human_friendly_type()
4204 ),
4205 vec![self.into()],
4206 )))?;
4207
4208 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
4209 let Some(start) = crate::try_f64_to_i64(start) else {
4210 return Err(KclError::new_semantic(KclErrorDetails::new(
4211 format!("Range start must be an integer, but found {start}"),
4212 vec![self.into()],
4213 )));
4214 };
4215 let Some(end) = crate::try_f64_to_i64(end) else {
4216 return Err(KclError::new_semantic(KclErrorDetails::new(
4217 format!("Range end must be an integer, but found {end}"),
4218 vec![self.into()],
4219 )));
4220 };
4221
4222 if end < start {
4223 return Err(KclError::new_semantic(KclErrorDetails::new(
4224 format!("Range start is greater than range end: {start} .. {end}"),
4225 vec![self.into()],
4226 )));
4227 }
4228
4229 let range: Vec<_> = if self.end_inclusive {
4230 (start..=end).collect()
4231 } else {
4232 (start..end).collect()
4233 };
4234
4235 let meta = vec![Metadata {
4236 source_range: self.into(),
4237 }];
4238
4239 Ok(KclValue::HomArray {
4240 value: range
4241 .into_iter()
4242 .map(|num| KclValue::Number {
4243 value: num as f64,
4244 ty,
4245 meta: meta.clone(),
4246 })
4247 .collect(),
4248 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
4249 }
4250 .continue_())
4251 }
4252}
4253
4254impl Node<ObjectExpression> {
4255 #[async_recursion]
4256 pub(super) async fn execute(
4257 &self,
4258 exec_state: &mut ExecState,
4259 ctx: &ExecutorContext,
4260 ) -> Result<KclValueControlFlow, KclError> {
4261 let mut object = HashMap::with_capacity(self.properties.len());
4262 for property in &self.properties {
4263 let metadata = Metadata::from(&property.value);
4264 let result = ctx
4265 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
4266 .await?;
4267 let result = control_continue!(result);
4268 object.insert(property.key.name.clone(), result);
4269 }
4270
4271 Ok(KclValue::Object {
4272 value: object,
4273 meta: vec![Metadata {
4274 source_range: self.into(),
4275 }],
4276 constrainable: false,
4277 }
4278 .continue_())
4279 }
4280}
4281
4282fn article_for<S: AsRef<str>>(s: S) -> &'static str {
4283 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
4285 "an"
4286 } else {
4287 "a"
4288 }
4289}
4290
4291fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
4292 v.as_ty_f64().ok_or_else(|| {
4293 let actual_type = v.human_friendly_type();
4294 KclError::new_semantic(KclErrorDetails::new(
4295 format!("Expected a number, but found {actual_type}",),
4296 vec![source_range],
4297 ))
4298 })
4299}
4300
4301impl Node<IfExpression> {
4302 #[async_recursion]
4303 pub(super) async fn get_result(
4304 &self,
4305 exec_state: &mut ExecState,
4306 ctx: &ExecutorContext,
4307 ) -> Result<KclValueControlFlow, KclError> {
4308 let cond_value = ctx
4310 .execute_expr(
4311 &self.cond,
4312 exec_state,
4313 &Metadata::from(self),
4314 &[],
4315 StatementKind::Expression,
4316 )
4317 .await?;
4318 let cond_value = control_continue!(cond_value);
4319 if cond_value.get_bool()? {
4320 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
4321 return Ok(block_result.unwrap());
4325 }
4326
4327 for else_if in &self.else_ifs {
4329 let cond_value = ctx
4330 .execute_expr(
4331 &else_if.cond,
4332 exec_state,
4333 &Metadata::from(self),
4334 &[],
4335 StatementKind::Expression,
4336 )
4337 .await?;
4338 let cond_value = control_continue!(cond_value);
4339 if cond_value.get_bool()? {
4340 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
4341 return Ok(block_result.unwrap());
4345 }
4346 }
4347
4348 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
4350 .await
4351 .map(|expr| expr.unwrap())
4352 }
4353}
4354
4355#[derive(Debug)]
4356enum Property {
4357 UInt(usize),
4358 String(String),
4359}
4360
4361impl Property {
4362 #[allow(clippy::too_many_arguments)]
4363 async fn try_from<'a>(
4364 computed: bool,
4365 value: Expr,
4366 exec_state: &mut ExecState,
4367 sr: SourceRange,
4368 ctx: &ExecutorContext,
4369 metadata: &Metadata,
4370 annotations: &[Node<Annotation>],
4371 statement_kind: StatementKind<'a>,
4372 ) -> Result<Self, KclError> {
4373 let property_sr = vec![sr];
4374 if !computed {
4375 let Expr::Name(identifier) = value else {
4376 return Err(KclError::new_semantic(KclErrorDetails::new(
4378 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
4379 .to_owned(),
4380 property_sr,
4381 )));
4382 };
4383 return Ok(Property::String(identifier.to_string()));
4384 }
4385
4386 let prop_value = ctx
4387 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
4388 .await?;
4389 let prop_value = match prop_value.control {
4390 ControlFlowKind::Continue => prop_value.into_value(),
4391 ControlFlowKind::Exit => {
4392 let message = "Early return inside array brackets is currently not supported".to_owned();
4393 debug_assert!(false, "{}", &message);
4394 return Err(internal_err(message, sr));
4395 }
4396 };
4397 match prop_value {
4398 KclValue::Number { value, ty, meta: _ } => {
4399 if !matches!(
4400 ty,
4401 NumericType::Unknown
4402 | NumericType::Default { .. }
4403 | NumericType::Known(crate::exec::UnitType::Count)
4404 ) {
4405 return Err(KclError::new_semantic(KclErrorDetails::new(
4406 format!(
4407 "{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"
4408 ),
4409 property_sr,
4410 )));
4411 }
4412 if let Some(x) = crate::try_f64_to_usize(value) {
4413 Ok(Property::UInt(x))
4414 } else {
4415 Err(KclError::new_semantic(KclErrorDetails::new(
4416 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
4417 property_sr,
4418 )))
4419 }
4420 }
4421 _ => Err(KclError::new_semantic(KclErrorDetails::new(
4422 "Only numbers (>= 0) can be indexes".to_owned(),
4423 vec![sr],
4424 ))),
4425 }
4426 }
4427}
4428
4429impl Property {
4430 fn type_name(&self) -> &'static str {
4431 match self {
4432 Property::UInt(_) => "number",
4433 Property::String(_) => "string",
4434 }
4435 }
4436}
4437
4438impl Node<PipeExpression> {
4439 #[async_recursion]
4440 pub(super) async fn get_result(
4441 &self,
4442 exec_state: &mut ExecState,
4443 ctx: &ExecutorContext,
4444 ) -> Result<KclValueControlFlow, KclError> {
4445 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
4446 }
4447}
4448
4449#[cfg(test)]
4450mod test {
4451 use std::sync::Arc;
4452
4453 use tokio::io::AsyncWriteExt;
4454
4455 use super::*;
4456 use crate::ExecutorSettings;
4457 use crate::errors::Severity;
4458 use crate::exec::UnitType;
4459 use crate::execution::ContextType;
4460 use crate::execution::parse_execute;
4461
4462 #[tokio::test(flavor = "multi_thread")]
4463 async fn ascription() {
4464 let program = r#"
4465a = 42: number
4466b = a: number
4467p = {
4468 origin = { x = 0, y = 0, z = 0 },
4469 xAxis = { x = 1, y = 0, z = 0 },
4470 yAxis = { x = 0, y = 1, z = 0 },
4471 zAxis = { x = 0, y = 0, z = 1 }
4472}: Plane
4473arr1 = [42]: [number(cm)]
4474"#;
4475
4476 let result = parse_execute(program).await.unwrap();
4477 let mem = result.exec_state.stack();
4478 assert!(matches!(
4479 mem.memory
4480 .get_from("p", result.mem_env, SourceRange::default(), 0)
4481 .unwrap(),
4482 KclValue::Plane { .. }
4483 ));
4484 let arr1 = mem
4485 .memory
4486 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
4487 .unwrap();
4488 if let KclValue::HomArray { value, ty } = arr1 {
4489 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
4490 assert_eq!(
4491 *ty,
4492 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
4493 );
4494 if let KclValue::Number { value, ty, .. } = &value[0] {
4496 assert_eq!(*value, 42.0);
4498 assert_eq!(
4499 *ty,
4500 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
4501 );
4502 } else {
4503 panic!("Expected a number; found {:?}", value[0]);
4504 }
4505 } else {
4506 panic!("Expected HomArray; found {arr1:?}");
4507 }
4508
4509 let program = r#"
4510a = 42: string
4511"#;
4512 let result = parse_execute(program).await;
4513 let err = result.unwrap_err();
4514 assert!(
4515 err.to_string()
4516 .contains("could not coerce a number (with type `number`) to type `string`"),
4517 "Expected error but found {err:?}"
4518 );
4519
4520 let program = r#"
4521a = 42: Plane
4522"#;
4523 let result = parse_execute(program).await;
4524 let err = result.unwrap_err();
4525 assert!(
4526 err.to_string()
4527 .contains("could not coerce a number (with type `number`) to type `Plane`"),
4528 "Expected error but found {err:?}"
4529 );
4530
4531 let program = r#"
4532arr = [0]: [string]
4533"#;
4534 let result = parse_execute(program).await;
4535 let err = result.unwrap_err();
4536 assert!(
4537 err.to_string().contains(
4538 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
4539 ),
4540 "Expected error but found {err:?}"
4541 );
4542
4543 let program = r#"
4544mixedArr = [0, "a"]: [number(mm)]
4545"#;
4546 let result = parse_execute(program).await;
4547 let err = result.unwrap_err();
4548 assert!(
4549 err.to_string().contains(
4550 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4551 ),
4552 "Expected error but found {err:?}"
4553 );
4554
4555 let program = r#"
4556mixedArr = [0, "a"]: [mm]
4557"#;
4558 let result = parse_execute(program).await;
4559 let err = result.unwrap_err();
4560 assert!(
4561 err.to_string().contains(
4562 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4563 ),
4564 "Expected error but found {err:?}"
4565 );
4566 }
4567
4568 #[tokio::test(flavor = "multi_thread")]
4569 async fn neg_plane() {
4570 let program = r#"
4571p = {
4572 origin = { x = 0, y = 0, z = 0 },
4573 xAxis = { x = 1, y = 0, z = 0 },
4574 yAxis = { x = 0, y = 1, z = 0 },
4575}: Plane
4576p2 = -p
4577"#;
4578
4579 let result = parse_execute(program).await.unwrap();
4580 let mem = result.exec_state.stack();
4581 match mem
4582 .memory
4583 .get_from("p2", result.mem_env, SourceRange::default(), 0)
4584 .unwrap()
4585 {
4586 KclValue::Plane { value } => {
4587 assert_eq!(value.info.x_axis.x, -1.0);
4588 assert_eq!(value.info.x_axis.y, 0.0);
4589 assert_eq!(value.info.x_axis.z, 0.0);
4590 }
4591 _ => unreachable!(),
4592 }
4593 }
4594
4595 #[tokio::test(flavor = "multi_thread")]
4596 async fn multiple_returns() {
4597 let program = r#"fn foo() {
4598 return 0
4599 return 42
4600}
4601
4602a = foo()
4603"#;
4604
4605 let result = parse_execute(program).await;
4606 assert!(result.unwrap_err().to_string().contains("return"));
4607 }
4608
4609 #[tokio::test(flavor = "multi_thread")]
4610 async fn load_all_modules() {
4611 let program_a_kcl = r#"
4613export a = 1
4614"#;
4615 let program_b_kcl = r#"
4617import a from 'a.kcl'
4618
4619export b = a + 1
4620"#;
4621 let program_c_kcl = r#"
4623import a from 'a.kcl'
4624
4625export c = a + 2
4626"#;
4627
4628 let main_kcl = r#"
4630import b from 'b.kcl'
4631import c from 'c.kcl'
4632
4633d = b + c
4634"#;
4635
4636 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
4637 .parse_errs_as_err()
4638 .unwrap();
4639
4640 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
4641
4642 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
4643 .await
4644 .unwrap()
4645 .write_all(main_kcl.as_bytes())
4646 .await
4647 .unwrap();
4648
4649 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
4650 .await
4651 .unwrap()
4652 .write_all(program_a_kcl.as_bytes())
4653 .await
4654 .unwrap();
4655
4656 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
4657 .await
4658 .unwrap()
4659 .write_all(program_b_kcl.as_bytes())
4660 .await
4661 .unwrap();
4662
4663 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
4664 .await
4665 .unwrap()
4666 .write_all(program_c_kcl.as_bytes())
4667 .await
4668 .unwrap();
4669
4670 let exec_ctxt = ExecutorContext {
4671 engine: Arc::new(Box::new(
4672 crate::engine::conn_mock::EngineConnection::new()
4673 .map_err(|err| {
4674 internal_err(
4675 format!("Failed to create mock engine connection: {err}"),
4676 SourceRange::default(),
4677 )
4678 })
4679 .unwrap(),
4680 )),
4681 fs: Arc::new(crate::fs::FileManager::new()),
4682 settings: ExecutorSettings {
4683 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
4684 ..Default::default()
4685 },
4686 context_type: ContextType::Mock,
4687 };
4688 let mut exec_state = ExecState::new(&exec_ctxt);
4689
4690 exec_ctxt
4691 .run(
4692 &crate::Program {
4693 ast: main.clone(),
4694 original_file_contents: "".to_owned(),
4695 },
4696 &mut exec_state,
4697 )
4698 .await
4699 .unwrap();
4700 }
4701
4702 #[tokio::test(flavor = "multi_thread")]
4703 async fn user_coercion() {
4704 let program = r#"fn foo(x: Axis2d) {
4705 return 0
4706}
4707
4708foo(x = { direction = [0, 0], origin = [0, 0]})
4709"#;
4710
4711 parse_execute(program).await.unwrap();
4712
4713 let program = r#"fn foo(x: Axis3d) {
4714 return 0
4715}
4716
4717foo(x = { direction = [0, 0], origin = [0, 0]})
4718"#;
4719
4720 parse_execute(program).await.unwrap_err();
4721 }
4722
4723 #[tokio::test(flavor = "multi_thread")]
4724 async fn coerce_return() {
4725 let program = r#"fn foo(): number(mm) {
4726 return 42
4727}
4728
4729a = foo()
4730"#;
4731
4732 parse_execute(program).await.unwrap();
4733
4734 let program = r#"fn foo(): mm {
4735 return 42
4736}
4737
4738a = foo()
4739"#;
4740
4741 parse_execute(program).await.unwrap();
4742
4743 let program = r#"fn foo(): number(mm) {
4744 return { bar: 42 }
4745}
4746
4747a = foo()
4748"#;
4749
4750 parse_execute(program).await.unwrap_err();
4751
4752 let program = r#"fn foo(): mm {
4753 return { bar: 42 }
4754}
4755
4756a = foo()
4757"#;
4758
4759 parse_execute(program).await.unwrap_err();
4760 }
4761
4762 #[tokio::test(flavor = "multi_thread")]
4763 async fn test_sensible_error_when_missing_equals_in_kwarg() {
4764 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)"]
4765 .into_iter()
4766 .enumerate()
4767 {
4768 let program = format!(
4769 "fn foo() {{ return 0 }}
4770z = 0
4771fn f(x, y, z) {{ return 0 }}
4772{call}"
4773 );
4774 let err = parse_execute(&program).await.unwrap_err();
4775 let msg = err.message();
4776 assert!(
4777 msg.contains("This argument needs a label, but it doesn't have one"),
4778 "failed test {i}: {msg}"
4779 );
4780 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
4781 if i == 0 {
4782 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
4783 }
4784 }
4785 }
4786
4787 #[tokio::test(flavor = "multi_thread")]
4788 async fn default_param_for_unlabeled() {
4789 let ast = r#"fn myExtrude(@sk, length) {
4792 return extrude(sk, length)
4793}
4794sketch001 = startSketchOn(XY)
4795 |> circle(center = [0, 0], radius = 93.75)
4796 |> myExtrude(length = 40)
4797"#;
4798
4799 parse_execute(ast).await.unwrap();
4800 }
4801
4802 #[tokio::test(flavor = "multi_thread")]
4803 async fn dont_use_unlabelled_as_input() {
4804 let ast = r#"length = 10
4806startSketchOn(XY)
4807 |> circle(center = [0, 0], radius = 93.75)
4808 |> extrude(length)
4809"#;
4810
4811 parse_execute(ast).await.unwrap();
4812 }
4813
4814 #[tokio::test(flavor = "multi_thread")]
4815 async fn ascription_in_binop() {
4816 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
4817 parse_execute(ast).await.unwrap();
4818
4819 let ast = r#"foo = tan(0): rad - 4deg"#;
4820 parse_execute(ast).await.unwrap();
4821 }
4822
4823 #[tokio::test(flavor = "multi_thread")]
4824 async fn neg_sqrt() {
4825 let ast = r#"bad = sqrt(-2)"#;
4826
4827 let e = parse_execute(ast).await.unwrap_err();
4828 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
4830 }
4831
4832 #[tokio::test(flavor = "multi_thread")]
4833 async fn non_array_fns() {
4834 let ast = r#"push(1, item = 2)
4835pop(1)
4836map(1, f = fn(@x) { return x + 1 })
4837reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
4838
4839 parse_execute(ast).await.unwrap();
4840 }
4841
4842 #[tokio::test(flavor = "multi_thread")]
4843 async fn non_array_indexing() {
4844 let good = r#"a = 42
4845good = a[0]
4846"#;
4847 let result = parse_execute(good).await.unwrap();
4848 let mem = result.exec_state.stack();
4849 let num = mem
4850 .memory
4851 .get_from("good", result.mem_env, SourceRange::default(), 0)
4852 .unwrap()
4853 .as_ty_f64()
4854 .unwrap();
4855 assert_eq!(num.n, 42.0);
4856
4857 let bad = r#"a = 42
4858bad = a[1]
4859"#;
4860
4861 parse_execute(bad).await.unwrap_err();
4862 }
4863
4864 #[tokio::test(flavor = "multi_thread")]
4865 async fn coerce_unknown_to_length() {
4866 let ast = r#"x = 2mm * 2mm
4867y = x: number(Length)"#;
4868 let e = parse_execute(ast).await.unwrap_err();
4869 assert!(
4870 e.message().contains("could not coerce"),
4871 "Error message: '{}'",
4872 e.message()
4873 );
4874
4875 let ast = r#"x = 2mm
4876y = x: number(Length)"#;
4877 let result = parse_execute(ast).await.unwrap();
4878 let mem = result.exec_state.stack();
4879 let num = mem
4880 .memory
4881 .get_from("y", result.mem_env, SourceRange::default(), 0)
4882 .unwrap()
4883 .as_ty_f64()
4884 .unwrap();
4885 assert_eq!(num.n, 2.0);
4886 assert_eq!(num.ty, NumericType::mm());
4887 }
4888
4889 #[tokio::test(flavor = "multi_thread")]
4890 async fn one_warning_unknown() {
4891 let ast = r#"
4892// Should warn once
4893a = PI * 2
4894// Should warn once
4895b = (PI * 2) / 3
4896// Should not warn
4897c = ((PI * 2) / 3): number(deg)
4898"#;
4899
4900 let result = parse_execute(ast).await.unwrap();
4901 assert_eq!(result.exec_state.issues().len(), 2);
4902 }
4903
4904 #[tokio::test(flavor = "multi_thread")]
4905 async fn non_count_indexing() {
4906 let ast = r#"x = [0, 0]
4907y = x[1mm]
4908"#;
4909 parse_execute(ast).await.unwrap_err();
4910
4911 let ast = r#"x = [0, 0]
4912y = 1deg
4913z = x[y]
4914"#;
4915 parse_execute(ast).await.unwrap_err();
4916
4917 let ast = r#"x = [0, 0]
4918y = x[0mm + 1]
4919"#;
4920 parse_execute(ast).await.unwrap_err();
4921 }
4922
4923 #[tokio::test(flavor = "multi_thread")]
4924 async fn getting_property_of_plane() {
4925 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
4926 parse_execute(&ast).await.unwrap();
4927 }
4928
4929 #[cfg(feature = "artifact-graph")]
4930 #[tokio::test(flavor = "multi_thread")]
4931 async fn no_artifacts_from_within_hole_call() {
4932 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
4937 let out = parse_execute(&ast).await.unwrap();
4938
4939 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4941
4942 let expected = 5;
4946 assert_eq!(
4947 actual_operations.len(),
4948 expected,
4949 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4950 actual_operations.len(),
4951 );
4952 }
4953
4954 #[cfg(feature = "artifact-graph")]
4955 #[tokio::test(flavor = "multi_thread")]
4956 async fn feature_tree_annotation_on_user_defined_kcl() {
4957 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4960 let out = parse_execute(&ast).await.unwrap();
4961
4962 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4964
4965 let expected = 0;
4966 assert_eq!(
4967 actual_operations.len(),
4968 expected,
4969 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4970 actual_operations.len(),
4971 );
4972 }
4973
4974 #[cfg(feature = "artifact-graph")]
4975 #[tokio::test(flavor = "multi_thread")]
4976 async fn no_feature_tree_annotation_on_user_defined_kcl() {
4977 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4980 let out = parse_execute(&ast).await.unwrap();
4981
4982 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4984
4985 let expected = 2;
4986 assert_eq!(
4987 actual_operations.len(),
4988 expected,
4989 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4990 actual_operations.len(),
4991 );
4992 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
4993 assert!(matches!(actual_operations[1], Operation::GroupEnd));
4994 }
4995
4996 #[tokio::test(flavor = "multi_thread")]
4997 async fn custom_warning() {
4998 let warn = r#"
4999a = PI * 2
5000"#;
5001 let result = parse_execute(warn).await.unwrap();
5002 assert_eq!(result.exec_state.issues().len(), 1);
5003 assert_eq!(result.exec_state.issues()[0].severity, Severity::Warning);
5004
5005 let allow = r#"
5006@warnings(allow = unknownUnits)
5007a = PI * 2
5008"#;
5009 let result = parse_execute(allow).await.unwrap();
5010 assert_eq!(result.exec_state.issues().len(), 0);
5011
5012 let deny = r#"
5013@warnings(deny = [unknownUnits])
5014a = PI * 2
5015"#;
5016 let result = parse_execute(deny).await.unwrap();
5017 assert_eq!(result.exec_state.issues().len(), 1);
5018 assert_eq!(result.exec_state.issues()[0].severity, Severity::Error);
5019 }
5020
5021 #[tokio::test(flavor = "multi_thread")]
5022 async fn sketch_block_unqualified_functions_use_sketch2() {
5023 let ast = r#"
5024s = sketch(on = XY) {
5025 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
5026 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
5027 coincident([line1.end, line2.start])
5028}
5029"#;
5030 let result = parse_execute(ast).await.unwrap();
5031 let mem = result.exec_state.stack();
5032 let sketch_value = mem
5033 .memory
5034 .get_from("s", result.mem_env, SourceRange::default(), 0)
5035 .unwrap();
5036
5037 let KclValue::Object { value, .. } = sketch_value else {
5038 panic!("Expected sketch block to return an object, got {sketch_value:?}");
5039 };
5040
5041 assert!(value.contains_key("line1"));
5042 assert!(value.contains_key("line2"));
5043 assert!(!value.contains_key("line"));
5046 assert!(!value.contains_key("coincident"));
5047 }
5048
5049 #[tokio::test(flavor = "multi_thread")]
5050 async fn solver_module_is_not_available_outside_sketch_blocks() {
5051 let err = parse_execute("a = solver::ORIGIN").await.unwrap_err();
5052 assert!(err.message().contains("solver"), "Error message: '{}'", err.message());
5053
5054 let err = parse_execute(
5055 r#"@settings(experimentalFeatures = allow)
5056
5057import "std::solver""#,
5058 )
5059 .await
5060 .unwrap_err();
5061 assert!(
5062 err.message().contains("only available inside sketch blocks"),
5063 "Error message: '{}'",
5064 err.message()
5065 );
5066 }
5067
5068 #[tokio::test(flavor = "multi_thread")]
5069 async fn cannot_solid_extrude_an_open_profile() {
5070 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
5073 let program = crate::Program::parse_no_errs(&code).expect("should parse");
5074 let exec_ctxt = ExecutorContext::new_mock(None).await;
5075 let mut exec_state = ExecState::new(&exec_ctxt);
5076
5077 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
5078 assert!(matches!(err, KclError::Semantic { .. }));
5079 exec_ctxt.close().await;
5080 }
5081}