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