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 exec_state,
2813 args,
2814 )
2815 .await?;
2816 return Ok(result.into());
2817 }
2818 } else if self.operator == BinaryOperator::Sub {
2819 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2821 let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
2822 let result = crate::std::csg::inner_subtract(
2823 vec![*left.clone()],
2824 vec![*right.clone()],
2825 Default::default(),
2826 exec_state,
2827 args,
2828 )
2829 .await?;
2830 return Ok(result.into());
2831 }
2832 } else if self.operator == BinaryOperator::And
2833 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2834 {
2835 let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
2837 let result = crate::std::csg::inner_intersect(
2838 vec![*left.clone(), *right.clone()],
2839 Default::default(),
2840 exec_state,
2841 args,
2842 )
2843 .await?;
2844 return Ok(result.into());
2845 }
2846
2847 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2849 let KclValue::Bool { value: left_value, .. } = left_value else {
2850 return Err(KclError::new_semantic(KclErrorDetails::new(
2851 format!(
2852 "Cannot apply logical operator to non-boolean value: {}",
2853 left_value.human_friendly_type()
2854 ),
2855 vec![self.left.clone().into()],
2856 )));
2857 };
2858 let KclValue::Bool { value: right_value, .. } = right_value else {
2859 return Err(KclError::new_semantic(KclErrorDetails::new(
2860 format!(
2861 "Cannot apply logical operator to non-boolean value: {}",
2862 right_value.human_friendly_type()
2863 ),
2864 vec![self.right.clone().into()],
2865 )));
2866 };
2867 let raw_value = match self.operator {
2868 BinaryOperator::Or => left_value || right_value,
2869 BinaryOperator::And => left_value && right_value,
2870 _ => unreachable!(),
2871 };
2872 return Ok(KclValue::Bool { value: raw_value, meta });
2873 }
2874
2875 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2877 match (&left_value, &right_value) {
2878 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2880 if left_value.id == right_value.id =>
2881 {
2882 return Ok(KclValue::Bool { value: true, meta });
2883 }
2884 (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
2886 let constraint = Constraint::ScalarEqual(
2887 var0.id.to_constraint_id(self.as_source_range())?,
2888 var1.id.to_constraint_id(self.as_source_range())?,
2889 );
2890 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2891 let message = "Being inside a sketch block should have already been checked above".to_owned();
2892 debug_assert!(false, "{}", &message);
2893 return Err(internal_err(message, self));
2894 };
2895 sketch_block_state.solver_constraints.push(constraint);
2896 return Ok(KclValue::Bool { value: true, meta });
2897 }
2898 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2900 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2901 let number_value = normalize_to_solver_distance_unit(
2902 input_number,
2903 input_number.into(),
2904 exec_state,
2905 "fixed constraint value",
2906 )?;
2907 let Some(n) = number_value.as_ty_f64() else {
2908 let message = format!(
2909 "Expected number after coercion, but found {}",
2910 number_value.human_friendly_type()
2911 );
2912 debug_assert!(false, "{}", &message);
2913 return Err(internal_err(message, self));
2914 };
2915 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2916 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2917 let message = "Being inside a sketch block should have already been checked above".to_owned();
2918 debug_assert!(false, "{}", &message);
2919 return Err(internal_err(message, self));
2920 };
2921 sketch_block_state.solver_constraints.push(constraint);
2922 return Ok(KclValue::Bool { value: true, meta });
2923 }
2924 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
2926 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
2927 let number_value = match constraint.kind {
2928 SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
2930 input_number,
2931 input_number.into(),
2932 exec_state,
2933 "fixed constraint value",
2934 )?,
2935 SketchConstraintKind::Distance { .. }
2937 | SketchConstraintKind::Radius { .. }
2938 | SketchConstraintKind::Diameter { .. }
2939 | SketchConstraintKind::HorizontalDistance { .. }
2940 | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
2941 input_number,
2942 input_number.into(),
2943 exec_state,
2944 "fixed constraint value",
2945 )?,
2946 };
2947 let Some(n) = number_value.as_ty_f64() else {
2948 let message = format!(
2949 "Expected number after coercion, but found {}",
2950 number_value.human_friendly_type()
2951 );
2952 debug_assert!(false, "{}", &message);
2953 return Err(internal_err(message, self));
2954 };
2955 #[cfg(feature = "artifact-graph")]
2957 let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
2958 &self.right
2959 } else {
2960 &self.left
2961 };
2962 #[cfg(feature = "artifact-graph")]
2963 let source = {
2964 use crate::unparser::ExprContext;
2965 let mut buf = String::new();
2966 number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
2967 crate::frontend::sketch::ConstraintSource {
2968 expr: buf,
2969 is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
2970 }
2971 };
2972
2973 match &constraint.kind {
2974 SketchConstraintKind::Angle { line0, line1 } => {
2975 let range = self.as_source_range();
2976 let ax = line0.vars[0].x.to_constraint_id(range)?;
2979 let ay = line0.vars[0].y.to_constraint_id(range)?;
2980 let bx = line0.vars[1].x.to_constraint_id(range)?;
2981 let by = line0.vars[1].y.to_constraint_id(range)?;
2982 let cx = line1.vars[0].x.to_constraint_id(range)?;
2983 let cy = line1.vars[0].y.to_constraint_id(range)?;
2984 let dx = line1.vars[1].x.to_constraint_id(range)?;
2985 let dy = line1.vars[1].y.to_constraint_id(range)?;
2986 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(
2987 ezpz::datatypes::inputs::DatumPoint::new_xy(ax, ay),
2988 ezpz::datatypes::inputs::DatumPoint::new_xy(bx, by),
2989 );
2990 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(
2991 ezpz::datatypes::inputs::DatumPoint::new_xy(cx, cy),
2992 ezpz::datatypes::inputs::DatumPoint::new_xy(dx, dy),
2993 );
2994 let desired_angle = match n.ty {
2995 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Degrees))
2996 | NumericType::Default {
2997 len: _,
2998 angle: kcmc::units::UnitAngle::Degrees,
2999 } => ezpz::datatypes::Angle::from_degrees(n.n),
3000 NumericType::Known(crate::exec::UnitType::Angle(kcmc::units::UnitAngle::Radians))
3001 | NumericType::Default {
3002 len: _,
3003 angle: kcmc::units::UnitAngle::Radians,
3004 } => ezpz::datatypes::Angle::from_radians(n.n),
3005 NumericType::Known(crate::exec::UnitType::Count)
3006 | NumericType::Known(crate::exec::UnitType::GenericLength)
3007 | NumericType::Known(crate::exec::UnitType::GenericAngle)
3008 | NumericType::Known(crate::exec::UnitType::Length(_))
3009 | NumericType::Unknown
3010 | NumericType::Any => {
3011 let message = format!("Expected angle but found {:?}", n);
3012 debug_assert!(false, "{}", &message);
3013 return Err(internal_err(message, self));
3014 }
3015 };
3016 let solver_constraint = Constraint::LinesAtAngle(
3017 solver_line0,
3018 solver_line1,
3019 ezpz::datatypes::AngleKind::Other(desired_angle),
3020 );
3021 #[cfg(feature = "artifact-graph")]
3022 let constraint_id = exec_state.next_object_id();
3023 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3024 let message =
3025 "Being inside a sketch block should have already been checked above".to_owned();
3026 debug_assert!(false, "{}", &message);
3027 return Err(internal_err(message, self));
3028 };
3029 sketch_block_state.solver_constraints.push(solver_constraint);
3030 #[cfg(feature = "artifact-graph")]
3031 {
3032 use crate::execution::Artifact;
3033 use crate::execution::CodeRef;
3034 use crate::execution::SketchBlockConstraint;
3035 use crate::execution::SketchBlockConstraintType;
3036 use crate::front::Angle;
3037
3038 let Some(sketch_id) = sketch_block_state.sketch_id else {
3039 let message = "Sketch id missing for constraint artifact".to_owned();
3040 debug_assert!(false, "{}", &message);
3041 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3042 };
3043 let sketch_constraint = crate::front::Constraint::Angle(Angle {
3044 lines: vec![line0.object_id, line1.object_id],
3045 angle: n.try_into().map_err(|_| {
3046 internal_err("Failed to convert angle units numeric suffix:", range)
3047 })?,
3048 source,
3049 });
3050 sketch_block_state.sketch_constraints.push(constraint_id);
3051 let artifact_id = exec_state.next_artifact_id();
3052 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3053 id: artifact_id,
3054 sketch_id,
3055 constraint_id,
3056 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3057 code_ref: CodeRef::placeholder(range),
3058 }));
3059 exec_state.add_scene_object(
3060 Object {
3061 id: constraint_id,
3062 kind: ObjectKind::Constraint {
3063 constraint: sketch_constraint,
3064 },
3065 label: Default::default(),
3066 comments: Default::default(),
3067 artifact_id,
3068 source: range.into(),
3069 },
3070 range,
3071 );
3072 }
3073 }
3074 SketchConstraintKind::Distance { points } => {
3075 let range = self.as_source_range();
3076 let p0 = &points[0];
3077 let p1 = &points[1];
3078 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3079 p0.vars.x.to_constraint_id(range)?,
3080 p0.vars.y.to_constraint_id(range)?,
3081 );
3082 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3083 p1.vars.x.to_constraint_id(range)?,
3084 p1.vars.y.to_constraint_id(range)?,
3085 );
3086 let solver_constraint = Constraint::Distance(solver_pt0, solver_pt1, n.n);
3087
3088 #[cfg(feature = "artifact-graph")]
3089 let constraint_id = exec_state.next_object_id();
3090 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3091 let message =
3092 "Being inside a sketch block should have already been checked above".to_owned();
3093 debug_assert!(false, "{}", &message);
3094 return Err(internal_err(message, self));
3095 };
3096 sketch_block_state.solver_constraints.push(solver_constraint);
3097 #[cfg(feature = "artifact-graph")]
3098 {
3099 use crate::execution::Artifact;
3100 use crate::execution::CodeRef;
3101 use crate::execution::SketchBlockConstraint;
3102 use crate::execution::SketchBlockConstraintType;
3103 use crate::front::Distance;
3104
3105 let Some(sketch_id) = sketch_block_state.sketch_id else {
3106 let message = "Sketch id missing for constraint artifact".to_owned();
3107 debug_assert!(false, "{}", &message);
3108 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3109 };
3110 let sketch_constraint = crate::front::Constraint::Distance(Distance {
3111 points: vec![p0.object_id, p1.object_id],
3112 distance: n.try_into().map_err(|_| {
3113 internal_err("Failed to convert distance units numeric suffix:", range)
3114 })?,
3115 source,
3116 });
3117 sketch_block_state.sketch_constraints.push(constraint_id);
3118 let artifact_id = exec_state.next_artifact_id();
3119 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3120 id: artifact_id,
3121 sketch_id,
3122 constraint_id,
3123 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3124 code_ref: CodeRef::placeholder(range),
3125 }));
3126 exec_state.add_scene_object(
3127 Object {
3128 id: constraint_id,
3129 kind: ObjectKind::Constraint {
3130 constraint: sketch_constraint,
3131 },
3132 label: Default::default(),
3133 comments: Default::default(),
3134 artifact_id,
3135 source: range.into(),
3136 },
3137 range,
3138 );
3139 }
3140 }
3141 SketchConstraintKind::Radius { points } | SketchConstraintKind::Diameter { points } => {
3142 #[derive(Clone, Copy)]
3143 enum CircularSegmentConstraintTarget {
3144 Arc {
3145 #[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
3146 object_id: ObjectId,
3147 end: [crate::execution::SketchVarId; 2],
3148 },
3149 Circle {
3150 #[cfg_attr(not(feature = "artifact-graph"), allow(dead_code))]
3151 object_id: ObjectId,
3152 },
3153 }
3154
3155 fn sketch_var_initial_value(
3156 sketch_vars: &[KclValue],
3157 id: crate::execution::SketchVarId,
3158 exec_state: &mut ExecState,
3159 range: SourceRange,
3160 ) -> Result<f64, KclError> {
3161 sketch_vars
3162 .get(id.0)
3163 .and_then(KclValue::as_sketch_var)
3164 .map(|sketch_var| {
3165 sketch_var
3166 .initial_value_to_solver_units(
3167 exec_state,
3168 range,
3169 "circle radius initial value",
3170 )
3171 .map(|value| value.n)
3172 })
3173 .transpose()?
3174 .ok_or_else(|| {
3175 internal_err(
3176 format!("Missing sketch variable initial value for id {}", id.0),
3177 range,
3178 )
3179 })
3180 }
3181
3182 let range = self.as_source_range();
3183 let center = &points[0];
3184 let start = &points[1];
3185 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
3186 return Err(internal_err(
3187 "Being inside a sketch block should have already been checked above",
3188 self,
3189 ));
3190 };
3191 let (constraint_name, is_diameter) = match &constraint.kind {
3192 SketchConstraintKind::Radius { .. } => ("radius", false),
3193 SketchConstraintKind::Diameter { .. } => ("diameter", true),
3194 _ => unreachable!(),
3195 };
3196 let sketch_vars = sketch_block_state.sketch_vars.clone();
3197 let target_segment = sketch_block_state
3198 .needed_by_engine
3199 .iter()
3200 .find_map(|seg| match &seg.kind {
3201 UnsolvedSegmentKind::Arc {
3202 center_object_id,
3203 start_object_id,
3204 end,
3205 ..
3206 } if *center_object_id == center.object_id
3207 && *start_object_id == start.object_id =>
3208 {
3209 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
3210 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
3211 (*end_x, *end_y)
3212 }
3213 _ => return None,
3214 };
3215 Some(CircularSegmentConstraintTarget::Arc {
3216 object_id: seg.object_id,
3217 end: [end_x_var, end_y_var],
3218 })
3219 }
3220 UnsolvedSegmentKind::Circle {
3221 center_object_id,
3222 start_object_id,
3223 ..
3224 } if *center_object_id == center.object_id
3225 && *start_object_id == start.object_id =>
3226 {
3227 Some(CircularSegmentConstraintTarget::Circle {
3228 object_id: seg.object_id,
3229 })
3230 }
3231 _ => None,
3232 })
3233 .ok_or_else(|| {
3234 internal_err(
3235 format!("Could not find circular segment for {} constraint", constraint_name),
3236 range,
3237 )
3238 })?;
3239 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
3240 let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3241 center.vars.x.to_constraint_id(range)?,
3242 center.vars.y.to_constraint_id(range)?,
3243 );
3244 let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3245 start.vars.x.to_constraint_id(range)?,
3246 start.vars.y.to_constraint_id(range)?,
3247 );
3248 let solver_constraint = match target_segment {
3249 CircularSegmentConstraintTarget::Arc { end, .. } => {
3250 let solver_arc = ezpz::datatypes::inputs::DatumCircularArc {
3251 center: center_point,
3252 start: start_point,
3253 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
3254 end[0].to_constraint_id(range)?,
3255 end[1].to_constraint_id(range)?,
3256 ),
3257 };
3258 Constraint::ArcRadius(solver_arc, radius_value)
3259 }
3260 CircularSegmentConstraintTarget::Circle { .. } => {
3261 let sketch_var_ty = solver_numeric_type(exec_state);
3262 let start_x =
3263 sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
3264 let start_y =
3265 sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
3266 let center_x =
3267 sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
3268 let center_y =
3269 sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
3270
3271 let radius_initial_value = (start_x - center_x).hypot(start_y - center_y);
3273
3274 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3275 let message =
3276 "Being inside a sketch block should have already been checked above"
3277 .to_owned();
3278 debug_assert!(false, "{}", &message);
3279 return Err(internal_err(message, self));
3280 };
3281 let radius_id = sketch_block_state.next_sketch_var_id();
3282 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3283 value: Box::new(crate::execution::SketchVar {
3284 id: radius_id,
3285 initial_value: radius_initial_value,
3286 ty: sketch_var_ty,
3287 meta: vec![],
3288 }),
3289 });
3290 let radius =
3291 ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
3292 let solver_circle = ezpz::datatypes::inputs::DatumCircle {
3293 center: center_point,
3294 radius,
3295 };
3296 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
3297 start_point,
3298 center_point,
3299 radius,
3300 ));
3301 Constraint::CircleRadius(solver_circle, radius_value)
3302 }
3303 };
3304
3305 #[cfg(feature = "artifact-graph")]
3306 let constraint_id = exec_state.next_object_id();
3307 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3308 let message =
3309 "Being inside a sketch block should have already been checked above".to_owned();
3310 debug_assert!(false, "{}", &message);
3311 return Err(internal_err(message, self));
3312 };
3313 sketch_block_state.solver_constraints.push(solver_constraint);
3314 #[cfg(feature = "artifact-graph")]
3315 {
3316 use crate::execution::Artifact;
3317 use crate::execution::CodeRef;
3318 use crate::execution::SketchBlockConstraint;
3319 use crate::execution::SketchBlockConstraintType;
3320 let segment_object_id = match target_segment {
3321 CircularSegmentConstraintTarget::Arc { object_id, .. }
3322 | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
3323 };
3324
3325 let constraint = if is_diameter {
3326 use crate::frontend::sketch::Diameter;
3327 crate::front::Constraint::Diameter(Diameter {
3328 arc: segment_object_id,
3329 diameter: n.try_into().map_err(|_| {
3330 internal_err("Failed to convert diameter units numeric suffix:", range)
3331 })?,
3332 source,
3333 })
3334 } else {
3335 use crate::frontend::sketch::Radius;
3336 crate::front::Constraint::Radius(Radius {
3337 arc: segment_object_id,
3338 radius: n.try_into().map_err(|_| {
3339 internal_err("Failed to convert radius units numeric suffix:", range)
3340 })?,
3341 source,
3342 })
3343 };
3344 sketch_block_state.sketch_constraints.push(constraint_id);
3345 let Some(sketch_id) = sketch_block_state.sketch_id else {
3346 let message = "Sketch id missing for constraint artifact".to_owned();
3347 debug_assert!(false, "{}", &message);
3348 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3349 };
3350 let artifact_id = exec_state.next_artifact_id();
3351 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3352 id: artifact_id,
3353 sketch_id,
3354 constraint_id,
3355 constraint_type: SketchBlockConstraintType::from(&constraint),
3356 code_ref: CodeRef::placeholder(range),
3357 }));
3358 exec_state.add_scene_object(
3359 Object {
3360 id: constraint_id,
3361 kind: ObjectKind::Constraint { constraint },
3362 label: Default::default(),
3363 comments: Default::default(),
3364 artifact_id,
3365 source: range.into(),
3366 },
3367 range,
3368 );
3369 }
3370 }
3371 SketchConstraintKind::HorizontalDistance { points } => {
3372 let range = self.as_source_range();
3373 let p0 = &points[0];
3374 let p1 = &points[1];
3375 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3376 p0.vars.x.to_constraint_id(range)?,
3377 p0.vars.y.to_constraint_id(range)?,
3378 );
3379 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3380 p1.vars.x.to_constraint_id(range)?,
3381 p1.vars.y.to_constraint_id(range)?,
3382 );
3383 let solver_constraint = ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n);
3387
3388 #[cfg(feature = "artifact-graph")]
3389 let constraint_id = exec_state.next_object_id();
3390 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3391 let message =
3392 "Being inside a sketch block should have already been checked above".to_owned();
3393 debug_assert!(false, "{}", &message);
3394 return Err(internal_err(message, self));
3395 };
3396 sketch_block_state.solver_constraints.push(solver_constraint);
3397 #[cfg(feature = "artifact-graph")]
3398 {
3399 use crate::execution::Artifact;
3400 use crate::execution::CodeRef;
3401 use crate::execution::SketchBlockConstraint;
3402 use crate::execution::SketchBlockConstraintType;
3403 use crate::front::Distance;
3404
3405 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
3406 points: vec![p0.object_id, p1.object_id],
3407 distance: n.try_into().map_err(|_| {
3408 internal_err("Failed to convert distance units numeric suffix:", range)
3409 })?,
3410 source,
3411 });
3412 sketch_block_state.sketch_constraints.push(constraint_id);
3413 let Some(sketch_id) = sketch_block_state.sketch_id else {
3414 let message = "Sketch id missing for constraint artifact".to_owned();
3415 debug_assert!(false, "{}", &message);
3416 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3417 };
3418 let artifact_id = exec_state.next_artifact_id();
3419 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3420 id: artifact_id,
3421 sketch_id,
3422 constraint_id,
3423 constraint_type: SketchBlockConstraintType::from(&constraint),
3424 code_ref: CodeRef::placeholder(range),
3425 }));
3426 exec_state.add_scene_object(
3427 Object {
3428 id: constraint_id,
3429 kind: ObjectKind::Constraint { constraint },
3430 label: Default::default(),
3431 comments: Default::default(),
3432 artifact_id,
3433 source: range.into(),
3434 },
3435 range,
3436 );
3437 }
3438 }
3439 SketchConstraintKind::VerticalDistance { points } => {
3440 let range = self.as_source_range();
3441 let p0 = &points[0];
3442 let p1 = &points[1];
3443 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3444 p0.vars.x.to_constraint_id(range)?,
3445 p0.vars.y.to_constraint_id(range)?,
3446 );
3447 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3448 p1.vars.x.to_constraint_id(range)?,
3449 p1.vars.y.to_constraint_id(range)?,
3450 );
3451 let solver_constraint = ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n);
3455
3456 #[cfg(feature = "artifact-graph")]
3457 let constraint_id = exec_state.next_object_id();
3458 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3459 let message =
3460 "Being inside a sketch block should have already been checked above".to_owned();
3461 debug_assert!(false, "{}", &message);
3462 return Err(internal_err(message, self));
3463 };
3464 sketch_block_state.solver_constraints.push(solver_constraint);
3465 #[cfg(feature = "artifact-graph")]
3466 {
3467 use crate::execution::Artifact;
3468 use crate::execution::CodeRef;
3469 use crate::execution::SketchBlockConstraint;
3470 use crate::execution::SketchBlockConstraintType;
3471 use crate::front::Distance;
3472
3473 let constraint = crate::front::Constraint::VerticalDistance(Distance {
3474 points: vec![p0.object_id, p1.object_id],
3475 distance: n.try_into().map_err(|_| {
3476 internal_err("Failed to convert distance units numeric suffix:", range)
3477 })?,
3478 source,
3479 });
3480 sketch_block_state.sketch_constraints.push(constraint_id);
3481 let Some(sketch_id) = sketch_block_state.sketch_id else {
3482 let message = "Sketch id missing for constraint artifact".to_owned();
3483 debug_assert!(false, "{}", &message);
3484 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3485 };
3486 let artifact_id = exec_state.next_artifact_id();
3487 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3488 id: artifact_id,
3489 sketch_id,
3490 constraint_id,
3491 constraint_type: SketchBlockConstraintType::from(&constraint),
3492 code_ref: CodeRef::placeholder(range),
3493 }));
3494 exec_state.add_scene_object(
3495 Object {
3496 id: constraint_id,
3497 kind: ObjectKind::Constraint { constraint },
3498 label: Default::default(),
3499 comments: Default::default(),
3500 artifact_id,
3501 source: range.into(),
3502 },
3503 range,
3504 );
3505 }
3506 }
3507 }
3508 return Ok(KclValue::Bool { value: true, meta });
3509 }
3510 _ => {
3511 return Err(KclError::new_semantic(KclErrorDetails::new(
3512 format!(
3513 "Cannot create an equivalence constraint between values of these types: {} and {}",
3514 left_value.human_friendly_type(),
3515 right_value.human_friendly_type()
3516 ),
3517 vec![self.into()],
3518 )));
3519 }
3520 }
3521 }
3522
3523 let left = number_as_f64(&left_value, self.left.clone().into())?;
3524 let right = number_as_f64(&right_value, self.right.clone().into())?;
3525
3526 let value = match self.operator {
3527 BinaryOperator::Add => {
3528 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3529 self.warn_on_unknown(&ty, "Adding", exec_state);
3530 KclValue::Number { value: l + r, meta, ty }
3531 }
3532 BinaryOperator::Sub => {
3533 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
3534 self.warn_on_unknown(&ty, "Subtracting", exec_state);
3535 KclValue::Number { value: l - r, meta, ty }
3536 }
3537 BinaryOperator::Mul => {
3538 let (l, r, ty) = NumericType::combine_mul(left, right);
3539 self.warn_on_unknown(&ty, "Multiplying", exec_state);
3540 KclValue::Number { value: l * r, meta, ty }
3541 }
3542 BinaryOperator::Div => {
3543 let (l, r, ty) = NumericType::combine_div(left, right);
3544 self.warn_on_unknown(&ty, "Dividing", exec_state);
3545 KclValue::Number { value: l / r, meta, ty }
3546 }
3547 BinaryOperator::Mod => {
3548 let (l, r, ty) = NumericType::combine_mod(left, right);
3549 self.warn_on_unknown(&ty, "Modulo of", exec_state);
3550 KclValue::Number { value: l % r, meta, ty }
3551 }
3552 BinaryOperator::Pow => KclValue::Number {
3553 value: left.n.powf(right.n),
3554 meta,
3555 ty: exec_state.current_default_units(),
3556 },
3557 BinaryOperator::Neq => {
3558 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3559 self.warn_on_unknown(&ty, "Comparing", exec_state);
3560 KclValue::Bool { value: l != r, meta }
3561 }
3562 BinaryOperator::Gt => {
3563 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3564 self.warn_on_unknown(&ty, "Comparing", exec_state);
3565 KclValue::Bool { value: l > r, meta }
3566 }
3567 BinaryOperator::Gte => {
3568 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3569 self.warn_on_unknown(&ty, "Comparing", exec_state);
3570 KclValue::Bool { value: l >= r, meta }
3571 }
3572 BinaryOperator::Lt => {
3573 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3574 self.warn_on_unknown(&ty, "Comparing", exec_state);
3575 KclValue::Bool { value: l < r, meta }
3576 }
3577 BinaryOperator::Lte => {
3578 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3579 self.warn_on_unknown(&ty, "Comparing", exec_state);
3580 KclValue::Bool { value: l <= r, meta }
3581 }
3582 BinaryOperator::Eq => {
3583 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
3584 self.warn_on_unknown(&ty, "Comparing", exec_state);
3585 KclValue::Bool { value: l == r, meta }
3586 }
3587 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
3588 };
3589
3590 Ok(value)
3591 }
3592
3593 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
3594 internal_err("missing result while evaluating binary expression", node)
3595 }
3596
3597 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
3598 if ty == &NumericType::Unknown {
3599 let sr = self.as_source_range();
3600 exec_state.clear_units_warnings(&sr);
3601 let mut err = CompilationError::err(
3602 sr,
3603 format!(
3604 "{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)`."
3605 ),
3606 );
3607 err.tag = crate::errors::Tag::UnknownNumericUnits;
3608 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
3609 }
3610 }
3611}
3612
3613impl Node<UnaryExpression> {
3614 pub(super) async fn get_result(
3615 &self,
3616 exec_state: &mut ExecState,
3617 ctx: &ExecutorContext,
3618 ) -> Result<KclValueControlFlow, KclError> {
3619 match self.operator {
3620 UnaryOperator::Not => {
3621 let value = self.argument.get_result(exec_state, ctx).await?;
3622 let value = control_continue!(value);
3623 let KclValue::Bool {
3624 value: bool_value,
3625 meta: _,
3626 } = value
3627 else {
3628 return Err(KclError::new_semantic(KclErrorDetails::new(
3629 format!(
3630 "Cannot apply unary operator ! to non-boolean value: {}",
3631 value.human_friendly_type()
3632 ),
3633 vec![self.into()],
3634 )));
3635 };
3636 let meta = vec![Metadata {
3637 source_range: self.into(),
3638 }];
3639 let negated = KclValue::Bool {
3640 value: !bool_value,
3641 meta,
3642 };
3643
3644 Ok(negated.continue_())
3645 }
3646 UnaryOperator::Neg => {
3647 let value = self.argument.get_result(exec_state, ctx).await?;
3648 let value = control_continue!(value);
3649 let err = || {
3650 KclError::new_semantic(KclErrorDetails::new(
3651 format!(
3652 "You can only negate numbers, planes, or lines, but this is a {}",
3653 value.human_friendly_type()
3654 ),
3655 vec![self.into()],
3656 ))
3657 };
3658 match &value {
3659 KclValue::Number { value, ty, .. } => {
3660 let meta = vec![Metadata {
3661 source_range: self.into(),
3662 }];
3663 Ok(KclValue::Number {
3664 value: -value,
3665 meta,
3666 ty: *ty,
3667 }
3668 .continue_())
3669 }
3670 KclValue::Plane { value } => {
3671 let mut plane = value.clone();
3672 if plane.info.x_axis.x != 0.0 {
3673 plane.info.x_axis.x *= -1.0;
3674 }
3675 if plane.info.x_axis.y != 0.0 {
3676 plane.info.x_axis.y *= -1.0;
3677 }
3678 if plane.info.x_axis.z != 0.0 {
3679 plane.info.x_axis.z *= -1.0;
3680 }
3681
3682 plane.id = exec_state.next_uuid();
3683 plane.object_id = None;
3684 Ok(KclValue::Plane { value: plane }.continue_())
3685 }
3686 KclValue::Object {
3687 value: values, meta, ..
3688 } => {
3689 let Some(direction) = values.get("direction") else {
3691 return Err(err());
3692 };
3693
3694 let direction = match direction {
3695 KclValue::Tuple { value: values, meta } => {
3696 let values = values
3697 .iter()
3698 .map(|v| match v {
3699 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3700 value: *value * -1.0,
3701 ty: *ty,
3702 meta: meta.clone(),
3703 }),
3704 _ => Err(err()),
3705 })
3706 .collect::<Result<Vec<_>, _>>()?;
3707
3708 KclValue::Tuple {
3709 value: values,
3710 meta: meta.clone(),
3711 }
3712 }
3713 KclValue::HomArray {
3714 value: values,
3715 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
3716 } => {
3717 let values = values
3718 .iter()
3719 .map(|v| match v {
3720 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
3721 value: *value * -1.0,
3722 ty: *ty,
3723 meta: meta.clone(),
3724 }),
3725 _ => Err(err()),
3726 })
3727 .collect::<Result<Vec<_>, _>>()?;
3728
3729 KclValue::HomArray {
3730 value: values,
3731 ty: ty.clone(),
3732 }
3733 }
3734 _ => return Err(err()),
3735 };
3736
3737 let mut value = values.clone();
3738 value.insert("direction".to_owned(), direction);
3739 Ok(KclValue::Object {
3740 value,
3741 meta: meta.clone(),
3742 constrainable: false,
3743 }
3744 .continue_())
3745 }
3746 _ => Err(err()),
3747 }
3748 }
3749 UnaryOperator::Plus => {
3750 let operand = self.argument.get_result(exec_state, ctx).await?;
3751 let operand = control_continue!(operand);
3752 match operand {
3753 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
3754 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3755 format!(
3756 "You can only apply unary + to numbers or planes, but this is a {}",
3757 operand.human_friendly_type()
3758 ),
3759 vec![self.into()],
3760 ))),
3761 }
3762 }
3763 }
3764 }
3765}
3766
3767pub(crate) async fn execute_pipe_body(
3768 exec_state: &mut ExecState,
3769 body: &[Expr],
3770 source_range: SourceRange,
3771 ctx: &ExecutorContext,
3772) -> Result<KclValueControlFlow, KclError> {
3773 let Some((first, body)) = body.split_first() else {
3774 return Err(KclError::new_semantic(KclErrorDetails::new(
3775 "Pipe expressions cannot be empty".to_owned(),
3776 vec![source_range],
3777 )));
3778 };
3779 let meta = Metadata {
3784 source_range: SourceRange::from(first),
3785 };
3786 let output = ctx
3787 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
3788 .await?;
3789 let output = control_continue!(output);
3790
3791 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
3795 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
3797 exec_state.mod_local.pipe_value = previous_pipe_value;
3799
3800 result
3801}
3802
3803#[async_recursion]
3806async fn inner_execute_pipe_body(
3807 exec_state: &mut ExecState,
3808 body: &[Expr],
3809 ctx: &ExecutorContext,
3810) -> Result<KclValueControlFlow, KclError> {
3811 for expression in body {
3812 if let Expr::TagDeclarator(_) = expression {
3813 return Err(KclError::new_semantic(KclErrorDetails::new(
3814 format!("This cannot be in a PipeExpression: {expression:?}"),
3815 vec![expression.into()],
3816 )));
3817 }
3818 let metadata = Metadata {
3819 source_range: SourceRange::from(expression),
3820 };
3821 let output = ctx
3822 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
3823 .await?;
3824 let output = control_continue!(output);
3825 exec_state.mod_local.pipe_value = Some(output);
3826 }
3827 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
3829 Ok(final_output.continue_())
3830}
3831
3832impl Node<TagDeclarator> {
3833 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
3834 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
3835 value: self.name.clone(),
3836 info: Vec::new(),
3837 meta: vec![Metadata {
3838 source_range: self.into(),
3839 }],
3840 }));
3841
3842 exec_state
3843 .mut_stack()
3844 .add(self.name.clone(), memory_item, self.into())?;
3845
3846 Ok(self.into())
3847 }
3848}
3849
3850impl Node<ArrayExpression> {
3851 #[async_recursion]
3852 pub(super) async fn execute(
3853 &self,
3854 exec_state: &mut ExecState,
3855 ctx: &ExecutorContext,
3856 ) -> Result<KclValueControlFlow, KclError> {
3857 let mut results = Vec::with_capacity(self.elements.len());
3858
3859 for element in &self.elements {
3860 let metadata = Metadata::from(element);
3861 let value = ctx
3864 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
3865 .await?;
3866 let value = control_continue!(value);
3867
3868 results.push(value);
3869 }
3870
3871 Ok(KclValue::HomArray {
3872 value: results,
3873 ty: RuntimeType::Primitive(PrimitiveType::Any),
3874 }
3875 .continue_())
3876 }
3877}
3878
3879impl Node<ArrayRangeExpression> {
3880 #[async_recursion]
3881 pub(super) async fn execute(
3882 &self,
3883 exec_state: &mut ExecState,
3884 ctx: &ExecutorContext,
3885 ) -> Result<KclValueControlFlow, KclError> {
3886 let metadata = Metadata::from(&self.start_element);
3887 let start_val = ctx
3888 .execute_expr(
3889 &self.start_element,
3890 exec_state,
3891 &metadata,
3892 &[],
3893 StatementKind::Expression,
3894 )
3895 .await?;
3896 let start_val = control_continue!(start_val);
3897 let start = start_val
3898 .as_ty_f64()
3899 .ok_or(KclError::new_semantic(KclErrorDetails::new(
3900 format!(
3901 "Expected number for range start but found {}",
3902 start_val.human_friendly_type()
3903 ),
3904 vec![self.into()],
3905 )))?;
3906 let metadata = Metadata::from(&self.end_element);
3907 let end_val = ctx
3908 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
3909 .await?;
3910 let end_val = control_continue!(end_val);
3911 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
3912 format!(
3913 "Expected number for range end but found {}",
3914 end_val.human_friendly_type()
3915 ),
3916 vec![self.into()],
3917 )))?;
3918
3919 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
3920 let Some(start) = crate::try_f64_to_i64(start) else {
3921 return Err(KclError::new_semantic(KclErrorDetails::new(
3922 format!("Range start must be an integer, but found {start}"),
3923 vec![self.into()],
3924 )));
3925 };
3926 let Some(end) = crate::try_f64_to_i64(end) else {
3927 return Err(KclError::new_semantic(KclErrorDetails::new(
3928 format!("Range end must be an integer, but found {end}"),
3929 vec![self.into()],
3930 )));
3931 };
3932
3933 if end < start {
3934 return Err(KclError::new_semantic(KclErrorDetails::new(
3935 format!("Range start is greater than range end: {start} .. {end}"),
3936 vec![self.into()],
3937 )));
3938 }
3939
3940 let range: Vec<_> = if self.end_inclusive {
3941 (start..=end).collect()
3942 } else {
3943 (start..end).collect()
3944 };
3945
3946 let meta = vec![Metadata {
3947 source_range: self.into(),
3948 }];
3949
3950 Ok(KclValue::HomArray {
3951 value: range
3952 .into_iter()
3953 .map(|num| KclValue::Number {
3954 value: num as f64,
3955 ty,
3956 meta: meta.clone(),
3957 })
3958 .collect(),
3959 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
3960 }
3961 .continue_())
3962 }
3963}
3964
3965impl Node<ObjectExpression> {
3966 #[async_recursion]
3967 pub(super) async fn execute(
3968 &self,
3969 exec_state: &mut ExecState,
3970 ctx: &ExecutorContext,
3971 ) -> Result<KclValueControlFlow, KclError> {
3972 let mut object = HashMap::with_capacity(self.properties.len());
3973 for property in &self.properties {
3974 let metadata = Metadata::from(&property.value);
3975 let result = ctx
3976 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
3977 .await?;
3978 let result = control_continue!(result);
3979 object.insert(property.key.name.clone(), result);
3980 }
3981
3982 Ok(KclValue::Object {
3983 value: object,
3984 meta: vec![Metadata {
3985 source_range: self.into(),
3986 }],
3987 constrainable: false,
3988 }
3989 .continue_())
3990 }
3991}
3992
3993fn article_for<S: AsRef<str>>(s: S) -> &'static str {
3994 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
3996 "an"
3997 } else {
3998 "a"
3999 }
4000}
4001
4002fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
4003 v.as_ty_f64().ok_or_else(|| {
4004 let actual_type = v.human_friendly_type();
4005 KclError::new_semantic(KclErrorDetails::new(
4006 format!("Expected a number, but found {actual_type}",),
4007 vec![source_range],
4008 ))
4009 })
4010}
4011
4012impl Node<IfExpression> {
4013 #[async_recursion]
4014 pub(super) async fn get_result(
4015 &self,
4016 exec_state: &mut ExecState,
4017 ctx: &ExecutorContext,
4018 ) -> Result<KclValueControlFlow, KclError> {
4019 let cond_value = ctx
4021 .execute_expr(
4022 &self.cond,
4023 exec_state,
4024 &Metadata::from(self),
4025 &[],
4026 StatementKind::Expression,
4027 )
4028 .await?;
4029 let cond_value = control_continue!(cond_value);
4030 if cond_value.get_bool()? {
4031 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
4032 return Ok(block_result.unwrap());
4036 }
4037
4038 for else_if in &self.else_ifs {
4040 let cond_value = ctx
4041 .execute_expr(
4042 &else_if.cond,
4043 exec_state,
4044 &Metadata::from(self),
4045 &[],
4046 StatementKind::Expression,
4047 )
4048 .await?;
4049 let cond_value = control_continue!(cond_value);
4050 if cond_value.get_bool()? {
4051 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
4052 return Ok(block_result.unwrap());
4056 }
4057 }
4058
4059 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
4061 .await
4062 .map(|expr| expr.unwrap())
4063 }
4064}
4065
4066#[derive(Debug)]
4067enum Property {
4068 UInt(usize),
4069 String(String),
4070}
4071
4072impl Property {
4073 #[allow(clippy::too_many_arguments)]
4074 async fn try_from<'a>(
4075 computed: bool,
4076 value: Expr,
4077 exec_state: &mut ExecState,
4078 sr: SourceRange,
4079 ctx: &ExecutorContext,
4080 metadata: &Metadata,
4081 annotations: &[Node<Annotation>],
4082 statement_kind: StatementKind<'a>,
4083 ) -> Result<Self, KclError> {
4084 let property_sr = vec![sr];
4085 if !computed {
4086 let Expr::Name(identifier) = value else {
4087 return Err(KclError::new_semantic(KclErrorDetails::new(
4089 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
4090 .to_owned(),
4091 property_sr,
4092 )));
4093 };
4094 return Ok(Property::String(identifier.to_string()));
4095 }
4096
4097 let prop_value = ctx
4098 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
4099 .await?;
4100 let prop_value = match prop_value.control {
4101 ControlFlowKind::Continue => prop_value.into_value(),
4102 ControlFlowKind::Exit => {
4103 let message = "Early return inside array brackets is currently not supported".to_owned();
4104 debug_assert!(false, "{}", &message);
4105 return Err(internal_err(message, sr));
4106 }
4107 };
4108 match prop_value {
4109 KclValue::Number { value, ty, meta: _ } => {
4110 if !matches!(
4111 ty,
4112 NumericType::Unknown
4113 | NumericType::Default { .. }
4114 | NumericType::Known(crate::exec::UnitType::Count)
4115 ) {
4116 return Err(KclError::new_semantic(KclErrorDetails::new(
4117 format!(
4118 "{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"
4119 ),
4120 property_sr,
4121 )));
4122 }
4123 if let Some(x) = crate::try_f64_to_usize(value) {
4124 Ok(Property::UInt(x))
4125 } else {
4126 Err(KclError::new_semantic(KclErrorDetails::new(
4127 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
4128 property_sr,
4129 )))
4130 }
4131 }
4132 _ => Err(KclError::new_semantic(KclErrorDetails::new(
4133 "Only numbers (>= 0) can be indexes".to_owned(),
4134 vec![sr],
4135 ))),
4136 }
4137 }
4138}
4139
4140impl Property {
4141 fn type_name(&self) -> &'static str {
4142 match self {
4143 Property::UInt(_) => "number",
4144 Property::String(_) => "string",
4145 }
4146 }
4147}
4148
4149impl Node<PipeExpression> {
4150 #[async_recursion]
4151 pub(super) async fn get_result(
4152 &self,
4153 exec_state: &mut ExecState,
4154 ctx: &ExecutorContext,
4155 ) -> Result<KclValueControlFlow, KclError> {
4156 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
4157 }
4158}
4159
4160#[cfg(test)]
4161mod test {
4162 use std::sync::Arc;
4163
4164 use tokio::io::AsyncWriteExt;
4165
4166 use super::*;
4167 use crate::ExecutorSettings;
4168 use crate::errors::Severity;
4169 use crate::exec::UnitType;
4170 use crate::execution::ContextType;
4171 use crate::execution::parse_execute;
4172
4173 #[tokio::test(flavor = "multi_thread")]
4174 async fn ascription() {
4175 let program = r#"
4176a = 42: number
4177b = a: number
4178p = {
4179 origin = { x = 0, y = 0, z = 0 },
4180 xAxis = { x = 1, y = 0, z = 0 },
4181 yAxis = { x = 0, y = 1, z = 0 },
4182 zAxis = { x = 0, y = 0, z = 1 }
4183}: Plane
4184arr1 = [42]: [number(cm)]
4185"#;
4186
4187 let result = parse_execute(program).await.unwrap();
4188 let mem = result.exec_state.stack();
4189 assert!(matches!(
4190 mem.memory
4191 .get_from("p", result.mem_env, SourceRange::default(), 0)
4192 .unwrap(),
4193 KclValue::Plane { .. }
4194 ));
4195 let arr1 = mem
4196 .memory
4197 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
4198 .unwrap();
4199 if let KclValue::HomArray { value, ty } = arr1 {
4200 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
4201 assert_eq!(
4202 *ty,
4203 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
4204 );
4205 if let KclValue::Number { value, ty, .. } = &value[0] {
4207 assert_eq!(*value, 42.0);
4209 assert_eq!(
4210 *ty,
4211 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
4212 );
4213 } else {
4214 panic!("Expected a number; found {:?}", value[0]);
4215 }
4216 } else {
4217 panic!("Expected HomArray; found {arr1:?}");
4218 }
4219
4220 let program = r#"
4221a = 42: string
4222"#;
4223 let result = parse_execute(program).await;
4224 let err = result.unwrap_err();
4225 assert!(
4226 err.to_string()
4227 .contains("could not coerce a number (with type `number`) to type `string`"),
4228 "Expected error but found {err:?}"
4229 );
4230
4231 let program = r#"
4232a = 42: Plane
4233"#;
4234 let result = parse_execute(program).await;
4235 let err = result.unwrap_err();
4236 assert!(
4237 err.to_string()
4238 .contains("could not coerce a number (with type `number`) to type `Plane`"),
4239 "Expected error but found {err:?}"
4240 );
4241
4242 let program = r#"
4243arr = [0]: [string]
4244"#;
4245 let result = parse_execute(program).await;
4246 let err = result.unwrap_err();
4247 assert!(
4248 err.to_string().contains(
4249 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
4250 ),
4251 "Expected error but found {err:?}"
4252 );
4253
4254 let program = r#"
4255mixedArr = [0, "a"]: [number(mm)]
4256"#;
4257 let result = parse_execute(program).await;
4258 let err = result.unwrap_err();
4259 assert!(
4260 err.to_string().contains(
4261 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4262 ),
4263 "Expected error but found {err:?}"
4264 );
4265
4266 let program = r#"
4267mixedArr = [0, "a"]: [mm]
4268"#;
4269 let result = parse_execute(program).await;
4270 let err = result.unwrap_err();
4271 assert!(
4272 err.to_string().contains(
4273 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
4274 ),
4275 "Expected error but found {err:?}"
4276 );
4277 }
4278
4279 #[tokio::test(flavor = "multi_thread")]
4280 async fn neg_plane() {
4281 let program = r#"
4282p = {
4283 origin = { x = 0, y = 0, z = 0 },
4284 xAxis = { x = 1, y = 0, z = 0 },
4285 yAxis = { x = 0, y = 1, z = 0 },
4286}: Plane
4287p2 = -p
4288"#;
4289
4290 let result = parse_execute(program).await.unwrap();
4291 let mem = result.exec_state.stack();
4292 match mem
4293 .memory
4294 .get_from("p2", result.mem_env, SourceRange::default(), 0)
4295 .unwrap()
4296 {
4297 KclValue::Plane { value } => {
4298 assert_eq!(value.info.x_axis.x, -1.0);
4299 assert_eq!(value.info.x_axis.y, 0.0);
4300 assert_eq!(value.info.x_axis.z, 0.0);
4301 }
4302 _ => unreachable!(),
4303 }
4304 }
4305
4306 #[tokio::test(flavor = "multi_thread")]
4307 async fn multiple_returns() {
4308 let program = r#"fn foo() {
4309 return 0
4310 return 42
4311}
4312
4313a = foo()
4314"#;
4315
4316 let result = parse_execute(program).await;
4317 assert!(result.unwrap_err().to_string().contains("return"));
4318 }
4319
4320 #[tokio::test(flavor = "multi_thread")]
4321 async fn load_all_modules() {
4322 let program_a_kcl = r#"
4324export a = 1
4325"#;
4326 let program_b_kcl = r#"
4328import a from 'a.kcl'
4329
4330export b = a + 1
4331"#;
4332 let program_c_kcl = r#"
4334import a from 'a.kcl'
4335
4336export c = a + 2
4337"#;
4338
4339 let main_kcl = r#"
4341import b from 'b.kcl'
4342import c from 'c.kcl'
4343
4344d = b + c
4345"#;
4346
4347 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
4348 .parse_errs_as_err()
4349 .unwrap();
4350
4351 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
4352
4353 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
4354 .await
4355 .unwrap()
4356 .write_all(main_kcl.as_bytes())
4357 .await
4358 .unwrap();
4359
4360 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
4361 .await
4362 .unwrap()
4363 .write_all(program_a_kcl.as_bytes())
4364 .await
4365 .unwrap();
4366
4367 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
4368 .await
4369 .unwrap()
4370 .write_all(program_b_kcl.as_bytes())
4371 .await
4372 .unwrap();
4373
4374 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
4375 .await
4376 .unwrap()
4377 .write_all(program_c_kcl.as_bytes())
4378 .await
4379 .unwrap();
4380
4381 let exec_ctxt = ExecutorContext {
4382 engine: Arc::new(Box::new(
4383 crate::engine::conn_mock::EngineConnection::new()
4384 .map_err(|err| {
4385 internal_err(
4386 format!("Failed to create mock engine connection: {err}"),
4387 SourceRange::default(),
4388 )
4389 })
4390 .unwrap(),
4391 )),
4392 fs: Arc::new(crate::fs::FileManager::new()),
4393 settings: ExecutorSettings {
4394 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
4395 ..Default::default()
4396 },
4397 context_type: ContextType::Mock,
4398 };
4399 let mut exec_state = ExecState::new(&exec_ctxt);
4400
4401 exec_ctxt
4402 .run(
4403 &crate::Program {
4404 ast: main.clone(),
4405 original_file_contents: "".to_owned(),
4406 },
4407 &mut exec_state,
4408 )
4409 .await
4410 .unwrap();
4411 }
4412
4413 #[tokio::test(flavor = "multi_thread")]
4414 async fn user_coercion() {
4415 let program = r#"fn foo(x: Axis2d) {
4416 return 0
4417}
4418
4419foo(x = { direction = [0, 0], origin = [0, 0]})
4420"#;
4421
4422 parse_execute(program).await.unwrap();
4423
4424 let program = r#"fn foo(x: Axis3d) {
4425 return 0
4426}
4427
4428foo(x = { direction = [0, 0], origin = [0, 0]})
4429"#;
4430
4431 parse_execute(program).await.unwrap_err();
4432 }
4433
4434 #[tokio::test(flavor = "multi_thread")]
4435 async fn coerce_return() {
4436 let program = r#"fn foo(): number(mm) {
4437 return 42
4438}
4439
4440a = foo()
4441"#;
4442
4443 parse_execute(program).await.unwrap();
4444
4445 let program = r#"fn foo(): mm {
4446 return 42
4447}
4448
4449a = foo()
4450"#;
4451
4452 parse_execute(program).await.unwrap();
4453
4454 let program = r#"fn foo(): number(mm) {
4455 return { bar: 42 }
4456}
4457
4458a = foo()
4459"#;
4460
4461 parse_execute(program).await.unwrap_err();
4462
4463 let program = r#"fn foo(): mm {
4464 return { bar: 42 }
4465}
4466
4467a = foo()
4468"#;
4469
4470 parse_execute(program).await.unwrap_err();
4471 }
4472
4473 #[tokio::test(flavor = "multi_thread")]
4474 async fn test_sensible_error_when_missing_equals_in_kwarg() {
4475 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)"]
4476 .into_iter()
4477 .enumerate()
4478 {
4479 let program = format!(
4480 "fn foo() {{ return 0 }}
4481z = 0
4482fn f(x, y, z) {{ return 0 }}
4483{call}"
4484 );
4485 let err = parse_execute(&program).await.unwrap_err();
4486 let msg = err.message();
4487 assert!(
4488 msg.contains("This argument needs a label, but it doesn't have one"),
4489 "failed test {i}: {msg}"
4490 );
4491 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
4492 if i == 0 {
4493 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
4494 }
4495 }
4496 }
4497
4498 #[tokio::test(flavor = "multi_thread")]
4499 async fn default_param_for_unlabeled() {
4500 let ast = r#"fn myExtrude(@sk, length) {
4503 return extrude(sk, length)
4504}
4505sketch001 = startSketchOn(XY)
4506 |> circle(center = [0, 0], radius = 93.75)
4507 |> myExtrude(length = 40)
4508"#;
4509
4510 parse_execute(ast).await.unwrap();
4511 }
4512
4513 #[tokio::test(flavor = "multi_thread")]
4514 async fn dont_use_unlabelled_as_input() {
4515 let ast = r#"length = 10
4517startSketchOn(XY)
4518 |> circle(center = [0, 0], radius = 93.75)
4519 |> extrude(length)
4520"#;
4521
4522 parse_execute(ast).await.unwrap();
4523 }
4524
4525 #[tokio::test(flavor = "multi_thread")]
4526 async fn ascription_in_binop() {
4527 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
4528 parse_execute(ast).await.unwrap();
4529
4530 let ast = r#"foo = tan(0): rad - 4deg"#;
4531 parse_execute(ast).await.unwrap();
4532 }
4533
4534 #[tokio::test(flavor = "multi_thread")]
4535 async fn neg_sqrt() {
4536 let ast = r#"bad = sqrt(-2)"#;
4537
4538 let e = parse_execute(ast).await.unwrap_err();
4539 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
4541 }
4542
4543 #[tokio::test(flavor = "multi_thread")]
4544 async fn non_array_fns() {
4545 let ast = r#"push(1, item = 2)
4546pop(1)
4547map(1, f = fn(@x) { return x + 1 })
4548reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
4549
4550 parse_execute(ast).await.unwrap();
4551 }
4552
4553 #[tokio::test(flavor = "multi_thread")]
4554 async fn non_array_indexing() {
4555 let good = r#"a = 42
4556good = a[0]
4557"#;
4558 let result = parse_execute(good).await.unwrap();
4559 let mem = result.exec_state.stack();
4560 let num = mem
4561 .memory
4562 .get_from("good", result.mem_env, SourceRange::default(), 0)
4563 .unwrap()
4564 .as_ty_f64()
4565 .unwrap();
4566 assert_eq!(num.n, 42.0);
4567
4568 let bad = r#"a = 42
4569bad = a[1]
4570"#;
4571
4572 parse_execute(bad).await.unwrap_err();
4573 }
4574
4575 #[tokio::test(flavor = "multi_thread")]
4576 async fn coerce_unknown_to_length() {
4577 let ast = r#"x = 2mm * 2mm
4578y = x: number(Length)"#;
4579 let e = parse_execute(ast).await.unwrap_err();
4580 assert!(
4581 e.message().contains("could not coerce"),
4582 "Error message: '{}'",
4583 e.message()
4584 );
4585
4586 let ast = r#"x = 2mm
4587y = x: number(Length)"#;
4588 let result = parse_execute(ast).await.unwrap();
4589 let mem = result.exec_state.stack();
4590 let num = mem
4591 .memory
4592 .get_from("y", result.mem_env, SourceRange::default(), 0)
4593 .unwrap()
4594 .as_ty_f64()
4595 .unwrap();
4596 assert_eq!(num.n, 2.0);
4597 assert_eq!(num.ty, NumericType::mm());
4598 }
4599
4600 #[tokio::test(flavor = "multi_thread")]
4601 async fn one_warning_unknown() {
4602 let ast = r#"
4603// Should warn once
4604a = PI * 2
4605// Should warn once
4606b = (PI * 2) / 3
4607// Should not warn
4608c = ((PI * 2) / 3): number(deg)
4609"#;
4610
4611 let result = parse_execute(ast).await.unwrap();
4612 assert_eq!(result.exec_state.errors().len(), 2);
4613 }
4614
4615 #[tokio::test(flavor = "multi_thread")]
4616 async fn non_count_indexing() {
4617 let ast = r#"x = [0, 0]
4618y = x[1mm]
4619"#;
4620 parse_execute(ast).await.unwrap_err();
4621
4622 let ast = r#"x = [0, 0]
4623y = 1deg
4624z = x[y]
4625"#;
4626 parse_execute(ast).await.unwrap_err();
4627
4628 let ast = r#"x = [0, 0]
4629y = x[0mm + 1]
4630"#;
4631 parse_execute(ast).await.unwrap_err();
4632 }
4633
4634 #[tokio::test(flavor = "multi_thread")]
4635 async fn getting_property_of_plane() {
4636 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
4637 parse_execute(&ast).await.unwrap();
4638 }
4639
4640 #[cfg(feature = "artifact-graph")]
4641 #[tokio::test(flavor = "multi_thread")]
4642 async fn no_artifacts_from_within_hole_call() {
4643 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
4648 let out = parse_execute(&ast).await.unwrap();
4649
4650 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4652
4653 let expected = 5;
4657 assert_eq!(
4658 actual_operations.len(),
4659 expected,
4660 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4661 actual_operations.len(),
4662 );
4663 }
4664
4665 #[cfg(feature = "artifact-graph")]
4666 #[tokio::test(flavor = "multi_thread")]
4667 async fn feature_tree_annotation_on_user_defined_kcl() {
4668 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4671 let out = parse_execute(&ast).await.unwrap();
4672
4673 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4675
4676 let expected = 0;
4677 assert_eq!(
4678 actual_operations.len(),
4679 expected,
4680 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4681 actual_operations.len(),
4682 );
4683 }
4684
4685 #[cfg(feature = "artifact-graph")]
4686 #[tokio::test(flavor = "multi_thread")]
4687 async fn no_feature_tree_annotation_on_user_defined_kcl() {
4688 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
4691 let out = parse_execute(&ast).await.unwrap();
4692
4693 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
4695
4696 let expected = 2;
4697 assert_eq!(
4698 actual_operations.len(),
4699 expected,
4700 "expected {expected} operations, received {}:\n{actual_operations:#?}",
4701 actual_operations.len(),
4702 );
4703 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
4704 assert!(matches!(actual_operations[1], Operation::GroupEnd));
4705 }
4706
4707 #[tokio::test(flavor = "multi_thread")]
4708 async fn custom_warning() {
4709 let warn = r#"
4710a = PI * 2
4711"#;
4712 let result = parse_execute(warn).await.unwrap();
4713 assert_eq!(result.exec_state.errors().len(), 1);
4714 assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
4715
4716 let allow = r#"
4717@warnings(allow = unknownUnits)
4718a = PI * 2
4719"#;
4720 let result = parse_execute(allow).await.unwrap();
4721 assert_eq!(result.exec_state.errors().len(), 0);
4722
4723 let deny = r#"
4724@warnings(deny = [unknownUnits])
4725a = PI * 2
4726"#;
4727 let result = parse_execute(deny).await.unwrap();
4728 assert_eq!(result.exec_state.errors().len(), 1);
4729 assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
4730 }
4731
4732 #[tokio::test(flavor = "multi_thread")]
4733 async fn sketch_block_unqualified_functions_use_sketch2() {
4734 let ast = r#"
4735@settings(experimentalFeatures = allow)
4736s = sketch(on = XY) {
4737 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
4738 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
4739 coincident([line1.end, line2.start])
4740}
4741"#;
4742 let result = parse_execute(ast).await.unwrap();
4743 let mem = result.exec_state.stack();
4744 let sketch_value = mem
4745 .memory
4746 .get_from("s", result.mem_env, SourceRange::default(), 0)
4747 .unwrap();
4748
4749 let KclValue::Object { value, .. } = sketch_value else {
4750 panic!("Expected sketch block to return an object, got {sketch_value:?}");
4751 };
4752
4753 assert!(value.contains_key("line1"));
4754 assert!(value.contains_key("line2"));
4755 assert!(!value.contains_key("line"));
4758 assert!(!value.contains_key("coincident"));
4759 }
4760
4761 #[tokio::test(flavor = "multi_thread")]
4762 async fn cannot_solid_extrude_an_open_profile() {
4763 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
4766 let program = crate::Program::parse_no_errs(&code).expect("should parse");
4767 let exec_ctxt = ExecutorContext::new_mock(None).await;
4768 let mut exec_state = ExecState::new(&exec_ctxt);
4769
4770 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
4771 assert!(matches!(err, KclError::Semantic { .. }));
4772 exec_ctxt.close().await;
4773 }
4774}