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