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