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