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