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