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