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