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