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