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 let Some(id) = exec_state.id_for_module(resolved_path) {
867 return Ok(id);
868 }
869
870 let id = exec_state.next_module_id();
871 exec_state.add_path_to_source_id(resolved_path.clone(), id);
873 let source = resolved_path.source(&self.fs, source_range).await?;
874 exec_state.add_id_to_source(id, source.clone());
875 let parsed = crate::parsing::parse_str(&source.source, id)
876 .parse_errs_as_err()
877 .unwrap();
878 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
879 Ok(id)
880 }
881 }
882 }
883
884 pub(super) async fn exec_module_for_items(
885 &self,
886 module_id: ModuleId,
887 exec_state: &mut ExecState,
888 source_range: SourceRange,
889 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
890 let path = exec_state.global.module_infos[&module_id].path.clone();
891 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
892 let result = match &mut repr {
895 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
896 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
897 ModuleRepr::Kcl(program, cache) => self
898 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
899 .await
900 .map(|outcome| {
901 *cache = Some(outcome.clone());
902 (outcome.environment, outcome.exports)
903 }),
904 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
905 "Cannot import items from foreign modules".to_owned(),
906 vec![geom.source_range],
907 ))),
908 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
909 };
910
911 exec_state.global.module_infos[&module_id].restore_repr(repr);
912 result
913 }
914
915 async fn exec_module_for_result(
916 &self,
917 module_id: ModuleId,
918 exec_state: &mut ExecState,
919 source_range: SourceRange,
920 ) -> Result<Option<KclValue>, KclError> {
921 let path = exec_state.global.module_infos[&module_id].path.clone();
922 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
923 let result = match &mut repr {
926 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
927 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
928 ModuleRepr::Kcl(program, cached_items) => {
929 let result = self
930 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
931 .await;
932 match result {
933 Ok(outcome) => {
934 let value = outcome.last_expr.clone();
935 *cached_items = Some(outcome);
936 Ok(value)
937 }
938 Err(e) => Err(e),
939 }
940 }
941 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
942 ModuleRepr::Foreign(geom, cached) => {
943 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
944 .await
945 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
946
947 match result {
948 Ok(val) => {
949 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
950 Ok(val)
951 }
952 Err(e) => Err(e),
953 }
954 }
955 ModuleRepr::Dummy => unreachable!(),
956 };
957
958 exec_state.global.module_infos[&module_id].restore_repr(repr);
959
960 result
961 }
962
963 pub async fn exec_module_from_ast(
964 &self,
965 program: &Node<Program>,
966 module_id: ModuleId,
967 path: &ModulePath,
968 exec_state: &mut ExecState,
969 source_range: SourceRange,
970 preserve_mem: PreserveMem,
971 ) -> Result<ModuleExecutionOutcome, KclError> {
972 exec_state.global.mod_loader.enter_module(path);
973 let result = self
974 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
975 .await;
976 exec_state.global.mod_loader.leave_module(path, source_range)?;
977
978 result.map_err(|(err, _, _)| {
981 match err {
982 KclError::ImportCycle { .. } => {
983 err.override_source_ranges(vec![source_range])
985 }
986 KclError::EngineHangup { .. } | KclError::EngineInternal { .. } => {
987 err.override_source_ranges(vec![source_range])
990 }
991 _ => {
992 KclError::new_semantic(KclErrorDetails::new(
994 format!(
995 "Error loading imported file ({path}). Open it to view more details.\n {}",
996 err.message()
997 ),
998 vec![source_range],
999 ))
1000 }
1001 }
1002 })
1003 }
1004
1005 #[async_recursion]
1006 pub(crate) async fn execute_expr<'a: 'async_recursion>(
1007 &self,
1008 init: &Expr,
1009 exec_state: &mut ExecState,
1010 metadata: &Metadata,
1011 annotations: &[Node<Annotation>],
1012 statement_kind: StatementKind<'a>,
1013 ) -> Result<KclValueControlFlow, KclError> {
1014 let item = match init {
1015 Expr::None(none) => KclValue::from(none).continue_(),
1016 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state).continue_(),
1017 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?.continue_(),
1018 Expr::Name(name) => {
1019 let being_declared = exec_state.mod_local.being_declared.clone();
1020 let value = name
1021 .get_result(exec_state, self)
1022 .await
1023 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
1024 .clone();
1025 if let KclValue::Module { value: module_id, meta } = value {
1026 self.exec_module_for_result(
1027 module_id,
1028 exec_state,
1029 metadata.source_range
1030 ).await?.map(|v| v.continue_())
1031 .unwrap_or_else(|| {
1032 exec_state.warn(CompilationIssue::err(
1033 metadata.source_range,
1034 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
1035 ),
1036 annotations::WARN_MOD_RETURN_VALUE);
1037
1038 let mut new_meta = vec![metadata.to_owned()];
1039 new_meta.extend(meta);
1040 KclValue::KclNone {
1041 value: Default::default(),
1042 meta: new_meta,
1043 }.continue_()
1044 })
1045 } else {
1046 value.continue_()
1047 }
1048 }
1049 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
1050 Expr::FunctionExpression(function_expression) => {
1051 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
1052 let experimental = attrs.map(|a| a.experimental).unwrap_or_default();
1053
1054 let include_in_feature_tree = attrs.unwrap_or_default().include_in_feature_tree;
1056 let (mut closure, placeholder_env_ref) = if let Some(attrs) = attrs
1057 && (attrs.impl_ == annotations::Impl::Rust
1058 || attrs.impl_ == annotations::Impl::RustConstrainable
1059 || attrs.impl_ == annotations::Impl::RustConstraint)
1060 {
1061 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
1062 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
1063 (
1064 KclValue::Function {
1065 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
1066 meta: vec![metadata.to_owned()],
1067 },
1068 None,
1069 )
1070 } else {
1071 return Err(KclError::new_semantic(KclErrorDetails::new(
1072 "Rust implementation of functions is restricted to the standard library".to_owned(),
1073 vec![metadata.source_range],
1074 )));
1075 }
1076 } else {
1077 let std_props = function_expression
1078 .name_str()
1079 .and_then(|name| exec_state.mod_local.path.build_std_fully_qualified_name(name))
1080 .map(|name| StdFnProps::default(&name));
1081 let (env_ref, placeholder_env_ref) = if function_expression.name.is_some() {
1085 let dummy = EnvironmentRef::dummy();
1088 (dummy, Some(dummy))
1089 } else {
1090 (exec_state.mut_stack().snapshot(), None)
1091 };
1092 (
1093 KclValue::Function {
1094 value: Box::new(FunctionSource::kcl(
1095 function_expression.clone(),
1096 env_ref,
1097 KclFunctionSourceParams {
1098 std_props,
1099 experimental,
1100 include_in_feature_tree,
1101 },
1102 )),
1103 meta: vec![metadata.to_owned()],
1104 },
1105 placeholder_env_ref,
1106 )
1107 };
1108
1109 if let Some(fn_name) = &function_expression.name {
1112 if let Some(placeholder_env_ref) = placeholder_env_ref {
1116 closure = exec_state.mut_stack().add_recursive_closure(
1117 fn_name.name.to_owned(),
1118 closure,
1119 placeholder_env_ref,
1120 metadata.source_range,
1121 )?;
1122 } else {
1123 exec_state
1125 .mut_stack()
1126 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
1127 }
1128 }
1129
1130 closure.continue_()
1131 }
1132 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
1133 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
1134 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
1135 StatementKind::Declaration { name } => {
1136 let message = format!(
1137 "you cannot declare variable {name} as %, because % can only be used in function calls"
1138 );
1139
1140 return Err(KclError::new_semantic(KclErrorDetails::new(
1141 message,
1142 vec![pipe_substitution.into()],
1143 )));
1144 }
1145 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
1146 Some(x) => x.continue_(),
1147 None => {
1148 return Err(KclError::new_semantic(KclErrorDetails::new(
1149 "cannot use % outside a pipe expression".to_owned(),
1150 vec![pipe_substitution.into()],
1151 )));
1152 }
1153 },
1154 },
1155 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
1156 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
1157 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
1158 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
1159 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
1160 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
1161 Expr::LabelledExpression(expr) => {
1162 let value_cf = self
1163 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
1164 .await?;
1165 let value = control_continue!(value_cf);
1166 exec_state
1167 .mut_stack()
1168 .add(expr.label.name.clone(), value.clone(), init.into())?;
1169 value.continue_()
1171 }
1172 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
1173 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
1174 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
1175 };
1176 Ok(item)
1177 }
1178}
1179
1180fn sketch_mode_should_skip(expr: &Expr) -> bool {
1183 match expr {
1184 Expr::SketchBlock(sketch_block) => !sketch_block.is_being_edited,
1185 _ => true,
1186 }
1187}
1188
1189fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
1192 let KclError::UndefinedValue { name, mut details } = e else {
1193 return e;
1194 };
1195 if let (Some(name0), Some(name1)) = (&being_declared, &name)
1199 && name0 == name1
1200 {
1201 details.message = format!(
1202 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
1203 );
1204 }
1205 KclError::UndefinedValue { details, name }
1206}
1207
1208impl Node<AscribedExpression> {
1209 #[async_recursion]
1210 pub(super) async fn get_result(
1211 &self,
1212 exec_state: &mut ExecState,
1213 ctx: &ExecutorContext,
1214 ) -> Result<KclValueControlFlow, KclError> {
1215 let metadata = Metadata {
1216 source_range: SourceRange::from(self),
1217 };
1218 let result = ctx
1219 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
1220 .await?;
1221 let result = control_continue!(result);
1222 apply_ascription(&result, &self.ty, exec_state, self.into()).map(KclValue::continue_)
1223 }
1224}
1225
1226impl Node<SketchBlock> {
1227 pub(super) async fn get_result(
1228 &self,
1229 exec_state: &mut ExecState,
1230 ctx: &ExecutorContext,
1231 ) -> Result<KclValueControlFlow, KclError> {
1232 if exec_state.mod_local.sketch_block.is_some() {
1233 return Err(KclError::new_semantic(KclErrorDetails::new(
1235 "Cannot execute a sketch block from within another sketch block".to_owned(),
1236 vec![SourceRange::from(self)],
1237 )));
1238 }
1239
1240 let range = SourceRange::from(self);
1241
1242 let (sketch_id, sketch_surface) = match self.exec_arguments(exec_state, ctx).await {
1244 Ok(x) => x,
1245 Err(cf_error) => match cf_error {
1246 EarlyReturn::Value(cf_value) => return Ok(cf_value),
1248 EarlyReturn::Error(err) => return Err(err),
1249 },
1250 };
1251 let on_object_id = if let Some(object_id) = sketch_surface.object_id() {
1252 object_id
1253 } else {
1254 let message = "The `on` argument should have an object after ensure_sketch_plane_in_engine".to_owned();
1255 debug_assert!(false, "{message}");
1256 return Err(internal_err(message, range));
1257 };
1258 #[cfg(not(feature = "artifact-graph"))]
1259 let _ = on_object_id;
1260 #[cfg(not(feature = "artifact-graph"))]
1261 let _ = sketch_id;
1262 #[cfg(feature = "artifact-graph")]
1263 let sketch_ctor_on = sketch_on_frontend_plane(&self.arguments, on_object_id);
1264 #[cfg(feature = "artifact-graph")]
1265 let sketch_block_artifact_id = {
1266 use crate::execution::Artifact;
1267 use crate::execution::ArtifactId;
1268 use crate::execution::CodeRef;
1269 use crate::execution::SketchBlock;
1270 use crate::front::Plane;
1271 use crate::front::SourceRef;
1272
1273 let on_object = exec_state.mod_local.artifacts.scene_object_by_id(on_object_id);
1274
1275 let plane_artifact_id = on_object.map(|object| object.artifact_id);
1277
1278 let standard_plane = match &sketch_ctor_on {
1279 Plane::Default(plane) => Some(*plane),
1280 Plane::Object(_) => None,
1281 };
1282
1283 let artifact_id = ArtifactId::from(exec_state.next_uuid());
1284 let sketch_scene_object = Object {
1286 id: sketch_id,
1287 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
1288 args: crate::front::SketchCtor { on: sketch_ctor_on },
1289 plane: on_object_id,
1290 segments: Default::default(),
1291 constraints: Default::default(),
1292 }),
1293 label: Default::default(),
1294 comments: Default::default(),
1295 artifact_id,
1296 source: SourceRef::new(self.into(), self.node_path.clone()),
1297 };
1298 exec_state.set_scene_object(sketch_scene_object);
1299
1300 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
1302 id: artifact_id,
1303 standard_plane,
1304 plane_id: plane_artifact_id,
1305 path_id: None,
1309 code_ref: CodeRef::placeholder(range),
1310 sketch_id,
1311 }));
1312 artifact_id
1313 };
1314
1315 let (return_result, variables, sketch_block_state) = {
1316 self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
1318
1319 #[cfg(feature = "artifact-graph")]
1321 let initial_sketch_block_state = {
1322 SketchBlockState {
1323 sketch_id: Some(sketch_id),
1324 ..Default::default()
1325 }
1326 };
1327 #[cfg(not(feature = "artifact-graph"))]
1328 let initial_sketch_block_state = SketchBlockState::default();
1329
1330 let original_value = exec_state.mod_local.sketch_block.replace(initial_sketch_block_state);
1331
1332 let original_sketch_mode = std::mem::replace(&mut exec_state.mod_local.sketch_mode, false);
1335
1336 let (result, block_variables) = match self.load_sketch2_into_current_scope(exec_state, ctx, range).await {
1341 Ok(()) => {
1342 let parent = exec_state.mut_stack().snapshot();
1343 exec_state.mut_stack().push_new_env_for_call(parent);
1344 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1345 let block_variables = exec_state
1346 .stack()
1347 .find_all_in_current_env()
1348 .map(|(name, value)| (name.clone(), value.clone()))
1349 .collect::<IndexMap<_, _>>();
1350 exec_state.mut_stack().pop_env();
1351 (result, block_variables)
1352 }
1353 Err(err) => (Err(err), IndexMap::new()),
1354 };
1355
1356 exec_state.mod_local.sketch_mode = original_sketch_mode;
1357
1358 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1359
1360 exec_state.mut_stack().pop_env();
1362
1363 (result, block_variables, sketch_block_state)
1364 };
1365
1366 return_result?;
1368 let Some(sketch_block_state) = sketch_block_state else {
1369 debug_assert!(false, "Sketch block state should still be set to Some from just above");
1370 return Err(internal_err(
1371 "Sketch block state should still be set to Some from just above",
1372 self,
1373 ));
1374 };
1375 #[cfg(feature = "artifact-graph")]
1376 let mut sketch_block_state = sketch_block_state;
1377
1378 let constraints = sketch_block_state
1380 .solver_constraints
1381 .iter()
1382 .cloned()
1383 .map(ezpz::ConstraintRequest::highest_priority)
1384 .chain(
1385 sketch_block_state
1387 .solver_optional_constraints
1388 .iter()
1389 .cloned()
1390 .map(|c| ezpz::ConstraintRequest::new(c, 1)),
1391 )
1392 .collect::<Vec<_>>();
1393 let initial_guesses = sketch_block_state
1394 .sketch_vars
1395 .iter()
1396 .map(|v| {
1397 let Some(sketch_var) = v.as_sketch_var() else {
1398 return Err(internal_err("Expected sketch variable", self));
1399 };
1400 let constraint_id = sketch_var.id.to_constraint_id(range)?;
1401 let number_value = KclValue::Number {
1403 value: sketch_var.initial_value,
1404 ty: sketch_var.ty,
1405 meta: sketch_var.meta.clone(),
1406 };
1407 let initial_guess_value = normalize_to_solver_distance_unit(
1408 &number_value,
1409 v.into(),
1410 exec_state,
1411 "sketch variable initial value",
1412 )?;
1413 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1414 n.n
1415 } else {
1416 let message = format!(
1417 "Expected number after coercion, but found {}",
1418 initial_guess_value.human_friendly_type()
1419 );
1420 debug_assert!(false, "{}", &message);
1421 return Err(internal_err(message, self));
1422 };
1423 Ok((constraint_id, initial_guess))
1424 })
1425 .collect::<Result<Vec<_>, KclError>>()?;
1426 let config = ezpz::Config::default()
1428 .with_max_iterations(50)
1429 .with_convergence_tolerance(SOLVER_CONVERGENCE_TOLERANCE);
1430 let solve_result = if exec_state.mod_local.freedom_analysis {
1431 ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
1432 let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
1433 (outcome.outcome, Some(freedom_analysis))
1434 })
1435 } else {
1436 ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1437 };
1438 let num_required_constraints = sketch_block_state.solver_constraints.len();
1440 let all_constraints: Vec<ezpz::Constraint> = sketch_block_state
1441 .solver_constraints
1442 .iter()
1443 .cloned()
1444 .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
1445 .collect();
1446
1447 let (solve_outcome, solve_analysis) = match solve_result {
1448 Ok((solved, freedom)) => {
1449 let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
1450 (outcome, freedom)
1451 }
1452 Err(failure) => {
1453 match &failure.error {
1454 NonLinearSystemError::FaerMatrix { .. }
1455 | NonLinearSystemError::Faer { .. }
1456 | NonLinearSystemError::FaerSolve { .. }
1457 | NonLinearSystemError::FaerSvd(..)
1458 | NonLinearSystemError::DidNotConverge => {
1459 exec_state.warn(
1462 CompilationIssue::err(range, "Constraint solver failed to find a solution".to_owned()),
1463 annotations::WARN_SOLVER,
1464 );
1465 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1466 (
1467 Solved {
1468 final_values,
1469 iterations: Default::default(),
1470 warnings: failure.warnings,
1471 priority_solved: Default::default(),
1472 variables_in_conflicts: Default::default(),
1473 },
1474 None,
1475 )
1476 }
1477 NonLinearSystemError::EmptySystemNotAllowed
1478 | NonLinearSystemError::WrongNumberGuesses { .. }
1479 | NonLinearSystemError::MissingGuess { .. }
1480 | NonLinearSystemError::NotFound(..) => {
1481 #[cfg(target_arch = "wasm32")]
1484 web_sys::console::error_1(
1485 &format!("Internal error from constraint solver: {}", &failure.error).into(),
1486 );
1487 return Err(internal_err(
1488 format!("Internal error from constraint solver: {}", &failure.error),
1489 self,
1490 ));
1491 }
1492 _ => {
1493 return Err(internal_err(
1495 format!("Error from constraint solver: {}", &failure.error),
1496 self,
1497 ));
1498 }
1499 }
1500 }
1501 };
1502 #[cfg(not(feature = "artifact-graph"))]
1503 let _ = solve_analysis;
1504 for warning in &solve_outcome.warnings {
1506 let message = if let Some(index) = warning.about_constraint.as_ref() {
1507 format!("{}; constraint index {}", &warning.content, index)
1508 } else {
1509 format!("{}", &warning.content)
1510 };
1511 exec_state.warn(CompilationIssue::err(range, message), annotations::WARN_SOLVER);
1512 }
1513 let sketch_engine_id = exec_state.next_uuid();
1515 let solution_ty = solver_numeric_type(exec_state);
1516 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1517 for unsolved_segment in &sketch_block_state.needed_by_engine {
1518 solved_segments.push(substitute_sketch_var_in_segment(
1519 unsolved_segment.clone(),
1520 &sketch_surface,
1521 sketch_engine_id,
1522 None,
1523 &solve_outcome,
1524 solver_numeric_type(exec_state),
1525 solve_analysis.as_ref(),
1526 )?);
1527 }
1528 #[cfg(feature = "artifact-graph")]
1529 {
1530 exec_state.mod_local.artifacts.var_solutions =
1536 sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
1537 }
1538
1539 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1541
1542 let sketch = create_segments_in_engine(
1544 &sketch_surface,
1545 sketch_engine_id,
1546 &mut solved_segments,
1547 &sketch_block_state.segment_tags,
1548 ctx,
1549 exec_state,
1550 range,
1551 )
1552 .await?;
1553
1554 #[cfg(feature = "artifact-graph")]
1555 {
1556 use crate::execution::Artifact;
1557 if let Some(sketch_artifact_id) = sketch.as_ref().map(|s| s.artifact_id) {
1559 if let Some(Artifact::SketchBlock(sketch_block_artifact)) =
1560 exec_state.artifact_mut(sketch_block_artifact_id)
1561 {
1562 sketch_block_artifact.path_id = Some(sketch_artifact_id);
1563 } else {
1564 let message = "Sketch block artifact not found, so path couldn't be linked to it".to_owned();
1565 debug_assert!(false, "{message}");
1566 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1567 }
1568 }
1569 }
1570
1571 let variables = substitute_sketch_vars(
1576 variables,
1577 &sketch_surface,
1578 sketch_engine_id,
1579 sketch.as_ref(),
1580 &solve_outcome,
1581 solution_ty,
1582 solve_analysis.as_ref(),
1583 )?;
1584
1585 #[cfg(not(feature = "artifact-graph"))]
1586 drop(scene_objects);
1587 #[cfg(feature = "artifact-graph")]
1588 {
1589 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1590 for scene_object in scene_objects {
1591 segment_object_ids.push(scene_object.id);
1592 exec_state.set_scene_object(scene_object);
1594 }
1595 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
1597 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1598 debug_assert!(false, "{}", &message);
1599 return Err(internal_err(message, range));
1600 };
1601 let ObjectKind::Sketch(sketch) = &mut sketch_object.kind else {
1602 let message = format!(
1603 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1604 sketch_id, sketch_object
1605 );
1606 debug_assert!(
1607 false,
1608 "{}; scene_objects={:#?}",
1609 &message, &exec_state.mod_local.artifacts.scene_objects
1610 );
1611 return Err(internal_err(message, range));
1612 };
1613 sketch.segments.extend(segment_object_ids);
1614 sketch
1616 .constraints
1617 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1618
1619 exec_state.push_op(Operation::SketchSolve {
1621 sketch_id,
1622 node_path: NodePath::placeholder(),
1623 source_range: range,
1624 });
1625 }
1626
1627 let properties = self.sketch_properties(sketch, variables);
1628 let metadata = Metadata {
1629 source_range: SourceRange::from(self),
1630 };
1631 let return_value = KclValue::Object {
1632 value: properties,
1633 constrainable: Default::default(),
1634 meta: vec![metadata],
1635 };
1636 Ok(if self.is_being_edited {
1637 return_value.exit()
1640 } else {
1641 return_value.continue_()
1642 })
1643 }
1644
1645 async fn exec_arguments(
1655 &self,
1656 exec_state: &mut ExecState,
1657 ctx: &ExecutorContext,
1658 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
1659 let range = SourceRange::from(self);
1660
1661 if !exec_state.sketch_mode() {
1662 let mut labeled = IndexMap::new();
1668 for labeled_arg in &self.arguments {
1669 let source_range = SourceRange::from(labeled_arg.arg.clone());
1670 let metadata = Metadata { source_range };
1671 let value_cf = ctx
1672 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
1673 .await?;
1674 let value = early_return!(value_cf);
1675 let arg = Arg::new(value, source_range);
1676 match &labeled_arg.label {
1677 Some(label) => {
1678 labeled.insert(label.name.clone(), arg);
1679 }
1680 None => {
1681 let name = labeled_arg.arg.ident_name();
1682 if let Some(name) = name {
1683 labeled.insert(name.to_owned(), arg);
1684 } else {
1685 return Err(KclError::new_semantic(KclErrorDetails::new(
1686 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
1687 vec![SourceRange::from(&labeled_arg.arg)],
1688 ))
1689 .into());
1690 }
1691 }
1692 }
1693 }
1694 let mut args = Args::new_no_args(
1695 range,
1696 self.node_path.clone(),
1697 ctx.clone(),
1698 Some("sketch block".to_owned()),
1699 );
1700 args.labeled = labeled;
1701
1702 let arg_on_value: KclValue =
1703 args.get_kw_arg(SKETCH_BLOCK_PARAM_ON, &RuntimeType::sketch_or_surface(), exec_state)?;
1704
1705 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
1706 let message =
1707 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
1708 debug_assert!(false, "{message}");
1709 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
1710 };
1711 let mut sketch_surface = arg_on.into_sketch_surface();
1712
1713 match &mut sketch_surface {
1716 SketchSurface::Plane(plane) => {
1717 ensure_sketch_plane_in_engine(plane, exec_state, ctx, range, self.node_path.clone()).await?;
1719 }
1720 SketchSurface::Face(_) => {
1721 }
1723 }
1724
1725 let sketch_id = exec_state.next_object_id();
1731 #[cfg(feature = "artifact-graph")]
1732 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
1733 let on_cache_name = sketch_on_cache_name(sketch_id);
1734 exec_state.mut_stack().add(on_cache_name, arg_on_value, range)?;
1736
1737 Ok((sketch_id, sketch_surface))
1738 } else {
1739 let sketch_id = exec_state.next_object_id();
1746 #[cfg(feature = "artifact-graph")]
1747 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
1748 let on_cache_name = sketch_on_cache_name(sketch_id);
1749 let arg_on_value = exec_state.stack().get(&on_cache_name, range)?.clone();
1750
1751 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
1752 let message =
1753 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
1754 debug_assert!(false, "{message}");
1755 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
1756 };
1757 let mut sketch_surface = arg_on.into_sketch_surface();
1758
1759 if sketch_surface.object_id().is_none() {
1762 #[cfg(not(feature = "artifact-graph"))]
1763 {
1764 sketch_surface.set_object_id(exec_state.next_object_id());
1767 }
1768 #[cfg(feature = "artifact-graph")]
1769 {
1770 let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
1773 return Err(internal_err(
1774 "In sketch mode, the `on` plane argument must refer to an existing plane object.",
1775 range,
1776 )
1777 .into());
1778 };
1779 sketch_surface.set_object_id(last_object.id);
1780 }
1781 }
1782
1783 Ok((sketch_id, sketch_surface))
1784 }
1785 }
1786
1787 async fn load_sketch2_into_current_scope(
1788 &self,
1789 exec_state: &mut ExecState,
1790 ctx: &ExecutorContext,
1791 source_range: SourceRange,
1792 ) -> Result<(), KclError> {
1793 let path = vec!["std".to_owned(), "solver".to_owned()];
1794 let resolved_path = ModulePath::from_std_import_path(&path)?;
1795 let module_id = ctx
1796 .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
1797 .await?;
1798 let (env_ref, exports) = ctx.exec_module_for_items(module_id, exec_state, source_range).await?;
1799
1800 for name in exports {
1801 let value = exec_state
1802 .stack()
1803 .memory
1804 .get_from(&name, env_ref, source_range, 0)?
1805 .clone();
1806 exec_state.mut_stack().add(name, value, source_range)?;
1807 }
1808 Ok(())
1809 }
1810
1811 pub(crate) fn sketch_properties(
1815 &self,
1816 sketch: Option<Sketch>,
1817 variables: HashMap<String, KclValue>,
1818 ) -> HashMap<String, KclValue> {
1819 let Some(sketch) = sketch else {
1820 return variables;
1823 };
1824
1825 let mut properties = variables;
1826
1827 let sketch_value = KclValue::Sketch {
1828 value: Box::new(sketch),
1829 };
1830 let mut meta_map = HashMap::with_capacity(1);
1831 meta_map.insert(SKETCH_OBJECT_META_SKETCH.to_owned(), sketch_value);
1832 let meta_value = KclValue::Object {
1833 value: meta_map,
1834 constrainable: false,
1835 meta: vec![Metadata {
1836 source_range: SourceRange::from(self),
1837 }],
1838 };
1839
1840 properties.insert(SKETCH_OBJECT_META.to_owned(), meta_value);
1841
1842 properties
1843 }
1844}
1845
1846impl SketchBlock {
1847 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1848 exec_state.mut_stack().push_new_env_for_call(parent);
1849 }
1850}
1851
1852impl Node<SketchVar> {
1853 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1854 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1855 return Err(KclError::new_semantic(KclErrorDetails::new(
1856 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1857 vec![SourceRange::from(self)],
1858 )));
1859 };
1860 let id = sketch_block_state.next_sketch_var_id();
1861 let sketch_var = if let Some(initial) = &self.initial {
1862 KclValue::from_sketch_var_literal(initial, id, exec_state)
1863 } else {
1864 let metadata = Metadata {
1865 source_range: SourceRange::from(self),
1866 };
1867
1868 KclValue::SketchVar {
1869 value: Box::new(super::SketchVar {
1870 id,
1871 initial_value: 0.0,
1872 ty: NumericType::default(),
1873 meta: vec![metadata],
1874 }),
1875 }
1876 };
1877
1878 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1879 return Err(KclError::new_semantic(KclErrorDetails::new(
1880 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1881 vec![SourceRange::from(self)],
1882 )));
1883 };
1884 sketch_block_state.sketch_vars.push(sketch_var.clone());
1885
1886 Ok(sketch_var)
1887 }
1888}
1889
1890fn apply_ascription(
1891 value: &KclValue,
1892 ty: &Node<Type>,
1893 exec_state: &mut ExecState,
1894 source_range: SourceRange,
1895) -> Result<KclValue, KclError> {
1896 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false, false)
1897 .map_err(|e| KclError::new_semantic(e.into()))?;
1898
1899 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1900 exec_state.clear_units_warnings(&source_range);
1901 }
1902
1903 value.coerce(&ty, false, exec_state).map_err(|_| {
1904 let suggestion = if ty == RuntimeType::length() {
1905 ", you might try coercing to a fully specified numeric type such as `mm`"
1906 } else if ty == RuntimeType::angle() {
1907 ", you might try coercing to a fully specified numeric type such as `deg`"
1908 } else {
1909 ""
1910 };
1911 let ty_str = if let Some(ty) = value.principal_type() {
1912 format!("(with type `{ty}`) ")
1913 } else {
1914 String::new()
1915 };
1916 KclError::new_semantic(KclErrorDetails::new(
1917 format!(
1918 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1919 value.human_friendly_type()
1920 ),
1921 vec![source_range],
1922 ))
1923 })
1924}
1925
1926impl BinaryPart {
1927 #[async_recursion]
1928 pub(super) async fn get_result(
1929 &self,
1930 exec_state: &mut ExecState,
1931 ctx: &ExecutorContext,
1932 ) -> Result<KclValueControlFlow, KclError> {
1933 match self {
1934 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
1935 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned().map(KclValue::continue_),
1936 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1937 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1938 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1939 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1940 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1941 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1942 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1943 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1944 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1945 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
1946 }
1947 }
1948}
1949
1950impl Node<Name> {
1951 pub(super) async fn get_result<'a>(
1952 &self,
1953 exec_state: &'a mut ExecState,
1954 ctx: &ExecutorContext,
1955 ) -> Result<&'a KclValue, KclError> {
1956 let being_declared = exec_state.mod_local.being_declared.clone();
1957 self.get_result_inner(exec_state, ctx)
1958 .await
1959 .map_err(|e| var_in_own_ref_err(e, &being_declared))
1960 }
1961
1962 async fn get_result_inner<'a>(
1963 &self,
1964 exec_state: &'a mut ExecState,
1965 ctx: &ExecutorContext,
1966 ) -> Result<&'a KclValue, KclError> {
1967 if self.abs_path {
1968 return Err(KclError::new_semantic(KclErrorDetails::new(
1969 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1970 self.as_source_ranges(),
1971 )));
1972 }
1973
1974 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1975
1976 if self.path.is_empty() {
1977 let item_value = exec_state.stack().get(&self.name.name, self.into());
1978 if item_value.is_ok() {
1979 return item_value;
1980 }
1981 return exec_state.stack().get(&mod_name, self.into());
1982 }
1983
1984 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1985 for p in &self.path {
1986 let value = match mem_spec {
1987 Some((env, exports)) => {
1988 if !exports.contains(&p.name) {
1989 return Err(KclError::new_semantic(KclErrorDetails::new(
1990 format!("Item {} not found in module's exported items", p.name),
1991 p.as_source_ranges(),
1992 )));
1993 }
1994
1995 exec_state
1996 .stack()
1997 .memory
1998 .get_from(&p.name, env, p.as_source_range(), 0)?
1999 }
2000 None => exec_state
2001 .stack()
2002 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
2003 };
2004
2005 let KclValue::Module { value: module_id, .. } = value else {
2006 return Err(KclError::new_semantic(KclErrorDetails::new(
2007 format!(
2008 "Identifier in path must refer to a module, found {}",
2009 value.human_friendly_type()
2010 ),
2011 p.as_source_ranges(),
2012 )));
2013 };
2014
2015 mem_spec = Some(
2016 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
2017 .await?,
2018 );
2019 }
2020
2021 let (env, exports) = mem_spec.unwrap();
2022
2023 let item_exported = exports.contains(&self.name.name);
2024 let item_value = exec_state
2025 .stack()
2026 .memory
2027 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
2028
2029 if item_exported && item_value.is_ok() {
2031 return item_value;
2032 }
2033
2034 let mod_exported = exports.contains(&mod_name);
2035 let mod_value = exec_state
2036 .stack()
2037 .memory
2038 .get_from(&mod_name, env, self.name.as_source_range(), 0);
2039
2040 if mod_exported && mod_value.is_ok() {
2042 return mod_value;
2043 }
2044
2045 if item_value.is_err() && mod_value.is_err() {
2047 return item_value;
2048 }
2049
2050 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
2052 Err(KclError::new_semantic(KclErrorDetails::new(
2053 format!("Item {} not found in module's exported items", self.name.name),
2054 self.name.as_source_ranges(),
2055 )))
2056 }
2057}
2058
2059impl Node<MemberExpression> {
2060 async fn get_result(
2061 &self,
2062 exec_state: &mut ExecState,
2063 ctx: &ExecutorContext,
2064 ) -> Result<KclValueControlFlow, KclError> {
2065 let meta = Metadata {
2066 source_range: SourceRange::from(self),
2067 };
2068 let property = Property::try_from(
2071 self.computed,
2072 self.property.clone(),
2073 exec_state,
2074 self.into(),
2075 ctx,
2076 &meta,
2077 &[],
2078 StatementKind::Expression,
2079 )
2080 .await?;
2081 let object_cf = ctx
2082 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
2083 .await?;
2084 let object = control_continue!(object_cf);
2085
2086 match (object, property, self.computed) {
2088 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
2089 "at" => match &segment.repr {
2090 SegmentRepr::Unsolved { segment } => {
2091 match &segment.kind {
2092 UnsolvedSegmentKind::Point { position, .. } => {
2093 Ok(KclValue::HomArray {
2095 value: vec![
2096 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
2097 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
2098 ],
2099 ty: RuntimeType::any(),
2100 }
2101 .continue_())
2102 }
2103 _ => Err(KclError::new_undefined_value(
2104 KclErrorDetails::new(
2105 format!("Property '{property}' not found in segment"),
2106 vec![self.clone().into()],
2107 ),
2108 None,
2109 )),
2110 }
2111 }
2112 SegmentRepr::Solved { segment } => {
2113 match &segment.kind {
2114 SegmentKind::Point { position, .. } => {
2115 Ok(KclValue::array_from_point2d(
2117 [position[0].n, position[1].n],
2118 position[0].ty,
2119 segment.meta.clone(),
2120 )
2121 .continue_())
2122 }
2123 _ => Err(KclError::new_undefined_value(
2124 KclErrorDetails::new(
2125 format!("Property '{property}' not found in segment"),
2126 vec![self.clone().into()],
2127 ),
2128 None,
2129 )),
2130 }
2131 }
2132 },
2133 "start" => match &segment.repr {
2134 SegmentRepr::Unsolved { segment } => match &segment.kind {
2135 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2136 KclErrorDetails::new(
2137 format!("Property '{property}' not found in point segment"),
2138 vec![self.clone().into()],
2139 ),
2140 None,
2141 )),
2142 UnsolvedSegmentKind::Line {
2143 start,
2144 ctor,
2145 start_object_id,
2146 ..
2147 } => Ok(KclValue::Segment {
2148 value: Box::new(AbstractSegment {
2149 repr: SegmentRepr::Unsolved {
2150 segment: Box::new(UnsolvedSegment {
2151 id: segment.id,
2152 object_id: *start_object_id,
2153 kind: UnsolvedSegmentKind::Point {
2154 position: start.clone(),
2155 ctor: Box::new(PointCtor {
2156 position: ctor.start.clone(),
2157 }),
2158 },
2159 tag: segment.tag.clone(),
2160 node_path: segment.node_path.clone(),
2161 meta: segment.meta.clone(),
2162 }),
2163 },
2164 meta: segment.meta.clone(),
2165 }),
2166 }
2167 .continue_()),
2168 UnsolvedSegmentKind::Arc {
2169 start,
2170 ctor,
2171 start_object_id,
2172 ..
2173 } => Ok(KclValue::Segment {
2174 value: Box::new(AbstractSegment {
2175 repr: SegmentRepr::Unsolved {
2176 segment: Box::new(UnsolvedSegment {
2177 id: segment.id,
2178 object_id: *start_object_id,
2179 kind: UnsolvedSegmentKind::Point {
2180 position: start.clone(),
2181 ctor: Box::new(PointCtor {
2182 position: ctor.start.clone(),
2183 }),
2184 },
2185 tag: segment.tag.clone(),
2186 node_path: segment.node_path.clone(),
2187 meta: segment.meta.clone(),
2188 }),
2189 },
2190 meta: segment.meta.clone(),
2191 }),
2192 }
2193 .continue_()),
2194 UnsolvedSegmentKind::Circle {
2195 start,
2196 ctor,
2197 start_object_id,
2198 ..
2199 } => Ok(KclValue::Segment {
2200 value: Box::new(AbstractSegment {
2201 repr: SegmentRepr::Unsolved {
2202 segment: Box::new(UnsolvedSegment {
2203 id: segment.id,
2204 object_id: *start_object_id,
2205 kind: UnsolvedSegmentKind::Point {
2206 position: start.clone(),
2207 ctor: Box::new(PointCtor {
2208 position: ctor.start.clone(),
2209 }),
2210 },
2211 tag: segment.tag.clone(),
2212 node_path: segment.node_path.clone(),
2213 meta: segment.meta.clone(),
2214 }),
2215 },
2216 meta: segment.meta.clone(),
2217 }),
2218 }
2219 .continue_()),
2220 },
2221 SegmentRepr::Solved { segment } => match &segment.kind {
2222 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2223 KclErrorDetails::new(
2224 format!("Property '{property}' not found in point segment"),
2225 vec![self.clone().into()],
2226 ),
2227 None,
2228 )),
2229 SegmentKind::Line {
2230 start,
2231 ctor,
2232 start_object_id,
2233 start_freedom,
2234 ..
2235 } => Ok(KclValue::Segment {
2236 value: Box::new(AbstractSegment {
2237 repr: SegmentRepr::Solved {
2238 segment: Box::new(Segment {
2239 id: segment.id,
2240 object_id: *start_object_id,
2241 kind: SegmentKind::Point {
2242 position: start.clone(),
2243 ctor: Box::new(PointCtor {
2244 position: ctor.start.clone(),
2245 }),
2246 freedom: *start_freedom,
2247 },
2248 surface: segment.surface.clone(),
2249 sketch_id: segment.sketch_id,
2250 sketch: segment.sketch.clone(),
2251 tag: segment.tag.clone(),
2252 node_path: segment.node_path.clone(),
2253 meta: segment.meta.clone(),
2254 }),
2255 },
2256 meta: segment.meta.clone(),
2257 }),
2258 }
2259 .continue_()),
2260 SegmentKind::Arc {
2261 start,
2262 ctor,
2263 start_object_id,
2264 start_freedom,
2265 ..
2266 } => Ok(KclValue::Segment {
2267 value: Box::new(AbstractSegment {
2268 repr: SegmentRepr::Solved {
2269 segment: Box::new(Segment {
2270 id: segment.id,
2271 object_id: *start_object_id,
2272 kind: SegmentKind::Point {
2273 position: start.clone(),
2274 ctor: Box::new(PointCtor {
2275 position: ctor.start.clone(),
2276 }),
2277 freedom: *start_freedom,
2278 },
2279 surface: segment.surface.clone(),
2280 sketch_id: segment.sketch_id,
2281 sketch: segment.sketch.clone(),
2282 tag: segment.tag.clone(),
2283 node_path: segment.node_path.clone(),
2284 meta: segment.meta.clone(),
2285 }),
2286 },
2287 meta: segment.meta.clone(),
2288 }),
2289 }
2290 .continue_()),
2291 SegmentKind::Circle {
2292 start,
2293 ctor,
2294 start_object_id,
2295 start_freedom,
2296 ..
2297 } => Ok(KclValue::Segment {
2298 value: Box::new(AbstractSegment {
2299 repr: SegmentRepr::Solved {
2300 segment: Box::new(Segment {
2301 id: segment.id,
2302 object_id: *start_object_id,
2303 kind: SegmentKind::Point {
2304 position: start.clone(),
2305 ctor: Box::new(PointCtor {
2306 position: ctor.start.clone(),
2307 }),
2308 freedom: *start_freedom,
2309 },
2310 surface: segment.surface.clone(),
2311 sketch_id: segment.sketch_id,
2312 sketch: segment.sketch.clone(),
2313 tag: segment.tag.clone(),
2314 node_path: segment.node_path.clone(),
2315 meta: segment.meta.clone(),
2316 }),
2317 },
2318 meta: segment.meta.clone(),
2319 }),
2320 }
2321 .continue_()),
2322 },
2323 },
2324 "end" => match &segment.repr {
2325 SegmentRepr::Unsolved { segment } => match &segment.kind {
2326 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2327 KclErrorDetails::new(
2328 format!("Property '{property}' not found in point segment"),
2329 vec![self.clone().into()],
2330 ),
2331 None,
2332 )),
2333 UnsolvedSegmentKind::Line {
2334 end,
2335 ctor,
2336 end_object_id,
2337 ..
2338 } => Ok(KclValue::Segment {
2339 value: Box::new(AbstractSegment {
2340 repr: SegmentRepr::Unsolved {
2341 segment: Box::new(UnsolvedSegment {
2342 id: segment.id,
2343 object_id: *end_object_id,
2344 kind: UnsolvedSegmentKind::Point {
2345 position: end.clone(),
2346 ctor: Box::new(PointCtor {
2347 position: ctor.end.clone(),
2348 }),
2349 },
2350 tag: segment.tag.clone(),
2351 node_path: segment.node_path.clone(),
2352 meta: segment.meta.clone(),
2353 }),
2354 },
2355 meta: segment.meta.clone(),
2356 }),
2357 }
2358 .continue_()),
2359 UnsolvedSegmentKind::Arc {
2360 end,
2361 ctor,
2362 end_object_id,
2363 ..
2364 } => Ok(KclValue::Segment {
2365 value: Box::new(AbstractSegment {
2366 repr: SegmentRepr::Unsolved {
2367 segment: Box::new(UnsolvedSegment {
2368 id: segment.id,
2369 object_id: *end_object_id,
2370 kind: UnsolvedSegmentKind::Point {
2371 position: end.clone(),
2372 ctor: Box::new(PointCtor {
2373 position: ctor.end.clone(),
2374 }),
2375 },
2376 tag: segment.tag.clone(),
2377 node_path: segment.node_path.clone(),
2378 meta: segment.meta.clone(),
2379 }),
2380 },
2381 meta: segment.meta.clone(),
2382 }),
2383 }
2384 .continue_()),
2385 UnsolvedSegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2386 KclErrorDetails::new(
2387 format!("Property '{property}' not found in segment"),
2388 vec![self.into()],
2389 ),
2390 None,
2391 )),
2392 },
2393 SegmentRepr::Solved { segment } => match &segment.kind {
2394 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2395 KclErrorDetails::new(
2396 format!("Property '{property}' not found in point segment"),
2397 vec![self.clone().into()],
2398 ),
2399 None,
2400 )),
2401 SegmentKind::Line {
2402 end,
2403 ctor,
2404 end_object_id,
2405 end_freedom,
2406 ..
2407 } => Ok(KclValue::Segment {
2408 value: Box::new(AbstractSegment {
2409 repr: SegmentRepr::Solved {
2410 segment: Box::new(Segment {
2411 id: segment.id,
2412 object_id: *end_object_id,
2413 kind: SegmentKind::Point {
2414 position: end.clone(),
2415 ctor: Box::new(PointCtor {
2416 position: ctor.end.clone(),
2417 }),
2418 freedom: *end_freedom,
2419 },
2420 surface: segment.surface.clone(),
2421 sketch_id: segment.sketch_id,
2422 sketch: segment.sketch.clone(),
2423 tag: segment.tag.clone(),
2424 node_path: segment.node_path.clone(),
2425 meta: segment.meta.clone(),
2426 }),
2427 },
2428 meta: segment.meta.clone(),
2429 }),
2430 }
2431 .continue_()),
2432 SegmentKind::Arc {
2433 end,
2434 ctor,
2435 end_object_id,
2436 end_freedom,
2437 ..
2438 } => Ok(KclValue::Segment {
2439 value: Box::new(AbstractSegment {
2440 repr: SegmentRepr::Solved {
2441 segment: Box::new(Segment {
2442 id: segment.id,
2443 object_id: *end_object_id,
2444 kind: SegmentKind::Point {
2445 position: end.clone(),
2446 ctor: Box::new(PointCtor {
2447 position: ctor.end.clone(),
2448 }),
2449 freedom: *end_freedom,
2450 },
2451 surface: segment.surface.clone(),
2452 sketch_id: segment.sketch_id,
2453 sketch: segment.sketch.clone(),
2454 tag: segment.tag.clone(),
2455 node_path: segment.node_path.clone(),
2456 meta: segment.meta.clone(),
2457 }),
2458 },
2459 meta: segment.meta.clone(),
2460 }),
2461 }
2462 .continue_()),
2463 SegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2464 KclErrorDetails::new(
2465 format!("Property '{property}' not found in segment"),
2466 vec![self.into()],
2467 ),
2468 None,
2469 )),
2470 },
2471 },
2472 "center" => match &segment.repr {
2473 SegmentRepr::Unsolved { segment } => match &segment.kind {
2474 UnsolvedSegmentKind::Arc {
2475 center,
2476 ctor,
2477 center_object_id,
2478 ..
2479 } => Ok(KclValue::Segment {
2480 value: Box::new(AbstractSegment {
2481 repr: SegmentRepr::Unsolved {
2482 segment: Box::new(UnsolvedSegment {
2483 id: segment.id,
2484 object_id: *center_object_id,
2485 kind: UnsolvedSegmentKind::Point {
2486 position: center.clone(),
2487 ctor: Box::new(PointCtor {
2488 position: ctor.center.clone(),
2489 }),
2490 },
2491 tag: segment.tag.clone(),
2492 node_path: segment.node_path.clone(),
2493 meta: segment.meta.clone(),
2494 }),
2495 },
2496 meta: segment.meta.clone(),
2497 }),
2498 }
2499 .continue_()),
2500 UnsolvedSegmentKind::Circle {
2501 center,
2502 ctor,
2503 center_object_id,
2504 ..
2505 } => Ok(KclValue::Segment {
2506 value: Box::new(AbstractSegment {
2507 repr: SegmentRepr::Unsolved {
2508 segment: Box::new(UnsolvedSegment {
2509 id: segment.id,
2510 object_id: *center_object_id,
2511 kind: UnsolvedSegmentKind::Point {
2512 position: center.clone(),
2513 ctor: Box::new(PointCtor {
2514 position: ctor.center.clone(),
2515 }),
2516 },
2517 tag: segment.tag.clone(),
2518 node_path: segment.node_path.clone(),
2519 meta: segment.meta.clone(),
2520 }),
2521 },
2522 meta: segment.meta.clone(),
2523 }),
2524 }
2525 .continue_()),
2526 _ => Err(KclError::new_undefined_value(
2527 KclErrorDetails::new(
2528 format!("Property '{property}' not found in segment"),
2529 vec![self.clone().into()],
2530 ),
2531 None,
2532 )),
2533 },
2534 SegmentRepr::Solved { segment } => match &segment.kind {
2535 SegmentKind::Arc {
2536 center,
2537 ctor,
2538 center_object_id,
2539 center_freedom,
2540 ..
2541 } => Ok(KclValue::Segment {
2542 value: Box::new(AbstractSegment {
2543 repr: SegmentRepr::Solved {
2544 segment: Box::new(Segment {
2545 id: segment.id,
2546 object_id: *center_object_id,
2547 kind: SegmentKind::Point {
2548 position: center.clone(),
2549 ctor: Box::new(PointCtor {
2550 position: ctor.center.clone(),
2551 }),
2552 freedom: *center_freedom,
2553 },
2554 surface: segment.surface.clone(),
2555 sketch_id: segment.sketch_id,
2556 sketch: segment.sketch.clone(),
2557 tag: segment.tag.clone(),
2558 node_path: segment.node_path.clone(),
2559 meta: segment.meta.clone(),
2560 }),
2561 },
2562 meta: segment.meta.clone(),
2563 }),
2564 }
2565 .continue_()),
2566 SegmentKind::Circle {
2567 center,
2568 ctor,
2569 center_object_id,
2570 center_freedom,
2571 ..
2572 } => Ok(KclValue::Segment {
2573 value: Box::new(AbstractSegment {
2574 repr: SegmentRepr::Solved {
2575 segment: Box::new(Segment {
2576 id: segment.id,
2577 object_id: *center_object_id,
2578 kind: SegmentKind::Point {
2579 position: center.clone(),
2580 ctor: Box::new(PointCtor {
2581 position: ctor.center.clone(),
2582 }),
2583 freedom: *center_freedom,
2584 },
2585 surface: segment.surface.clone(),
2586 sketch_id: segment.sketch_id,
2587 sketch: segment.sketch.clone(),
2588 tag: segment.tag.clone(),
2589 node_path: segment.node_path.clone(),
2590 meta: segment.meta.clone(),
2591 }),
2592 },
2593 meta: segment.meta.clone(),
2594 }),
2595 }
2596 .continue_()),
2597 _ => Err(KclError::new_undefined_value(
2598 KclErrorDetails::new(
2599 format!("Property '{property}' not found in segment"),
2600 vec![self.clone().into()],
2601 ),
2602 None,
2603 )),
2604 },
2605 },
2606 other => Err(KclError::new_undefined_value(
2607 KclErrorDetails::new(
2608 format!("Property '{other}' not found in segment"),
2609 vec![self.clone().into()],
2610 ),
2611 None,
2612 )),
2613 },
2614 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
2615 "zAxis" => {
2616 let (p, u) = plane.info.z_axis.as_3_dims();
2617 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2618 }
2619 "yAxis" => {
2620 let (p, u) = plane.info.y_axis.as_3_dims();
2621 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2622 }
2623 "xAxis" => {
2624 let (p, u) = plane.info.x_axis.as_3_dims();
2625 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2626 }
2627 "origin" => {
2628 let (p, u) = plane.info.origin.as_3_dims();
2629 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2630 }
2631 other => Err(KclError::new_undefined_value(
2632 KclErrorDetails::new(
2633 format!("Property '{other}' not found in plane"),
2634 vec![self.clone().into()],
2635 ),
2636 None,
2637 )),
2638 },
2639 (KclValue::Object { value: map, .. }, Property::String(property), false) => {
2640 if let Some(value) = map.get(&property) {
2641 Ok(value.to_owned().continue_())
2642 } else {
2643 Err(KclError::new_undefined_value(
2644 KclErrorDetails::new(
2645 format!("Property '{property}' not found in object"),
2646 vec![self.clone().into()],
2647 ),
2648 None,
2649 ))
2650 }
2651 }
2652 (KclValue::Object { .. }, Property::String(property), true) => {
2653 Err(KclError::new_semantic(KclErrorDetails::new(
2654 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
2655 vec![self.clone().into()],
2656 )))
2657 }
2658 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
2659 if i == 0
2660 && let Some(value) = map.get("x")
2661 {
2662 return Ok(value.to_owned().continue_());
2663 }
2664 if i == 1
2665 && let Some(value) = map.get("y")
2666 {
2667 return Ok(value.to_owned().continue_());
2668 }
2669 if i == 2
2670 && let Some(value) = map.get("z")
2671 {
2672 return Ok(value.to_owned().continue_());
2673 }
2674 let t = p.type_name();
2675 let article = article_for(t);
2676 Err(KclError::new_semantic(KclErrorDetails::new(
2677 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
2678 vec![self.clone().into()],
2679 )))
2680 }
2681 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
2682 let value_of_arr = arr.get(index);
2683 if let Some(value) = value_of_arr {
2684 Ok(value.to_owned().continue_())
2685 } else {
2686 Err(KclError::new_undefined_value(
2687 KclErrorDetails::new(
2688 format!("The array doesn't have any item at index {index}"),
2689 vec![self.clone().into()],
2690 ),
2691 None,
2692 ))
2693 }
2694 }
2695 (obj, Property::UInt(0), _) => Ok(obj.continue_()),
2698 (KclValue::HomArray { .. }, p, _) => {
2699 let t = p.type_name();
2700 let article = article_for(t);
2701 Err(KclError::new_semantic(KclErrorDetails::new(
2702 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
2703 vec![self.clone().into()],
2704 )))
2705 }
2706 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
2707 let Some(sketch) = value.sketch() else {
2708 return Err(KclError::new_semantic(KclErrorDetails::new(
2709 "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
2710 vec![self.clone().into()],
2711 )));
2712 };
2713 Ok(KclValue::Sketch {
2714 value: Box::new(sketch.clone()),
2715 }
2716 .continue_())
2717 }
2718 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
2719 Err(KclError::new_semantic(KclErrorDetails::new(
2721 format!(
2722 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
2723 geometry.human_friendly_type()
2724 ),
2725 vec![self.clone().into()],
2726 )))
2727 }
2728 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
2729 meta: vec![Metadata {
2730 source_range: SourceRange::from(self.clone()),
2731 }],
2732 value: sk
2733 .tags
2734 .iter()
2735 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
2736 .collect(),
2737 constrainable: false,
2738 }
2739 .continue_()),
2740 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
2741 Err(KclError::new_semantic(KclErrorDetails::new(
2742 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
2743 vec![self.clone().into()],
2744 )))
2745 }
2746 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
2747 format!(
2748 "Only objects can have members accessed with dot notation, but you're trying to access {}",
2749 being_indexed.human_friendly_type()
2750 ),
2751 vec![self.clone().into()],
2752 ))),
2753 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
2754 format!(
2755 "Only arrays can be indexed, but you're trying to index {}",
2756 being_indexed.human_friendly_type()
2757 ),
2758 vec![self.clone().into()],
2759 ))),
2760 }
2761 }
2762}
2763
2764impl Node<BinaryExpression> {
2765 pub(super) async fn get_result(
2766 &self,
2767 exec_state: &mut ExecState,
2768 ctx: &ExecutorContext,
2769 ) -> Result<KclValueControlFlow, KclError> {
2770 enum State {
2771 EvaluateLeft(Node<BinaryExpression>),
2772 FromLeft {
2773 node: Node<BinaryExpression>,
2774 },
2775 EvaluateRight {
2776 node: Node<BinaryExpression>,
2777 left: KclValue,
2778 },
2779 FromRight {
2780 node: Node<BinaryExpression>,
2781 left: KclValue,
2782 },
2783 }
2784
2785 let mut stack = vec![State::EvaluateLeft(self.clone())];
2786 let mut last_result: Option<KclValue> = None;
2787
2788 while let Some(state) = stack.pop() {
2789 match state {
2790 State::EvaluateLeft(node) => {
2791 let left_part = node.left.clone();
2792 match left_part {
2793 BinaryPart::BinaryExpression(child) => {
2794 stack.push(State::FromLeft { node });
2795 stack.push(State::EvaluateLeft(*child));
2796 }
2797 part => {
2798 let left_value = part.get_result(exec_state, ctx).await?;
2799 let left_value = control_continue!(left_value);
2800 stack.push(State::EvaluateRight { node, left: left_value });
2801 }
2802 }
2803 }
2804 State::FromLeft { node } => {
2805 let Some(left_value) = last_result.take() else {
2806 return Err(Self::missing_result_error(&node));
2807 };
2808 stack.push(State::EvaluateRight { node, left: left_value });
2809 }
2810 State::EvaluateRight { node, left } => {
2811 let right_part = node.right.clone();
2812 match right_part {
2813 BinaryPart::BinaryExpression(child) => {
2814 stack.push(State::FromRight { node, left });
2815 stack.push(State::EvaluateLeft(*child));
2816 }
2817 part => {
2818 let right_value = part.get_result(exec_state, ctx).await?;
2819 let right_value = control_continue!(right_value);
2820 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2821 last_result = Some(result);
2822 }
2823 }
2824 }
2825 State::FromRight { node, left } => {
2826 let Some(right_value) = last_result.take() else {
2827 return Err(Self::missing_result_error(&node));
2828 };
2829 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2830 last_result = Some(result);
2831 }
2832 }
2833 }
2834
2835 last_result
2836 .map(KclValue::continue_)
2837 .ok_or_else(|| Self::missing_result_error(self))
2838 }
2839
2840 async fn apply_operator(
2841 &self,
2842 exec_state: &mut ExecState,
2843 ctx: &ExecutorContext,
2844 left_value: KclValue,
2845 right_value: KclValue,
2846 ) -> Result<KclValue, KclError> {
2847 let mut meta = left_value.metadata();
2848 meta.extend(right_value.metadata());
2849
2850 if self.operator == BinaryOperator::Add
2852 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
2853 (&left_value, &right_value)
2854 {
2855 return Ok(KclValue::String {
2856 value: format!("{left}{right}"),
2857 meta,
2858 });
2859 }
2860
2861 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
2863 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2864 let args = Args::new_no_args(
2865 self.into(),
2866 self.node_path.clone(),
2867 ctx.clone(),
2868 Some("union".to_owned()),
2869 );
2870 let result = crate::std::csg::inner_union(
2871 vec![*left.clone(), *right.clone()],
2872 Default::default(),
2873 crate::std::csg::CsgAlgorithm::Latest,
2874 exec_state,
2875 args,
2876 )
2877 .await?;
2878 return Ok(result.into());
2879 }
2880 } else if self.operator == BinaryOperator::Sub {
2881 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2883 let args = Args::new_no_args(
2884 self.into(),
2885 self.node_path.clone(),
2886 ctx.clone(),
2887 Some("subtract".to_owned()),
2888 );
2889 let result = crate::std::csg::inner_subtract(
2890 vec![*left.clone()],
2891 vec![*right.clone()],
2892 Default::default(),
2893 crate::std::csg::CsgAlgorithm::Latest,
2894 exec_state,
2895 args,
2896 )
2897 .await?;
2898 return Ok(result.into());
2899 }
2900 } else if self.operator == BinaryOperator::And
2901 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2902 {
2903 let args = Args::new_no_args(
2905 self.into(),
2906 self.node_path.clone(),
2907 ctx.clone(),
2908 Some("intersect".to_owned()),
2909 );
2910 let result = crate::std::csg::inner_intersect(
2911 vec![*left.clone(), *right.clone()],
2912 Default::default(),
2913 crate::std::csg::CsgAlgorithm::Latest,
2914 exec_state,
2915 args,
2916 )
2917 .await?;
2918 return Ok(result.into());
2919 }
2920
2921 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2923 let KclValue::Bool { value: left_value, .. } = left_value else {
2924 return Err(KclError::new_semantic(KclErrorDetails::new(
2925 format!(
2926 "Cannot apply logical operator to non-boolean value: {}",
2927 left_value.human_friendly_type()
2928 ),
2929 vec![self.left.clone().into()],
2930 )));
2931 };
2932 let KclValue::Bool { value: right_value, .. } = right_value else {
2933 return Err(KclError::new_semantic(KclErrorDetails::new(
2934 format!(
2935 "Cannot apply logical operator to non-boolean value: {}",
2936 right_value.human_friendly_type()
2937 ),
2938 vec![self.right.clone().into()],
2939 )));
2940 };
2941 let raw_value = match self.operator {
2942 BinaryOperator::Or => left_value || right_value,
2943 BinaryOperator::And => left_value && right_value,
2944 _ => unreachable!(),
2945 };
2946 return Ok(KclValue::Bool { value: raw_value, meta });
2947 }
2948
2949 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2951 match (&left_value, &right_value) {
2952 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2954 if left_value.id == right_value.id =>
2955 {
2956 return Ok(KclValue::none());
2957 }
2958 (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
2960 let constraint = Constraint::ScalarEqual(
2961 var0.id.to_constraint_id(self.as_source_range())?,
2962 var1.id.to_constraint_id(self.as_source_range())?,
2963 );
2964 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2965 let message = "Being inside a sketch block should have already been checked above".to_owned();
2966 debug_assert!(false, "{}", &message);
2967 return Err(internal_err(message, self));
2968 };
2969 sketch_block_state.solver_constraints.push(constraint);
2970 return Ok(KclValue::none());
2971 }
2972 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2974 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2975 let number_value = normalize_to_solver_distance_unit(
2976 input_number,
2977 input_number.into(),
2978 exec_state,
2979 "fixed constraint value",
2980 )?;
2981 let Some(n) = number_value.as_ty_f64() else {
2982 let message = format!(
2983 "Expected number after coercion, but found {}",
2984 number_value.human_friendly_type()
2985 );
2986 debug_assert!(false, "{}", &message);
2987 return Err(internal_err(message, self));
2988 };
2989 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2990 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2991 let message = "Being inside a sketch block should have already been checked above".to_owned();
2992 debug_assert!(false, "{}", &message);
2993 return Err(internal_err(message, self));
2994 };
2995 sketch_block_state.solver_constraints.push(constraint);
2996 exec_state.warn_experimental("scalar fixed constraint", self.as_source_range());
2997 return Ok(KclValue::none());
2998 }
2999 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
3001 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
3002 let number_value = match constraint.kind {
3003 SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
3005 input_number,
3006 input_number.into(),
3007 exec_state,
3008 "fixed constraint value",
3009 )?,
3010 SketchConstraintKind::Distance { .. }
3012 | SketchConstraintKind::Radius { .. }
3013 | SketchConstraintKind::Diameter { .. }
3014 | SketchConstraintKind::HorizontalDistance { .. }
3015 | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
3016 input_number,
3017 input_number.into(),
3018 exec_state,
3019 "fixed constraint value",
3020 )?,
3021 };
3022 let Some(n) = number_value.as_ty_f64() else {
3023 let message = format!(
3024 "Expected number after coercion, but found {}",
3025 number_value.human_friendly_type()
3026 );
3027 debug_assert!(false, "{}", &message);
3028 return Err(internal_err(message, self));
3029 };
3030 #[cfg(feature = "artifact-graph")]
3032 let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
3033 &self.right
3034 } else {
3035 &self.left
3036 };
3037 #[cfg(feature = "artifact-graph")]
3038 let source = {
3039 use crate::unparser::ExprContext;
3040 let mut buf = String::new();
3041 number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
3042 crate::frontend::sketch::ConstraintSource {
3043 expr: buf,
3044 is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
3045 }
3046 };
3047
3048 match &constraint.kind {
3049 SketchConstraintKind::Angle { line0, line1 } => {
3050 let range = self.as_source_range();
3051 let ax = line0.vars[0].x.to_constraint_id(range)?;
3054 let ay = line0.vars[0].y.to_constraint_id(range)?;
3055 let bx = line0.vars[1].x.to_constraint_id(range)?;
3056 let by = line0.vars[1].y.to_constraint_id(range)?;
3057 let cx = line1.vars[0].x.to_constraint_id(range)?;
3058 let cy = line1.vars[0].y.to_constraint_id(range)?;
3059 let dx = line1.vars[1].x.to_constraint_id(range)?;
3060 let dy = line1.vars[1].y.to_constraint_id(range)?;
3061 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(
3062 ezpz::datatypes::inputs::DatumPoint::new_xy(ax, ay),
3063 ezpz::datatypes::inputs::DatumPoint::new_xy(bx, by),
3064 );
3065 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(
3066 ezpz::datatypes::inputs::DatumPoint::new_xy(cx, cy),
3067 ezpz::datatypes::inputs::DatumPoint::new_xy(dx, dy),
3068 );
3069 let desired_angle = match n.ty {
3070 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Degrees))
3071 | NumericType::Default {
3072 len: _,
3073 angle: kcmc::units::UnitAngle::Degrees,
3074 } => ezpz::datatypes::Angle::from_degrees(n.n),
3075 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Radians))
3076 | NumericType::Default {
3077 len: _,
3078 angle: kcmc::units::UnitAngle::Radians,
3079 } => ezpz::datatypes::Angle::from_radians(n.n),
3080 NumericType::Known(crate::exec::UnitType::Count)
3081 | NumericType::Known(crate::exec::UnitType::GenericLength)
3082 | NumericType::Known(crate::exec::UnitType::GenericAngle)
3083 | NumericType::Known(crate::exec::UnitType::Length(_))
3084 | NumericType::Unknown
3085 | NumericType::Any => {
3086 let message = format!("Expected angle but found {:?}", n);
3087 debug_assert!(false, "{}", &message);
3088 return Err(internal_err(message, self));
3089 }
3090 };
3091 let solver_constraint = Constraint::LinesAtAngle(
3092 solver_line0,
3093 solver_line1,
3094 ezpz::datatypes::AngleKind::Other(desired_angle),
3095 );
3096 #[cfg(feature = "artifact-graph")]
3097 let constraint_id = exec_state.next_object_id();
3098 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3099 let message =
3100 "Being inside a sketch block should have already been checked above".to_owned();
3101 debug_assert!(false, "{}", &message);
3102 return Err(internal_err(message, self));
3103 };
3104 sketch_block_state.solver_constraints.push(solver_constraint);
3105 #[cfg(feature = "artifact-graph")]
3106 {
3107 use crate::execution::Artifact;
3108 use crate::execution::CodeRef;
3109 use crate::execution::SketchBlockConstraint;
3110 use crate::execution::SketchBlockConstraintType;
3111 use crate::front::Angle;
3112 use crate::front::SourceRef;
3113
3114 let Some(sketch_id) = sketch_block_state.sketch_id else {
3115 let message = "Sketch id missing for constraint artifact".to_owned();
3116 debug_assert!(false, "{}", &message);
3117 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3118 };
3119 let sketch_constraint = crate::front::Constraint::Angle(Angle {
3120 lines: vec![line0.object_id, line1.object_id],
3121 angle: n.try_into().map_err(|_| {
3122 internal_err("Failed to convert angle units numeric suffix:", range)
3123 })?,
3124 source,
3125 });
3126 sketch_block_state.sketch_constraints.push(constraint_id);
3127 let artifact_id = exec_state.next_artifact_id();
3128 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3129 id: artifact_id,
3130 sketch_id,
3131 constraint_id,
3132 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3133 code_ref: CodeRef::placeholder(range),
3134 }));
3135 exec_state.add_scene_object(
3136 Object {
3137 id: constraint_id,
3138 kind: ObjectKind::Constraint {
3139 constraint: sketch_constraint,
3140 },
3141 label: Default::default(),
3142 comments: Default::default(),
3143 artifact_id,
3144 source: SourceRef::new(range, self.node_path.clone()),
3145 },
3146 range,
3147 );
3148 }
3149 }
3150 SketchConstraintKind::Distance { points } => {
3151 let range = self.as_source_range();
3152 let p0 = &points[0];
3153 let p1 = &points[1];
3154 let sketch_var_ty = solver_numeric_type(exec_state);
3155 #[cfg(feature = "artifact-graph")]
3156 let constraint_id = exec_state.next_object_id();
3157 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3158 let message =
3159 "Being inside a sketch block should have already been checked above".to_owned();
3160 debug_assert!(false, "{}", &message);
3161 return Err(internal_err(message, self));
3162 };
3163 match (p0, p1) {
3164 (
3165 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3166 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3167 ) => {
3168 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3169 p0.vars.x.to_constraint_id(range)?,
3170 p0.vars.y.to_constraint_id(range)?,
3171 );
3172 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3173 p1.vars.x.to_constraint_id(range)?,
3174 p1.vars.y.to_constraint_id(range)?,
3175 );
3176 sketch_block_state
3177 .solver_constraints
3178 .push(Constraint::Distance(solver_pt0, solver_pt1, n.n));
3179 }
3180 (
3181 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3182 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3183 )
3184 | (
3185 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3186 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3187 ) => {
3188 let origin_x_id = sketch_block_state.next_sketch_var_id();
3189 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3190 value: Box::new(crate::execution::SketchVar {
3191 id: origin_x_id,
3192 initial_value: 0.0,
3193 ty: sketch_var_ty,
3194 meta: vec![],
3195 }),
3196 });
3197 let origin_y_id = sketch_block_state.next_sketch_var_id();
3198 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3199 value: Box::new(crate::execution::SketchVar {
3200 id: origin_y_id,
3201 initial_value: 0.0,
3202 ty: sketch_var_ty,
3203 meta: vec![],
3204 }),
3205 });
3206 let origin_x = origin_x_id.to_constraint_id(range)?;
3207 let origin_y = origin_y_id.to_constraint_id(range)?;
3208 sketch_block_state
3209 .solver_constraints
3210 .push(Constraint::Fixed(origin_x, 0.0));
3211 sketch_block_state
3212 .solver_constraints
3213 .push(Constraint::Fixed(origin_y, 0.0));
3214 let solver_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3215 point.vars.x.to_constraint_id(range)?,
3216 point.vars.y.to_constraint_id(range)?,
3217 );
3218 let origin_point = ezpz::datatypes::inputs::DatumPoint::new_xy(origin_x, origin_y);
3219 sketch_block_state.solver_constraints.push(Constraint::Distance(
3220 solver_point,
3221 origin_point,
3222 n.n,
3223 ));
3224 }
3225 (
3226 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3227 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3228 ) => {
3229 return Err(internal_err(
3230 "distance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3231 range,
3232 ));
3233 }
3234 }
3235 #[cfg(feature = "artifact-graph")]
3236 {
3237 use crate::execution::Artifact;
3238 use crate::execution::CodeRef;
3239 use crate::execution::SketchBlockConstraint;
3240 use crate::execution::SketchBlockConstraintType;
3241 use crate::front::Distance;
3242 use crate::front::SourceRef;
3243 use crate::frontend::sketch::ConstraintSegment;
3244
3245 let Some(sketch_id) = sketch_block_state.sketch_id else {
3246 let message = "Sketch id missing for constraint artifact".to_owned();
3247 debug_assert!(false, "{}", &message);
3248 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3249 };
3250 let sketch_constraint = crate::front::Constraint::Distance(Distance {
3251 points: vec![
3252 match p0 {
3253 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3254 ConstraintSegment::from(point.object_id)
3255 }
3256 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3257 ConstraintSegment::ORIGIN
3258 }
3259 },
3260 match p1 {
3261 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3262 ConstraintSegment::from(point.object_id)
3263 }
3264 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3265 ConstraintSegment::ORIGIN
3266 }
3267 },
3268 ],
3269 distance: n.try_into().map_err(|_| {
3270 internal_err("Failed to convert distance units numeric suffix:", range)
3271 })?,
3272 source,
3273 });
3274 sketch_block_state.sketch_constraints.push(constraint_id);
3275 let artifact_id = exec_state.next_artifact_id();
3276 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3277 id: artifact_id,
3278 sketch_id,
3279 constraint_id,
3280 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3281 code_ref: CodeRef::placeholder(range),
3282 }));
3283 exec_state.add_scene_object(
3284 Object {
3285 id: constraint_id,
3286 kind: ObjectKind::Constraint {
3287 constraint: sketch_constraint,
3288 },
3289 label: Default::default(),
3290 comments: Default::default(),
3291 artifact_id,
3292 source: SourceRef::new(range, self.node_path.clone()),
3293 },
3294 range,
3295 );
3296 }
3297 }
3298 SketchConstraintKind::Radius { points } | SketchConstraintKind::Diameter { points } => {
3299 #[derive(Clone, Copy)]
3300 enum CircularSegmentConstraintTarget {
3301 Arc {
3302 #[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
3303 object_id: ObjectId,
3304 end: [crate::execution::SketchVarId; 2],
3305 },
3306 Circle {
3307 #[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
3308 object_id: ObjectId,
3309 },
3310 }
3311
3312 fn sketch_var_initial_value(
3313 sketch_vars: &[KclValue],
3314 id: crate::execution::SketchVarId,
3315 exec_state: &mut ExecState,
3316 range: SourceRange,
3317 ) -> Result<f64, KclError> {
3318 sketch_vars
3319 .get(id.0)
3320 .and_then(KclValue::as_sketch_var)
3321 .map(|sketch_var| {
3322 sketch_var
3323 .initial_value_to_solver_units(
3324 exec_state,
3325 range,
3326 "circle radius initial value",
3327 )
3328 .map(|value| value.n)
3329 })
3330 .transpose()?
3331 .ok_or_else(|| {
3332 internal_err(
3333 format!("Missing sketch variable initial value for id {}", id.0),
3334 range,
3335 )
3336 })
3337 }
3338
3339 let range = self.as_source_range();
3340 let center = &points[0];
3341 let start = &points[1];
3342 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
3343 return Err(internal_err(
3344 "Being inside a sketch block should have already been checked above",
3345 self,
3346 ));
3347 };
3348 let (constraint_name, is_diameter) = match &constraint.kind {
3349 SketchConstraintKind::Radius { .. } => ("radius", false),
3350 SketchConstraintKind::Diameter { .. } => ("diameter", true),
3351 _ => unreachable!(),
3352 };
3353 let sketch_vars = sketch_block_state.sketch_vars.clone();
3354 let target_segment = sketch_block_state
3355 .needed_by_engine
3356 .iter()
3357 .find_map(|seg| match &seg.kind {
3358 UnsolvedSegmentKind::Arc {
3359 center_object_id,
3360 start_object_id,
3361 end,
3362 ..
3363 } if *center_object_id == center.object_id
3364 && *start_object_id == start.object_id =>
3365 {
3366 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
3367 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
3368 (*end_x, *end_y)
3369 }
3370 _ => return None,
3371 };
3372 Some(CircularSegmentConstraintTarget::Arc {
3373 object_id: seg.object_id,
3374 end: [end_x_var, end_y_var],
3375 })
3376 }
3377 UnsolvedSegmentKind::Circle {
3378 center_object_id,
3379 start_object_id,
3380 ..
3381 } if *center_object_id == center.object_id
3382 && *start_object_id == start.object_id =>
3383 {
3384 Some(CircularSegmentConstraintTarget::Circle {
3385 object_id: seg.object_id,
3386 })
3387 }
3388 _ => None,
3389 })
3390 .ok_or_else(|| {
3391 internal_err(
3392 format!("Could not find circular segment for {} constraint", constraint_name),
3393 range,
3394 )
3395 })?;
3396 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
3397 let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3398 center.vars.x.to_constraint_id(range)?,
3399 center.vars.y.to_constraint_id(range)?,
3400 );
3401 let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3402 start.vars.x.to_constraint_id(range)?,
3403 start.vars.y.to_constraint_id(range)?,
3404 );
3405 let solver_constraint = match target_segment {
3406 CircularSegmentConstraintTarget::Arc { end, .. } => {
3407 let solver_arc = ezpz::datatypes::inputs::DatumCircularArc {
3408 center: center_point,
3409 start: start_point,
3410 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
3411 end[0].to_constraint_id(range)?,
3412 end[1].to_constraint_id(range)?,
3413 ),
3414 };
3415 Constraint::ArcRadius(solver_arc, radius_value)
3416 }
3417 CircularSegmentConstraintTarget::Circle { .. } => {
3418 let sketch_var_ty = solver_numeric_type(exec_state);
3419 let start_x =
3420 sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
3421 let start_y =
3422 sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
3423 let center_x =
3424 sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
3425 let center_y =
3426 sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
3427
3428 let radius_initial_value = (start_x - center_x).hypot(start_y - center_y);
3430
3431 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3432 let message =
3433 "Being inside a sketch block should have already been checked above"
3434 .to_owned();
3435 debug_assert!(false, "{}", &message);
3436 return Err(internal_err(message, self));
3437 };
3438 let radius_id = sketch_block_state.next_sketch_var_id();
3439 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3440 value: Box::new(crate::execution::SketchVar {
3441 id: radius_id,
3442 initial_value: radius_initial_value,
3443 ty: sketch_var_ty,
3444 meta: vec![],
3445 }),
3446 });
3447 let radius =
3448 ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
3449 let solver_circle = ezpz::datatypes::inputs::DatumCircle {
3450 center: center_point,
3451 radius,
3452 };
3453 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
3454 start_point,
3455 center_point,
3456 radius,
3457 ));
3458 Constraint::CircleRadius(solver_circle, radius_value)
3459 }
3460 };
3461
3462 #[cfg(feature = "artifact-graph")]
3463 let constraint_id = exec_state.next_object_id();
3464 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3465 let message =
3466 "Being inside a sketch block should have already been checked above".to_owned();
3467 debug_assert!(false, "{}", &message);
3468 return Err(internal_err(message, self));
3469 };
3470 sketch_block_state.solver_constraints.push(solver_constraint);
3471 #[cfg(feature = "artifact-graph")]
3472 {
3473 use crate::execution::Artifact;
3474 use crate::execution::CodeRef;
3475 use crate::execution::SketchBlockConstraint;
3476 use crate::execution::SketchBlockConstraintType;
3477 use crate::front::SourceRef;
3478 let segment_object_id = match target_segment {
3479 CircularSegmentConstraintTarget::Arc { object_id, .. }
3480 | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
3481 };
3482
3483 let constraint = if is_diameter {
3484 use crate::frontend::sketch::Diameter;
3485 crate::front::Constraint::Diameter(Diameter {
3486 arc: segment_object_id,
3487 diameter: n.try_into().map_err(|_| {
3488 internal_err("Failed to convert diameter units numeric suffix:", range)
3489 })?,
3490 source,
3491 })
3492 } else {
3493 use crate::frontend::sketch::Radius;
3494 crate::front::Constraint::Radius(Radius {
3495 arc: segment_object_id,
3496 radius: n.try_into().map_err(|_| {
3497 internal_err("Failed to convert radius units numeric suffix:", range)
3498 })?,
3499 source,
3500 })
3501 };
3502 sketch_block_state.sketch_constraints.push(constraint_id);
3503 let Some(sketch_id) = sketch_block_state.sketch_id else {
3504 let message = "Sketch id missing for constraint artifact".to_owned();
3505 debug_assert!(false, "{}", &message);
3506 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3507 };
3508 let artifact_id = exec_state.next_artifact_id();
3509 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3510 id: artifact_id,
3511 sketch_id,
3512 constraint_id,
3513 constraint_type: SketchBlockConstraintType::from(&constraint),
3514 code_ref: CodeRef::placeholder(range),
3515 }));
3516 exec_state.add_scene_object(
3517 Object {
3518 id: constraint_id,
3519 kind: ObjectKind::Constraint { constraint },
3520 label: Default::default(),
3521 comments: Default::default(),
3522 artifact_id,
3523 source: SourceRef::new(range, self.node_path.clone()),
3524 },
3525 range,
3526 );
3527 }
3528 }
3529 SketchConstraintKind::HorizontalDistance { points } => {
3530 let range = self.as_source_range();
3531 let p0 = &points[0];
3532 let p1 = &points[1];
3533 #[cfg(feature = "artifact-graph")]
3534 let constraint_id = exec_state.next_object_id();
3535 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3536 let message =
3537 "Being inside a sketch block should have already been checked above".to_owned();
3538 debug_assert!(false, "{}", &message);
3539 return Err(internal_err(message, self));
3540 };
3541 match (p0, p1) {
3542 (
3543 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3544 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3545 ) => {
3546 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3547 p0.vars.x.to_constraint_id(range)?,
3548 p0.vars.y.to_constraint_id(range)?,
3549 );
3550 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3551 p1.vars.x.to_constraint_id(range)?,
3552 p1.vars.y.to_constraint_id(range)?,
3553 );
3554 sketch_block_state
3555 .solver_constraints
3556 .push(ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n));
3557 }
3558 (
3559 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3560 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3561 ) => {
3562 sketch_block_state
3564 .solver_constraints
3565 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, -n.n));
3566 }
3567 (
3568 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3569 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3570 ) => {
3571 sketch_block_state
3573 .solver_constraints
3574 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, n.n));
3575 }
3576 (
3577 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3578 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3579 ) => {
3580 return Err(internal_err(
3581 "horizontalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3582 range,
3583 ));
3584 }
3585 }
3586 #[cfg(feature = "artifact-graph")]
3587 {
3588 use crate::execution::Artifact;
3589 use crate::execution::CodeRef;
3590 use crate::execution::SketchBlockConstraint;
3591 use crate::execution::SketchBlockConstraintType;
3592 use crate::front::Distance;
3593 use crate::front::SourceRef;
3594 use crate::frontend::sketch::ConstraintSegment;
3595
3596 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
3597 points: vec![
3598 match p0 {
3599 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3600 ConstraintSegment::from(point.object_id)
3601 }
3602 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3603 ConstraintSegment::ORIGIN
3604 }
3605 },
3606 match p1 {
3607 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3608 ConstraintSegment::from(point.object_id)
3609 }
3610 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3611 ConstraintSegment::ORIGIN
3612 }
3613 },
3614 ],
3615 distance: n.try_into().map_err(|_| {
3616 internal_err("Failed to convert distance units numeric suffix:", range)
3617 })?,
3618 source,
3619 });
3620 sketch_block_state.sketch_constraints.push(constraint_id);
3621 let Some(sketch_id) = sketch_block_state.sketch_id else {
3622 let message = "Sketch id missing for constraint artifact".to_owned();
3623 debug_assert!(false, "{}", &message);
3624 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3625 };
3626 let artifact_id = exec_state.next_artifact_id();
3627 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3628 id: artifact_id,
3629 sketch_id,
3630 constraint_id,
3631 constraint_type: SketchBlockConstraintType::from(&constraint),
3632 code_ref: CodeRef::placeholder(range),
3633 }));
3634 exec_state.add_scene_object(
3635 Object {
3636 id: constraint_id,
3637 kind: ObjectKind::Constraint { constraint },
3638 label: Default::default(),
3639 comments: Default::default(),
3640 artifact_id,
3641 source: SourceRef::new(range, self.node_path.clone()),
3642 },
3643 range,
3644 );
3645 }
3646 }
3647 SketchConstraintKind::VerticalDistance { points } => {
3648 let range = self.as_source_range();
3649 let p0 = &points[0];
3650 let p1 = &points[1];
3651 #[cfg(feature = "artifact-graph")]
3652 let constraint_id = exec_state.next_object_id();
3653 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3654 let message =
3655 "Being inside a sketch block should have already been checked above".to_owned();
3656 debug_assert!(false, "{}", &message);
3657 return Err(internal_err(message, self));
3658 };
3659 match (p0, p1) {
3660 (
3661 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3662 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3663 ) => {
3664 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3665 p0.vars.x.to_constraint_id(range)?,
3666 p0.vars.y.to_constraint_id(range)?,
3667 );
3668 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3669 p1.vars.x.to_constraint_id(range)?,
3670 p1.vars.y.to_constraint_id(range)?,
3671 );
3672 sketch_block_state
3673 .solver_constraints
3674 .push(ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n));
3675 }
3676 (
3677 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3678 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3679 ) => {
3680 sketch_block_state
3681 .solver_constraints
3682 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, -n.n));
3683 }
3684 (
3685 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3686 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3687 ) => {
3688 sketch_block_state
3689 .solver_constraints
3690 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, n.n));
3691 }
3692 (
3693 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3694 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3695 ) => {
3696 return Err(internal_err(
3697 "verticalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3698 range,
3699 ));
3700 }
3701 }
3702 #[cfg(feature = "artifact-graph")]
3703 {
3704 use crate::execution::Artifact;
3705 use crate::execution::CodeRef;
3706 use crate::execution::SketchBlockConstraint;
3707 use crate::execution::SketchBlockConstraintType;
3708 use crate::front::Distance;
3709 use crate::front::SourceRef;
3710 use crate::frontend::sketch::ConstraintSegment;
3711
3712 let constraint = crate::front::Constraint::VerticalDistance(Distance {
3713 points: vec![
3714 match p0 {
3715 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3716 ConstraintSegment::from(point.object_id)
3717 }
3718 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3719 ConstraintSegment::ORIGIN
3720 }
3721 },
3722 match p1 {
3723 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3724 ConstraintSegment::from(point.object_id)
3725 }
3726 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3727 ConstraintSegment::ORIGIN
3728 }
3729 },
3730 ],
3731 distance: n.try_into().map_err(|_| {
3732 internal_err("Failed to convert distance units numeric suffix:", range)
3733 })?,
3734 source,
3735 });
3736 sketch_block_state.sketch_constraints.push(constraint_id);
3737 let Some(sketch_id) = sketch_block_state.sketch_id else {
3738 let message = "Sketch id missing for constraint artifact".to_owned();
3739 debug_assert!(false, "{}", &message);
3740 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3741 };
3742 let artifact_id = exec_state.next_artifact_id();
3743 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3744 id: artifact_id,
3745 sketch_id,
3746 constraint_id,
3747 constraint_type: SketchBlockConstraintType::from(&constraint),
3748 code_ref: CodeRef::placeholder(range),
3749 }));
3750 exec_state.add_scene_object(
3751 Object {
3752 id: constraint_id,
3753 kind: ObjectKind::Constraint { constraint },
3754 label: Default::default(),
3755 comments: Default::default(),
3756 artifact_id,
3757 source: SourceRef::new(range, self.node_path.clone()),
3758 },
3759 range,
3760 );
3761 }
3762 }
3763 }
3764 return Ok(KclValue::none());
3765 }
3766 _ => {
3767 return Err(KclError::new_semantic(KclErrorDetails::new(
3768 format!(
3769 "Cannot create an equivalence constraint between values of these types: {} and {}",
3770 left_value.human_friendly_type(),
3771 right_value.human_friendly_type()
3772 ),
3773 vec![self.into()],
3774 )));
3775 }
3776 }
3777 }
3778
3779 let left = number_as_f64(&left_value, self.left.clone().into())?;
3780 let right = number_as_f64(&right_value, self.right.clone().into())?;
3781
3782 let value = match self.operator {
3783 BinaryOperator::Add => {
3784 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3785 self.warn_on_unknown(&ty, "Adding", exec_state);
3786 KclValue::Number { value: l + r, meta, ty }
3787 }
3788 BinaryOperator::Sub => {
3789 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3790 self.warn_on_unknown(&ty, "Subtracting", exec_state);
3791 KclValue::Number { value: l - r, meta, ty }
3792 }
3793 BinaryOperator::Mul => {
3794 let (l, r, ty) = NumericType::combine_mul(left, right);
3795 self.warn_on_unknown(&ty, "Multiplying", exec_state);
3796 KclValue::Number { value: l * r, meta, ty }
3797 }
3798 BinaryOperator::Div => {
3799 let (l, r, ty) = NumericType::combine_div(left, right);
3800 self.warn_on_unknown(&ty, "Dividing", exec_state);
3801 KclValue::Number { value: l / r, meta, ty }
3802 }
3803 BinaryOperator::Mod => {
3804 let (l, r, ty) = NumericType::combine_mod(left, right);
3805 self.warn_on_unknown(&ty, "Modulo of", exec_state);
3806 KclValue::Number { value: l % r, meta, ty }
3807 }
3808 BinaryOperator::Pow => KclValue::Number {
3809 value: left.n.powf(right.n),
3810 meta,
3811 ty: exec_state.current_default_units(),
3812 },
3813 BinaryOperator::Neq => {
3814 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3815 self.warn_on_unknown(&ty, "Comparing", exec_state);
3816 KclValue::Bool { value: l != r, meta }
3817 }
3818 BinaryOperator::Gt => {
3819 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3820 self.warn_on_unknown(&ty, "Comparing", exec_state);
3821 KclValue::Bool { value: l > r, meta }
3822 }
3823 BinaryOperator::Gte => {
3824 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3825 self.warn_on_unknown(&ty, "Comparing", exec_state);
3826 KclValue::Bool { value: l >= r, meta }
3827 }
3828 BinaryOperator::Lt => {
3829 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3830 self.warn_on_unknown(&ty, "Comparing", exec_state);
3831 KclValue::Bool { value: l < r, meta }
3832 }
3833 BinaryOperator::Lte => {
3834 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3835 self.warn_on_unknown(&ty, "Comparing", exec_state);
3836 KclValue::Bool { value: l <= r, meta }
3837 }
3838 BinaryOperator::Eq => {
3839 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3840 self.warn_on_unknown(&ty, "Comparing", exec_state);
3841 KclValue::Bool { value: l == r, meta }
3842 }
3843 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
3844 };
3845
3846 Ok(value)
3847 }
3848
3849 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
3850 internal_err("missing result while evaluating binary expression", node)
3851 }
3852
3853 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
3854 if ty == &NumericType::Unknown {
3855 let sr = self.as_source_range();
3856 exec_state.clear_units_warnings(&sr);
3857 let mut err = CompilationIssue::err(
3858 sr,
3859 format!(
3860 "{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)`."
3861 ),
3862 );
3863 err.tag = crate::errors::Tag::UnknownNumericUnits;
3864 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
3865 }
3866 }
3867}
3868
3869impl Node<UnaryExpression> {
3870 pub(super) async fn get_result(
3871 &self,
3872 exec_state: &mut ExecState,
3873 ctx: &ExecutorContext,
3874 ) -> Result<KclValueControlFlow, KclError> {
3875 match self.operator {
3876 UnaryOperator::Not => {
3877 let value = self.argument.get_result(exec_state, ctx).await?;
3878 let value = control_continue!(value);
3879 let KclValue::Bool {
3880 value: bool_value,
3881 meta: _,
3882 } = value
3883 else {
3884 return Err(KclError::new_semantic(KclErrorDetails::new(
3885 format!(
3886 "Cannot apply unary operator ! to non-boolean value: {}",
3887 value.human_friendly_type()
3888 ),
3889 vec![self.into()],
3890 )));
3891 };
3892 let meta = vec![Metadata {
3893 source_range: self.into(),
3894 }];
3895 let negated = KclValue::Bool {
3896 value: !bool_value,
3897 meta,
3898 };
3899
3900 Ok(negated.continue_())
3901 }
3902 UnaryOperator::Neg => {
3903 let value = self.argument.get_result(exec_state, ctx).await?;
3904 let value = control_continue!(value);
3905 let err = || {
3906 KclError::new_semantic(KclErrorDetails::new(
3907 format!(
3908 "You can only negate numbers, planes, or lines, but this is a {}",
3909 value.human_friendly_type()
3910 ),
3911 vec![self.into()],
3912 ))
3913 };
3914 match &value {
3915 KclValue::Number { value, ty, .. } => {
3916 let meta = vec![Metadata {
3917 source_range: self.into(),
3918 }];
3919 Ok(KclValue::Number {
3920 value: -value,
3921 meta,
3922 ty: *ty,
3923 }
3924 .continue_())
3925 }
3926 KclValue::Plane { value } => {
3927 let mut plane = value.clone();
3928 if plane.info.x_axis.x != 0.0 {
3929 plane.info.x_axis.x *= -1.0;
3930 }
3931 if plane.info.x_axis.y != 0.0 {
3932 plane.info.x_axis.y *= -1.0;
3933 }
3934 if plane.info.x_axis.z != 0.0 {
3935 plane.info.x_axis.z *= -1.0;
3936 }
3937
3938 plane.id = exec_state.next_uuid();
3939 plane.object_id = None;
3940 Ok(KclValue::Plane { value: plane }.continue_())
3941 }
3942 KclValue::Object {
3943 value: values, meta, ..
3944 } => {
3945 let Some(direction) = values.get("direction") else {
3947 return Err(err());
3948 };
3949
3950 let direction = match direction {
3951 KclValue::Tuple { value: values, meta } => {
3952 let values = values
3953 .iter()
3954 .map(|v| match v {
3955 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3956 value: *value * -1.0,
3957 ty: *ty,
3958 meta: meta.clone(),
3959 }),
3960 _ => Err(err()),
3961 })
3962 .collect::<Result<Vec<_>, _>>()?;
3963
3964 KclValue::Tuple {
3965 value: values,
3966 meta: meta.clone(),
3967 }
3968 }
3969 KclValue::HomArray {
3970 value: values,
3971 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
3972 } => {
3973 let values = values
3974 .iter()
3975 .map(|v| match v {
3976 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3977 value: *value * -1.0,
3978 ty: *ty,
3979 meta: meta.clone(),
3980 }),
3981 _ => Err(err()),
3982 })
3983 .collect::<Result<Vec<_>, _>>()?;
3984
3985 KclValue::HomArray {
3986 value: values,
3987 ty: ty.clone(),
3988 }
3989 }
3990 _ => return Err(err()),
3991 };
3992
3993 let mut value = values.clone();
3994 value.insert("direction".to_owned(), direction);
3995 Ok(KclValue::Object {
3996 value,
3997 meta: meta.clone(),
3998 constrainable: false,
3999 }
4000 .continue_())
4001 }
4002 _ => Err(err()),
4003 }
4004 }
4005 UnaryOperator::Plus => {
4006 let operand = self.argument.get_result(exec_state, ctx).await?;
4007 let operand = control_continue!(operand);
4008 match operand {
4009 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
4010 _ => Err(KclError::new_semantic(KclErrorDetails::new(
4011 format!(
4012 "You can only apply unary + to numbers or planes, but this is a {}",
4013 operand.human_friendly_type()
4014 ),
4015 vec![self.into()],
4016 ))),
4017 }
4018 }
4019 }
4020 }
4021}
4022
4023pub(crate) async fn execute_pipe_body(
4024 exec_state: &mut ExecState,
4025 body: &[Expr],
4026 source_range: SourceRange,
4027 ctx: &ExecutorContext,
4028) -> Result<KclValueControlFlow, KclError> {
4029 let Some((first, body)) = body.split_first() else {
4030 return Err(KclError::new_semantic(KclErrorDetails::new(
4031 "Pipe expressions cannot be empty".to_owned(),
4032 vec![source_range],
4033 )));
4034 };
4035 let meta = Metadata {
4040 source_range: SourceRange::from(first),
4041 };
4042 let output = ctx
4043 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
4044 .await?;
4045 let output = control_continue!(output);
4046
4047 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
4051 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
4053 exec_state.mod_local.pipe_value = previous_pipe_value;
4055
4056 result
4057}
4058
4059#[async_recursion]
4062async fn inner_execute_pipe_body(
4063 exec_state: &mut ExecState,
4064 body: &[Expr],
4065 ctx: &ExecutorContext,
4066) -> Result<KclValueControlFlow, KclError> {
4067 for expression in body {
4068 if let Expr::TagDeclarator(_) = expression {
4069 return Err(KclError::new_semantic(KclErrorDetails::new(
4070 format!("This cannot be in a PipeExpression: {expression:?}"),
4071 vec![expression.into()],
4072 )));
4073 }
4074 let metadata = Metadata {
4075 source_range: SourceRange::from(expression),
4076 };
4077 let output = ctx
4078 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
4079 .await?;
4080 let output = control_continue!(output);
4081 exec_state.mod_local.pipe_value = Some(output);
4082 }
4083 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
4085 Ok(final_output.continue_())
4086}
4087
4088impl Node<TagDeclarator> {
4089 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
4090 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
4091 value: self.name.clone(),
4092 info: Vec::new(),
4093 meta: vec![Metadata {
4094 source_range: self.into(),
4095 }],
4096 }));
4097
4098 exec_state
4099 .mut_stack()
4100 .add(self.name.clone(), memory_item, self.into())?;
4101
4102 Ok(self.into())
4103 }
4104}
4105
4106impl Node<ArrayExpression> {
4107 #[async_recursion]
4108 pub(super) async fn execute(
4109 &self,
4110 exec_state: &mut ExecState,
4111 ctx: &ExecutorContext,
4112 ) -> Result<KclValueControlFlow, KclError> {
4113 let mut results = Vec::with_capacity(self.elements.len());
4114
4115 for element in &self.elements {
4116 let metadata = Metadata::from(element);
4117 let value = ctx
4120 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
4121 .await?;
4122 let value = control_continue!(value);
4123
4124 results.push(value);
4125 }
4126
4127 Ok(KclValue::HomArray {
4128 value: results,
4129 ty: RuntimeType::Primitive(PrimitiveType::Any),
4130 }
4131 .continue_())
4132 }
4133}
4134
4135impl Node<ArrayRangeExpression> {
4136 #[async_recursion]
4137 pub(super) async fn execute(
4138 &self,
4139 exec_state: &mut ExecState,
4140 ctx: &ExecutorContext,
4141 ) -> Result<KclValueControlFlow, KclError> {
4142 let metadata = Metadata::from(&self.start_element);
4143 let start_val = ctx
4144 .execute_expr(
4145 &self.start_element,
4146 exec_state,
4147 &metadata,
4148 &[],
4149 StatementKind::Expression,
4150 )
4151 .await?;
4152 let start_val = control_continue!(start_val);
4153 let start = start_val
4154 .as_ty_f64()
4155 .ok_or(KclError::new_semantic(KclErrorDetails::new(
4156 format!(
4157 "Expected number for range start but found {}",
4158 start_val.human_friendly_type()
4159 ),
4160 vec![self.into()],
4161 )))?;
4162 let metadata = Metadata::from(&self.end_element);
4163 let end_val = ctx
4164 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
4165 .await?;
4166 let end_val = control_continue!(end_val);
4167 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
4168 format!(
4169 "Expected number for range end but found {}",
4170 end_val.human_friendly_type()
4171 ),
4172 vec![self.into()],
4173 )))?;
4174
4175 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
4176 let Some(start) = crate::try_f64_to_i64(start) else {
4177 return Err(KclError::new_semantic(KclErrorDetails::new(
4178 format!("Range start must be an integer, but found {start}"),
4179 vec![self.into()],
4180 )));
4181 };
4182 let Some(end) = crate::try_f64_to_i64(end) else {
4183 return Err(KclError::new_semantic(KclErrorDetails::new(
4184 format!("Range end must be an integer, but found {end}"),
4185 vec![self.into()],
4186 )));
4187 };
4188
4189 if end < start {
4190 return Err(KclError::new_semantic(KclErrorDetails::new(
4191 format!("Range start is greater than range end: {start} .. {end}"),
4192 vec![self.into()],
4193 )));
4194 }
4195
4196 let range: Vec<_> = if self.end_inclusive {
4197 (start..=end).collect()
4198 } else {
4199 (start..end).collect()
4200 };
4201
4202 let meta = vec![Metadata {
4203 source_range: self.into(),
4204 }];
4205
4206 Ok(KclValue::HomArray {
4207 value: range
4208 .into_iter()
4209 .map(|num| KclValue::Number {
4210 value: num as f64,
4211 ty,
4212 meta: meta.clone(),
4213 })
4214 .collect(),
4215 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
4216 }
4217 .continue_())
4218 }
4219}
4220
4221impl Node<ObjectExpression> {
4222 #[async_recursion]
4223 pub(super) async fn execute(
4224 &self,
4225 exec_state: &mut ExecState,
4226 ctx: &ExecutorContext,
4227 ) -> Result<KclValueControlFlow, KclError> {
4228 let mut object = HashMap::with_capacity(self.properties.len());
4229 for property in &self.properties {
4230 let metadata = Metadata::from(&property.value);
4231 let result = ctx
4232 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
4233 .await?;
4234 let result = control_continue!(result);
4235 object.insert(property.key.name.clone(), result);
4236 }
4237
4238 Ok(KclValue::Object {
4239 value: object,
4240 meta: vec![Metadata {
4241 source_range: self.into(),
4242 }],
4243 constrainable: false,
4244 }
4245 .continue_())
4246 }
4247}
4248
4249fn article_for<S: AsRef<str>>(s: S) -> &'static str {
4250 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
4252 "an"
4253 } else {
4254 "a"
4255 }
4256}
4257
4258fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
4259 v.as_ty_f64().ok_or_else(|| {
4260 let actual_type = v.human_friendly_type();
4261 KclError::new_semantic(KclErrorDetails::new(
4262 format!("Expected a number, but found {actual_type}",),
4263 vec![source_range],
4264 ))
4265 })
4266}
4267
4268impl Node<IfExpression> {
4269 #[async_recursion]
4270 pub(super) async fn get_result(
4271 &self,
4272 exec_state: &mut ExecState,
4273 ctx: &ExecutorContext,
4274 ) -> Result<KclValueControlFlow, KclError> {
4275 let cond_value = ctx
4277 .execute_expr(
4278 &self.cond,
4279 exec_state,
4280 &Metadata::from(self),
4281 &[],
4282 StatementKind::Expression,
4283 )
4284 .await?;
4285 let cond_value = control_continue!(cond_value);
4286 if cond_value.get_bool()? {
4287 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
4288 return Ok(block_result.unwrap());
4292 }
4293
4294 for else_if in &self.else_ifs {
4296 let cond_value = ctx
4297 .execute_expr(
4298 &else_if.cond,
4299 exec_state,
4300 &Metadata::from(self),
4301 &[],
4302 StatementKind::Expression,
4303 )
4304 .await?;
4305 let cond_value = control_continue!(cond_value);
4306 if cond_value.get_bool()? {
4307 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
4308 return Ok(block_result.unwrap());
4312 }
4313 }
4314
4315 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
4317 .await
4318 .map(|expr| expr.unwrap())
4319 }
4320}
4321
4322#[derive(Debug)]
4323enum Property {
4324 UInt(usize),
4325 String(String),
4326}
4327
4328impl Property {
4329 #[allow(clippy::too_many_arguments)]
4330 async fn try_from<'a>(
4331 computed: bool,
4332 value: Expr,
4333 exec_state: &mut ExecState,
4334 sr: SourceRange,
4335 ctx: &ExecutorContext,
4336 metadata: &Metadata,
4337 annotations: &[Node<Annotation>],
4338 statement_kind: StatementKind<'a>,
4339 ) -> Result<Self, KclError> {
4340 let property_sr = vec![sr];
4341 if !computed {
4342 let Expr::Name(identifier) = value else {
4343 return Err(KclError::new_semantic(KclErrorDetails::new(
4345 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
4346 .to_owned(),
4347 property_sr,
4348 )));
4349 };
4350 return Ok(Property::String(identifier.to_string()));
4351 }
4352
4353 let prop_value = ctx
4354 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
4355 .await?;
4356 let prop_value = match prop_value.control {
4357 ControlFlowKind::Continue => prop_value.into_value(),
4358 ControlFlowKind::Exit => {
4359 let message = "Early return inside array brackets is currently not supported".to_owned();
4360 debug_assert!(false, "{}", &message);
4361 return Err(internal_err(message, sr));
4362 }
4363 };
4364 match prop_value {
4365 KclValue::Number { value, ty, meta: _ } => {
4366 if !matches!(
4367 ty,
4368 NumericType::Unknown
4369 | NumericType::Default { .. }
4370 | NumericType::Known(crate::exec::UnitType::Count)
4371 ) {
4372 return Err(KclError::new_semantic(KclErrorDetails::new(
4373 format!(
4374 "{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"
4375 ),
4376 property_sr,
4377 )));
4378 }
4379 if let Some(x) = crate::try_f64_to_usize(value) {
4380 Ok(Property::UInt(x))
4381 } else {
4382 Err(KclError::new_semantic(KclErrorDetails::new(
4383 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
4384 property_sr,
4385 )))
4386 }
4387 }
4388 _ => Err(KclError::new_semantic(KclErrorDetails::new(
4389 "Only numbers (>= 0) can be indexes".to_owned(),
4390 vec![sr],
4391 ))),
4392 }
4393 }
4394}
4395
4396impl Property {
4397 fn type_name(&self) -> &'static str {
4398 match self {
4399 Property::UInt(_) => "number",
4400 Property::String(_) => "string",
4401 }
4402 }
4403}
4404
4405impl Node<PipeExpression> {
4406 #[async_recursion]
4407 pub(super) async fn get_result(
4408 &self,
4409 exec_state: &mut ExecState,
4410 ctx: &ExecutorContext,
4411 ) -> Result<KclValueControlFlow, KclError> {
4412 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
4413 }
4414}
4415
4416#[cfg(test)]
4417mod test {
4418 use std::sync::Arc;
4419
4420 use tokio::io::AsyncWriteExt;
4421
4422 use super::*;
4423 use crate::ExecutorSettings;
4424 use crate::errors::Severity;
4425 use crate::exec::UnitType;
4426 use crate::execution::ContextType;
4427 use crate::execution::parse_execute;
4428
4429 #[tokio::test(flavor = "multi_thread")]
4430 async fn ascription() {
4431 let program = r#"
4432a = 42: number
4433b = a: number
4434p = {
4435 origin = { x = 0, y = 0, z = 0 },
4436 xAxis = { x = 1, y = 0, z = 0 },
4437 yAxis = { x = 0, y = 1, z = 0 },
4438 zAxis = { x = 0, y = 0, z = 1 }
4439}: Plane
4440arr1 = [42]: [number(cm)]
4441"#;
4442
4443 let result = parse_execute(program).await.unwrap();
4444 let mem = result.exec_state.stack();
4445 assert!(matches!(
4446 mem.memory
4447 .get_from("p", result.mem_env, SourceRange::default(), 0)
4448 .unwrap(),
4449 KclValue::Plane { .. }
4450 ));
4451 let arr1 = mem
4452 .memory
4453 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
4454 .unwrap();
4455 if let KclValue::HomArray { value, ty } = arr1 {
4456 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
4457 assert_eq!(
4458 *ty,
4459 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
4460 );
4461 if let KclValue::Number { value, ty, .. } = &value[0] {
4463 assert_eq!(*value, 42.0);
4465 assert_eq!(
4466 *ty,
4467 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
4468 );
4469 } else {
4470 panic!("Expected a number; found {:?}", value[0]);
4471 }
4472 } else {
4473 panic!("Expected HomArray; found {arr1:?}");
4474 }
4475
4476 let program = r#"
4477a = 42: string
4478"#;
4479 let result = parse_execute(program).await;
4480 let err = result.unwrap_err();
4481 assert!(
4482 err.to_string()
4483 .contains("could not coerce a number (with type `number`) to type `string`"),
4484 "Expected error but found {err:?}"
4485 );
4486
4487 let program = r#"
4488a = 42: Plane
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 `Plane`"),
4495 "Expected error but found {err:?}"
4496 );
4497
4498 let program = r#"
4499arr = [0]: [string]
4500"#;
4501 let result = parse_execute(program).await;
4502 let err = result.unwrap_err();
4503 assert!(
4504 err.to_string().contains(
4505 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
4506 ),
4507 "Expected error but found {err:?}"
4508 );
4509
4510 let program = r#"
4511mixedArr = [0, "a"]: [number(mm)]
4512"#;
4513 let result = parse_execute(program).await;
4514 let err = result.unwrap_err();
4515 assert!(
4516 err.to_string().contains(
4517 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4518 ),
4519 "Expected error but found {err:?}"
4520 );
4521
4522 let program = r#"
4523mixedArr = [0, "a"]: [mm]
4524"#;
4525 let result = parse_execute(program).await;
4526 let err = result.unwrap_err();
4527 assert!(
4528 err.to_string().contains(
4529 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4530 ),
4531 "Expected error but found {err:?}"
4532 );
4533 }
4534
4535 #[tokio::test(flavor = "multi_thread")]
4536 async fn neg_plane() {
4537 let program = r#"
4538p = {
4539 origin = { x = 0, y = 0, z = 0 },
4540 xAxis = { x = 1, y = 0, z = 0 },
4541 yAxis = { x = 0, y = 1, z = 0 },
4542}: Plane
4543p2 = -p
4544"#;
4545
4546 let result = parse_execute(program).await.unwrap();
4547 let mem = result.exec_state.stack();
4548 match mem
4549 .memory
4550 .get_from("p2", result.mem_env, SourceRange::default(), 0)
4551 .unwrap()
4552 {
4553 KclValue::Plane { value } => {
4554 assert_eq!(value.info.x_axis.x, -1.0);
4555 assert_eq!(value.info.x_axis.y, 0.0);
4556 assert_eq!(value.info.x_axis.z, 0.0);
4557 }
4558 _ => unreachable!(),
4559 }
4560 }
4561
4562 #[tokio::test(flavor = "multi_thread")]
4563 async fn multiple_returns() {
4564 let program = r#"fn foo() {
4565 return 0
4566 return 42
4567}
4568
4569a = foo()
4570"#;
4571
4572 let result = parse_execute(program).await;
4573 assert!(result.unwrap_err().to_string().contains("return"));
4574 }
4575
4576 #[tokio::test(flavor = "multi_thread")]
4577 async fn load_all_modules() {
4578 let program_a_kcl = r#"
4580export a = 1
4581"#;
4582 let program_b_kcl = r#"
4584import a from 'a.kcl'
4585
4586export b = a + 1
4587"#;
4588 let program_c_kcl = r#"
4590import a from 'a.kcl'
4591
4592export c = a + 2
4593"#;
4594
4595 let main_kcl = r#"
4597import b from 'b.kcl'
4598import c from 'c.kcl'
4599
4600d = b + c
4601"#;
4602
4603 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
4604 .parse_errs_as_err()
4605 .unwrap();
4606
4607 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
4608
4609 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
4610 .await
4611 .unwrap()
4612 .write_all(main_kcl.as_bytes())
4613 .await
4614 .unwrap();
4615
4616 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
4617 .await
4618 .unwrap()
4619 .write_all(program_a_kcl.as_bytes())
4620 .await
4621 .unwrap();
4622
4623 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
4624 .await
4625 .unwrap()
4626 .write_all(program_b_kcl.as_bytes())
4627 .await
4628 .unwrap();
4629
4630 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
4631 .await
4632 .unwrap()
4633 .write_all(program_c_kcl.as_bytes())
4634 .await
4635 .unwrap();
4636
4637 let exec_ctxt = ExecutorContext {
4638 engine: Arc::new(Box::new(
4639 crate::engine::conn_mock::EngineConnection::new()
4640 .map_err(|err| {
4641 internal_err(
4642 format!("Failed to create mock engine connection: {err}"),
4643 SourceRange::default(),
4644 )
4645 })
4646 .unwrap(),
4647 )),
4648 fs: Arc::new(crate::fs::FileManager::new()),
4649 settings: ExecutorSettings {
4650 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
4651 ..Default::default()
4652 },
4653 context_type: ContextType::Mock,
4654 };
4655 let mut exec_state = ExecState::new(&exec_ctxt);
4656
4657 exec_ctxt
4658 .run(
4659 &crate::Program {
4660 ast: main.clone(),
4661 original_file_contents: "".to_owned(),
4662 },
4663 &mut exec_state,
4664 )
4665 .await
4666 .unwrap();
4667 }
4668
4669 #[tokio::test(flavor = "multi_thread")]
4670 async fn user_coercion() {
4671 let program = r#"fn foo(x: Axis2d) {
4672 return 0
4673}
4674
4675foo(x = { direction = [0, 0], origin = [0, 0]})
4676"#;
4677
4678 parse_execute(program).await.unwrap();
4679
4680 let program = r#"fn foo(x: Axis3d) {
4681 return 0
4682}
4683
4684foo(x = { direction = [0, 0], origin = [0, 0]})
4685"#;
4686
4687 parse_execute(program).await.unwrap_err();
4688 }
4689
4690 #[tokio::test(flavor = "multi_thread")]
4691 async fn coerce_return() {
4692 let program = r#"fn foo(): number(mm) {
4693 return 42
4694}
4695
4696a = foo()
4697"#;
4698
4699 parse_execute(program).await.unwrap();
4700
4701 let program = r#"fn foo(): mm {
4702 return 42
4703}
4704
4705a = foo()
4706"#;
4707
4708 parse_execute(program).await.unwrap();
4709
4710 let program = r#"fn foo(): number(mm) {
4711 return { bar: 42 }
4712}
4713
4714a = foo()
4715"#;
4716
4717 parse_execute(program).await.unwrap_err();
4718
4719 let program = r#"fn foo(): mm {
4720 return { bar: 42 }
4721}
4722
4723a = foo()
4724"#;
4725
4726 parse_execute(program).await.unwrap_err();
4727 }
4728
4729 #[tokio::test(flavor = "multi_thread")]
4730 async fn test_sensible_error_when_missing_equals_in_kwarg() {
4731 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)"]
4732 .into_iter()
4733 .enumerate()
4734 {
4735 let program = format!(
4736 "fn foo() {{ return 0 }}
4737z = 0
4738fn f(x, y, z) {{ return 0 }}
4739{call}"
4740 );
4741 let err = parse_execute(&program).await.unwrap_err();
4742 let msg = err.message();
4743 assert!(
4744 msg.contains("This argument needs a label, but it doesn't have one"),
4745 "failed test {i}: {msg}"
4746 );
4747 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
4748 if i == 0 {
4749 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
4750 }
4751 }
4752 }
4753
4754 #[tokio::test(flavor = "multi_thread")]
4755 async fn default_param_for_unlabeled() {
4756 let ast = r#"fn myExtrude(@sk, length) {
4759 return extrude(sk, length)
4760}
4761sketch001 = startSketchOn(XY)
4762 |> circle(center = [0, 0], radius = 93.75)
4763 |> myExtrude(length = 40)
4764"#;
4765
4766 parse_execute(ast).await.unwrap();
4767 }
4768
4769 #[tokio::test(flavor = "multi_thread")]
4770 async fn dont_use_unlabelled_as_input() {
4771 let ast = r#"length = 10
4773startSketchOn(XY)
4774 |> circle(center = [0, 0], radius = 93.75)
4775 |> extrude(length)
4776"#;
4777
4778 parse_execute(ast).await.unwrap();
4779 }
4780
4781 #[tokio::test(flavor = "multi_thread")]
4782 async fn ascription_in_binop() {
4783 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
4784 parse_execute(ast).await.unwrap();
4785
4786 let ast = r#"foo = tan(0): rad - 4deg"#;
4787 parse_execute(ast).await.unwrap();
4788 }
4789
4790 #[tokio::test(flavor = "multi_thread")]
4791 async fn neg_sqrt() {
4792 let ast = r#"bad = sqrt(-2)"#;
4793
4794 let e = parse_execute(ast).await.unwrap_err();
4795 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
4797 }
4798
4799 #[tokio::test(flavor = "multi_thread")]
4800 async fn non_array_fns() {
4801 let ast = r#"push(1, item = 2)
4802pop(1)
4803map(1, f = fn(@x) { return x + 1 })
4804reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
4805
4806 parse_execute(ast).await.unwrap();
4807 }
4808
4809 #[tokio::test(flavor = "multi_thread")]
4810 async fn non_array_indexing() {
4811 let good = r#"a = 42
4812good = a[0]
4813"#;
4814 let result = parse_execute(good).await.unwrap();
4815 let mem = result.exec_state.stack();
4816 let num = mem
4817 .memory
4818 .get_from("good", result.mem_env, SourceRange::default(), 0)
4819 .unwrap()
4820 .as_ty_f64()
4821 .unwrap();
4822 assert_eq!(num.n, 42.0);
4823
4824 let bad = r#"a = 42
4825bad = a[1]
4826"#;
4827
4828 parse_execute(bad).await.unwrap_err();
4829 }
4830
4831 #[tokio::test(flavor = "multi_thread")]
4832 async fn coerce_unknown_to_length() {
4833 let ast = r#"x = 2mm * 2mm
4834y = x: number(Length)"#;
4835 let e = parse_execute(ast).await.unwrap_err();
4836 assert!(
4837 e.message().contains("could not coerce"),
4838 "Error message: '{}'",
4839 e.message()
4840 );
4841
4842 let ast = r#"x = 2mm
4843y = x: number(Length)"#;
4844 let result = parse_execute(ast).await.unwrap();
4845 let mem = result.exec_state.stack();
4846 let num = mem
4847 .memory
4848 .get_from("y", result.mem_env, SourceRange::default(), 0)
4849 .unwrap()
4850 .as_ty_f64()
4851 .unwrap();
4852 assert_eq!(num.n, 2.0);
4853 assert_eq!(num.ty, NumericType::mm());
4854 }
4855
4856 #[tokio::test(flavor = "multi_thread")]
4857 async fn one_warning_unknown() {
4858 let ast = r#"
4859// Should warn once
4860a = PI * 2
4861// Should warn once
4862b = (PI * 2) / 3
4863// Should not warn
4864c = ((PI * 2) / 3): number(deg)
4865"#;
4866
4867 let result = parse_execute(ast).await.unwrap();
4868 assert_eq!(result.exec_state.issues().len(), 2);
4869 }
4870
4871 #[tokio::test(flavor = "multi_thread")]
4872 async fn non_count_indexing() {
4873 let ast = r#"x = [0, 0]
4874y = x[1mm]
4875"#;
4876 parse_execute(ast).await.unwrap_err();
4877
4878 let ast = r#"x = [0, 0]
4879y = 1deg
4880z = x[y]
4881"#;
4882 parse_execute(ast).await.unwrap_err();
4883
4884 let ast = r#"x = [0, 0]
4885y = x[0mm + 1]
4886"#;
4887 parse_execute(ast).await.unwrap_err();
4888 }
4889
4890 #[tokio::test(flavor = "multi_thread")]
4891 async fn getting_property_of_plane() {
4892 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
4893 parse_execute(&ast).await.unwrap();
4894 }
4895
4896 #[cfg(feature = "artifact-graph")]
4897 #[tokio::test(flavor = "multi_thread")]
4898 async fn no_artifacts_from_within_hole_call() {
4899 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
4904 let out = parse_execute(&ast).await.unwrap();
4905
4906 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4908
4909 let expected = 5;
4913 assert_eq!(
4914 actual_operations.len(),
4915 expected,
4916 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4917 actual_operations.len(),
4918 );
4919 }
4920
4921 #[cfg(feature = "artifact-graph")]
4922 #[tokio::test(flavor = "multi_thread")]
4923 async fn feature_tree_annotation_on_user_defined_kcl() {
4924 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4927 let out = parse_execute(&ast).await.unwrap();
4928
4929 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4931
4932 let expected = 0;
4933 assert_eq!(
4934 actual_operations.len(),
4935 expected,
4936 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4937 actual_operations.len(),
4938 );
4939 }
4940
4941 #[cfg(feature = "artifact-graph")]
4942 #[tokio::test(flavor = "multi_thread")]
4943 async fn no_feature_tree_annotation_on_user_defined_kcl() {
4944 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4947 let out = parse_execute(&ast).await.unwrap();
4948
4949 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4951
4952 let expected = 2;
4953 assert_eq!(
4954 actual_operations.len(),
4955 expected,
4956 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4957 actual_operations.len(),
4958 );
4959 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
4960 assert!(matches!(actual_operations[1], Operation::GroupEnd));
4961 }
4962
4963 #[tokio::test(flavor = "multi_thread")]
4964 async fn custom_warning() {
4965 let warn = r#"
4966a = PI * 2
4967"#;
4968 let result = parse_execute(warn).await.unwrap();
4969 assert_eq!(result.exec_state.issues().len(), 1);
4970 assert_eq!(result.exec_state.issues()[0].severity, Severity::Warning);
4971
4972 let allow = r#"
4973@warnings(allow = unknownUnits)
4974a = PI * 2
4975"#;
4976 let result = parse_execute(allow).await.unwrap();
4977 assert_eq!(result.exec_state.issues().len(), 0);
4978
4979 let deny = r#"
4980@warnings(deny = [unknownUnits])
4981a = PI * 2
4982"#;
4983 let result = parse_execute(deny).await.unwrap();
4984 assert_eq!(result.exec_state.issues().len(), 1);
4985 assert_eq!(result.exec_state.issues()[0].severity, Severity::Error);
4986 }
4987
4988 #[tokio::test(flavor = "multi_thread")]
4989 async fn sketch_block_unqualified_functions_use_sketch2() {
4990 let ast = r#"
4991s = sketch(on = XY) {
4992 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
4993 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
4994 coincident([line1.end, line2.start])
4995}
4996"#;
4997 let result = parse_execute(ast).await.unwrap();
4998 let mem = result.exec_state.stack();
4999 let sketch_value = mem
5000 .memory
5001 .get_from("s", result.mem_env, SourceRange::default(), 0)
5002 .unwrap();
5003
5004 let KclValue::Object { value, .. } = sketch_value else {
5005 panic!("Expected sketch block to return an object, got {sketch_value:?}");
5006 };
5007
5008 assert!(value.contains_key("line1"));
5009 assert!(value.contains_key("line2"));
5010 assert!(!value.contains_key("line"));
5013 assert!(!value.contains_key("coincident"));
5014 }
5015
5016 #[tokio::test(flavor = "multi_thread")]
5017 async fn cannot_solid_extrude_an_open_profile() {
5018 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
5021 let program = crate::Program::parse_no_errs(&code).expect("should parse");
5022 let exec_ctxt = ExecutorContext::new_mock(None).await;
5023 let mut exec_state = ExecState::new(&exec_ctxt);
5024
5025 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
5026 assert!(matches!(err, KclError::Semantic { .. }));
5027 exec_ctxt.close().await;
5028 }
5029}