1use std::collections::HashMap;
2use std::sync::Arc;
3
4use async_recursion::async_recursion;
5use ezpz::Constraint;
6use ezpz::NonLinearSystemError;
7use ezpz::datatypes::inputs::DatumPoint;
8use indexmap::IndexMap;
9use kcl_api::Group;
10use kcl_api::NumericType;
11use kcl_api::Operation;
12use kcl_api::UnitAngle;
13
14use crate::CompilationIssue;
15use crate::NodePath;
16use crate::NodePathExt;
17use crate::SourceRange;
18use crate::errors::KclError;
19use crate::errors::KclErrorDetails;
20use crate::exec::Sketch;
21use crate::execution::AbstractSegment;
22use crate::execution::AngleConstraintMode;
23use crate::execution::AngleRayDirection;
24use crate::execution::AngleSector;
25use crate::execution::Artifact;
26use crate::execution::ArtifactId;
27use crate::execution::BodyType;
28use crate::execution::ConstrainableLine2d;
29use crate::execution::ConstraintKind;
30use crate::execution::EarlyReturn;
31use crate::execution::EnvironmentRef;
32use crate::execution::ExecState;
33use crate::execution::ExecutorContext;
34use crate::execution::KclValue;
35use crate::execution::KclValueControlFlow;
36use crate::execution::KclVersion;
37use crate::execution::LegacyAngleRefactorMeta;
38use crate::execution::Metadata;
39use crate::execution::ModelingCmdMeta;
40use crate::execution::ModuleArtifactState;
41use crate::execution::PendingLegacyAngleRefactorMeta;
42use crate::execution::PreserveMem;
43use crate::execution::RefactorMetadata;
44use crate::execution::SKETCH_BLOCK_PARAM_ON;
45use crate::execution::SKETCH_OBJECT_META;
46use crate::execution::SKETCH_OBJECT_META_SKETCH;
47use crate::execution::Segment;
48use crate::execution::SegmentKind;
49use crate::execution::SegmentRepr;
50use crate::execution::SketchConstraintKind;
51use crate::execution::SketchSurface;
52use crate::execution::SolverArc;
53use crate::execution::StatementKind;
54use crate::execution::TagIdentifier;
55use crate::execution::UnsolvedExpr;
56use crate::execution::UnsolvedSegment;
57use crate::execution::UnsolvedSegmentKind;
58use crate::execution::annotations;
59use crate::execution::annotations::FnAttrs;
60use crate::execution::cad_op::op_from_kcl_value;
61use crate::execution::control_continue;
62use crate::execution::early_return;
63use crate::execution::fn_call::Arg;
64use crate::execution::fn_call::Args;
65use crate::execution::fn_call::unexpected_kw_arg_message;
66use crate::execution::kcl_value::EnumTypeDef;
67use crate::execution::kcl_value::EnumTypeId;
68use crate::execution::kcl_value::EnumValue;
69use crate::execution::kcl_value::FunctionSource;
70use crate::execution::kcl_value::KclFunctionSourceParams;
71use crate::execution::kcl_value::KclObjectKind;
72use crate::execution::kcl_value::TypeDef;
73use crate::execution::memory::SKETCH_PREFIX;
74use crate::execution::memory::{self};
75use crate::execution::sketch_constraint_status_for_sketch;
76use crate::execution::sketch_solve::FreedomAnalysis;
77use crate::execution::sketch_solve::Solved;
78use crate::execution::sketch_solve::UnsatisfiedDirectionalConstraint;
79use crate::execution::sketch_solve::create_segment_scene_objects;
80use crate::execution::sketch_solve::normalize_to_solver_angle_unit;
81use crate::execution::sketch_solve::normalize_to_solver_distance_unit;
82use crate::execution::sketch_solve::solver_numeric_type;
83use crate::execution::sketch_solve::substitute_sketch_var_in_segment;
84use crate::execution::sketch_solve::substitute_sketch_vars;
85use crate::execution::state::ModuleState;
86use crate::execution::state::SketchBlockState;
87use crate::execution::types::CoercionMode;
88use crate::execution::types::NumericTypeExt;
89use crate::execution::types::PrimitiveType;
90use crate::execution::types::RuntimeType;
91use crate::execution::types::resolve_named_type_def;
92use crate::execution::types::type_value_named_by_segment;
93use crate::front::ArcDirection;
94use crate::front::LineCtor;
95use crate::front::Object;
96use crate::front::ObjectId;
97use crate::front::ObjectKind;
98use crate::front::PointCtor;
99use crate::modules::ModuleExecutionOutcome;
100use crate::modules::ModuleId;
101use crate::modules::ModulePath;
102use crate::modules::ModuleRepr;
103use crate::parsing::ast::types::ABSOLUTE_PATHS_NOT_SUPPORTED;
104use crate::parsing::ast::types::Annotation;
105use crate::parsing::ast::types::ArrayExpression;
106use crate::parsing::ast::types::ArrayRangeExpression;
107use crate::parsing::ast::types::AscribedExpression;
108use crate::parsing::ast::types::BinaryExpression;
109use crate::parsing::ast::types::BinaryOperator;
110use crate::parsing::ast::types::BinaryPart;
111use crate::parsing::ast::types::BodyItem;
112use crate::parsing::ast::types::CodeBlock;
113use crate::parsing::ast::types::Expr;
114use crate::parsing::ast::types::FunctionExpression;
115use crate::parsing::ast::types::Identifier;
116use crate::parsing::ast::types::IfExpression;
117use crate::parsing::ast::types::ImportPath;
118use crate::parsing::ast::types::ImportSelector;
119use crate::parsing::ast::types::ImportStatement;
120use crate::parsing::ast::types::ItemVisibility;
121use crate::parsing::ast::types::MemberExpression;
122use crate::parsing::ast::types::Name;
123use crate::parsing::ast::types::Node;
124use crate::parsing::ast::types::ObjectExpression;
125use crate::parsing::ast::types::PipeExpression;
126use crate::parsing::ast::types::Program;
127use crate::parsing::ast::types::ReturnStatement;
128use crate::parsing::ast::types::SketchBlock;
129use crate::parsing::ast::types::SketchVar;
130use crate::parsing::ast::types::TagDeclarator;
131use crate::parsing::ast::types::Type;
132use crate::parsing::ast::types::TypeDeclaration;
133use crate::parsing::ast::types::TypeDeclarationDefinition;
134use crate::parsing::ast::types::UnaryExpression;
135use crate::parsing::ast::types::UnaryOperator;
136use crate::parsing::ast::types::VariableDeclaration;
137use crate::std::StdFnProps;
138use crate::std::args::FromKclValue;
139use crate::std::args::TyF64;
140use crate::std::shapes::SketchOrSurface;
141use crate::std::sketch::ensure_sketch_plane_in_engine;
142use crate::std::solver::SOLVER_CONVERGENCE_TOLERANCE;
143use crate::std::solver::create_segments_in_engine;
144use crate::std::utils::intersect_lines_2d;
145use crate::std::utils::normalize_rad;
146use crate::std::utils::vec2_dot;
147use crate::std::utils::vec2_len;
148use crate::std::utils::vec2_sub;
149use crate::walk::Visitable;
150
151fn internal_err(message: impl Into<String>, range: impl Into<SourceRange>) -> KclError {
152 KclError::new_internal(KclErrorDetails::new(message.into(), vec![range.into()]))
153}
154
155fn signed_distance_conflict_hint(solve_outcome: &Solved) -> String {
156 let hints = solve_outcome
157 .unsatisfied_directional_constraints
158 .iter()
159 .map(|constraint| match constraint {
160 UnsatisfiedDirectionalConstraint::Horizontal(expected) if *expected > 0.0 => {
161 "Unsatisfied signed horizontalDistance constraint: a positive right-hand side requires the second point to be right of the first (second.x - first.x > 0)."
162 }
163 UnsatisfiedDirectionalConstraint::Horizontal(expected) if *expected < 0.0 => {
164 "Unsatisfied signed horizontalDistance constraint: a negative right-hand side requires the second point to be left of the first (second.x - first.x < 0)."
165 }
166 UnsatisfiedDirectionalConstraint::Horizontal(_) => {
167 "Unsatisfied signed horizontalDistance constraint: a zero right-hand side requires both points to have the same X coordinate."
168 }
169 UnsatisfiedDirectionalConstraint::Vertical(expected) if *expected > 0.0 => {
170 "Unsatisfied signed verticalDistance constraint: a positive right-hand side requires the second point to be above the first (second.y - first.y > 0)."
171 }
172 UnsatisfiedDirectionalConstraint::Vertical(expected) if *expected < 0.0 => {
173 "Unsatisfied signed verticalDistance constraint: a negative right-hand side requires the second point to be below the first (second.y - first.y < 0)."
174 }
175 UnsatisfiedDirectionalConstraint::Vertical(_) => {
176 "Unsatisfied signed verticalDistance constraint: a zero right-hand side requires both points to have the same Y coordinate."
177 }
178 })
179 .collect::<Vec<_>>();
180
181 if hints.is_empty() {
182 String::new()
183 } else {
184 format!(" {}", hints.join(" "))
185 }
186}
187
188fn datum_point_from_constrainable(
189 point: &crate::execution::ConstrainablePoint2d,
190 range: SourceRange,
191) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
192 Ok(ezpz::datatypes::inputs::DatumPoint::new_xy(
193 point.vars.x.to_constraint_id(range)?,
194 point.vars.y.to_constraint_id(range)?,
195 ))
196}
197
198fn push_fixed_origin_point(
199 sketch_block_state: &mut SketchBlockState,
200 sketch_var_ty: NumericType,
201 range: SourceRange,
202) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
203 let origin_x_id = sketch_block_state.next_sketch_var_id();
204 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
205 value: Box::new(crate::execution::SketchVar {
206 id: origin_x_id,
207 initial_value: 0.0,
208 ty: sketch_var_ty,
209 node_path: None,
211 meta: vec![],
212 }),
213 });
214 let origin_y_id = sketch_block_state.next_sketch_var_id();
215 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
216 value: Box::new(crate::execution::SketchVar {
217 id: origin_y_id,
218 initial_value: 0.0,
219 ty: sketch_var_ty,
220 node_path: None,
222 meta: vec![],
223 }),
224 });
225
226 sketch_block_state
227 .solver_constraints
228 .push(Constraint::Fixed(origin_x_id.to_constraint_id(range)?, 0.0));
229 sketch_block_state
230 .solver_constraints
231 .push(Constraint::Fixed(origin_y_id.to_constraint_id(range)?, 0.0));
232
233 Ok(ezpz::datatypes::inputs::DatumPoint::new_xy(
234 origin_x_id.to_constraint_id(range)?,
235 origin_y_id.to_constraint_id(range)?,
236 ))
237}
238
239fn datum_point_from_constrainable_or_origin(
240 sketch_block_state: &mut SketchBlockState,
241 sketch_var_ty: NumericType,
242 point: &crate::execution::ConstrainablePoint2dOrOrigin,
243 range: SourceRange,
244) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
245 match point {
246 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => datum_point_from_constrainable(point, range),
247 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
248 push_fixed_origin_point(sketch_block_state, sketch_var_ty, range)
249 }
250 }
251}
252
253fn datum_line_from_constrainable(
254 line: &crate::execution::ConstrainableLine2d,
255 range: SourceRange,
256) -> Result<ezpz::datatypes::inputs::DatumLineSegment, KclError> {
257 Ok(ezpz::datatypes::inputs::DatumLineSegment::new(
258 ezpz::datatypes::inputs::DatumPoint::new_xy(
259 line.vars[0].x.to_constraint_id(range)?,
260 line.vars[0].y.to_constraint_id(range)?,
261 ),
262 ezpz::datatypes::inputs::DatumPoint::new_xy(
263 line.vars[1].x.to_constraint_id(range)?,
264 line.vars[1].y.to_constraint_id(range)?,
265 ),
266 ))
267}
268
269fn push_hidden_sketch_point(
270 sketch_block_state: &mut SketchBlockState,
271 sketch_var_ty: NumericType,
272 initial: [f64; 2],
273 range: SourceRange,
274) -> Result<DatumPoint, KclError> {
275 let x_id = sketch_block_state.next_sketch_var_id();
276 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
277 value: Box::new(crate::execution::SketchVar {
278 id: x_id,
279 initial_value: initial[0],
280 ty: sketch_var_ty,
281 node_path: None,
282 meta: vec![],
283 }),
284 });
285 let y_id = sketch_block_state.next_sketch_var_id();
286 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
287 value: Box::new(crate::execution::SketchVar {
288 id: y_id,
289 initial_value: initial[1],
290 ty: sketch_var_ty,
291 node_path: None,
292 meta: vec![],
293 }),
294 });
295
296 Ok(DatumPoint::new_xy(
297 x_id.to_constraint_id(range)?,
298 y_id.to_constraint_id(range)?,
299 ))
300}
301
302fn front_angle_sector(sector: AngleSector) -> u8 {
303 match sector {
304 AngleSector::One => 1,
305 AngleSector::Two => 2,
306 AngleSector::Three => 3,
307 AngleSector::Four => 4,
308 }
309}
310
311#[derive(Clone, Copy)]
312struct AngleSectorRay {
313 line_index: usize,
314 direction: AngleRayDirection,
315}
316
317fn angle_sector_rays(sector: AngleSector, is_inverse: bool) -> [AngleSectorRay; 2] {
318 let rays = match sector {
319 AngleSector::One => [
320 AngleSectorRay {
321 line_index: 0,
322 direction: AngleRayDirection::Forward,
323 },
324 AngleSectorRay {
325 line_index: 1,
326 direction: AngleRayDirection::Forward,
327 },
328 ],
329 AngleSector::Two => [
330 AngleSectorRay {
331 line_index: 1,
332 direction: AngleRayDirection::Forward,
333 },
334 AngleSectorRay {
335 line_index: 0,
336 direction: AngleRayDirection::Reverse,
337 },
338 ],
339 AngleSector::Three => [
340 AngleSectorRay {
341 line_index: 0,
342 direction: AngleRayDirection::Reverse,
343 },
344 AngleSectorRay {
345 line_index: 1,
346 direction: AngleRayDirection::Reverse,
347 },
348 ],
349 AngleSector::Four => [
350 AngleSectorRay {
351 line_index: 1,
352 direction: AngleRayDirection::Reverse,
353 },
354 AngleSectorRay {
355 line_index: 0,
356 direction: AngleRayDirection::Forward,
357 },
358 ],
359 };
360 if is_inverse { [rays[1], rays[0]] } else { rays }
361}
362
363fn line_endpoint_datum(
364 line: &ConstrainableLine2d,
365 endpoint_index: usize,
366 range: SourceRange,
367) -> Result<DatumPoint, KclError> {
368 let Some(endpoint) = line.vars.get(endpoint_index) else {
369 return Err(internal_err("Invalid angle line endpoint index", range));
370 };
371
372 Ok(DatumPoint::new_xy(
373 endpoint.x.to_constraint_id(range)?,
374 endpoint.y.to_constraint_id(range)?,
375 ))
376}
377
378fn representative_angle_endpoint(
379 line: &ConstrainableLine2d,
380 initial_line: ([f64; 2], [f64; 2]),
381 vertex: [f64; 2],
382 range: SourceRange,
383) -> Result<(DatumPoint, AngleRayDirection), KclError> {
384 let start_delta = vec2_sub(initial_line.0, vertex);
385 let end_delta = vec2_sub(initial_line.1, vertex);
386 let endpoint_index = if vec2_len(end_delta) >= vec2_len(start_delta) {
387 1
388 } else {
389 0
390 };
391 let endpoint_delta = if endpoint_index == 1 { end_delta } else { start_delta };
392 if vec2_len(endpoint_delta) <= 1e-9 {
393 return Err(KclError::new_semantic(KclErrorDetails::new(
394 "angleDimension(lines = ..., sector = ...) requires each line to have an endpoint away from the intersection"
395 .to_owned(),
396 vec![range],
397 )));
398 }
399
400 let line_direction = vec2_sub(initial_line.1, initial_line.0);
401 let direction = if vec2_dot(endpoint_delta, line_direction) >= 0.0 {
402 AngleRayDirection::Forward
403 } else {
404 AngleRayDirection::Reverse
405 };
406
407 Ok((line_endpoint_datum(line, endpoint_index, range)?, direction))
408}
409
410fn remap_angle_for_representative_rays(
411 requested_rays: [AngleSectorRay; 2],
412 representative_directions: [AngleRayDirection; 2],
413 desired_angle: ezpz::datatypes::Angle,
414) -> ezpz::datatypes::Angle {
415 let mut requested_directions = representative_directions;
416 for ray in requested_rays {
417 requested_directions[ray.line_index] = ray.direction;
418 }
419
420 let sign_offset = if (requested_directions[0] != representative_directions[0])
421 ^ (requested_directions[1] != representative_directions[1])
422 {
423 std::f64::consts::PI
424 } else {
425 0.0
426 };
427
428 let desired = desired_angle.to_radians();
429 let representative_angle = if requested_rays[0].line_index == 0 {
430 desired - sign_offset
431 } else {
432 -desired - sign_offset
433 };
434
435 ezpz::datatypes::Angle::from_radians(normalize_rad(representative_angle))
436}
437
438struct PointsAtAngleLineData {
439 initial_vertex: [f64; 2],
440 representative_points: [DatumPoint; 2],
441 angle_kind: ezpz::datatypes::AngleKind,
442}
443
444enum AngleConstraintLowering {
445 LinesAtAngle(Box<PendingLegacyAngleRefactorMeta>),
446 PointsAtAngle(PointsAtAngleLineData),
447}
448
449fn solved_angle_line(line: &ConstrainableLine2d, final_values: &[f64]) -> Option<([f64; 2], [f64; 2])> {
450 let point = |index: usize| {
451 let point = line.vars.get(index)?;
452 Some([*final_values.get(point.x.0)?, *final_values.get(point.y.0)?])
453 };
454 Some((point(0)?, point(1)?))
455}
456
457fn angle_ray_vector(lines: [[f64; 2]; 2], ray: AngleSectorRay) -> [f64; 2] {
458 let direction = lines[ray.line_index];
459 match ray.direction {
460 AngleRayDirection::Forward => direction,
461 AngleRayDirection::Reverse => [-direction[0], -direction[1]],
462 }
463}
464
465fn directed_angle(from: [f64; 2], to: [f64; 2]) -> f64 {
466 let cross = from[0] * to[1] - from[1] * to[0];
467 libm::atan2(cross, vec2_dot(from, to)).rem_euclid(std::f64::consts::TAU)
468}
469
470fn circular_angle_distance(a: f64, b: f64) -> f64 {
471 let delta = (a - b).abs().rem_euclid(std::f64::consts::TAU);
472 libm::fmin(delta, std::f64::consts::TAU - delta)
473}
474
475fn legacy_angle_arc_midpoint_angle(
476 lines: [([f64; 2], [f64; 2]); 2],
477 directions: [[f64; 2]; 2],
478 vertex: [f64; 2],
479 desired: f64,
480) -> f64 {
481 let signed_distances = lines.map(|line| {
482 let direction = vec2_sub(line.1, line.0);
483 let length = vec2_len(direction);
484 [
485 vec2_dot(vec2_sub(line.0, vertex), direction) / length,
486 vec2_dot(vec2_sub(line.1, vertex), direction) / length,
487 ]
488 });
489 let overlap = [
490 libm::fmax(signed_distances[0][0], signed_distances[1][0]),
491 libm::fmin(signed_distances[0][1], signed_distances[1][1]),
492 ];
493 let radius = if overlap[1] >= overlap[0] {
496 let near_start = overlap[0] + (overlap[1] - overlap[0]) * 0.15;
497 let near_end = overlap[0] + (overlap[1] - overlap[0]) * 0.85;
498 if near_start.abs() < near_end.abs() {
499 near_start
500 } else {
501 near_end
502 }
503 } else {
504 let mut distances = signed_distances.into_iter().flatten().collect::<Vec<_>>();
505 distances.sort_by(f64::total_cmp);
506 distances[1]
507 };
508 let start = if radius < 0.0 {
509 [-directions[0][0], -directions[0][1]]
510 } else {
511 directions[0]
512 };
513
514 (libm::atan2(start[1], start[0]) + desired * 0.5).rem_euclid(std::f64::consts::TAU)
515}
516
517fn finalize_legacy_angle_refactor_meta(
518 pending: &PendingLegacyAngleRefactorMeta,
519 final_values: &[f64],
520) -> Option<LegacyAngleRefactorMeta> {
521 let line0 = solved_angle_line(&pending.lines[0], final_values)?;
522 let line1 = solved_angle_line(&pending.lines[1], final_values)?;
523 let vertex = intersect_lines_2d(line0, line1)?;
524 let directions = [vec2_sub(line0.1, line0.0), vec2_sub(line1.1, line1.0)];
525 if directions.iter().any(|direction| vec2_len(*direction) <= 1e-9) {
526 return None;
527 }
528
529 let desired = pending.desired_angle_radians.rem_euclid(std::f64::consts::TAU);
530 let sectors = [
531 AngleSector::One,
532 AngleSector::Two,
533 AngleSector::Three,
534 AngleSector::Four,
535 ];
536 let mut candidates = Vec::new();
537 for sector in sectors {
538 for inverse in [false, true] {
539 let rays = angle_sector_rays(sector, inverse);
540 let from = angle_ray_vector(directions, rays[0]);
541 let to = angle_ray_vector(directions, rays[1]);
542 if circular_angle_distance(directed_angle(from, to), desired) <= 1e-5 {
543 let midpoint = libm::atan2(from[1], from[0]) + desired * 0.5;
544 candidates.push((sector, inverse, midpoint.rem_euclid(std::f64::consts::TAU)));
545 }
546 }
547 }
548
549 let arc_midpoint_angle = legacy_angle_arc_midpoint_angle([line0, line1], directions, vertex, desired);
550 let selected = candidates.into_iter().min_by(|a, b| {
551 circular_angle_distance(a.2, arc_midpoint_angle).total_cmp(&circular_angle_distance(b.2, arc_midpoint_angle))
552 })?;
553
554 Some(LegacyAngleRefactorMeta {
555 source_range: pending.source_range,
556 sector: front_angle_sector(selected.0),
557 inverse: selected.1,
558 })
559}
560
561fn push_points_at_angle_for_lines(
562 sketch_block_state: &mut SketchBlockState,
563 sketch_var_ty: NumericType,
564 lines: [&ConstrainableLine2d; 2],
565 data: PointsAtAngleLineData,
566 range: SourceRange,
567) -> Result<(), KclError> {
568 let solver_line0 = datum_line_from_constrainable(lines[0], range)?;
569 let solver_line1 = datum_line_from_constrainable(lines[1], range)?;
570 let vertex = push_hidden_sketch_point(sketch_block_state, sketch_var_ty, data.initial_vertex, range)?;
571
572 sketch_block_state
573 .solver_constraints
574 .push(Constraint::PointLineDistance(vertex, solver_line0, 0.0));
575 sketch_block_state
576 .solver_constraints
577 .push(Constraint::PointLineDistance(vertex, solver_line1, 0.0));
578 sketch_block_state.solver_constraints.push(Constraint::PointsAtAngle(
579 vertex,
580 data.representative_points[0],
581 data.representative_points[1],
582 data.angle_kind,
583 ));
584
585 Ok(())
586}
587
588fn sketch_var_initial_value(
589 sketch_vars: &[KclValue],
590 id: crate::execution::SketchVarId,
591 exec_state: &mut ExecState,
592 range: SourceRange,
593 description: &str,
594) -> Result<f64, KclError> {
595 sketch_vars
596 .get(id.0)
597 .and_then(KclValue::as_sketch_var)
598 .map(|sketch_var| {
599 sketch_var
600 .initial_value_to_solver_units(exec_state, range, description)
601 .map(|value| value.n)
602 })
603 .transpose()?
604 .ok_or_else(|| internal_err(format!("Missing sketch variable initial value for id {}", id.0), range))
605}
606
607fn constrainable_point_initial_position(
608 sketch_vars: &[KclValue],
609 point: &crate::execution::ConstrainablePoint2d,
610 exec_state: &mut ExecState,
611 range: SourceRange,
612 description: &str,
613) -> Result<[f64; 2], KclError> {
614 Ok([
615 sketch_var_initial_value(sketch_vars, point.vars.x, exec_state, range, description)?,
616 sketch_var_initial_value(sketch_vars, point.vars.y, exec_state, range, description)?,
617 ])
618}
619
620fn constrainable_point_or_origin_initial_position(
621 sketch_vars: &[KclValue],
622 point: &crate::execution::ConstrainablePoint2dOrOrigin,
623 exec_state: &mut ExecState,
624 range: SourceRange,
625 description: &str,
626) -> Result<[f64; 2], KclError> {
627 match point {
628 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
629 constrainable_point_initial_position(sketch_vars, point, exec_state, range, description)
630 }
631 crate::execution::ConstrainablePoint2dOrOrigin::Origin => Ok([0.0, 0.0]),
632 }
633}
634
635fn constrainable_line_initial_positions(
641 sketch_vars: &[KclValue],
642 line: &crate::execution::ConstrainableLine2d,
643 exec_state: &mut ExecState,
644 range: SourceRange,
645 description: &str,
646) -> Result<([f64; 2], [f64; 2]), KclError> {
647 let start = crate::execution::ConstrainablePoint2d {
648 vars: line.vars[0].clone(),
649 object_id: line.object_id,
650 };
651 let end = crate::execution::ConstrainablePoint2d {
652 vars: line.vars[1].clone(),
653 object_id: line.object_id,
654 };
655 Ok((
656 constrainable_point_initial_position(sketch_vars, &start, exec_state, range, description)?,
657 constrainable_point_initial_position(sketch_vars, &end, exec_state, range, description)?,
658 ))
659}
660
661fn projected_point_on_line_initial_position(
662 sketch_vars: &[KclValue],
663 point: &crate::execution::ConstrainablePoint2dOrOrigin,
664 line: &crate::execution::ConstrainableLine2d,
665 exec_state: &mut ExecState,
666 range: SourceRange,
667) -> Result<[f64; 2], KclError> {
668 let point = constrainable_point_or_origin_initial_position(
669 sketch_vars,
670 point,
671 exec_state,
672 range,
673 "point-line distance initial point",
674 )?;
675 let (line_start, line_end) =
676 constrainable_line_initial_positions(sketch_vars, line, exec_state, range, "point-line distance initial line")?;
677 let dx = line_end[0] - line_start[0];
678 let dy = line_end[1] - line_start[1];
679 let len_sq = dx * dx + dy * dy;
680 if len_sq == 0.0 {
681 return Err(KclError::new_semantic(KclErrorDetails::new(
682 "distance() line input must have non-zero length".to_owned(),
683 vec![range],
684 )));
685 }
686
687 let t = ((point[0] - line_start[0]) * dx + (point[1] - line_start[1]) * dy) / len_sq;
690 Ok([line_start[0] + t * dx, line_start[1] + t * dy])
691}
692
693fn constrainable_points_initial_distance(
694 sketch_vars: &[KclValue],
695 point0: &crate::execution::ConstrainablePoint2d,
696 point1: &crate::execution::ConstrainablePoint2d,
697 exec_state: &mut ExecState,
698 range: SourceRange,
699 description: &str,
700) -> Result<f64, KclError> {
701 let p0 = constrainable_point_initial_position(sketch_vars, point0, exec_state, range, description)?;
702 let p1 = constrainable_point_initial_position(sketch_vars, point1, exec_state, range, description)?;
703 Ok(libm::hypot(p0[0] - p1[0], p0[1] - p1[1]))
704}
705
706#[derive(Clone, Copy)]
710struct CircularDistanceDatums {
711 center: ezpz::datatypes::inputs::DatumPoint,
712 start: ezpz::datatypes::inputs::DatumPoint,
713 end: Option<ezpz::datatypes::inputs::DatumPoint>,
714 radius_initial_value: f64,
715}
716
717fn circular_distance_datums(
718 sketch_vars: &[KclValue],
719 center: &crate::execution::ConstrainablePoint2d,
720 start: &crate::execution::ConstrainablePoint2d,
721 end: Option<&crate::execution::ConstrainablePoint2d>,
722 exec_state: &mut ExecState,
723 range: SourceRange,
724) -> Result<CircularDistanceDatums, KclError> {
725 Ok(CircularDistanceDatums {
726 center: datum_point_from_constrainable(center, range)?,
727 start: datum_point_from_constrainable(start, range)?,
728 end: end.map(|end| datum_point_from_constrainable(end, range)).transpose()?,
729 radius_initial_value: constrainable_points_initial_distance(
730 sketch_vars,
731 center,
732 start,
733 exec_state,
734 range,
735 "circular distance radius initial value",
736 )?,
737 })
738}
739
740fn circular_circular_support_initial_position(
741 sketch_vars: &[KclValue],
742 center0: &crate::execution::ConstrainablePoint2d,
743 center1: &crate::execution::ConstrainablePoint2d,
744 radius0: f64,
745 distance_value: f64,
746 exec_state: &mut ExecState,
747 range: SourceRange,
748) -> Result<[f64; 2], KclError> {
749 let center0_initial =
750 constrainable_point_initial_position(sketch_vars, center0, exec_state, range, "circular distance center")?;
751 let center1_initial =
752 constrainable_point_initial_position(sketch_vars, center1, exec_state, range, "circular distance center")?;
753 let dx = center1_initial[0] - center0_initial[0];
754 let dy = center1_initial[1] - center0_initial[1];
755 let center_distance = libm::hypot(dx, dy);
756 let support_distance = radius0 + distance_value / 2.0;
761
762 if center_distance <= f64::EPSILON {
763 return Ok([center0_initial[0] + support_distance, center0_initial[1]]);
766 }
767
768 Ok([
769 center0_initial[0] + dx / center_distance * support_distance,
770 center0_initial[1] + dy / center_distance * support_distance,
771 ])
772}
773
774fn push_circular_radius_constraints(
775 sketch_block_state: &mut SketchBlockState,
776 sketch_var_ty: NumericType,
777 circular: CircularDistanceDatums,
778 range: SourceRange,
779) -> Result<ezpz::datatypes::inputs::DatumCircle, KclError> {
780 let circular_radius_id = sketch_block_state.next_sketch_var_id();
784 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
785 value: Box::new(crate::execution::SketchVar {
786 id: circular_radius_id,
787 initial_value: circular.radius_initial_value,
788 ty: sketch_var_ty,
789 node_path: None,
791 meta: vec![],
792 }),
793 });
794 let circular_radius = ezpz::datatypes::inputs::DatumDistance::new(circular_radius_id.to_constraint_id(range)?);
795
796 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
797 circular.start,
798 circular.center,
799 circular_radius,
800 ));
801 if let Some(end) = circular.end {
802 sketch_block_state
803 .solver_constraints
804 .push(Constraint::DistanceVar(end, circular.center, circular_radius));
805 }
806
807 Ok(ezpz::datatypes::inputs::DatumCircle {
808 center: circular.center,
809 radius: circular_radius,
810 })
811}
812
813fn push_circular_distance_constraints(
814 sketch_block_state: &mut SketchBlockState,
815 sketch_var_ty: NumericType,
816 target_point: ezpz::datatypes::inputs::DatumPoint,
817 circular: CircularDistanceDatums,
818 distance_value: f64,
819 range: SourceRange,
820) -> Result<(), KclError> {
821 let circular_target = push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular, range)?;
822
823 let target_distance_id = sketch_block_state.next_sketch_var_id();
826 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
827 value: Box::new(crate::execution::SketchVar {
828 id: target_distance_id,
829 initial_value: distance_value,
830 ty: sketch_var_ty,
831 node_path: None,
833 meta: vec![],
834 }),
835 });
836 let target_distance = ezpz::datatypes::inputs::DatumDistance::new(target_distance_id.to_constraint_id(range)?);
837
838 sketch_block_state
839 .solver_constraints
840 .push(Constraint::Fixed(target_distance.id, distance_value));
841
842 let target_circle = ezpz::datatypes::inputs::DatumCircle {
843 center: target_point,
844 radius: target_distance,
845 };
846 sketch_block_state
847 .solver_constraints
848 .push(Constraint::CircleTangentToCircle(
849 target_circle,
850 circular_target,
851 ezpz::CircleSide::Exterior,
852 ));
853
854 Ok(())
855}
856
857fn sketch_on_cache_name(sketch_id: ObjectId) -> String {
858 format!("{SKETCH_PREFIX}{}_on", sketch_id.0)
859}
860
861fn default_plane_name_from_expr(expr: &Expr) -> Option<crate::engine::PlaneName> {
862 fn parse_name(name: &str, negative: bool) -> Option<crate::engine::PlaneName> {
863 use crate::engine::PlaneName;
864
865 match (name, negative) {
866 ("XY", false) => Some(PlaneName::Xy),
867 ("XY", true) => Some(PlaneName::NegXy),
868 ("XZ", false) => Some(PlaneName::Xz),
869 ("XZ", true) => Some(PlaneName::NegXz),
870 ("YZ", false) => Some(PlaneName::Yz),
871 ("YZ", true) => Some(PlaneName::NegYz),
872 _ => None,
873 }
874 }
875
876 match expr {
877 Expr::Name(name) => {
878 if !name.path.is_empty() {
879 return None;
880 }
881 parse_name(&name.name.name, false)
882 }
883 Expr::UnaryExpression(unary) => {
884 if unary.operator != UnaryOperator::Neg {
885 return None;
886 }
887 let crate::parsing::ast::types::BinaryPart::Name(name) = &unary.argument else {
888 return None;
889 };
890 if !name.path.is_empty() {
891 return None;
892 }
893 parse_name(&name.name.name, true)
894 }
895 _ => None,
896 }
897}
898
899fn sketch_on_frontend_plane(
900 arguments: &[crate::parsing::ast::types::LabeledArg],
901 on_object_id: crate::front::ObjectId,
902) -> crate::front::Plane {
903 for arg in arguments {
904 let Some(label) = &arg.label else {
905 continue;
906 };
907 if label.name != SKETCH_BLOCK_PARAM_ON {
908 continue;
909 }
910 if let Some(name) = default_plane_name_from_expr(&arg.arg) {
911 return crate::front::Plane::Default(name);
912 }
913 break;
914 }
915
916 crate::front::Plane::Object(on_object_id)
917}
918
919impl<'a> StatementKind<'a> {
920 fn expect_name(&self) -> &'a str {
921 match self {
922 StatementKind::Declaration { name } => name,
923 StatementKind::Expression => unreachable!(),
924 }
925 }
926}
927
928impl ExecutorContext {
929 pub(super) async fn handle_annotations(
931 &self,
932 annotations: impl Iterator<Item = &Node<Annotation>>,
933 body_type: BodyType,
934 exec_state: &mut ExecState,
935 ) -> Result<bool, KclError> {
936 let mut no_prelude = false;
937 for annotation in annotations {
938 let diagnostics_attr = annotations::diagnostics_attr_name(exec_state.kcl_version());
943 if annotation.name() == Some(annotations::SETTINGS) {
944 if matches!(body_type, BodyType::Root) {
945 let (updated_len, updated_angle) =
946 exec_state.mod_local.settings.update_from_annotation(annotation)?;
947 if updated_len {
948 exec_state.mod_local.explicit_length_units = true;
949 }
950 if updated_angle {
951 if exec_state.kcl_version() >= KclVersion::V3Preview {
952 return Err(KclError::new_semantic(KclErrorDetails::new(
953 "The `defaultAngleUnit` setting was removed in KCL 3.0; use explicit units for angles"
954 .to_owned(),
955 annotation.as_source_ranges(),
956 )));
957 }
958 exec_state.warn(
959 CompilationIssue::err(
960 annotation.as_source_range(),
961 "The `defaultAngleUnit` setting is deprecated; use explicit units for angles",
962 ),
963 annotations::WARN_ANGLE_UNITS,
964 );
965 }
966 } else {
967 exec_state.err(CompilationIssue::err(
968 annotation.as_source_range(),
969 "Settings can only be modified at the top level scope of a file",
970 ));
971 }
972 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
973 if matches!(body_type, BodyType::Root) {
974 no_prelude = true;
975 } else {
976 exec_state.err(CompilationIssue::err(
977 annotation.as_source_range(),
978 "The standard library can only be skipped at the top level scope of a file",
979 ));
980 }
981 } else if annotation.name() == Some(diagnostics_attr) {
982 if matches!(body_type, BodyType::Root) {
984 let props = annotations::expect_properties(diagnostics_attr, annotation)?;
985 for p in props {
986 match &*p.inner.key.name {
987 annotations::WARN_ALLOW => {
988 let allowed = annotations::many_of(
989 &p.inner.value,
990 &annotations::WARN_VALUES,
991 diagnostics_attr,
992 annotation.as_source_range(),
993 )?;
994 exec_state.mod_local.allowed_warnings = allowed;
995 }
996 annotations::WARN_DENY => {
997 let denied = annotations::many_of(
998 &p.inner.value,
999 &annotations::WARN_VALUES,
1000 diagnostics_attr,
1001 annotation.as_source_range(),
1002 )?;
1003 exec_state.mod_local.denied_warnings = denied;
1004 }
1005 name => {
1006 return Err(KclError::new_semantic(KclErrorDetails::new(
1007 format!(
1008 "Unexpected {diagnostics_attr} key: `{name}`; expected one of `{}`, `{}`",
1009 annotations::WARN_ALLOW,
1010 annotations::WARN_DENY,
1011 ),
1012 vec![annotation.as_source_range()],
1013 )));
1014 }
1015 }
1016 }
1017 } else {
1018 let message = match diagnostics_attr {
1019 annotations::WARNINGS => "Warnings can only be customized at the top level scope of a file",
1020 _ => "Diagnostics can only be customized at the top level scope of a file",
1021 };
1022 exec_state.err(CompilationIssue::err(annotation.as_source_range(), message));
1023 }
1024 } else if annotation.name() == Some(annotations::WARNINGS) {
1025 let mut issue = CompilationIssue::err(
1030 annotation.as_source_range(),
1031 format!(
1032 "The `@{old}` attribute was renamed to `@{new}` in KCL 3.0, so this attribute is ignored. Replace `@{old}` with `@{new}`; its `{allow}` and `{deny}` properties are unchanged.",
1033 old = annotations::WARNINGS,
1034 new = annotations::DIAGNOSTICS,
1035 allow = annotations::WARN_ALLOW,
1036 deny = annotations::WARN_DENY,
1037 ),
1038 );
1039 if let Some(name) = &annotation.name {
1040 issue = issue.with_suggestion(
1041 format!("Rename to `@{}`", annotations::DIAGNOSTICS),
1042 annotations::DIAGNOSTICS,
1043 Some(name.as_source_range()),
1044 crate::errors::Tag::None,
1045 );
1046 }
1047 exec_state.err(issue);
1048 } else {
1049 exec_state.warn(
1050 CompilationIssue::err(annotation.as_source_range(), "Unknown annotation"),
1051 annotations::WARN_UNKNOWN_ATTR,
1052 );
1053 }
1054 }
1055 Ok(no_prelude)
1056 }
1057
1058 pub(super) async fn exec_module_body(
1059 &self,
1060 program: &Node<Program>,
1061 exec_state: &mut ExecState,
1062 preserve_mem: PreserveMem,
1063 module_id: ModuleId,
1064 path: &ModulePath,
1065 ) -> Result<ModuleExecutionOutcome, (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>)> {
1066 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
1067
1068 exec_state
1071 .check_imported_module_kcl_version(path, program, None)
1072 .map_err(|err| (err, None, None))?;
1073
1074 let mut local_state = ModuleState::new(
1083 path.clone(),
1084 exec_state.stack().memory.clone(),
1085 Some(module_id),
1086 exec_state.mod_local.sketch_mode,
1087 exec_state.mod_local.freedom_analysis,
1088 );
1089 match preserve_mem {
1090 PreserveMem::Always => {
1091 exec_state
1092 .mod_local
1093 .artifacts
1094 .restore_scene_objects(&exec_state.global.root_module_artifacts.scene_objects);
1095 }
1096 PreserveMem::Normal => {
1097 local_state
1098 .artifacts
1099 .restore_scene_objects(&exec_state.mod_local.artifacts.scene_objects);
1100 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
1101 }
1102 }
1103
1104 let no_prelude = self
1105 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
1106 .await
1107 .map_err(|err| (err, None, None))?;
1108
1109 if preserve_mem.normal() {
1110 exec_state
1111 .mut_stack()
1112 .push_new_root_env(!no_prelude)
1113 .map_err(|err| (err, None, None))?;
1114 }
1115
1116 let result = self
1117 .exec_block(program, exec_state, crate::execution::BodyType::Root)
1118 .await;
1119
1120 let env_ref = match preserve_mem {
1121 PreserveMem::Always => exec_state.mut_stack().pop_and_preserve_env(),
1122 PreserveMem::Normal => exec_state.mut_stack().pop_env(),
1123 }
1124 .map_err(|err| (err, None, None))?;
1125 let module_artifacts = match preserve_mem {
1126 PreserveMem::Always => std::mem::take(&mut exec_state.mod_local.artifacts),
1127 PreserveMem::Normal => {
1128 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
1129 local_state.artifacts
1130 }
1131 };
1132
1133 crate::log::log(format!("leave {path}"));
1134
1135 result
1136 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
1137 .map(|last_expr| ModuleExecutionOutcome {
1138 last_expr: last_expr.map(|value_cf| value_cf.into_value()),
1139 environment: env_ref,
1140 exports: local_state.module_exports,
1141 artifacts: module_artifacts,
1142 })
1143 }
1144
1145 #[async_recursion]
1147 pub(super) async fn exec_block<'a, B>(
1148 &'a self,
1149 block: &'a B,
1150 exec_state: &mut ExecState,
1151 body_type: BodyType,
1152 ) -> Result<Option<KclValueControlFlow>, KclError>
1153 where
1154 B: CodeBlock + crate::execution::machine::ToMachineBlock + Sync,
1155 {
1156 if self.is_machine_executor() {
1157 return crate::execution::machine::run_block(self, block.to_machine_block(), exec_state, body_type).await;
1158 }
1159
1160 let mut last_expr = None;
1161 for statement in block.body() {
1163 match statement {
1164 BodyItem::ImportStatement(import_stmt) => {
1165 if exec_state.sketch_mode() {
1166 continue;
1167 }
1168 self.exec_import_statement(import_stmt, body_type, exec_state).await?;
1169 last_expr = None;
1170 }
1171 BodyItem::ExpressionStatement(expression_statement) => {
1172 if exec_state.sketch_mode() && sketch_mode_should_skip(&expression_statement.expression) {
1173 continue;
1174 }
1175
1176 let metadata = Metadata::from(expression_statement);
1177 let value = self
1178 .execute_expr(
1179 &expression_statement.expression,
1180 exec_state,
1181 &metadata,
1182 &[],
1183 StatementKind::Expression,
1184 )
1185 .await?;
1186
1187 let is_return = value.is_some_return();
1188 last_expr = Some(value);
1189
1190 if is_return {
1191 break;
1192 }
1193 }
1194 BodyItem::VariableDeclaration(variable_declaration) => {
1195 if exec_state.sketch_mode() && sketch_mode_should_skip(&variable_declaration.declaration.init) {
1196 continue;
1197 }
1198
1199 let var_name = variable_declaration.declaration.id.name.to_string();
1200 let source_range = SourceRange::from(&variable_declaration.declaration.init);
1201 let metadata = Metadata { source_range };
1202
1203 let annotations = &variable_declaration.outer_attrs;
1204
1205 let lhs = variable_declaration.inner.name().to_owned();
1208 let prev_being_declared = exec_state.mod_local.being_declared.take();
1209 exec_state.mod_local.being_declared = Some(lhs);
1210 let rhs_result = self
1211 .execute_expr(
1212 &variable_declaration.declaration.init,
1213 exec_state,
1214 &metadata,
1215 annotations,
1216 StatementKind::Declaration { name: &var_name },
1217 )
1218 .await;
1219 exec_state.mod_local.being_declared = prev_being_declared;
1221 let rhs = rhs_result?;
1222
1223 if rhs.is_some_return() {
1224 last_expr = Some(rhs);
1225 break;
1226 }
1227 let rhs =
1228 self.bind_variable_declaration(variable_declaration, rhs.into_value(), body_type, exec_state)?;
1229 last_expr = matches!(body_type, BodyType::Root).then_some(rhs.continue_());
1231 }
1232 BodyItem::TypeDeclaration(ty) => {
1233 if exec_state.sketch_mode() {
1234 continue;
1235 }
1236 self.exec_type_declaration(ty, body_type, exec_state).await?;
1237 last_expr = None;
1238 }
1239 BodyItem::ReturnStatement(return_statement) => {
1240 if exec_state.sketch_mode() && sketch_mode_should_skip(&return_statement.argument) {
1241 continue;
1242 }
1243
1244 let metadata = Metadata::from(return_statement);
1245
1246 if matches!(body_type, BodyType::Root) {
1247 return Err(KclError::new_semantic(KclErrorDetails::new(
1248 "Cannot return from outside a function.".to_owned(),
1249 vec![metadata.source_range],
1250 )));
1251 }
1252
1253 let value_cf = self
1254 .execute_expr(
1255 &return_statement.argument,
1256 exec_state,
1257 &metadata,
1258 &[],
1259 StatementKind::Expression,
1260 )
1261 .await?;
1262 if value_cf.is_some_return() {
1263 last_expr = Some(value_cf);
1264 break;
1265 }
1266 let value = value_cf.into_value();
1267 if exec_state.entry_point_version_is_v3_or_higher() {
1268 last_expr = Some(value.return_());
1272 break;
1273 }
1274 Self::bind_return_value(return_statement, value, exec_state)?;
1275 last_expr = None;
1276 }
1277 }
1278 }
1279
1280 if matches!(body_type, BodyType::Root)
1284 && let Some(cf) = &last_expr
1285 && cf.is_return()
1286 {
1287 return Err(KclError::new_semantic(KclErrorDetails::new(
1288 "Cannot return from outside a function.".to_owned(),
1289 cf.source_ranges(),
1290 )));
1291 }
1292
1293 if matches!(body_type, BodyType::Root) {
1294 exec_state
1296 .flush_batch(
1297 ModelingCmdMeta::new(exec_state, self, block.to_source_range()),
1298 true,
1301 )
1302 .await?;
1303 }
1304
1305 Ok(last_expr)
1306 }
1307
1308 pub(super) async fn exec_import_statement(
1312 &self,
1313 import_stmt: &Node<ImportStatement>,
1314 body_type: BodyType,
1315 exec_state: &mut ExecState,
1316 ) -> Result<(), KclError> {
1317 if !matches!(body_type, BodyType::Root) {
1318 return Err(KclError::new_semantic(KclErrorDetails::new(
1319 "Imports are only supported at the top-level of a file.".to_owned(),
1320 vec![import_stmt.into()],
1321 )));
1322 }
1323
1324 let source_range = SourceRange::from(import_stmt);
1325 let attrs = &import_stmt.outer_attrs;
1326 let module_path = ModulePath::from_import_path(
1327 &import_stmt.path,
1328 &self.settings.project_directory,
1329 &exec_state.mod_local.path,
1330 )?;
1331 let module_id = self
1332 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
1333 .await?;
1334
1335 if let ImportPath::Kcl { .. } = &import_stmt.path
1342 && let Some(ModuleRepr::Kcl(program, _)) =
1343 exec_state.global.module_infos.get(&module_id).map(|info| &info.repr)
1344 {
1345 exec_state.check_imported_module_kcl_version(&module_path, program, Some(source_range))?;
1346 }
1347
1348 if let ModulePath::Local { value, .. } = &module_path {
1349 let name = import_stmt
1350 .module_name()
1351 .unwrap_or_else(|| value.file_name().unwrap_or_default());
1352 exec_state.push_op(Operation::ModuleInstance {
1353 name,
1354 module_id,
1355 glob: matches!(import_stmt.selector, ImportSelector::Glob(_)),
1356 node_path: NodePath::placeholder(),
1357 source_range,
1358 });
1359 }
1360
1361 match &import_stmt.selector {
1362 ImportSelector::List { items } => {
1363 let (env_ref, module_exports) = self.exec_module_for_items(module_id, exec_state, source_range).await?;
1364 for import_item in items {
1365 let mem = &exec_state.stack().memory;
1367 let mut value = mem.get_from_owned(&import_item.name.name, env_ref, import_item.into(), 0);
1368 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
1369 let mut ty = mem.get_from_owned(&ty_name, env_ref, import_item.into(), 0);
1370 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
1371 let mut mod_value = mem.get_from_owned(&mod_name, env_ref, import_item.into(), 0);
1372
1373 if value.is_err() && ty.is_err() && mod_value.is_err() {
1374 return Err(KclError::new_undefined_value(
1375 KclErrorDetails::new(
1376 format!("{} is not defined in module", import_item.name.name),
1377 vec![SourceRange::from(&import_item.name)],
1378 ),
1379 None,
1380 ));
1381 }
1382
1383 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
1385 value = Err(KclError::new_semantic(KclErrorDetails::new(
1386 format!(
1387 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
1388 import_item.name.name
1389 ),
1390 vec![SourceRange::from(&import_item.name)],
1391 )));
1392 }
1393
1394 if ty.is_ok() && !module_exports.contains(&ty_name) {
1395 ty = Err(KclError::new_semantic(KclErrorDetails::new(
1396 format!(
1397 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
1398 import_item.name.name
1399 ),
1400 vec![SourceRange::from(&import_item.name)],
1401 )));
1402 }
1403
1404 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
1405 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
1406 format!(
1407 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
1408 import_item.name.name
1409 ),
1410 vec![SourceRange::from(&import_item.name)],
1411 )));
1412 }
1413
1414 if value.is_err() && ty.is_err() && mod_value.is_err() {
1415 return value.map(|_| ());
1416 }
1417
1418 if let Ok(value) = value {
1420 exec_state.mut_stack().add(
1421 import_item.identifier().to_owned(),
1422 value,
1423 SourceRange::from(&import_item.name),
1424 )?;
1425
1426 if let ItemVisibility::Export = import_stmt.visibility {
1427 exec_state
1428 .mod_local
1429 .module_exports
1430 .push(import_item.identifier().to_owned());
1431 }
1432 }
1433
1434 if let Ok(ty) = ty {
1435 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
1436 if matches!(
1437 &ty,
1438 KclValue::Type {
1439 value: TypeDef::Enum(_),
1440 ..
1441 }
1442 ) {
1443 reject_enum_clashing_with_module(
1444 exec_state,
1445 import_item.identifier(),
1446 SourceRange::from(&import_item.name),
1447 )?;
1448 }
1449 exec_state
1450 .mut_stack()
1451 .add(ty_name.clone(), ty, SourceRange::from(&import_item.name))?;
1452
1453 if let ItemVisibility::Export = import_stmt.visibility {
1454 exec_state.mod_local.module_exports.push(ty_name);
1455 }
1456 }
1457
1458 if let Ok(mod_value) = mod_value {
1459 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
1460 reject_module_clashing_with_enum(
1461 exec_state,
1462 import_item.identifier(),
1463 SourceRange::from(&import_item.name),
1464 )?;
1465 exec_state.mut_stack().add(
1466 mod_name.clone(),
1467 mod_value,
1468 SourceRange::from(&import_item.name),
1469 )?;
1470
1471 if let ItemVisibility::Export = import_stmt.visibility {
1472 exec_state.mod_local.module_exports.push(mod_name);
1473 }
1474 }
1475 }
1476 }
1477 ImportSelector::Glob(_) => {
1478 let (env_ref, module_exports) = self.exec_module_for_items(module_id, exec_state, source_range).await?;
1479 for name in module_exports.iter() {
1480 let item = exec_state
1481 .stack()
1482 .memory
1483 .get_from_owned(name, env_ref, source_range, 0)
1484 .map_err(|_err| {
1485 internal_err(
1486 format!("{name} is not defined in module (but was exported?)"),
1487 source_range,
1488 )
1489 })?;
1490 reject_glob_import_clash(exec_state, name, &item, source_range)?;
1491 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
1492
1493 if let ItemVisibility::Export = import_stmt.visibility {
1494 exec_state.mod_local.module_exports.push(name.clone());
1495 }
1496 }
1497 }
1498 ImportSelector::None { .. } => {
1499 let name = import_stmt.module_name().unwrap();
1500 reject_module_clashing_with_enum(exec_state, &name, source_range)?;
1501 let item = KclValue::Module {
1502 value: module_id,
1503 meta: vec![source_range.into()],
1504 };
1505 exec_state
1506 .mut_stack()
1507 .add(format!("{}{}", memory::MODULE_PREFIX, name), item, source_range)?;
1508 }
1509 }
1510
1511 Ok(())
1512 }
1513
1514 pub(super) async fn exec_type_declaration(
1516 &self,
1517 ty: &Node<TypeDeclaration>,
1518 body_type: BodyType,
1519 exec_state: &mut ExecState,
1520 ) -> Result<(), KclError> {
1521 let metadata = Metadata::from(ty);
1522 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
1523 match attrs.impl_ {
1524 annotations::Impl::Rust | annotations::Impl::RustConstrainable | annotations::Impl::RustConstraint => {
1525 let std_path = match &exec_state.mod_local.path {
1526 ModulePath::Std { value } => value,
1527 ModulePath::Local { .. } | ModulePath::Main => {
1528 return Err(KclError::new_semantic(KclErrorDetails::new(
1529 "User-defined types are not yet supported.".to_owned(),
1530 vec![metadata.source_range],
1531 )));
1532 }
1533 };
1534 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
1535 let value = KclValue::Type {
1536 value: TypeDef::RustRepr(t, props),
1537 meta: vec![metadata],
1538 experimental: attrs.experimental,
1539 };
1540 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
1541 exec_state
1542 .mut_stack()
1543 .add(name_in_mem.clone(), value, metadata.source_range)
1544 .map_err(|_| {
1545 KclError::new_semantic(KclErrorDetails::new(
1546 format!("Redefinition of type {}.", ty.name.name),
1547 vec![metadata.source_range],
1548 ))
1549 })?;
1550
1551 if let ItemVisibility::Export = ty.visibility {
1552 exec_state.mod_local.module_exports.push(name_in_mem);
1553 }
1554 }
1555 annotations::Impl::Primitive => {}
1557 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.definition {
1558 TypeDeclarationDefinition::Alias { ty: alias } => {
1559 let type_def = match alias.inner.clone() {
1560 Type::Named { name } => {
1561 match resolve_named_type_def(&name, exec_state, self, metadata.source_range, false).await? {
1562 def @ TypeDef::Enum(_) => def,
1563 def => TypeDef::Alias(def.into_runtime_type()),
1564 }
1565 }
1566 alias => TypeDef::Alias(
1567 RuntimeType::from_parsed(
1568 alias,
1569 exec_state,
1570 self,
1571 metadata.source_range,
1572 attrs.impl_ == annotations::Impl::KclConstrainable,
1573 false,
1574 )
1575 .await?,
1576 ),
1577 };
1578 if matches!(&type_def, TypeDef::Enum(_)) {
1579 reject_enum_clashing_with_module(exec_state, &ty.name.name, metadata.source_range)?;
1580 }
1581 let value = KclValue::Type {
1582 value: type_def,
1583 meta: vec![metadata],
1584 experimental: attrs.experimental,
1585 };
1586 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
1587 exec_state
1588 .mut_stack()
1589 .add(name_in_mem.clone(), value, metadata.source_range)
1590 .map_err(|_| {
1591 KclError::new_semantic(KclErrorDetails::new(
1592 format!("Redefinition of type {}.", ty.name.name),
1593 vec![metadata.source_range],
1594 ))
1595 })?;
1596
1597 if let ItemVisibility::Export = ty.visibility {
1598 exec_state.mod_local.module_exports.push(name_in_mem);
1599 }
1600 }
1601 TypeDeclarationDefinition::Bare => {
1602 return Err(KclError::new_semantic(KclErrorDetails::new(
1603 "User-defined types are not yet supported.".to_owned(),
1604 vec![metadata.source_range],
1605 )));
1606 }
1607 TypeDeclarationDefinition::Enum(decl) => {
1608 if !matches!(body_type, BodyType::Root) {
1613 return Err(KclError::new_semantic(KclErrorDetails::new(
1614 format!(
1615 "Enum declarations are only supported at the top-level of a file. Move `type {}` to the top-level.",
1616 ty.name.name
1617 ),
1618 vec![metadata.source_range],
1619 )));
1620 }
1621
1622 reject_enum_clashing_with_module(exec_state, &ty.name.name, metadata.source_range)?;
1623
1624 let variants = decl.variants.iter().map(|v| v.name.name.clone()).collect();
1625 let id = EnumTypeId::new(metadata.source_range.module_id(), ty.name.name.clone());
1626 let def = EnumTypeDef::new(id, variants).map_err(|duplicate| {
1630 KclError::new_semantic(KclErrorDetails::new(
1631 format!("Duplicate variant `{}` in enum `{}`.", duplicate.name, ty.name.name),
1632 vec![
1633 decl.variants[duplicate.first_index].as_source_range(),
1634 decl.variants[duplicate.duplicate_index].as_source_range(),
1635 ],
1636 ))
1637 })?;
1638
1639 let value = KclValue::Type {
1640 value: TypeDef::Enum(Arc::new(def)),
1641 meta: vec![metadata],
1642 experimental: attrs.experimental,
1643 };
1644 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
1645 exec_state
1646 .mut_stack()
1647 .add(name_in_mem.clone(), value, metadata.source_range)
1648 .map_err(|_| {
1649 KclError::new_semantic(KclErrorDetails::new(
1650 format!("Redefinition of type {}.", ty.name.name),
1651 vec![metadata.source_range],
1652 ))
1653 })?;
1654
1655 if let ItemVisibility::Export = ty.visibility {
1656 exec_state.mod_local.module_exports.push(name_in_mem);
1657 }
1658 }
1659 },
1660 }
1661
1662 Ok(())
1663 }
1664
1665 pub(super) fn bind_variable_declaration(
1672 &self,
1673 variable_declaration: &Node<VariableDeclaration>,
1674 rhs: KclValue,
1675 body_type: BodyType,
1676 exec_state: &mut ExecState,
1677 ) -> Result<KclValue, KclError> {
1678 let var_name = variable_declaration.declaration.id.name.to_string();
1679 let source_range = SourceRange::from(&variable_declaration.declaration.init);
1680 let mut rhs = rhs;
1681
1682 if let KclValue::Segment { value } = &mut rhs
1686 && let SegmentRepr::Unsolved { segment } = &mut value.repr
1687 {
1688 segment.tag = Some(TagIdentifier {
1689 value: variable_declaration.declaration.id.name.clone(),
1690 info: Default::default(),
1691 meta: vec![SourceRange::from(&variable_declaration.declaration.id).into()],
1692 });
1693 }
1694 let rhs = rhs; let should_bind_name = if let Some(fn_name) = variable_declaration.declaration.init.fn_declaring_name() {
1697 var_name != fn_name
1701 } else {
1702 true
1705 };
1706 if should_bind_name {
1707 exec_state
1708 .mut_stack()
1709 .add(var_name.clone(), rhs.clone(), source_range)?;
1710 }
1711
1712 if let Some(sketch_block_state) = exec_state.mod_local.sketch_block.as_mut()
1713 && let KclValue::Segment { value } = &rhs
1714 {
1715 let segment_object_id = match &value.repr {
1718 SegmentRepr::Unsolved { segment } => segment.object_id,
1719 SegmentRepr::Solved { segment } => segment.object_id,
1720 };
1721 sketch_block_state
1722 .segment_tags
1723 .entry(segment_object_id)
1724 .or_insert_with(|| {
1725 let id_node = &variable_declaration.declaration.id;
1726 Node::new(
1727 TagDeclarator {
1728 name: id_node.name.clone(),
1729 digest: None,
1730 },
1731 id_node.start,
1732 id_node.end,
1733 id_node.module_id,
1734 )
1735 });
1736 }
1737
1738 let should_show_in_feature_tree = !exec_state.mod_local.inside_stdlib && rhs.show_variable_in_feature_tree();
1742 if should_show_in_feature_tree {
1743 exec_state.push_op(Operation::VariableDeclaration {
1744 name: var_name.clone(),
1745 value: op_from_kcl_value(&rhs),
1746 visibility: variable_declaration.visibility,
1747 node_path: NodePath::placeholder(),
1748 source_range,
1749 });
1750 }
1751
1752 if let ItemVisibility::Export = variable_declaration.visibility {
1754 if matches!(body_type, BodyType::Root) {
1755 exec_state.mod_local.module_exports.push(var_name);
1756 } else {
1757 exec_state.err(CompilationIssue::err(
1758 variable_declaration.as_source_range(),
1759 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
1760 ));
1761 }
1762 }
1763 Ok(rhs)
1764 }
1765
1766 pub(super) fn bind_return_value(
1774 return_statement: &Node<ReturnStatement>,
1775 value: KclValue,
1776 exec_state: &mut ExecState,
1777 ) -> Result<(), KclError> {
1778 let metadata = Metadata::from(return_statement);
1779 exec_state
1780 .mut_stack()
1781 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
1782 .map_err(|_| {
1783 KclError::new_semantic(KclErrorDetails::new(
1784 "Multiple returns from a single function.".to_owned(),
1785 vec![metadata.source_range],
1786 ))
1787 })?;
1788 Ok(())
1789 }
1790
1791 pub async fn open_module(
1792 &self,
1793 path: &ImportPath,
1794 attrs: &[Node<Annotation>],
1795 resolved_path: &ModulePath,
1796 exec_state: &mut ExecState,
1797 source_range: SourceRange,
1798 ) -> Result<ModuleId, KclError> {
1799 match path {
1800 ImportPath::Kcl { .. } => {
1801 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
1802
1803 if let Some(id) = exec_state.id_for_module(resolved_path) {
1804 return Ok(id);
1805 }
1806
1807 let id = exec_state.next_module_id();
1808 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1810 let source = resolved_path.source(&self.fs, source_range).await?;
1811 exec_state.add_id_to_source(id, source.clone());
1812 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
1814 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
1815
1816 Ok(id)
1817 }
1818 ImportPath::Foreign { .. } => {
1819 if let Some(id) = exec_state.id_for_module(resolved_path) {
1820 return Ok(id);
1821 }
1822
1823 let id = exec_state.next_module_id();
1824 let path = resolved_path.expect_path();
1825 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1827 let format = super::import::format_from_annotations(attrs, path, source_range)?;
1828 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
1829 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
1830 Ok(id)
1831 }
1832 ImportPath::Std { .. } => {
1833 if resolved_path.is_solver_module() && exec_state.mod_local.sketch_block.is_none() {
1834 return Err(KclError::new_semantic(KclErrorDetails::new(
1835 format!("The `{resolved_path}` module is only available inside sketch blocks."),
1836 vec![source_range],
1837 )));
1838 }
1839
1840 if let Some(id) = exec_state.id_for_module(resolved_path) {
1841 return Ok(id);
1842 }
1843
1844 let id = exec_state.next_module_id();
1845 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1847 let source = resolved_path.source(&self.fs, source_range).await?;
1848 exec_state.add_id_to_source(id, source.clone());
1849 let parsed = crate::parsing::parse_str(&source.source, id)
1850 .parse_errs_as_err()
1851 .unwrap();
1852 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
1853 Ok(id)
1854 }
1855 }
1856 }
1857
1858 pub(super) async fn exec_module_for_items(
1859 &self,
1860 module_id: ModuleId,
1861 exec_state: &mut ExecState,
1862 source_range: SourceRange,
1863 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
1864 let path = exec_state.global.module_infos[&module_id].path.clone();
1865 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1866 let result = match &mut repr {
1869 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
1870 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
1871 ModuleRepr::Kcl(program, cache) => self
1872 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
1873 .await
1874 .map(|outcome| {
1875 *cache = Some(outcome.clone());
1876 (outcome.environment, outcome.exports)
1877 }),
1878 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1879 "Cannot import items from foreign modules".to_owned(),
1880 vec![geom.source_range],
1881 ))),
1882 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
1883 };
1884
1885 exec_state.global.module_infos[&module_id].restore_repr(repr);
1886 result
1887 }
1888
1889 async fn exec_module_for_result(
1890 &self,
1891 module_id: ModuleId,
1892 exec_state: &mut ExecState,
1893 source_range: SourceRange,
1894 ) -> Result<Option<KclValue>, KclError> {
1895 let path = exec_state.global.module_infos[&module_id].path.clone();
1896 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1897 let result = match &mut repr {
1900 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
1901 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
1902 ModuleRepr::Kcl(program, cached_items) => {
1903 let result = self
1904 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
1905 .await;
1906 match result {
1907 Ok(outcome) => {
1908 let value = outcome.last_expr.clone();
1909 *cached_items = Some(outcome);
1910 Ok(value)
1911 }
1912 Err(e) => Err(e),
1913 }
1914 }
1915 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
1916 ModuleRepr::Foreign(geom, cached) => {
1917 let caller_artifacts = std::mem::take(&mut exec_state.mod_local.artifacts);
1918 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
1919 .await
1920 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
1921 let module_artifacts = std::mem::replace(&mut exec_state.mod_local.artifacts, caller_artifacts);
1922
1923 match result {
1924 Ok(val) => {
1925 *cached = Some((val.clone(), module_artifacts));
1926 Ok(val)
1927 }
1928 Err(e) => {
1929 exec_state.mod_local.artifacts.extend(module_artifacts);
1932 Err(e.add_import_location(&path.import_name(), source_range))
1935 }
1936 }
1937 }
1938 ModuleRepr::Dummy => unreachable!(),
1939 };
1940
1941 exec_state.global.module_infos[&module_id].restore_repr(repr);
1942
1943 result
1944 }
1945
1946 pub async fn exec_module_from_ast(
1947 &self,
1948 program: &Node<Program>,
1949 module_id: ModuleId,
1950 path: &ModulePath,
1951 exec_state: &mut ExecState,
1952 source_range: SourceRange,
1953 preserve_mem: PreserveMem,
1954 ) -> Result<ModuleExecutionOutcome, KclError> {
1955 exec_state.global.mod_loader.enter_module(path);
1956 let result = self
1957 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
1958 .await;
1959 exec_state.global.mod_loader.leave_module(path, source_range)?;
1960
1961 result.map_err(|(err, _, _)| {
1964 match err {
1965 KclError::ImportCycle { .. } => {
1966 err.override_source_ranges(vec![source_range])
1968 }
1969 _ => err.add_import_location(&path.import_name(), source_range),
1973 }
1974 })
1975 }
1976
1977 pub(super) async fn resolve_name_for_eval(
1981 &self,
1982 name: &Node<Name>,
1983 metadata: &Metadata,
1984 exec_state: &mut ExecState,
1985 ) -> Result<KclValue, KclError> {
1986 let value = name.get_result(exec_state, self).await?;
1987 if let KclValue::Module { value: module_id, meta } = value {
1988 Ok(self
1989 .exec_module_for_result(module_id, exec_state, metadata.source_range)
1990 .await?
1991 .unwrap_or_else(|| {
1992 exec_state.warn(
1993 CompilationIssue::err(
1994 metadata.source_range,
1995 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
1996 ),
1997 annotations::WARN_MOD_RETURN_VALUE,
1998 );
1999
2000 let mut new_meta = vec![metadata.to_owned()];
2001 new_meta.extend(meta);
2002 KclValue::KclNone {
2003 value: Default::default(),
2004 meta: new_meta,
2005 }
2006 }))
2007 } else {
2008 Ok(value)
2009 }
2010 }
2011
2012 #[async_recursion]
2013 pub(crate) async fn execute_expr<'a: 'async_recursion>(
2014 &self,
2015 init: &Expr,
2016 exec_state: &mut ExecState,
2017 metadata: &Metadata,
2018 annotations: &[Node<Annotation>],
2019 statement_kind: StatementKind<'a>,
2020 ) -> Result<KclValueControlFlow, KclError> {
2021 let item = match init {
2022 Expr::None(none) => KclValue::from(none).continue_(),
2023 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state).continue_(),
2024 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?.continue_(),
2025 Expr::Name(name) => self
2026 .resolve_name_for_eval(name, metadata, exec_state)
2027 .await?
2028 .continue_(),
2029 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
2030 Expr::FunctionExpression(function_expression) => self
2031 .create_function_closure(function_expression, annotations, metadata, statement_kind, exec_state)
2032 .await?
2033 .continue_(),
2034 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
2035 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
2036 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
2037 StatementKind::Declaration { name } => {
2038 let message = format!(
2039 "you cannot declare variable {name} as %, because % can only be used in function calls"
2040 );
2041
2042 return Err(KclError::new_semantic(KclErrorDetails::new(
2043 message,
2044 vec![pipe_substitution.into()],
2045 )));
2046 }
2047 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
2048 Some(x) => x.continue_(),
2049 None => {
2050 return Err(KclError::new_semantic(KclErrorDetails::new(
2051 "cannot use % outside a pipe expression".to_owned(),
2052 vec![pipe_substitution.into()],
2053 )));
2054 }
2055 },
2056 },
2057 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
2058 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
2059 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
2060 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
2061 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
2062 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
2063 Expr::LabelledExpression(expr) => {
2064 let value_cf = self
2065 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
2066 .await?;
2067 let value = control_continue!(value_cf);
2068 exec_state
2069 .mut_stack()
2070 .add(expr.label.name.clone(), value.clone(), init.into())?;
2071 value.continue_()
2073 }
2074 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
2075 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
2076 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
2077 };
2078 Ok(item)
2079 }
2080
2081 pub(crate) async fn eval_expr_fresh_root(
2085 &self,
2086 expr: &Expr,
2087 exec_state: &mut ExecState,
2088 metadata: &Metadata,
2089 ) -> Result<KclValueControlFlow, KclError> {
2090 if self.is_machine_executor() {
2091 return crate::execution::machine::run_expr(self, expr, exec_state, metadata).await;
2092 }
2093 self.execute_expr(expr, exec_state, metadata, &[], StatementKind::Expression)
2094 .await
2095 }
2096
2097 pub(super) async fn create_function_closure(
2101 &self,
2102 function_expression: &crate::parsing::ast::types::BoxNode<FunctionExpression>,
2103 annotations: &[Node<Annotation>],
2104 metadata: &Metadata,
2105 statement_kind: StatementKind<'_>,
2106 exec_state: &mut ExecState,
2107 ) -> Result<KclValue, KclError> {
2108 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
2109 let experimental = attrs
2110 .as_ref()
2111 .map(|a| a.experimental)
2112 .unwrap_or_else(|| FnAttrs::default().experimental);
2114
2115 let include_in_feature_tree = attrs
2117 .as_ref()
2118 .map(|a| a.include_in_feature_tree)
2119 .unwrap_or_else(|| FnAttrs::default().include_in_feature_tree);
2121 let (mut closure, placeholder_env_ref) = if let Some(attrs) = attrs
2122 && (attrs.impl_ == annotations::Impl::Rust
2123 || attrs.impl_ == annotations::Impl::RustConstrainable
2124 || attrs.impl_ == annotations::Impl::RustConstraint)
2125 {
2126 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
2127 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
2128 (
2129 KclValue::Function {
2130 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
2131 meta: vec![metadata.to_owned()],
2132 },
2133 None,
2134 )
2135 } else {
2136 return Err(KclError::new_semantic(KclErrorDetails::new(
2137 "Rust implementation of functions is restricted to the standard library".to_owned(),
2138 vec![metadata.source_range],
2139 )));
2140 }
2141 } else {
2142 let std_props = function_expression
2143 .name_str()
2144 .and_then(|name| exec_state.mod_local.path.build_std_fully_qualified_name(name))
2145 .map(|name| StdFnProps::default(&name));
2146 let (env_ref, placeholder_env_ref) = if function_expression.name.is_some() {
2150 let dummy = EnvironmentRef::dummy();
2153 (dummy, Some(dummy))
2154 } else {
2155 (exec_state.mut_stack().snapshot()?, None)
2156 };
2157 (
2158 KclValue::Function {
2159 value: Box::new(FunctionSource::kcl(
2160 function_expression.clone(),
2161 env_ref,
2162 KclFunctionSourceParams {
2163 std_props,
2164 experimental,
2165 include_in_feature_tree,
2166 },
2167 )),
2168 meta: vec![metadata.to_owned()],
2169 },
2170 placeholder_env_ref,
2171 )
2172 };
2173
2174 if let KclValue::Function { value, .. } = &mut closure {
2178 value.resolve_signature_types(exec_state, self).await?;
2179 }
2180
2181 if let Some(fn_name) = &function_expression.name {
2184 if let Some(placeholder_env_ref) = placeholder_env_ref {
2188 closure = exec_state.mut_stack().add_recursive_closure(
2189 fn_name.name.to_owned(),
2190 closure,
2191 placeholder_env_ref,
2192 metadata.source_range,
2193 )?;
2194 } else {
2195 exec_state
2197 .mut_stack()
2198 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
2199 }
2200 }
2201
2202 Ok(closure)
2203 }
2204}
2205
2206fn module_enum_clash(name: &str, source_range: SourceRange) -> KclError {
2215 KclError::new_semantic(KclErrorDetails::new(
2216 format!(
2217 "An enum and a module cannot share the name `{name}` in the same scope, because `{name}::x` would be ambiguous. Rename one of them."
2218 ),
2219 vec![source_range],
2220 ))
2221}
2222
2223fn reject_enum_clashing_with_module(
2225 exec_state: &ExecState,
2226 name: &str,
2227 source_range: SourceRange,
2228) -> Result<(), KclError> {
2229 let key = format!("{}{}", memory::MODULE_PREFIX, name);
2230 if !exec_state.stack().cur_frame_contains(&key)? {
2231 return Ok(());
2232 }
2233
2234 Err(module_enum_clash(name, source_range))
2235}
2236
2237fn reject_module_clashing_with_enum(
2240 exec_state: &ExecState,
2241 name: &str,
2242 source_range: SourceRange,
2243) -> Result<(), KclError> {
2244 let key = format!("{}{}", memory::TYPE_PREFIX, name);
2245 if !exec_state.stack().cur_frame_contains(&key)? {
2246 return Ok(());
2247 }
2248
2249 let Ok(KclValue::Type {
2250 value: TypeDef::Enum(_),
2251 ..
2252 }) = exec_state.stack().get(&key, source_range)
2253 else {
2254 return Ok(());
2255 };
2256
2257 Err(module_enum_clash(name, source_range))
2258}
2259
2260fn different_enums_err(left: &EnumValue, right: &EnumValue, source_range: SourceRange) -> KclError {
2262 let left_name = left.enum_id().declared_name();
2263 let right_name = right.enum_id().declared_name();
2264
2265 let message = if left_name == right_name {
2266 format!(
2269 "Cannot compare two different enums that are both named `{left_name}`. They come from separate declarations."
2270 )
2271 } else {
2272 format!("Cannot compare enum `{left_name}` with enum `{right_name}`. They are different types.")
2273 };
2274
2275 KclError::new_semantic(KclErrorDetails::new(message, vec![source_range]))
2276}
2277
2278fn type_used_as_value(exec_state: &ExecState, name: &Node<Identifier>) -> Option<KclError> {
2284 let key = format!("{}{}", memory::TYPE_PREFIX, name.name);
2285 let KclValue::Type { value: def, .. } = exec_state.stack().get(&key, name.as_source_range()).ok()? else {
2286 return None;
2287 };
2288
2289 let suggestion = match &def {
2292 TypeDef::Enum(def) => def
2293 .variants()
2294 .first()
2295 .map(|variant| format!(" Use one of its variants, such as `{}::{variant}`.", name.name))
2296 .unwrap_or_default(),
2297 _ => String::new(),
2298 };
2299
2300 Some(KclError::new_semantic(KclErrorDetails::new(
2301 format!("`{}` is a type, not a value.{suggestion}", name.name),
2302 name.as_source_ranges(),
2303 )))
2304}
2305
2306enum EnumPathHead {
2307 Enum(Arc<EnumTypeDef>),
2308 NonEnumType,
2309}
2310
2311fn enum_named_by_segment(
2318 exec_state: &ExecState,
2319 segment: &Node<Identifier>,
2320 within: Option<&(EnvironmentRef, Vec<String>)>,
2321) -> Option<EnumPathHead> {
2322 match type_value_named_by_segment(exec_state, segment, within)? {
2323 KclValue::Type {
2324 value: TypeDef::Enum(def),
2325 ..
2326 } => Some(EnumPathHead::Enum(def)),
2327 KclValue::Type { .. } => Some(EnumPathHead::NonEnumType),
2328 _ => None,
2329 }
2330}
2331
2332fn non_enum_type_in_path(segment: &Node<Identifier>) -> KclError {
2333 KclError::new_semantic(KclErrorDetails::new(
2334 format!(
2335 "`{}` is a type that does not resolve to an enum, so it cannot be used as the head of a `::` path.",
2336 segment.name
2337 ),
2338 segment.as_source_ranges(),
2339 ))
2340}
2341
2342fn enum_variant_value(
2344 def: Arc<EnumTypeDef>,
2345 variant: &Node<Identifier>,
2346 exec_state: &mut ExecState,
2347) -> Result<KclValue, KclError> {
2348 let enum_name = def.id().declared_name();
2349
2350 if !def.has_variant(&variant.name) {
2351 let known = if def.variants().is_empty() {
2352 format!("Enum `{enum_name}` has no variants")
2353 } else {
2354 format!("Its variants are: {}", def.variants().join(", "))
2355 };
2356
2357 return Err(KclError::new_semantic(KclErrorDetails::new(
2358 format!("`{}` is not a variant of enum `{enum_name}`. {known}.", variant.name),
2359 variant.as_source_ranges(),
2360 )));
2361 }
2362
2363 exec_state.warn_experimental(&format!("the enum `{enum_name}`"), variant.as_source_range());
2370
2371 Ok(KclValue::Enum {
2375 value: Box::new(EnumValue::new(
2376 def,
2377 variant.name.clone(),
2378 vec![Metadata {
2379 source_range: variant.as_source_range(),
2380 }],
2381 )),
2382 })
2383}
2384
2385fn reject_glob_import_clash(
2388 exec_state: &ExecState,
2389 key: &str,
2390 item: &KclValue,
2391 source_range: SourceRange,
2392) -> Result<(), KclError> {
2393 if let Some(name) = key.strip_prefix(memory::MODULE_PREFIX) {
2394 return reject_module_clashing_with_enum(exec_state, name, source_range);
2395 }
2396
2397 if let Some(name) = key.strip_prefix(memory::TYPE_PREFIX)
2398 && matches!(
2399 item,
2400 KclValue::Type {
2401 value: TypeDef::Enum(_),
2402 ..
2403 }
2404 )
2405 {
2406 return reject_enum_clashing_with_module(exec_state, name, source_range);
2407 }
2408
2409 Ok(())
2410}
2411
2412pub(super) fn sketch_mode_should_skip(expr: &Expr) -> bool {
2415 fn contains_edited_sketch_block(node: crate::walk::Node<'_>) -> bool {
2416 if let crate::walk::Node::SketchBlock(sketch_block) = node {
2417 return sketch_block.is_being_edited;
2418 }
2419 node.children().into_iter().any(contains_edited_sketch_block)
2420 }
2421
2422 !contains_edited_sketch_block(expr.into())
2423}
2424
2425fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
2428 let KclError::UndefinedValue { name, mut details } = e else {
2429 return e;
2430 };
2431 if let (Some(name0), Some(name1)) = (&being_declared, &name)
2435 && name0 == name1
2436 {
2437 details.message = format!(
2438 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
2439 );
2440 }
2441 KclError::UndefinedValue { details, name }
2442}
2443
2444impl Node<AscribedExpression> {
2445 #[async_recursion]
2446 pub(super) async fn get_result(
2447 &self,
2448 exec_state: &mut ExecState,
2449 ctx: &ExecutorContext,
2450 ) -> Result<KclValueControlFlow, KclError> {
2451 let metadata = Metadata {
2452 source_range: SourceRange::from(self),
2453 };
2454 let result = ctx
2455 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
2456 .await?;
2457 let result = control_continue!(result);
2458 apply_ascription(&result, &self.ty, exec_state, ctx, self.into())
2459 .await
2460 .map(KclValue::continue_)
2461 }
2462}
2463
2464impl Node<SketchBlock> {
2465 pub(super) async fn get_result(
2466 &self,
2467 exec_state: &mut ExecState,
2468 ctx: &ExecutorContext,
2469 ) -> Result<KclValueControlFlow, KclError> {
2470 if exec_state.mod_local.sketch_block.is_some() {
2471 return Err(KclError::new_semantic(KclErrorDetails::new(
2473 "Cannot execute a sketch block from within another sketch block".to_owned(),
2474 vec![SourceRange::from(self)],
2475 )));
2476 }
2477
2478 let range = SourceRange::from(self);
2479
2480 let (sketch_id, sketch_surface) = match self.exec_arguments(exec_state, ctx).await {
2482 Ok(x) => x,
2483 Err(cf_error) => match cf_error {
2484 EarlyReturn::Value(cf_value) => return Ok(cf_value),
2486 EarlyReturn::Error(err) => return Err(err),
2487 },
2488 };
2489 let sketch_block_artifact_id = self.scene_setup(sketch_id, &sketch_surface, exec_state)?;
2490
2491 let (return_result, variables, sketch_block_state) = {
2492 self.prep_mem(exec_state.mut_stack().snapshot()?, exec_state)?;
2494
2495 let initial_sketch_block_state = {
2497 SketchBlockState {
2498 sketch_id: Some(sketch_id),
2499 ..Default::default()
2500 }
2501 };
2502
2503 let original_value = exec_state.mod_local.sketch_block.replace(initial_sketch_block_state);
2504
2505 let original_sketch_mode = std::mem::replace(&mut exec_state.mod_local.sketch_mode, false);
2508
2509 let (result, block_variables) = match self.load_sketch2_into_current_scope(exec_state, ctx, range).await {
2514 Ok(()) => {
2515 let parent = exec_state.mut_stack().snapshot()?;
2516 exec_state.mut_stack().push_new_env_for_call(parent)?;
2517 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
2518 let (result, block_variables) = match exec_state.stack().find_all_in_current_env() {
2519 Ok(block_variables) => (result, block_variables.into_iter().collect::<IndexMap<_, _>>()),
2520 Err(err) => (Err(err), IndexMap::new()),
2521 };
2522 let result = match exec_state.mut_stack().pop_env() {
2523 Ok(_) => result,
2524 Err(err) => Err(err),
2525 };
2526 (result, block_variables)
2527 }
2528 Err(err) => (Err(err), IndexMap::new()),
2529 };
2530
2531 exec_state.mod_local.sketch_mode = original_sketch_mode;
2532
2533 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
2534
2535 let result = match exec_state.mut_stack().pop_env() {
2537 Ok(_) => result,
2538 Err(err) => Err(err),
2539 };
2540
2541 (result, block_variables, sketch_block_state)
2542 };
2543
2544 let return_control_flow = return_result?;
2546 if let Some(control_flow) = return_control_flow
2551 && control_flow.is_some_return()
2552 {
2553 exec_state.push_op(Operation::GroupEnd);
2556 return Ok(control_flow);
2557 }
2558 let Some(sketch_block_state) = sketch_block_state else {
2559 debug_assert!(false, "Sketch block state should still be set to Some from just above");
2560 return Err(internal_err(
2561 "Sketch block state should still be set to Some from just above",
2562 self,
2563 ));
2564 };
2565 let return_value = self
2566 .finalize_sketch_block(
2567 sketch_id,
2568 &sketch_surface,
2569 sketch_block_artifact_id,
2570 variables,
2571 sketch_block_state,
2572 exec_state,
2573 ctx,
2574 )
2575 .await?;
2576 Ok(if self.is_being_edited {
2577 return_value.exit()
2580 } else {
2581 return_value.continue_()
2582 })
2583 }
2584
2585 async fn exec_arguments(
2595 &self,
2596 exec_state: &mut ExecState,
2597 ctx: &ExecutorContext,
2598 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2599 if !exec_state.sketch_mode() {
2600 let mut labeled = IndexMap::new();
2606 for labeled_arg in &self.arguments {
2607 let source_range = SourceRange::from(labeled_arg.arg.clone());
2608 let metadata = Metadata { source_range };
2609 let value_cf = ctx
2610 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
2611 .await?;
2612 let value = early_return!(value_cf);
2613 let arg = Arg::new(value, source_range);
2614 match &labeled_arg.label {
2615 Some(label) => {
2616 labeled.insert(label.name.clone(), arg);
2617 }
2618 None => {
2619 let name = labeled_arg.arg.ident_name();
2620 if let Some(name) = name {
2621 labeled.insert(name.to_owned(), arg);
2622 } else {
2623 return Err(KclError::new_semantic(KclErrorDetails::new(
2624 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
2625 vec![SourceRange::from(&labeled_arg.arg)],
2626 ))
2627 .into());
2628 }
2629 }
2630 }
2631 }
2632 self.finish_arguments_after_eval(labeled, exec_state, ctx).await
2633 } else {
2634 self.arguments_from_cache(exec_state)
2635 }
2636 }
2637
2638 pub(super) async fn finish_arguments_after_eval(
2642 &self,
2643 labeled: IndexMap<String, Arg>,
2644 exec_state: &mut ExecState,
2645 ctx: &ExecutorContext,
2646 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2647 let range = SourceRange::from(self);
2648 let mut args = Args::new_no_args(
2649 range,
2650 self.node_path.clone(),
2651 ctx.clone(),
2652 Some(SketchBlock::CALLEE_NAME.to_owned()),
2653 );
2654 args.labeled = labeled;
2655
2656 self.check_for_unexpected_arguments(&args, exec_state)?;
2664
2665 let arg_on_value: KclValue =
2666 args.get_kw_arg(SKETCH_BLOCK_PARAM_ON, &RuntimeType::sketch_or_surface(), exec_state)?;
2667
2668 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2669 let message = "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2670 debug_assert!(false, "{message}");
2671 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2672 };
2673 let mut sketch_surface = arg_on.into_sketch_surface();
2674
2675 match &mut sketch_surface {
2678 SketchSurface::Plane(plane) => {
2679 ensure_sketch_plane_in_engine(plane, exec_state, ctx, range, self.node_path.clone()).await?;
2681 }
2682 SketchSurface::Face(_) => {
2683 }
2685 }
2686
2687 let sketch_id = exec_state.next_object_id();
2693 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2694 let on_cache_name = sketch_on_cache_name(sketch_id);
2695 exec_state.mut_stack().add(on_cache_name, arg_on_value, range)?;
2697
2698 Ok((sketch_id, sketch_surface))
2699 }
2700
2701 pub(super) fn arguments_from_cache(
2704 &self,
2705 exec_state: &mut ExecState,
2706 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2707 let range = SourceRange::from(self);
2708 {
2709 let sketch_id = exec_state.next_object_id();
2716 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2717 let on_cache_name = sketch_on_cache_name(sketch_id);
2718 let arg_on_value = exec_state.stack().get_owned(&on_cache_name, range)?;
2719
2720 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2721 let message =
2722 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2723 debug_assert!(false, "{message}");
2724 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2725 };
2726 let mut sketch_surface = arg_on.into_sketch_surface();
2727
2728 if sketch_surface.object_id().is_none() {
2731 let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
2734 return Err(internal_err(
2735 "In sketch mode, the `on` plane argument must refer to an existing plane object.",
2736 range,
2737 )
2738 .into());
2739 };
2740 sketch_surface.set_object_id(last_object.id);
2741 }
2742
2743 Ok((sketch_id, sketch_surface))
2744 }
2745 }
2746
2747 pub(super) fn scene_setup(
2750 &self,
2751 sketch_id: ObjectId,
2752 sketch_surface: &SketchSurface,
2753 exec_state: &mut ExecState,
2754 ) -> Result<ArtifactId, KclError> {
2755 let range = SourceRange::from(self);
2756 let on_object_id = if let Some(object_id) = sketch_surface.object_id() {
2757 object_id
2758 } else {
2759 let message = "The `on` argument should have an object after ensure_sketch_plane_in_engine".to_owned();
2760 debug_assert!(false, "{message}");
2761 return Err(internal_err(message, range));
2762 };
2763 let sketch_ctor_on = sketch_on_frontend_plane(&self.arguments, on_object_id);
2764 let sketch_block_artifact_id = {
2765 use crate::execution::CodeRef;
2766 use crate::execution::SketchBlock;
2767 use crate::front::Plane;
2768 use crate::front::SourceRef;
2769
2770 let on_object = exec_state.mod_local.artifacts.scene_object_by_id(on_object_id);
2771
2772 let plane_artifact_id = on_object.map(|object| object.artifact_id);
2774 let plane_info = match &sketch_surface {
2775 SketchSurface::Plane(plane) => Some(super::artifact::artifact_plane_info(&plane.info)),
2776 SketchSurface::Face(_) => None,
2777 };
2778
2779 let standard_plane = match &sketch_ctor_on {
2780 Plane::Default(plane) => Some(*plane),
2781 Plane::Object(_) | Plane::PrimitiveFace(_) => None,
2782 };
2783
2784 let artifact_id = ArtifactId::from(exec_state.next_uuid());
2785 let label = exec_state.mod_local.being_declared.clone().unwrap_or_default();
2801 let sketch_scene_object = Object {
2803 id: sketch_id,
2804 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
2805 args: crate::front::SketchCtor { on: sketch_ctor_on },
2806 plane: on_object_id,
2807 segments: Default::default(),
2808 constraints: Default::default(),
2809 }),
2810 label,
2811 comments: Default::default(),
2812 artifact_id,
2813 source: SourceRef::new(self.into(), self.node_path.clone()),
2814 };
2815 exec_state.set_scene_object(sketch_scene_object);
2816
2817 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
2819 id: artifact_id,
2820 standard_plane,
2821 plane_id: plane_artifact_id,
2822 plane_info,
2823 path_id: None,
2827 code_ref: CodeRef::placeholder(range),
2828 sketch_id,
2829 }));
2830
2831 exec_state.push_op(Operation::GroupBegin {
2832 group: Group::SketchBlock { sketch_id },
2833 node_path: NodePath::placeholder(),
2834 source_range: range,
2835 });
2836 artifact_id
2837 };
2838 Ok(sketch_block_artifact_id)
2839 }
2840
2841 #[allow(clippy::too_many_arguments)]
2846 pub(super) async fn finalize_sketch_block(
2847 &self,
2848 sketch_id: ObjectId,
2849 sketch_surface: &SketchSurface,
2850 sketch_block_artifact_id: ArtifactId,
2851 variables: IndexMap<String, KclValue>,
2852 mut sketch_block_state: SketchBlockState,
2853 exec_state: &mut ExecState,
2854 ctx: &ExecutorContext,
2855 ) -> Result<KclValue, KclError> {
2856 let range = SourceRange::from(self);
2857 let constraints = sketch_block_state
2859 .solver_constraints
2860 .iter()
2861 .cloned()
2862 .map(ezpz::ConstraintRequest::highest_priority)
2863 .chain(
2864 sketch_block_state
2866 .solver_optional_constraints
2867 .iter()
2868 .cloned()
2869 .map(|c| ezpz::ConstraintRequest::new(c, 1)),
2870 )
2871 .collect::<Vec<_>>();
2872 let initial_guesses = sketch_block_state
2873 .sketch_vars
2874 .iter()
2875 .map(|v| {
2876 let Some(sketch_var) = v.as_sketch_var() else {
2877 return Err(internal_err("Expected sketch variable", self));
2878 };
2879 let constraint_id = sketch_var.id.to_constraint_id(range)?;
2880 let number_value = KclValue::Number {
2882 value: sketch_var.initial_value,
2883 ty: sketch_var.ty,
2884 meta: sketch_var.meta.clone(),
2885 };
2886 let initial_guess_value = normalize_to_solver_distance_unit(
2887 &number_value,
2888 v.into(),
2889 exec_state,
2890 "sketch variable initial value",
2891 )?;
2892 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
2893 n.n
2894 } else {
2895 let message = format!(
2896 "Expected number after coercion, but found {}",
2897 initial_guess_value.human_friendly_type()
2898 );
2899 debug_assert!(false, "{}", &message);
2900 return Err(internal_err(message, self));
2901 };
2902 Ok((constraint_id, initial_guess))
2903 })
2904 .collect::<Result<Vec<_>, KclError>>()?;
2905 let config = ezpz::Config::default()
2907 .with_max_iterations(50)
2908 .with_convergence_tolerance(SOLVER_CONVERGENCE_TOLERANCE);
2909 let solve_result = if exec_state.mod_local.freedom_analysis {
2910 ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
2911 let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
2912 (outcome.outcome, Some(freedom_analysis))
2913 })
2914 } else {
2915 ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
2916 };
2917 let num_required_constraints = sketch_block_state.solver_constraints.len();
2919 let all_constraints: Vec<ezpz::Constraint> = sketch_block_state
2920 .solver_constraints
2921 .iter()
2922 .cloned()
2923 .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
2924 .collect();
2925
2926 let (solve_outcome, solve_analysis) = match solve_result {
2927 Ok((solved, freedom)) => {
2928 if solved
2929 .final_values()
2930 .iter()
2931 .any(|number| number.is_infinite() || number.is_nan())
2932 {
2933 return Err(KclError::new_internal(KclErrorDetails::new(
2934 "KCL's 2D constraint solver returned an invalid number".to_owned(),
2935 vec![SourceRange::from(self)],
2936 )));
2937 }
2938 let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
2939 if !outcome.converged {
2940 exec_state.warn(
2941 CompilationIssue::err(range, "Constraint solver failed to find a solution".to_owned()),
2942 annotations::WARN_SOLVER,
2943 );
2944 }
2945 (outcome, freedom)
2946 }
2947 Err(failure) => {
2948 match &failure.error {
2949 NonLinearSystemError::FaerMatrix { .. }
2950 | NonLinearSystemError::Faer { .. }
2951 | NonLinearSystemError::FaerSolve { .. }
2952 | NonLinearSystemError::FaerSvd(..) => {
2953 exec_state.warn(
2956 CompilationIssue::err(range, "Internal error in constraint solver".to_owned()),
2957 annotations::WARN_SOLVER,
2958 );
2959 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
2960 (
2961 Solved {
2962 final_values,
2963 iterations: Default::default(),
2964 warnings: failure.warnings,
2965 priority_solved: Default::default(),
2966 variables_in_conflicts: Default::default(),
2967 unsatisfied_directional_constraints: Default::default(),
2968 converged: false,
2969 },
2970 None,
2971 )
2972 }
2973 NonLinearSystemError::EmptySystemNotAllowed
2974 | NonLinearSystemError::WrongNumberGuesses { .. }
2975 | NonLinearSystemError::MissingGuess { .. }
2976 | NonLinearSystemError::NotFound(..) => {
2977 #[cfg(target_arch = "wasm32")]
2980 web_sys::console::error_1(
2981 &format!("Internal error from constraint solver: {}", failure.error).into(),
2982 );
2983 return Err(internal_err(
2984 format!("Internal error from constraint solver: {}", failure.error),
2985 self,
2986 ));
2987 }
2988 _ => {
2989 return Err(internal_err(
2991 format!("Error from constraint solver: {}", failure.error),
2992 self,
2993 ));
2994 }
2995 }
2996 }
2997 };
2998 for warning in &solve_outcome.warnings {
3000 let message = if let Some(index) = warning.about_constraint.as_ref() {
3001 format!("{}; constraint index {}", warning.content, index)
3002 } else {
3003 format!("{}", warning.content)
3004 };
3005 exec_state.warn(CompilationIssue::err(range, message), annotations::WARN_SOLVER);
3006 }
3007 if solve_outcome.converged {
3008 exec_state.mod_local.artifacts.refactor_metadata.extend(
3009 sketch_block_state
3010 .pending_legacy_angle_refactor_metadata
3011 .iter()
3012 .filter_map(|pending| {
3013 finalize_legacy_angle_refactor_meta(pending, &solve_outcome.final_values)
3014 .map(RefactorMetadata::LegacyAngle)
3015 }),
3016 );
3017 }
3018 let sketch_engine_id = exec_state.next_uuid();
3020 let solution_ty = solver_numeric_type(exec_state);
3021 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
3022 for unsolved_segment in &sketch_block_state.needed_by_engine {
3023 solved_segments.push(substitute_sketch_var_in_segment(
3024 unsolved_segment.clone(),
3025 sketch_surface,
3026 sketch_engine_id,
3027 None,
3028 &solve_outcome,
3029 solver_numeric_type(exec_state),
3030 solve_analysis.as_ref(),
3031 )?);
3032 }
3033 exec_state.mod_local.artifacts.var_solutions =
3039 sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
3040
3041 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
3043
3044 let sketch = create_segments_in_engine(
3046 sketch_surface,
3047 sketch_engine_id,
3048 &mut solved_segments,
3049 &sketch_block_state.segment_tags,
3050 ctx,
3051 exec_state,
3052 range,
3053 )
3054 .await?;
3055
3056 if let Some(sketch_artifact_id) = sketch.as_ref().map(|s| s.artifact_id) {
3058 if let Some(Artifact::SketchBlock(sketch_block_artifact)) =
3059 exec_state.artifact_mut(sketch_block_artifact_id)
3060 {
3061 sketch_block_artifact.path_id = Some(sketch_artifact_id);
3062 } else {
3063 let message = "Sketch block artifact not found, so path couldn't be linked to it".to_owned();
3064 debug_assert!(false, "{message}");
3065 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3066 }
3067 }
3068
3069 let variables = substitute_sketch_vars(
3074 variables,
3075 sketch_surface,
3076 sketch_engine_id,
3077 sketch.as_ref(),
3078 &solve_outcome,
3079 solution_ty,
3080 solve_analysis.as_ref(),
3081 )?;
3082
3083 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
3084 for scene_object in scene_objects {
3085 segment_object_ids.push(scene_object.id);
3086 exec_state.set_scene_object(scene_object);
3088 }
3089 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
3091 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
3092 debug_assert!(false, "{}", &message);
3093 return Err(internal_err(message, range));
3094 };
3095 let ObjectKind::Sketch(front_sketch) = &mut sketch_object.kind else {
3096 let message = format!(
3097 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
3098 sketch_id, sketch_object
3099 );
3100 debug_assert!(
3101 false,
3102 "{}; scene_objects={:#?}",
3103 message, exec_state.mod_local.artifacts.scene_objects
3104 );
3105 return Err(internal_err(message, range));
3106 };
3107 front_sketch.segments.extend(segment_object_ids);
3108 front_sketch
3110 .constraints
3111 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
3112
3113 exec_state.push_op(Operation::GroupEnd);
3115
3116 if exec_state.mod_local.freedom_analysis {
3120 let status = {
3121 let scene_objects = &exec_state.mod_local.artifacts.scene_objects;
3122 scene_objects
3123 .get(sketch_id.0)
3124 .and_then(|obj| sketch_constraint_status_for_sketch(scene_objects, obj))
3125 };
3126 if let Some(status) = status
3127 && status.status == ConstraintKind::OverConstrained
3128 {
3129 let description = if status.conflict_count == 1 {
3130 "segment has"
3131 } else {
3132 "segments have"
3133 };
3134 let message = format!(
3135 "Sketch is over-constrained: {} {description} conflicting constraints.{}",
3136 status.conflict_count,
3137 signed_distance_conflict_hint(&solve_outcome),
3138 );
3139 exec_state.warn(
3140 CompilationIssue::err(range, message),
3141 annotations::WARN_OVER_CONSTRAINED_SKETCH,
3142 );
3143 }
3144 }
3145
3146 let properties = self.sketch_properties(sketch, variables);
3147 let metadata = Metadata {
3148 source_range: SourceRange::from(self),
3149 };
3150 let return_value = KclValue::Object {
3151 value: properties,
3152 constrainable: Default::default(),
3153 object_kind: KclObjectKind::Default,
3154 meta: vec![metadata],
3155 };
3156 Ok(return_value)
3157 }
3158
3159 fn check_for_unexpected_arguments(&self, args: &Args, exec_state: &mut ExecState) -> Result<(), KclError> {
3162 if !args.unlabeled.is_empty() {
3163 let message = "Sketch block doesn't support unlabeled arguments; argument shorthand should have already been desugared";
3164 debug_assert!(false, "{message}");
3165 return Err(KclError::new_internal(KclErrorDetails::new(
3166 message.to_owned(),
3167 vec![args.source_range],
3168 )));
3169 }
3170 for (label, arg) in &args.labeled {
3171 if label == SKETCH_BLOCK_PARAM_ON {
3172 continue;
3173 }
3174 exec_state.err(CompilationIssue::err(
3175 arg.source_range,
3176 unexpected_kw_arg_message(label, Some(SketchBlock::CALLEE_NAME)),
3177 ));
3178 }
3179 Ok(())
3180 }
3181
3182 pub(super) async fn load_sketch2_into_current_scope(
3183 &self,
3184 exec_state: &mut ExecState,
3185 ctx: &ExecutorContext,
3186 source_range: SourceRange,
3187 ) -> Result<(), KclError> {
3188 let path = vec!["std".to_owned(), "solver".to_owned()];
3189 let resolved_path = ModulePath::from_std_import_path(&path)?;
3190 let module_id = ctx
3191 .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
3192 .await?;
3193 let (env_ref, exports) = ctx.exec_module_for_items(module_id, exec_state, source_range).await?;
3194
3195 for name in exports {
3196 let value = exec_state
3197 .stack()
3198 .memory
3199 .get_from_owned(&name, env_ref, source_range, 0)?;
3200 exec_state.mut_stack().add(name, value, source_range)?;
3201 }
3202 Ok(())
3203 }
3204
3205 pub(crate) fn sketch_properties(
3209 &self,
3210 sketch: Option<Sketch>,
3211 variables: HashMap<String, KclValue>,
3212 ) -> HashMap<String, KclValue> {
3213 let Some(sketch) = sketch else {
3214 return variables;
3217 };
3218
3219 let mut properties = variables;
3220
3221 let sketch_value = KclValue::Sketch {
3222 value: Box::new(sketch),
3223 };
3224 let mut meta_map = HashMap::with_capacity(1);
3225 meta_map.insert(SKETCH_OBJECT_META_SKETCH.to_owned(), sketch_value);
3226 let meta_value = KclValue::Object {
3227 value: meta_map,
3228 constrainable: false,
3229 object_kind: KclObjectKind::Default,
3230 meta: vec![Metadata {
3231 source_range: SourceRange::from(self),
3232 }],
3233 };
3234
3235 properties.insert(SKETCH_OBJECT_META.to_owned(), meta_value);
3236
3237 properties
3238 }
3239}
3240
3241impl SketchBlock {
3242 pub(super) fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) -> Result<(), KclError> {
3243 exec_state.mut_stack().push_new_env_for_call(parent)
3244 }
3245}
3246
3247impl Node<SketchVar> {
3248 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
3249 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
3250 return Err(KclError::new_semantic(KclErrorDetails::new(
3251 "Cannot use a sketch variable outside of a sketch block".to_owned(),
3252 vec![SourceRange::from(self)],
3253 )));
3254 };
3255 let id = sketch_block_state.next_sketch_var_id();
3256 let sketch_var = if let Some(initial) = &self.initial {
3257 KclValue::from_sketch_var_literal(initial, id, self.node_path.clone(), exec_state)
3258 } else {
3259 let metadata = Metadata {
3260 source_range: SourceRange::from(self),
3261 };
3262
3263 KclValue::SketchVar {
3264 value: Box::new(super::SketchVar {
3265 id,
3266 initial_value: 0.0,
3267 ty: NumericType::default(),
3268 node_path: self.node_path.clone(),
3269 meta: vec![metadata],
3270 }),
3271 }
3272 };
3273
3274 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3275 return Err(KclError::new_semantic(KclErrorDetails::new(
3276 "Cannot use a sketch variable outside of a sketch block".to_owned(),
3277 vec![SourceRange::from(self)],
3278 )));
3279 };
3280 sketch_block_state.sketch_vars.push(sketch_var.clone());
3281
3282 Ok(sketch_var)
3283 }
3284}
3285
3286pub(super) async fn apply_ascription(
3287 value: &KclValue,
3288 ty: &Node<Type>,
3289 exec_state: &mut ExecState,
3290 ctx: &ExecutorContext,
3291 source_range: SourceRange,
3292) -> Result<KclValue, KclError> {
3293 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, ctx, value.into(), false, false).await?;
3294
3295 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
3296 exec_state.clear_units_warnings(&source_range);
3297 }
3298
3299 value.coerce(&ty, CoercionMode::explicit(), exec_state).map_err(|e| {
3300 if let Some(message) = e.message {
3303 return KclError::new_semantic(KclErrorDetails::new(message, vec![source_range]));
3304 }
3305
3306 let suggestion = if ty == RuntimeType::length() {
3307 ", you might try coercing to a fully specified numeric type such as `mm`"
3308 } else if ty == RuntimeType::angle() {
3309 ", you might try coercing to a fully specified numeric type such as `deg`"
3310 } else {
3311 ""
3312 };
3313 let ty_str = if let Some(ty) = value.principal_type() {
3314 format!("(with type `{ty}`) ")
3315 } else {
3316 String::new()
3317 };
3318 KclError::new_semantic(KclErrorDetails::new(
3319 format!(
3320 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
3321 value.human_friendly_type()
3322 ),
3323 vec![source_range],
3324 ))
3325 })
3326}
3327
3328impl BinaryPart {
3329 #[async_recursion]
3330 pub(super) async fn get_result(
3331 &self,
3332 exec_state: &mut ExecState,
3333 ctx: &ExecutorContext,
3334 ) -> Result<KclValueControlFlow, KclError> {
3335 match self {
3336 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
3337 BinaryPart::Name(name) => {
3338 let metadata = Metadata {
3339 source_range: SourceRange::from(&**name),
3340 };
3341 ctx.resolve_name_for_eval(name, &metadata, exec_state)
3342 .await
3343 .map(KclValue::continue_)
3344 }
3345 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
3346 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
3347 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
3348 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
3349 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
3350 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
3351 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
3352 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
3353 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
3354 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
3355 }
3356 }
3357}
3358
3359impl Node<Name> {
3360 pub(super) async fn get_result(
3361 &self,
3362 exec_state: &mut ExecState,
3363 ctx: &ExecutorContext,
3364 ) -> Result<KclValue, KclError> {
3365 let result = self.get_result_inner(exec_state, ctx).await;
3368 result.map_err(|e| var_in_own_ref_err(e, &exec_state.mod_local.being_declared))
3369 }
3370
3371 async fn get_result_inner(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
3372 if self.abs_path {
3373 return Err(KclError::new_semantic(KclErrorDetails::new(
3374 ABSOLUTE_PATHS_NOT_SUPPORTED.to_owned(),
3375 self.as_source_ranges(),
3376 )));
3377 }
3378
3379 if self.path.is_empty() {
3380 if let Ok(item_value) = exec_state.stack().get(&self.name.name, self.into()) {
3381 return Ok(item_value);
3382 }
3383
3384 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
3385 let not_defined = match exec_state.stack().get(&mod_name, self.into()) {
3386 Ok(module) => return Ok(module),
3387 Err(err) => err,
3388 };
3389
3390 return Err(type_used_as_value(exec_state, &self.name).unwrap_or(not_defined));
3393 }
3394
3395 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
3396 for (index, p) in self.path.iter().enumerate() {
3397 let non_enum_type = match enum_named_by_segment(exec_state, p, mem_spec.as_ref()) {
3400 Some(EnumPathHead::Enum(def)) => {
3401 if let Some(next) = self.path.get(index + 1) {
3402 return Err(KclError::new_semantic(KclErrorDetails::new(
3403 format!(
3404 "`{}` is an enum, so only a variant name can follow it. There is nothing to reach through `{}::{}`.",
3405 p.name, p.name, next.name
3406 ),
3407 p.as_source_ranges(),
3408 )));
3409 }
3410
3411 return enum_variant_value(def, &self.name, exec_state);
3412 }
3413 Some(EnumPathHead::NonEnumType) => true,
3414 None => false,
3415 };
3416
3417 let value = match mem_spec {
3418 Some((env, exports)) => {
3419 if !exports.contains(&p.name) {
3420 if non_enum_type {
3421 return Err(non_enum_type_in_path(p));
3422 }
3423 return Err(KclError::new_semantic(KclErrorDetails::new(
3424 format!("Item {} not found in module's exported items", p.name),
3425 p.as_source_ranges(),
3426 )));
3427 }
3428
3429 exec_state
3430 .stack()
3431 .memory
3432 .get_from_owned(&p.name, env, p.as_source_range(), 0)?
3433 }
3434 None => match exec_state
3435 .stack()
3436 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())
3437 {
3438 Ok(value) => value,
3439 Err(_) if non_enum_type => return Err(non_enum_type_in_path(p)),
3440 Err(err) => return Err(err),
3441 },
3442 };
3443
3444 let module_id = match value {
3445 KclValue::Module { value, .. } => value,
3446 value => {
3447 return Err(KclError::new_semantic(KclErrorDetails::new(
3448 format!(
3449 "Identifier in path must refer to a module, found {}",
3450 value.human_friendly_type()
3451 ),
3452 p.as_source_ranges(),
3453 )));
3454 }
3455 };
3456
3457 mem_spec = Some(
3458 ctx.exec_module_for_items(module_id, exec_state, p.as_source_range())
3459 .await?,
3460 );
3461 }
3462
3463 let (env, exports) = mem_spec.unwrap();
3464
3465 let item_exported = exports.contains(&self.name.name);
3466 let item_value = exec_state
3467 .stack()
3468 .memory
3469 .get_from_owned(&self.name.name, env, self.name.as_source_range(), 0);
3470
3471 if item_exported && item_value.is_ok() {
3473 return item_value;
3474 }
3475
3476 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
3477 let mod_exported = exports.contains(&mod_name);
3478 let mod_value = exec_state
3479 .stack()
3480 .memory
3481 .get_from_owned(&mod_name, env, self.name.as_source_range(), 0);
3482
3483 if mod_exported && mod_value.is_ok() {
3485 return mod_value;
3486 }
3487
3488 if item_value.is_err() && mod_value.is_err() {
3490 return item_value;
3491 }
3492
3493 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
3495 Err(KclError::new_semantic(KclErrorDetails::new(
3496 format!("Item {} not found in module's exported items", self.name.name),
3497 self.name.as_source_ranges(),
3498 )))
3499 }
3500}
3501
3502fn mock_array_may_have_engine_dependent_cardinality(ty: &RuntimeType) -> bool {
3503 match ty {
3504 RuntimeType::Primitive(
3505 PrimitiveType::Sketch
3506 | PrimitiveType::Solid
3507 | PrimitiveType::Face
3508 | PrimitiveType::Edge
3509 | PrimitiveType::BoundedEdge
3510 | PrimitiveType::ImportedGeometry,
3511 ) => true,
3512 RuntimeType::Union(types) => types.iter().any(mock_array_may_have_engine_dependent_cardinality),
3513 _ => false,
3514 }
3515}
3516
3517impl Node<MemberExpression> {
3518 async fn get_result(
3519 &self,
3520 exec_state: &mut ExecState,
3521 ctx: &ExecutorContext,
3522 ) -> Result<KclValueControlFlow, KclError> {
3523 if exec_state.entry_point_version_is_v3_or_higher() {
3524 let object = control_continue!(self.eval_object(exec_state, ctx).await?);
3527 let property = match self.eval_property(exec_state, ctx).await {
3528 Ok(property) => property,
3529 Err(EarlyReturn::Value(cf)) => return Ok(cf),
3532 Err(EarlyReturn::Error(err)) => return Err(err),
3533 };
3534 return self.apply_member(object, property, exec_state, ctx).await;
3535 }
3536
3537 let property = match self.eval_property(exec_state, ctx).await {
3541 Ok(property) => property,
3542 Err(EarlyReturn::Value(cf)) => return Ok(cf),
3545 Err(EarlyReturn::Error(err)) => return Err(err),
3546 };
3547 let object = control_continue!(self.eval_object(exec_state, ctx).await?);
3548 self.apply_member(object, property, exec_state, ctx).await
3549 }
3550
3551 async fn eval_object(
3556 &self,
3557 exec_state: &mut ExecState,
3558 ctx: &ExecutorContext,
3559 ) -> Result<KclValueControlFlow, KclError> {
3560 let object_meta = Metadata {
3561 source_range: SourceRange::from(&self.object),
3562 };
3563 ctx.execute_expr(&self.object, exec_state, &object_meta, &[], StatementKind::Expression)
3564 .await
3565 }
3566
3567 async fn eval_property(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<Property, EarlyReturn> {
3571 let property_meta = Metadata {
3572 source_range: SourceRange::from(&self.property),
3573 };
3574 Property::try_from(
3575 self.computed,
3576 self.property.clone(),
3577 exec_state,
3578 self.into(),
3579 ctx,
3580 &property_meta,
3581 &[],
3582 StatementKind::Expression,
3583 )
3584 .await
3585 }
3586
3587 pub(super) async fn apply_member(
3591 &self,
3592 object: KclValue,
3593 property: Property,
3594 exec_state: &mut ExecState,
3595 ctx: &ExecutorContext,
3596 ) -> Result<KclValueControlFlow, KclError> {
3597 let meta = Metadata {
3600 source_range: SourceRange::from(self),
3601 };
3602
3603 match (object, property, self.computed) {
3605 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
3606 "at" => match &segment.repr {
3607 SegmentRepr::Unsolved { segment } => {
3608 match &segment.kind {
3609 UnsolvedSegmentKind::Point { position, .. } => {
3610 Ok(KclValue::HomArray {
3612 value: vec![
3613 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
3614 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
3615 ],
3616 ty: RuntimeType::any(),
3617 }
3618 .continue_())
3619 }
3620 _ => Err(KclError::new_undefined_value(
3621 KclErrorDetails::new(
3622 format!("Property '{property}' not found in segment"),
3623 vec![self.clone().into()],
3624 ),
3625 None,
3626 )),
3627 }
3628 }
3629 SegmentRepr::Solved { segment } => {
3630 match &segment.kind {
3631 SegmentKind::Point { position, .. } => {
3632 Ok(KclValue::array_from_point2d(
3634 [position[0].n, position[1].n],
3635 position[0].ty,
3636 segment.meta.clone(),
3637 )
3638 .continue_())
3639 }
3640 _ => Err(KclError::new_undefined_value(
3641 KclErrorDetails::new(
3642 format!("Property '{property}' not found in segment"),
3643 vec![self.clone().into()],
3644 ),
3645 None,
3646 )),
3647 }
3648 }
3649 },
3650 "start" => match &segment.repr {
3651 SegmentRepr::Unsolved { segment } => match &segment.kind {
3652 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
3653 KclErrorDetails::new(
3654 format!("Property '{property}' not found in point segment"),
3655 vec![self.clone().into()],
3656 ),
3657 None,
3658 )),
3659 UnsolvedSegmentKind::Line {
3660 start,
3661 ctor,
3662 start_object_id,
3663 ..
3664 } => Ok(KclValue::Segment {
3665 value: Box::new(AbstractSegment {
3666 repr: SegmentRepr::Unsolved {
3667 segment: Box::new(UnsolvedSegment {
3668 id: segment.id,
3669 object_id: *start_object_id,
3670 kind: UnsolvedSegmentKind::Point {
3671 position: start.clone(),
3672 ctor: Box::new(PointCtor {
3673 position: ctor.start.clone(),
3674 }),
3675 },
3676 tag: segment.tag.clone(),
3677 node_path: segment.node_path.clone(),
3678 meta: segment.meta.clone(),
3679 }),
3680 },
3681 meta: segment.meta.clone(),
3682 }),
3683 }
3684 .continue_()),
3685 UnsolvedSegmentKind::Arc {
3686 start,
3687 ctor,
3688 start_object_id,
3689 ..
3690 } => Ok(KclValue::Segment {
3691 value: Box::new(AbstractSegment {
3692 repr: SegmentRepr::Unsolved {
3693 segment: Box::new(UnsolvedSegment {
3694 id: segment.id,
3695 object_id: *start_object_id,
3696 kind: UnsolvedSegmentKind::Point {
3697 position: start.clone(),
3698 ctor: Box::new(PointCtor {
3699 position: ctor.start.clone(),
3700 }),
3701 },
3702 tag: segment.tag.clone(),
3703 node_path: segment.node_path.clone(),
3704 meta: segment.meta.clone(),
3705 }),
3706 },
3707 meta: segment.meta.clone(),
3708 }),
3709 }
3710 .continue_()),
3711 UnsolvedSegmentKind::Circle {
3712 start,
3713 ctor,
3714 start_object_id,
3715 ..
3716 } => Ok(KclValue::Segment {
3717 value: Box::new(AbstractSegment {
3718 repr: SegmentRepr::Unsolved {
3719 segment: Box::new(UnsolvedSegment {
3720 id: segment.id,
3721 object_id: *start_object_id,
3722 kind: UnsolvedSegmentKind::Point {
3723 position: start.clone(),
3724 ctor: Box::new(PointCtor {
3725 position: ctor.start.clone(),
3726 }),
3727 },
3728 tag: segment.tag.clone(),
3729 node_path: segment.node_path.clone(),
3730 meta: segment.meta.clone(),
3731 }),
3732 },
3733 meta: segment.meta.clone(),
3734 }),
3735 }
3736 .continue_()),
3737 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
3738 KclErrorDetails::new(
3739 format!("Property '{property}' not found in segment"),
3740 vec![self.clone().into()],
3741 ),
3742 None,
3743 )),
3744 },
3745 SegmentRepr::Solved { segment } => match &segment.kind {
3746 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
3747 KclErrorDetails::new(
3748 format!("Property '{property}' not found in point segment"),
3749 vec![self.clone().into()],
3750 ),
3751 None,
3752 )),
3753 SegmentKind::Line {
3754 start,
3755 ctor,
3756 start_object_id,
3757 start_freedom,
3758 ..
3759 } => Ok(KclValue::Segment {
3760 value: Box::new(AbstractSegment {
3761 repr: SegmentRepr::Solved {
3762 segment: Box::new(Segment {
3763 id: segment.id,
3764 object_id: *start_object_id,
3765 kind: SegmentKind::Point {
3766 position: start.clone(),
3767 ctor: Box::new(PointCtor {
3768 position: ctor.start.clone(),
3769 }),
3770 freedom: *start_freedom,
3771 },
3772 surface: segment.surface.clone(),
3773 sketch_id: segment.sketch_id,
3774 sketch: segment.sketch.clone(),
3775 tag: segment.tag.clone(),
3776 node_path: segment.node_path.clone(),
3777 meta: segment.meta.clone(),
3778 }),
3779 },
3780 meta: segment.meta.clone(),
3781 }),
3782 }
3783 .continue_()),
3784 SegmentKind::Arc {
3785 start,
3786 ctor,
3787 start_object_id,
3788 start_freedom,
3789 ..
3790 } => Ok(KclValue::Segment {
3791 value: Box::new(AbstractSegment {
3792 repr: SegmentRepr::Solved {
3793 segment: Box::new(Segment {
3794 id: segment.id,
3795 object_id: *start_object_id,
3796 kind: SegmentKind::Point {
3797 position: start.clone(),
3798 ctor: Box::new(PointCtor {
3799 position: ctor.start.clone(),
3800 }),
3801 freedom: *start_freedom,
3802 },
3803 surface: segment.surface.clone(),
3804 sketch_id: segment.sketch_id,
3805 sketch: segment.sketch.clone(),
3806 tag: segment.tag.clone(),
3807 node_path: segment.node_path.clone(),
3808 meta: segment.meta.clone(),
3809 }),
3810 },
3811 meta: segment.meta.clone(),
3812 }),
3813 }
3814 .continue_()),
3815 SegmentKind::Circle {
3816 start,
3817 ctor,
3818 start_object_id,
3819 start_freedom,
3820 ..
3821 } => Ok(KclValue::Segment {
3822 value: Box::new(AbstractSegment {
3823 repr: SegmentRepr::Solved {
3824 segment: Box::new(Segment {
3825 id: segment.id,
3826 object_id: *start_object_id,
3827 kind: SegmentKind::Point {
3828 position: start.clone(),
3829 ctor: Box::new(PointCtor {
3830 position: ctor.start.clone(),
3831 }),
3832 freedom: *start_freedom,
3833 },
3834 surface: segment.surface.clone(),
3835 sketch_id: segment.sketch_id,
3836 sketch: segment.sketch.clone(),
3837 tag: segment.tag.clone(),
3838 node_path: segment.node_path.clone(),
3839 meta: segment.meta.clone(),
3840 }),
3841 },
3842 meta: segment.meta.clone(),
3843 }),
3844 }
3845 .continue_()),
3846 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
3847 KclErrorDetails::new(
3848 format!("Property '{property}' not found in segment"),
3849 vec![self.clone().into()],
3850 ),
3851 None,
3852 )),
3853 },
3854 },
3855 "end" => match &segment.repr {
3856 SegmentRepr::Unsolved { segment } => match &segment.kind {
3857 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
3858 KclErrorDetails::new(
3859 format!("Property '{property}' not found in point segment"),
3860 vec![self.clone().into()],
3861 ),
3862 None,
3863 )),
3864 UnsolvedSegmentKind::Line {
3865 end,
3866 ctor,
3867 end_object_id,
3868 ..
3869 } => Ok(KclValue::Segment {
3870 value: Box::new(AbstractSegment {
3871 repr: SegmentRepr::Unsolved {
3872 segment: Box::new(UnsolvedSegment {
3873 id: segment.id,
3874 object_id: *end_object_id,
3875 kind: UnsolvedSegmentKind::Point {
3876 position: end.clone(),
3877 ctor: Box::new(PointCtor {
3878 position: ctor.end.clone(),
3879 }),
3880 },
3881 tag: segment.tag.clone(),
3882 node_path: segment.node_path.clone(),
3883 meta: segment.meta.clone(),
3884 }),
3885 },
3886 meta: segment.meta.clone(),
3887 }),
3888 }
3889 .continue_()),
3890 UnsolvedSegmentKind::Arc {
3891 end,
3892 ctor,
3893 end_object_id,
3894 ..
3895 } => Ok(KclValue::Segment {
3896 value: Box::new(AbstractSegment {
3897 repr: SegmentRepr::Unsolved {
3898 segment: Box::new(UnsolvedSegment {
3899 id: segment.id,
3900 object_id: *end_object_id,
3901 kind: UnsolvedSegmentKind::Point {
3902 position: end.clone(),
3903 ctor: Box::new(PointCtor {
3904 position: ctor.end.clone(),
3905 }),
3906 },
3907 tag: segment.tag.clone(),
3908 node_path: segment.node_path.clone(),
3909 meta: segment.meta.clone(),
3910 }),
3911 },
3912 meta: segment.meta.clone(),
3913 }),
3914 }
3915 .continue_()),
3916 UnsolvedSegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
3917 KclErrorDetails::new(
3918 format!("Property '{property}' not found in segment"),
3919 vec![self.into()],
3920 ),
3921 None,
3922 )),
3923 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
3924 KclErrorDetails::new(
3925 format!("Property '{property}' not found in segment"),
3926 vec![self.clone().into()],
3927 ),
3928 None,
3929 )),
3930 },
3931 SegmentRepr::Solved { segment } => match &segment.kind {
3932 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
3933 KclErrorDetails::new(
3934 format!("Property '{property}' not found in point segment"),
3935 vec![self.clone().into()],
3936 ),
3937 None,
3938 )),
3939 SegmentKind::Line {
3940 end,
3941 ctor,
3942 end_object_id,
3943 end_freedom,
3944 ..
3945 } => Ok(KclValue::Segment {
3946 value: Box::new(AbstractSegment {
3947 repr: SegmentRepr::Solved {
3948 segment: Box::new(Segment {
3949 id: segment.id,
3950 object_id: *end_object_id,
3951 kind: SegmentKind::Point {
3952 position: end.clone(),
3953 ctor: Box::new(PointCtor {
3954 position: ctor.end.clone(),
3955 }),
3956 freedom: *end_freedom,
3957 },
3958 surface: segment.surface.clone(),
3959 sketch_id: segment.sketch_id,
3960 sketch: segment.sketch.clone(),
3961 tag: segment.tag.clone(),
3962 node_path: segment.node_path.clone(),
3963 meta: segment.meta.clone(),
3964 }),
3965 },
3966 meta: segment.meta.clone(),
3967 }),
3968 }
3969 .continue_()),
3970 SegmentKind::Arc {
3971 end,
3972 ctor,
3973 end_object_id,
3974 end_freedom,
3975 ..
3976 } => Ok(KclValue::Segment {
3977 value: Box::new(AbstractSegment {
3978 repr: SegmentRepr::Solved {
3979 segment: Box::new(Segment {
3980 id: segment.id,
3981 object_id: *end_object_id,
3982 kind: SegmentKind::Point {
3983 position: end.clone(),
3984 ctor: Box::new(PointCtor {
3985 position: ctor.end.clone(),
3986 }),
3987 freedom: *end_freedom,
3988 },
3989 surface: segment.surface.clone(),
3990 sketch_id: segment.sketch_id,
3991 sketch: segment.sketch.clone(),
3992 tag: segment.tag.clone(),
3993 node_path: segment.node_path.clone(),
3994 meta: segment.meta.clone(),
3995 }),
3996 },
3997 meta: segment.meta.clone(),
3998 }),
3999 }
4000 .continue_()),
4001 SegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
4002 KclErrorDetails::new(
4003 format!("Property '{property}' not found in segment"),
4004 vec![self.into()],
4005 ),
4006 None,
4007 )),
4008 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
4009 KclErrorDetails::new(
4010 format!("Property '{property}' not found in segment"),
4011 vec![self.clone().into()],
4012 ),
4013 None,
4014 )),
4015 },
4016 },
4017 "center" => match &segment.repr {
4018 SegmentRepr::Unsolved { segment } => match &segment.kind {
4019 UnsolvedSegmentKind::Arc {
4020 center,
4021 ctor,
4022 center_object_id,
4023 ..
4024 } => Ok(KclValue::Segment {
4025 value: Box::new(AbstractSegment {
4026 repr: SegmentRepr::Unsolved {
4027 segment: Box::new(UnsolvedSegment {
4028 id: segment.id,
4029 object_id: *center_object_id,
4030 kind: UnsolvedSegmentKind::Point {
4031 position: center.clone(),
4032 ctor: Box::new(PointCtor {
4033 position: ctor.center.clone(),
4034 }),
4035 },
4036 tag: segment.tag.clone(),
4037 node_path: segment.node_path.clone(),
4038 meta: segment.meta.clone(),
4039 }),
4040 },
4041 meta: segment.meta.clone(),
4042 }),
4043 }
4044 .continue_()),
4045 UnsolvedSegmentKind::Circle {
4046 center,
4047 ctor,
4048 center_object_id,
4049 ..
4050 } => Ok(KclValue::Segment {
4051 value: Box::new(AbstractSegment {
4052 repr: SegmentRepr::Unsolved {
4053 segment: Box::new(UnsolvedSegment {
4054 id: segment.id,
4055 object_id: *center_object_id,
4056 kind: UnsolvedSegmentKind::Point {
4057 position: center.clone(),
4058 ctor: Box::new(PointCtor {
4059 position: ctor.center.clone(),
4060 }),
4061 },
4062 tag: segment.tag.clone(),
4063 node_path: segment.node_path.clone(),
4064 meta: segment.meta.clone(),
4065 }),
4066 },
4067 meta: segment.meta.clone(),
4068 }),
4069 }
4070 .continue_()),
4071 _ => Err(KclError::new_undefined_value(
4072 KclErrorDetails::new(
4073 format!("Property '{property}' not found in segment"),
4074 vec![self.clone().into()],
4075 ),
4076 None,
4077 )),
4078 },
4079 SegmentRepr::Solved { segment } => match &segment.kind {
4080 SegmentKind::Arc {
4081 center,
4082 ctor,
4083 center_object_id,
4084 center_freedom,
4085 ..
4086 } => Ok(KclValue::Segment {
4087 value: Box::new(AbstractSegment {
4088 repr: SegmentRepr::Solved {
4089 segment: Box::new(Segment {
4090 id: segment.id,
4091 object_id: *center_object_id,
4092 kind: SegmentKind::Point {
4093 position: center.clone(),
4094 ctor: Box::new(PointCtor {
4095 position: ctor.center.clone(),
4096 }),
4097 freedom: *center_freedom,
4098 },
4099 surface: segment.surface.clone(),
4100 sketch_id: segment.sketch_id,
4101 sketch: segment.sketch.clone(),
4102 tag: segment.tag.clone(),
4103 node_path: segment.node_path.clone(),
4104 meta: segment.meta.clone(),
4105 }),
4106 },
4107 meta: segment.meta.clone(),
4108 }),
4109 }
4110 .continue_()),
4111 SegmentKind::Circle {
4112 center,
4113 ctor,
4114 center_object_id,
4115 center_freedom,
4116 ..
4117 } => Ok(KclValue::Segment {
4118 value: Box::new(AbstractSegment {
4119 repr: SegmentRepr::Solved {
4120 segment: Box::new(Segment {
4121 id: segment.id,
4122 object_id: *center_object_id,
4123 kind: SegmentKind::Point {
4124 position: center.clone(),
4125 ctor: Box::new(PointCtor {
4126 position: ctor.center.clone(),
4127 }),
4128 freedom: *center_freedom,
4129 },
4130 surface: segment.surface.clone(),
4131 sketch_id: segment.sketch_id,
4132 sketch: segment.sketch.clone(),
4133 tag: segment.tag.clone(),
4134 node_path: segment.node_path.clone(),
4135 meta: segment.meta.clone(),
4136 }),
4137 },
4138 meta: segment.meta.clone(),
4139 }),
4140 }
4141 .continue_()),
4142 _ => Err(KclError::new_undefined_value(
4143 KclErrorDetails::new(
4144 format!("Property '{property}' not found in segment"),
4145 vec![self.clone().into()],
4146 ),
4147 None,
4148 )),
4149 },
4150 },
4151 "controls" => match &segment.repr {
4152 SegmentRepr::Unsolved { segment } => match &segment.kind {
4153 UnsolvedSegmentKind::ControlPointSpline {
4154 controls,
4155 ctor,
4156 control_object_ids,
4157 ..
4158 } => Ok(KclValue::HomArray {
4159 value: controls
4160 .iter()
4161 .zip(control_object_ids.iter())
4162 .zip(ctor.points.iter())
4163 .map(|((position, object_id), ctor_point)| KclValue::Segment {
4164 value: Box::new(AbstractSegment {
4165 repr: SegmentRepr::Unsolved {
4166 segment: Box::new(UnsolvedSegment {
4167 id: segment.id,
4168 object_id: *object_id,
4169 kind: UnsolvedSegmentKind::Point {
4170 position: position.clone(),
4171 ctor: Box::new(PointCtor {
4172 position: ctor_point.clone(),
4173 }),
4174 },
4175 tag: segment.tag.clone(),
4176 node_path: segment.node_path.clone(),
4177 meta: segment.meta.clone(),
4178 }),
4179 },
4180 meta: segment.meta.clone(),
4181 }),
4182 })
4183 .collect(),
4184 ty: RuntimeType::segment(),
4185 }
4186 .continue_()),
4187 _ => Err(KclError::new_undefined_value(
4188 KclErrorDetails::new(
4189 format!("Property '{property}' not found in segment"),
4190 vec![self.clone().into()],
4191 ),
4192 None,
4193 )),
4194 },
4195 SegmentRepr::Solved { segment } => match &segment.kind {
4196 SegmentKind::ControlPointSpline {
4197 controls,
4198 ctor,
4199 control_object_ids,
4200 control_freedoms,
4201 ..
4202 } => Ok(KclValue::HomArray {
4203 value: controls
4204 .iter()
4205 .zip(control_object_ids.iter())
4206 .zip(control_freedoms.iter())
4207 .zip(ctor.points.iter())
4208 .map(|(((position, object_id), freedom), ctor_point)| KclValue::Segment {
4209 value: Box::new(AbstractSegment {
4210 repr: SegmentRepr::Solved {
4211 segment: Box::new(Segment {
4212 id: segment.id,
4213 object_id: *object_id,
4214 kind: SegmentKind::Point {
4215 position: position.clone(),
4216 ctor: Box::new(PointCtor {
4217 position: ctor_point.clone(),
4218 }),
4219 freedom: *freedom,
4220 },
4221 surface: segment.surface.clone(),
4222 sketch_id: segment.sketch_id,
4223 sketch: segment.sketch.clone(),
4224 tag: segment.tag.clone(),
4225 node_path: segment.node_path.clone(),
4226 meta: segment.meta.clone(),
4227 }),
4228 },
4229 meta: segment.meta.clone(),
4230 }),
4231 })
4232 .collect(),
4233 ty: RuntimeType::segment(),
4234 }
4235 .continue_()),
4236 _ => Err(KclError::new_undefined_value(
4237 KclErrorDetails::new(
4238 format!("Property '{property}' not found in segment"),
4239 vec![self.clone().into()],
4240 ),
4241 None,
4242 )),
4243 },
4244 },
4245 "edges" => match &segment.repr {
4246 SegmentRepr::Unsolved { segment } => match &segment.kind {
4247 UnsolvedSegmentKind::ControlPointSpline {
4248 controls,
4249 ctor,
4250 control_object_ids,
4251 control_polygon_edge_object_ids,
4252 construction,
4253 ..
4254 } => Ok(KclValue::HomArray {
4255 value: control_polygon_edge_object_ids
4256 .iter()
4257 .enumerate()
4258 .map(|(index, object_id)| KclValue::Segment {
4259 value: Box::new(AbstractSegment {
4260 repr: SegmentRepr::Unsolved {
4261 segment: Box::new(UnsolvedSegment {
4262 id: segment.id,
4263 object_id: *object_id,
4264 kind: UnsolvedSegmentKind::Line {
4265 start: controls[index].clone(),
4266 end: controls[index + 1].clone(),
4267 ctor: Box::new(LineCtor {
4268 start: ctor.points[index].clone(),
4269 end: ctor.points[index + 1].clone(),
4270 construction: Some(*construction),
4271 }),
4272 start_object_id: control_object_ids[index],
4273 end_object_id: control_object_ids[index + 1],
4274 construction: *construction,
4275 },
4276 tag: segment.tag.clone(),
4277 node_path: segment.node_path.clone(),
4278 meta: segment.meta.clone(),
4279 }),
4280 },
4281 meta: segment.meta.clone(),
4282 }),
4283 })
4284 .collect(),
4285 ty: RuntimeType::segment(),
4286 }
4287 .continue_()),
4288 _ => Err(KclError::new_undefined_value(
4289 KclErrorDetails::new(
4290 format!("Property '{property}' not found in segment"),
4291 vec![self.clone().into()],
4292 ),
4293 None,
4294 )),
4295 },
4296 SegmentRepr::Solved { segment } => match &segment.kind {
4297 SegmentKind::ControlPointSpline {
4298 controls,
4299 ctor,
4300 control_object_ids,
4301 control_polygon_edge_object_ids,
4302 control_freedoms,
4303 construction,
4304 ..
4305 } => Ok(KclValue::HomArray {
4306 value: control_polygon_edge_object_ids
4307 .iter()
4308 .enumerate()
4309 .map(|(index, object_id)| KclValue::Segment {
4310 value: Box::new(AbstractSegment {
4311 repr: SegmentRepr::Solved {
4312 segment: Box::new(Segment {
4313 id: segment.id,
4314 object_id: *object_id,
4315 kind: SegmentKind::Line {
4316 start: controls[index].clone(),
4317 end: controls[index + 1].clone(),
4318 ctor: Box::new(LineCtor {
4319 start: ctor.points[index].clone(),
4320 end: ctor.points[index + 1].clone(),
4321 construction: Some(*construction),
4322 }),
4323 start_object_id: control_object_ids[index],
4324 end_object_id: control_object_ids[index + 1],
4325 start_freedom: control_freedoms[index],
4326 end_freedom: control_freedoms[index + 1],
4327 construction: *construction,
4328 },
4329 surface: segment.surface.clone(),
4330 sketch_id: segment.sketch_id,
4331 sketch: segment.sketch.clone(),
4332 tag: segment.tag.clone(),
4333 node_path: segment.node_path.clone(),
4334 meta: segment.meta.clone(),
4335 }),
4336 },
4337 meta: segment.meta.clone(),
4338 }),
4339 })
4340 .collect(),
4341 ty: RuntimeType::segment(),
4342 }
4343 .continue_()),
4344 _ => Err(KclError::new_undefined_value(
4345 KclErrorDetails::new(
4346 format!("Property '{property}' not found in segment"),
4347 vec![self.clone().into()],
4348 ),
4349 None,
4350 )),
4351 },
4352 },
4353 other => Err(KclError::new_undefined_value(
4354 KclErrorDetails::new(
4355 format!("Property '{other}' not found in segment"),
4356 vec![self.clone().into()],
4357 ),
4358 None,
4359 )),
4360 },
4361 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
4362 "zAxis" => {
4363 let (p, u) = plane.info.z_axis.as_3_dims();
4364 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4365 }
4366 "yAxis" => {
4367 let (p, u) = plane.info.y_axis.as_3_dims();
4368 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4369 }
4370 "xAxis" => {
4371 let (p, u) = plane.info.x_axis.as_3_dims();
4372 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4373 }
4374 "origin" => {
4375 let (p, u) = plane.info.origin.as_3_dims();
4376 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4377 }
4378 other => Err(KclError::new_undefined_value(
4379 KclErrorDetails::new(
4380 format!("Property '{other}' not found in plane"),
4381 vec![self.clone().into()],
4382 ),
4383 None,
4384 )),
4385 },
4386 (
4387 KclValue::Object {
4388 value: map,
4389 object_kind,
4390 ..
4391 },
4392 Property::String(property),
4393 false,
4394 ) => {
4395 if let Some(value) = map.get(&property) {
4396 if object_kind
4397 .deprecated_solid_tag_names()
4398 .iter()
4399 .any(|tag_name| tag_name == &property)
4400 {
4401 exec_state.warn(
4402 CompilationIssue::err(
4403 SourceRange::from(self),
4404 format!(
4405 "Accessing solid-created face `{property}` through sketch tags is deprecated. Use the body's faces instead, e.g. `body.faces.{property}`."
4406 ),
4407 ),
4408 annotations::WARN_DEPRECATED,
4409 );
4410 }
4411 Ok(value.to_owned().continue_())
4412 } else {
4413 Err(KclError::new_undefined_value(
4414 KclErrorDetails::new(
4415 format!("Property '{property}' not found in object"),
4416 vec![self.clone().into()],
4417 ),
4418 None,
4419 ))
4420 }
4421 }
4422 (KclValue::Object { .. }, Property::String(property), true) => {
4423 Err(KclError::new_semantic(KclErrorDetails::new(
4424 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
4425 vec![self.clone().into()],
4426 )))
4427 }
4428 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
4429 if i == 0
4430 && let Some(value) = map.get("x")
4431 {
4432 return Ok(value.to_owned().continue_());
4433 }
4434 if i == 1
4435 && let Some(value) = map.get("y")
4436 {
4437 return Ok(value.to_owned().continue_());
4438 }
4439 if i == 2
4440 && let Some(value) = map.get("z")
4441 {
4442 return Ok(value.to_owned().continue_());
4443 }
4444 let t = p.type_name();
4445 let article = article_for(t);
4446 Err(KclError::new_semantic(KclErrorDetails::new(
4447 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
4448 vec![self.clone().into()],
4449 )))
4450 }
4451 (KclValue::HomArray { value: arr, ty }, Property::UInt(index), _) => {
4452 let value_of_arr = arr.get(index);
4453 let oob_error = KclError::new_undefined_value(
4455 KclErrorDetails::new(
4456 format!("The array doesn't have any item at index {index}"),
4457 vec![self.clone().into()],
4458 ),
4459 None,
4460 );
4461 if let Some(value) = value_of_arr {
4462 Ok(value.to_owned().continue_())
4464 } else if ctx.no_engine_commands().await
4465 && !exec_state.is_sketch_mode_execution()
4466 && mock_array_may_have_engine_dependent_cardinality(&ty)
4467 {
4468 let value = arr.first();
4478 value.map(|value| value.to_owned().continue_()).ok_or(oob_error)
4479 } else {
4480 Err(oob_error)
4481 }
4482 }
4483 (obj, Property::UInt(0), _) => Ok(obj.continue_()),
4486 (KclValue::HomArray { .. }, p, _) => {
4487 let t = p.type_name();
4488 let article = article_for(t);
4489 Err(KclError::new_semantic(KclErrorDetails::new(
4490 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
4491 vec![self.clone().into()],
4492 )))
4493 }
4494 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
4495 let Some(sketch) = value.sketch() else {
4496 return Err(KclError::new_semantic(KclErrorDetails::new(
4497 "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
4498 vec![self.clone().into()],
4499 )));
4500 };
4501 Ok(KclValue::Sketch {
4502 value: Box::new(sketch.clone()),
4503 }
4504 .continue_())
4505 }
4506 (KclValue::Solid { value: solid }, Property::String(prop), false) if prop == "faces" => {
4507 Ok(KclValue::Object {
4508 meta: vec![Metadata {
4509 source_range: SourceRange::from(self.clone()),
4510 }],
4511 value: solid
4512 .faces
4513 .iter()
4514 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
4515 .collect(),
4516 constrainable: false,
4517 object_kind: KclObjectKind::Default,
4518 }
4519 .continue_())
4520 }
4521 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
4522 Err(KclError::new_semantic(KclErrorDetails::new(
4524 format!(
4525 "Property `{prop}` not found on {}. You can get a solid's faces through `exampleSolid.faces`, or its sketch tags through `exampleSolid.sketch.tags`.",
4526 geometry.human_friendly_type()
4527 ),
4528 vec![self.clone().into()],
4529 )))
4530 }
4531 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
4532 meta: vec![Metadata {
4533 source_range: SourceRange::from(self.clone()),
4534 }],
4535 value: sk
4536 .tags
4537 .iter()
4538 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
4539 .collect(),
4540 constrainable: false,
4541 object_kind: KclObjectKind::SketchTags {
4542 deprecated_solid_tag_names: sk
4543 .tags
4544 .iter()
4545 .filter(|(_, tag)| tag.is_body_created_tag())
4546 .map(|(name, _)| name.to_owned())
4547 .collect(),
4548 },
4549 }
4550 .continue_()),
4551 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
4552 Err(KclError::new_semantic(KclErrorDetails::new(
4553 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
4554 vec![self.clone().into()],
4555 )))
4556 }
4557 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
4558 format!(
4559 "Only objects can have members accessed with dot notation, but you're trying to access {}",
4560 being_indexed.human_friendly_type()
4561 ),
4562 vec![self.clone().into()],
4563 ))),
4564 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
4565 format!(
4566 "Only arrays can be indexed, but you're trying to index {}",
4567 being_indexed.human_friendly_type()
4568 ),
4569 vec![self.clone().into()],
4570 ))),
4571 }
4572 }
4573}
4574
4575impl Node<BinaryExpression> {
4576 pub(super) async fn get_result(
4577 &self,
4578 exec_state: &mut ExecState,
4579 ctx: &ExecutorContext,
4580 ) -> Result<KclValueControlFlow, KclError> {
4581 enum State {
4582 EvaluateLeft(Node<BinaryExpression>),
4583 FromLeft {
4584 node: Node<BinaryExpression>,
4585 },
4586 EvaluateRight {
4587 node: Node<BinaryExpression>,
4588 left: KclValue,
4589 },
4590 FromRight {
4591 node: Node<BinaryExpression>,
4592 left: KclValue,
4593 },
4594 }
4595
4596 let mut stack = vec![State::EvaluateLeft(self.clone())];
4597 let mut last_result: Option<KclValue> = None;
4598
4599 while let Some(state) = stack.pop() {
4600 match state {
4601 State::EvaluateLeft(node) => {
4602 let left_part = node.left.clone();
4603 match left_part {
4604 BinaryPart::BinaryExpression(child) => {
4605 stack.push(State::FromLeft { node });
4606 stack.push(State::EvaluateLeft(child.into_node()));
4607 }
4608 part => {
4609 let left_value = part.get_result(exec_state, ctx).await?;
4610 let left_value = control_continue!(left_value);
4611 stack.push(State::EvaluateRight { node, left: left_value });
4612 }
4613 }
4614 }
4615 State::FromLeft { node } => {
4616 let Some(left_value) = last_result.take() else {
4617 return Err(Self::missing_result_error(&node));
4618 };
4619 stack.push(State::EvaluateRight { node, left: left_value });
4620 }
4621 State::EvaluateRight { node, left } => {
4622 let right_part = node.right.clone();
4623 match right_part {
4624 BinaryPart::BinaryExpression(child) => {
4625 stack.push(State::FromRight { node, left });
4626 stack.push(State::EvaluateLeft(child.into_node()));
4627 }
4628 part => {
4629 let right_value = part.get_result(exec_state, ctx).await?;
4630 let right_value = control_continue!(right_value);
4631 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
4632 last_result = Some(result);
4633 }
4634 }
4635 }
4636 State::FromRight { node, left } => {
4637 let Some(right_value) = last_result.take() else {
4638 return Err(Self::missing_result_error(&node));
4639 };
4640 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
4641 last_result = Some(result);
4642 }
4643 }
4644 }
4645
4646 last_result
4647 .map(KclValue::continue_)
4648 .ok_or_else(|| Self::missing_result_error(self))
4649 }
4650
4651 pub(super) async fn apply_operator(
4652 &self,
4653 exec_state: &mut ExecState,
4654 ctx: &ExecutorContext,
4655 left_value: KclValue,
4656 right_value: KclValue,
4657 ) -> Result<KclValue, KclError> {
4658 let mut meta = left_value.metadata();
4659 meta.extend(right_value.metadata());
4660
4661 if self.operator == BinaryOperator::Add
4663 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
4664 (&left_value, &right_value)
4665 {
4666 return Ok(KclValue::String {
4667 value: format!("{left}{right}"),
4668 meta,
4669 });
4670 }
4671
4672 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
4674 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
4675 let args = Args::new_no_args(
4676 self.into(),
4677 self.node_path.clone(),
4678 ctx.clone(),
4679 Some("union".to_owned()),
4680 );
4681 let result = crate::std::csg::inner_union(
4682 vec![*left.clone(), *right.clone()],
4683 Default::default(),
4684 crate::std::csg::CsgAlgorithm::Latest,
4685 exec_state,
4686 args,
4687 )
4688 .await?;
4689 return Ok(result.into());
4690 }
4691 } else if self.operator == BinaryOperator::Sub {
4692 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
4694 let args = Args::new_no_args(
4695 self.into(),
4696 self.node_path.clone(),
4697 ctx.clone(),
4698 Some("subtract".to_owned()),
4699 );
4700 let result = crate::std::csg::inner_subtract(
4701 vec![*left.clone()],
4702 vec![*right.clone()],
4703 Default::default(),
4704 crate::std::csg::CsgAlgorithm::Latest,
4705 exec_state,
4706 args,
4707 )
4708 .await?;
4709 return Ok(result.into());
4710 }
4711 } else if self.operator == BinaryOperator::And
4712 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
4713 {
4714 let args = Args::new_no_args(
4716 self.into(),
4717 self.node_path.clone(),
4718 ctx.clone(),
4719 Some("intersect".to_owned()),
4720 );
4721 let result = crate::std::csg::inner_intersect(
4722 vec![*left.clone(), *right.clone()],
4723 Default::default(),
4724 crate::std::csg::CsgAlgorithm::Latest,
4725 exec_state,
4726 args,
4727 )
4728 .await?;
4729 return Ok(result.into());
4730 }
4731
4732 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
4734 let KclValue::Bool { value: left_value, .. } = left_value else {
4735 return Err(KclError::new_semantic(KclErrorDetails::new(
4736 format!(
4737 "Cannot apply logical operator to non-boolean value: {}",
4738 left_value.human_friendly_type()
4739 ),
4740 vec![self.left.clone().into()],
4741 )));
4742 };
4743 let KclValue::Bool { value: right_value, .. } = right_value else {
4744 return Err(KclError::new_semantic(KclErrorDetails::new(
4745 format!(
4746 "Cannot apply logical operator to non-boolean value: {}",
4747 right_value.human_friendly_type()
4748 ),
4749 vec![self.right.clone().into()],
4750 )));
4751 };
4752 let raw_value = match self.operator {
4753 BinaryOperator::Or => left_value || right_value,
4754 BinaryOperator::And => left_value && right_value,
4755 _ => unreachable!(),
4756 };
4757 return Ok(KclValue::Bool { value: raw_value, meta });
4758 }
4759
4760 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
4762 match (&left_value, &right_value) {
4763 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
4765 if left_value.id == right_value.id =>
4766 {
4767 return Ok(KclValue::none());
4768 }
4769 (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
4771 let constraint = Constraint::ScalarEqual(
4772 var0.id.to_constraint_id(self.as_source_range())?,
4773 var1.id.to_constraint_id(self.as_source_range())?,
4774 );
4775 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4776 let message = "Being inside a sketch block should have already been checked above".to_owned();
4777 debug_assert!(false, "{}", &message);
4778 return Err(internal_err(message, self));
4779 };
4780 sketch_block_state.solver_constraints.push(constraint);
4781 return Ok(KclValue::none());
4782 }
4783 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
4785 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
4786 let number_value = normalize_to_solver_distance_unit(
4787 input_number,
4788 input_number.into(),
4789 exec_state,
4790 "fixed constraint value",
4791 )?;
4792 let Some(n) = number_value.as_ty_f64() else {
4793 let message = format!(
4794 "Expected number after coercion, but found {}",
4795 number_value.human_friendly_type()
4796 );
4797 debug_assert!(false, "{}", &message);
4798 return Err(internal_err(message, self));
4799 };
4800 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
4801 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4802 let message = "Being inside a sketch block should have already been checked above".to_owned();
4803 debug_assert!(false, "{}", &message);
4804 return Err(internal_err(message, self));
4805 };
4806 sketch_block_state.solver_constraints.push(constraint);
4807 exec_state.warn_experimental("scalar fixed constraint", self.as_source_range());
4808 return Ok(KclValue::none());
4809 }
4810 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
4812 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
4813 let number_value = match constraint.kind {
4814 SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
4816 input_number,
4817 input_number.into(),
4818 exec_state,
4819 "fixed constraint value",
4820 )?,
4821 SketchConstraintKind::Distance { .. }
4823 | SketchConstraintKind::PointLineDistance { .. }
4824 | SketchConstraintKind::LineLineDistance { .. }
4825 | SketchConstraintKind::PointCircularDistance { .. }
4826 | SketchConstraintKind::LineCircularDistance { .. }
4827 | SketchConstraintKind::CircularCircularDistance { .. }
4828 | SketchConstraintKind::Radius { .. }
4829 | SketchConstraintKind::Diameter { .. }
4830 | SketchConstraintKind::HorizontalDistance { .. }
4831 | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
4832 input_number,
4833 input_number.into(),
4834 exec_state,
4835 "fixed constraint value",
4836 )?,
4837 };
4838 let Some(n) = number_value.as_ty_f64() else {
4839 let message = format!(
4840 "Expected number after coercion, but found {}",
4841 number_value.human_friendly_type()
4842 );
4843 debug_assert!(false, "{}", &message);
4844 return Err(internal_err(message, self));
4845 };
4846 let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
4848 &self.right
4849 } else {
4850 &self.left
4851 };
4852 let source = {
4853 use crate::unparser::ExprContext;
4854 let mut buf = String::new();
4855 number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
4856 crate::frontend::sketch::ConstraintSource {
4857 expr: buf,
4858 is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
4859 }
4860 };
4861
4862 match &constraint.kind {
4863 SketchConstraintKind::Angle {
4864 line0,
4865 line1,
4866 mode,
4867 label_position,
4868 } => {
4869 let range = self.as_source_range();
4870 let desired_angle = match n.ty {
4871 NumericType::Known(crate::exec::UnitType::Angle(crate::exec::UnitAngle::Degrees))
4872 | NumericType::Default {
4873 len: _,
4874 angle: UnitAngle::Degrees,
4875 } => ezpz::datatypes::Angle::from_degrees(n.n),
4876 NumericType::Known(crate::exec::UnitType::Angle(crate::exec::UnitAngle::Radians))
4877 | NumericType::Default {
4878 len: _,
4879 angle: UnitAngle::Radians,
4880 } => ezpz::datatypes::Angle::from_radians(n.n),
4881 NumericType::Known(crate::exec::UnitType::Count)
4882 | NumericType::Known(crate::exec::UnitType::GenericLength)
4883 | NumericType::Known(crate::exec::UnitType::GenericAngle)
4884 | NumericType::Known(crate::exec::UnitType::Length(_))
4885 | NumericType::Unknown
4886 | NumericType::Any => {
4887 let message = format!("Expected angle but found {:?}", n);
4888 debug_assert!(false, "{}", &message);
4889 return Err(internal_err(message, self));
4890 }
4891 };
4892 let angle_lowering = match *mode {
4893 AngleConstraintMode::LinesAtAngle => {
4894 AngleConstraintLowering::LinesAtAngle(Box::new(PendingLegacyAngleRefactorMeta {
4895 source_range: constraint
4896 .meta
4897 .first()
4898 .map(|meta| meta.source_range)
4899 .unwrap_or(range),
4900 lines: [line0.clone(), line1.clone()],
4901 desired_angle_radians: desired_angle.to_radians(),
4902 }))
4903 }
4904 AngleConstraintMode::PointsAtAngle { sector, inverse } => {
4905 let sketch_vars = exec_state
4906 .mod_local
4907 .sketch_block
4908 .as_ref()
4909 .ok_or_else(|| {
4910 internal_err(
4911 "Being inside a sketch block should have already been checked above",
4912 self,
4913 )
4914 })?
4915 .sketch_vars
4916 .clone();
4917 let initial_line0 = constrainable_line_initial_positions(
4918 &sketch_vars,
4919 line0,
4920 exec_state,
4921 range,
4922 "angle line0",
4923 )?;
4924 let initial_line1 = constrainable_line_initial_positions(
4925 &sketch_vars,
4926 line1,
4927 exec_state,
4928 range,
4929 "angle line1",
4930 )?;
4931 let Some(initial_vertex) = intersect_lines_2d(initial_line0, initial_line1) else {
4932 return Err(KclError::new_semantic(KclErrorDetails::new(
4933 "angleDimension(lines = ..., sector = ...) requires non-parallel lines"
4934 .to_owned(),
4935 vec![range],
4936 )));
4937 };
4938 let (line0_representative, line0_direction) =
4939 representative_angle_endpoint(line0, initial_line0, initial_vertex, range)?;
4940 let (line1_representative, line1_direction) =
4941 representative_angle_endpoint(line1, initial_line1, initial_vertex, range)?;
4942 let sector_rays = angle_sector_rays(sector, inverse);
4943 let angle_kind =
4944 ezpz::datatypes::AngleKind::Other(remap_angle_for_representative_rays(
4945 sector_rays,
4946 [line0_direction, line1_direction],
4947 desired_angle,
4948 ));
4949 AngleConstraintLowering::PointsAtAngle(PointsAtAngleLineData {
4950 initial_vertex,
4951 representative_points: [line0_representative, line1_representative],
4952 angle_kind,
4953 })
4954 }
4955 };
4956 let sketch_var_ty = solver_numeric_type(exec_state);
4957 let constraint_id = exec_state.next_object_id();
4958 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4959 let message =
4960 "Being inside a sketch block should have already been checked above".to_owned();
4961 debug_assert!(false, "{}", &message);
4962 return Err(internal_err(message, self));
4963 };
4964 match angle_lowering {
4965 AngleConstraintLowering::LinesAtAngle(refactor_meta) => {
4966 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4967 datum_line_from_constrainable(line0, range)?,
4968 datum_line_from_constrainable(line1, range)?,
4969 ezpz::datatypes::AngleKind::Other(desired_angle),
4970 ));
4971 sketch_block_state
4972 .pending_legacy_angle_refactor_metadata
4973 .push(*refactor_meta);
4974 }
4975 AngleConstraintLowering::PointsAtAngle(points_at_angle_data) => {
4976 push_points_at_angle_for_lines(
4977 sketch_block_state,
4978 sketch_var_ty,
4979 [line0, line1],
4980 points_at_angle_data,
4981 range,
4982 )?
4983 }
4984 }
4985 use crate::execution::Artifact;
4986 use crate::execution::CodeRef;
4987 use crate::execution::SketchBlockConstraint;
4988 use crate::front::Angle;
4989 use crate::front::SourceRef;
4990
4991 let Some(sketch_id) = sketch_block_state.sketch_id else {
4992 let message = "Sketch id missing for constraint artifact".to_owned();
4993 debug_assert!(false, "{}", &message);
4994 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4995 };
4996 let (sector, inverse) = match *mode {
4997 AngleConstraintMode::LinesAtAngle => (None, None),
4998 AngleConstraintMode::PointsAtAngle { sector, inverse } => {
4999 (Some(front_angle_sector(sector)), Some(inverse))
5000 }
5001 };
5002 let sketch_constraint = crate::front::Constraint::Angle(Angle {
5003 lines: vec![line0.object_id, line1.object_id],
5004 angle: n.try_into().map_err(|_| {
5005 internal_err("Failed to convert angle units numeric suffix:", range)
5006 })?,
5007 sector,
5008 inverse,
5009 label_position: label_position.clone(),
5010 source,
5011 });
5012 sketch_block_state.sketch_constraints.push(constraint_id);
5013 let artifact_id = exec_state.next_artifact_id();
5014 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5015 id: artifact_id,
5016 sketch_id,
5017 constraint_id,
5018 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5019 code_ref: CodeRef::placeholder(range),
5020 }));
5021 exec_state.add_scene_object(
5022 Object {
5023 id: constraint_id,
5024 kind: ObjectKind::Constraint {
5025 constraint: sketch_constraint,
5026 },
5027 label: Default::default(),
5028 comments: Default::default(),
5029 artifact_id,
5030 source: SourceRef::new(range, self.node_path.clone()),
5031 },
5032 range,
5033 );
5034 }
5035 SketchConstraintKind::Distance { points, label_position } => {
5036 let range = self.as_source_range();
5037 let p0 = &points[0];
5038 let p1 = &points[1];
5039 let sketch_var_ty = solver_numeric_type(exec_state);
5040 let constraint_id = exec_state.next_object_id();
5041 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5042 let message =
5043 "Being inside a sketch block should have already been checked above".to_owned();
5044 debug_assert!(false, "{}", &message);
5045 return Err(internal_err(message, self));
5046 };
5047 match (p0, p1) {
5048 (
5049 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
5050 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
5051 ) => {
5052 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5053 p0.vars.x.to_constraint_id(range)?,
5054 p0.vars.y.to_constraint_id(range)?,
5055 );
5056 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5057 p1.vars.x.to_constraint_id(range)?,
5058 p1.vars.y.to_constraint_id(range)?,
5059 );
5060 sketch_block_state
5061 .solver_constraints
5062 .push(Constraint::Distance(solver_pt0, solver_pt1, n.n));
5063 }
5064 (
5065 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
5066 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5067 )
5068 | (
5069 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5070 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
5071 ) => {
5072 let origin_x_id = sketch_block_state.next_sketch_var_id();
5073 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5074 value: Box::new(crate::execution::SketchVar {
5075 id: origin_x_id,
5076 initial_value: 0.0,
5077 ty: sketch_var_ty,
5078 node_path: None,
5080 meta: vec![],
5081 }),
5082 });
5083 let origin_y_id = sketch_block_state.next_sketch_var_id();
5084 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5085 value: Box::new(crate::execution::SketchVar {
5086 id: origin_y_id,
5087 initial_value: 0.0,
5088 ty: sketch_var_ty,
5089 node_path: None,
5091 meta: vec![],
5092 }),
5093 });
5094 let origin_x = origin_x_id.to_constraint_id(range)?;
5095 let origin_y = origin_y_id.to_constraint_id(range)?;
5096 sketch_block_state
5097 .solver_constraints
5098 .push(Constraint::Fixed(origin_x, 0.0));
5099 sketch_block_state
5100 .solver_constraints
5101 .push(Constraint::Fixed(origin_y, 0.0));
5102 let solver_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5103 point.vars.x.to_constraint_id(range)?,
5104 point.vars.y.to_constraint_id(range)?,
5105 );
5106 let origin_point = ezpz::datatypes::inputs::DatumPoint::new_xy(origin_x, origin_y);
5107 sketch_block_state.solver_constraints.push(Constraint::Distance(
5108 solver_point,
5109 origin_point,
5110 n.n,
5111 ));
5112 }
5113 (
5114 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5115 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5116 ) => {
5117 return Err(internal_err(
5118 "distance() cannot constrain ORIGIN against ORIGIN".to_owned(),
5119 range,
5120 ));
5121 }
5122 }
5123 use crate::execution::Artifact;
5124 use crate::execution::CodeRef;
5125 use crate::execution::SketchBlockConstraint;
5126 use crate::front::Distance;
5127 use crate::front::SourceRef;
5128 use crate::frontend::sketch::ConstraintSegment;
5129
5130 let Some(sketch_id) = sketch_block_state.sketch_id else {
5131 let message = "Sketch id missing for constraint artifact".to_owned();
5132 debug_assert!(false, "{}", &message);
5133 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5134 };
5135 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5136 segments: vec![
5137 match p0 {
5138 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5139 ConstraintSegment::from(point.object_id)
5140 }
5141 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5142 ConstraintSegment::ORIGIN
5143 }
5144 },
5145 match p1 {
5146 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5147 ConstraintSegment::from(point.object_id)
5148 }
5149 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5150 ConstraintSegment::ORIGIN
5151 }
5152 },
5153 ],
5154 distance: n.try_into().map_err(|_| {
5155 internal_err("Failed to convert distance units numeric suffix:", range)
5156 })?,
5157 label_position: label_position.clone(),
5158 source,
5159 });
5160 sketch_block_state.sketch_constraints.push(constraint_id);
5161 let artifact_id = exec_state.next_artifact_id();
5162 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5163 id: artifact_id,
5164 sketch_id,
5165 constraint_id,
5166 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5167 code_ref: CodeRef::placeholder(range),
5168 }));
5169 exec_state.add_scene_object(
5170 Object {
5171 id: constraint_id,
5172 kind: ObjectKind::Constraint {
5173 constraint: sketch_constraint,
5174 },
5175 label: Default::default(),
5176 comments: Default::default(),
5177 artifact_id,
5178 source: SourceRef::new(range, self.node_path.clone()),
5179 },
5180 range,
5181 );
5182 }
5183 SketchConstraintKind::PointLineDistance {
5184 point,
5185 line,
5186 input_object_ids,
5187 label_position,
5188 } => {
5189 let range = self.as_source_range();
5190 let sketch_var_ty = solver_numeric_type(exec_state);
5191 let sketch_vars = exec_state
5192 .mod_local
5193 .sketch_block
5194 .as_ref()
5195 .ok_or_else(|| {
5196 internal_err(
5197 "Being inside a sketch block should have already been checked above",
5198 self,
5199 )
5200 })?
5201 .sketch_vars
5202 .clone();
5203 let support_initial =
5204 projected_point_on_line_initial_position(&sketch_vars, point, line, exec_state, range)?;
5205 let solver_line = datum_line_from_constrainable(line, range)?;
5206
5207 let constraint_id = exec_state.next_object_id();
5208 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5209 let message =
5210 "Being inside a sketch block should have already been checked above".to_owned();
5211 debug_assert!(false, "{}", &message);
5212 return Err(internal_err(message, self));
5213 };
5214
5215 let solver_point = datum_point_from_constrainable_or_origin(
5221 sketch_block_state,
5222 sketch_var_ty,
5223 point,
5224 range,
5225 )?;
5226 let support_x_id = sketch_block_state.next_sketch_var_id();
5227 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5228 value: Box::new(crate::execution::SketchVar {
5229 id: support_x_id,
5230 initial_value: support_initial[0],
5231 ty: sketch_var_ty,
5232 node_path: None,
5234 meta: vec![],
5235 }),
5236 });
5237 let support_y_id = sketch_block_state.next_sketch_var_id();
5238 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5239 value: Box::new(crate::execution::SketchVar {
5240 id: support_y_id,
5241 initial_value: support_initial[1],
5242 ty: sketch_var_ty,
5243 node_path: None,
5245 meta: vec![],
5246 }),
5247 });
5248 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5249 support_x_id.to_constraint_id(range)?,
5250 support_y_id.to_constraint_id(range)?,
5251 );
5252 let support_line =
5253 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
5254
5255 sketch_block_state
5256 .solver_constraints
5257 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
5258 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5259 support_line,
5260 solver_line,
5261 ezpz::datatypes::AngleKind::Perpendicular,
5262 ));
5263 sketch_block_state.solver_constraints.push(Constraint::Distance(
5264 solver_point,
5265 support_point,
5266 n.n,
5267 ));
5268
5269 use crate::execution::Artifact;
5270 use crate::execution::CodeRef;
5271 use crate::execution::SketchBlockConstraint;
5272 use crate::front::Distance;
5273 use crate::front::SourceRef;
5274 use crate::frontend::sketch::ConstraintSegment;
5275
5276 let Some(sketch_id) = sketch_block_state.sketch_id else {
5277 let message = "Sketch id missing for constraint artifact".to_owned();
5278 debug_assert!(false, "{}", &message);
5279 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5280 };
5281 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5282 segments: input_object_ids
5283 .iter()
5284 .copied()
5285 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
5286 .collect(),
5287 distance: n.try_into().map_err(|_| {
5288 internal_err("Failed to convert distance units numeric suffix:", range)
5289 })?,
5290 label_position: label_position.clone(),
5291 source,
5292 });
5293 sketch_block_state.sketch_constraints.push(constraint_id);
5294 let artifact_id = exec_state.next_artifact_id();
5295 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5296 id: artifact_id,
5297 sketch_id,
5298 constraint_id,
5299 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5300 code_ref: CodeRef::placeholder(range),
5301 }));
5302 exec_state.add_scene_object(
5303 Object {
5304 id: constraint_id,
5305 kind: ObjectKind::Constraint {
5306 constraint: sketch_constraint,
5307 },
5308 label: Default::default(),
5309 comments: Default::default(),
5310 artifact_id,
5311 source: SourceRef::new(range, self.node_path.clone()),
5312 },
5313 range,
5314 );
5315 }
5316 SketchConstraintKind::LineLineDistance {
5317 line0,
5318 line1,
5319 input_object_ids,
5320 label_position,
5321 } => {
5322 let range = self.as_source_range();
5323 let reference_point = crate::execution::ConstrainablePoint2d {
5324 vars: line0.vars[0].clone(),
5325 object_id: line0.object_id,
5326 };
5327 let sketch_var_ty = solver_numeric_type(exec_state);
5328 let sketch_vars = exec_state
5329 .mod_local
5330 .sketch_block
5331 .as_ref()
5332 .ok_or_else(|| {
5333 internal_err(
5334 "Being inside a sketch block should have already been checked above",
5335 self,
5336 )
5337 })?
5338 .sketch_vars
5339 .clone();
5340 let support_initial = projected_point_on_line_initial_position(
5341 &sketch_vars,
5342 &crate::execution::ConstrainablePoint2dOrOrigin::Point(reference_point.clone()),
5343 line1,
5344 exec_state,
5345 range,
5346 )?;
5347 let solver_point = datum_point_from_constrainable(&reference_point, range)?;
5348 let solver_line0 = datum_line_from_constrainable(line0, range)?;
5349 let solver_line1 = datum_line_from_constrainable(line1, range)?;
5350
5351 let constraint_id = exec_state.next_object_id();
5352 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5353 let message =
5354 "Being inside a sketch block should have already been checked above".to_owned();
5355 debug_assert!(false, "{}", &message);
5356 return Err(internal_err(message, self));
5357 };
5358
5359 let support_x_id = sketch_block_state.next_sketch_var_id();
5365 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5366 value: Box::new(crate::execution::SketchVar {
5367 id: support_x_id,
5368 initial_value: support_initial[0],
5369 ty: sketch_var_ty,
5370 node_path: None,
5372 meta: vec![],
5373 }),
5374 });
5375 let support_y_id = sketch_block_state.next_sketch_var_id();
5376 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5377 value: Box::new(crate::execution::SketchVar {
5378 id: support_y_id,
5379 initial_value: support_initial[1],
5380 ty: sketch_var_ty,
5381 node_path: None,
5383 meta: vec![],
5384 }),
5385 });
5386 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5387 support_x_id.to_constraint_id(range)?,
5388 support_y_id.to_constraint_id(range)?,
5389 );
5390 let support_line =
5391 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
5392
5393 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5394 solver_line0,
5395 solver_line1,
5396 ezpz::datatypes::AngleKind::Parallel,
5397 ));
5398 sketch_block_state
5399 .solver_constraints
5400 .push(Constraint::PointLineDistance(support_point, solver_line1, 0.0));
5401 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5402 support_line,
5403 solver_line1,
5404 ezpz::datatypes::AngleKind::Perpendicular,
5405 ));
5406 sketch_block_state.solver_constraints.push(Constraint::Distance(
5407 solver_point,
5408 support_point,
5409 n.n,
5410 ));
5411
5412 use crate::execution::Artifact;
5413 use crate::execution::CodeRef;
5414 use crate::execution::SketchBlockConstraint;
5415 use crate::front::Distance;
5416 use crate::front::SourceRef;
5417 use crate::frontend::sketch::ConstraintSegment;
5418
5419 let Some(sketch_id) = sketch_block_state.sketch_id else {
5420 let message = "Sketch id missing for constraint artifact".to_owned();
5421 debug_assert!(false, "{}", &message);
5422 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5423 };
5424 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5425 segments: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
5426 distance: n.try_into().map_err(|_| {
5427 internal_err("Failed to convert distance units numeric suffix:", range)
5428 })?,
5429 label_position: label_position.clone(),
5430 source,
5431 });
5432 sketch_block_state.sketch_constraints.push(constraint_id);
5433 let artifact_id = exec_state.next_artifact_id();
5434 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5435 id: artifact_id,
5436 sketch_id,
5437 constraint_id,
5438 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5439 code_ref: CodeRef::placeholder(range),
5440 }));
5441 exec_state.add_scene_object(
5442 Object {
5443 id: constraint_id,
5444 kind: ObjectKind::Constraint {
5445 constraint: sketch_constraint,
5446 },
5447 label: Default::default(),
5448 comments: Default::default(),
5449 artifact_id,
5450 source: SourceRef::new(range, self.node_path.clone()),
5451 },
5452 range,
5453 );
5454 }
5455 SketchConstraintKind::PointCircularDistance {
5456 point,
5457 center,
5458 start,
5459 end,
5460 input_object_ids,
5461 label_position,
5462 } => {
5463 let range = self.as_source_range();
5464 let sketch_var_ty = solver_numeric_type(exec_state);
5465 let sketch_vars = exec_state
5466 .mod_local
5467 .sketch_block
5468 .as_ref()
5469 .ok_or_else(|| {
5470 internal_err(
5471 "Being inside a sketch block should have already been checked above",
5472 self,
5473 )
5474 })?
5475 .sketch_vars
5476 .clone();
5477 let circular =
5478 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
5479
5480 let constraint_id = exec_state.next_object_id();
5481 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5482 let message =
5483 "Being inside a sketch block should have already been checked above".to_owned();
5484 debug_assert!(false, "{}", &message);
5485 return Err(internal_err(message, self));
5486 };
5487
5488 let target_point = datum_point_from_constrainable_or_origin(
5493 sketch_block_state,
5494 sketch_var_ty,
5495 point,
5496 range,
5497 )?;
5498 push_circular_distance_constraints(
5499 sketch_block_state,
5500 sketch_var_ty,
5501 target_point,
5502 circular,
5503 n.n,
5504 range,
5505 )?;
5506
5507 use crate::execution::Artifact;
5508 use crate::execution::CodeRef;
5509 use crate::execution::SketchBlockConstraint;
5510 use crate::front::Distance;
5511 use crate::front::SourceRef;
5512 use crate::frontend::sketch::ConstraintSegment;
5513
5514 let Some(sketch_id) = sketch_block_state.sketch_id else {
5515 let message = "Sketch id missing for constraint artifact".to_owned();
5516 debug_assert!(false, "{}", &message);
5517 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5518 };
5519 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5520 segments: input_object_ids
5521 .iter()
5522 .copied()
5523 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
5524 .collect(),
5525 distance: n.try_into().map_err(|_| {
5526 internal_err("Failed to convert distance units numeric suffix:", range)
5527 })?,
5528 label_position: label_position.clone(),
5529 source,
5530 });
5531 sketch_block_state.sketch_constraints.push(constraint_id);
5532 let artifact_id = exec_state.next_artifact_id();
5533 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5534 id: artifact_id,
5535 sketch_id,
5536 constraint_id,
5537 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5538 code_ref: CodeRef::placeholder(range),
5539 }));
5540 exec_state.add_scene_object(
5541 Object {
5542 id: constraint_id,
5543 kind: ObjectKind::Constraint {
5544 constraint: sketch_constraint,
5545 },
5546 label: Default::default(),
5547 comments: Default::default(),
5548 artifact_id,
5549 source: SourceRef::new(range, self.node_path.clone()),
5550 },
5551 range,
5552 );
5553 }
5554 SketchConstraintKind::LineCircularDistance {
5555 line,
5556 center,
5557 start,
5558 end,
5559 input_object_ids,
5560 label_position,
5561 } => {
5562 let range = self.as_source_range();
5563 let sketch_var_ty = solver_numeric_type(exec_state);
5564 let sketch_vars = exec_state
5565 .mod_local
5566 .sketch_block
5567 .as_ref()
5568 .ok_or_else(|| {
5569 internal_err(
5570 "Being inside a sketch block should have already been checked above",
5571 self,
5572 )
5573 })?
5574 .sketch_vars
5575 .clone();
5576 let support_initial = projected_point_on_line_initial_position(
5577 &sketch_vars,
5578 &crate::execution::ConstrainablePoint2dOrOrigin::Point(center.clone()),
5579 line,
5580 exec_state,
5581 range,
5582 )?;
5583 let solver_line = datum_line_from_constrainable(line, range)?;
5584 let circular =
5585 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
5586
5587 let constraint_id = exec_state.next_object_id();
5588 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5589 let message =
5590 "Being inside a sketch block should have already been checked above".to_owned();
5591 debug_assert!(false, "{}", &message);
5592 return Err(internal_err(message, self));
5593 };
5594
5595 let support_x_id = sketch_block_state.next_sketch_var_id();
5601 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5602 value: Box::new(crate::execution::SketchVar {
5603 id: support_x_id,
5604 initial_value: support_initial[0],
5605 ty: sketch_var_ty,
5606 node_path: None,
5608 meta: vec![],
5609 }),
5610 });
5611 let support_y_id = sketch_block_state.next_sketch_var_id();
5612 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5613 value: Box::new(crate::execution::SketchVar {
5614 id: support_y_id,
5615 initial_value: support_initial[1],
5616 ty: sketch_var_ty,
5617 node_path: None,
5619 meta: vec![],
5620 }),
5621 });
5622 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5623 support_x_id.to_constraint_id(range)?,
5624 support_y_id.to_constraint_id(range)?,
5625 );
5626 let support_line =
5627 ezpz::datatypes::inputs::DatumLineSegment::new(circular.center, support_point);
5628
5629 sketch_block_state
5630 .solver_constraints
5631 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
5632 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5633 support_line,
5634 solver_line,
5635 ezpz::datatypes::AngleKind::Perpendicular,
5636 ));
5637 push_circular_distance_constraints(
5638 sketch_block_state,
5639 sketch_var_ty,
5640 support_point,
5641 circular,
5642 n.n,
5643 range,
5644 )?;
5645
5646 use crate::execution::Artifact;
5647 use crate::execution::CodeRef;
5648 use crate::execution::SketchBlockConstraint;
5649 use crate::front::Distance;
5650 use crate::front::SourceRef;
5651 use crate::frontend::sketch::ConstraintSegment;
5652
5653 let Some(sketch_id) = sketch_block_state.sketch_id else {
5654 let message = "Sketch id missing for constraint artifact".to_owned();
5655 debug_assert!(false, "{}", &message);
5656 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5657 };
5658 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5659 segments: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
5660 distance: n.try_into().map_err(|_| {
5661 internal_err("Failed to convert distance units numeric suffix:", range)
5662 })?,
5663 label_position: label_position.clone(),
5664 source,
5665 });
5666 sketch_block_state.sketch_constraints.push(constraint_id);
5667 let artifact_id = exec_state.next_artifact_id();
5668 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5669 id: artifact_id,
5670 sketch_id,
5671 constraint_id,
5672 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5673 code_ref: CodeRef::placeholder(range),
5674 }));
5675 exec_state.add_scene_object(
5676 Object {
5677 id: constraint_id,
5678 kind: ObjectKind::Constraint {
5679 constraint: sketch_constraint,
5680 },
5681 label: Default::default(),
5682 comments: Default::default(),
5683 artifact_id,
5684 source: SourceRef::new(range, self.node_path.clone()),
5685 },
5686 range,
5687 );
5688 }
5689 SketchConstraintKind::CircularCircularDistance {
5690 center0,
5691 start0,
5692 end0,
5693 center1,
5694 start1,
5695 end1,
5696 input_object_ids,
5697 label_position,
5698 } => {
5699 let range = self.as_source_range();
5700 let sketch_var_ty = solver_numeric_type(exec_state);
5701 let sketch_vars = exec_state
5702 .mod_local
5703 .sketch_block
5704 .as_ref()
5705 .ok_or_else(|| {
5706 internal_err(
5707 "Being inside a sketch block should have already been checked above",
5708 self,
5709 )
5710 })?
5711 .sketch_vars
5712 .clone();
5713 let circular0 = circular_distance_datums(
5714 &sketch_vars,
5715 center0,
5716 start0,
5717 end0.as_ref(),
5718 exec_state,
5719 range,
5720 )?;
5721 let circular1 = circular_distance_datums(
5722 &sketch_vars,
5723 center1,
5724 start1,
5725 end1.as_ref(),
5726 exec_state,
5727 range,
5728 )?;
5729 let support_initial = circular_circular_support_initial_position(
5730 &sketch_vars,
5731 center0,
5732 center1,
5733 circular0.radius_initial_value,
5734 n.n,
5735 exec_state,
5736 range,
5737 )?;
5738
5739 let constraint_id = exec_state.next_object_id();
5740 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5741 let message =
5742 "Being inside a sketch block should have already been checked above".to_owned();
5743 debug_assert!(false, "{}", &message);
5744 return Err(internal_err(message, self));
5745 };
5746
5747 let circular_target0 =
5753 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular0, range)?;
5754 let circular_target1 =
5755 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular1, range)?;
5756
5757 let support_x_id = sketch_block_state.next_sketch_var_id();
5758 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5759 value: Box::new(crate::execution::SketchVar {
5760 id: support_x_id,
5761 initial_value: support_initial[0],
5762 ty: sketch_var_ty,
5763 node_path: None,
5765 meta: vec![],
5766 }),
5767 });
5768 let support_y_id = sketch_block_state.next_sketch_var_id();
5769 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5770 value: Box::new(crate::execution::SketchVar {
5771 id: support_y_id,
5772 initial_value: support_initial[1],
5773 ty: sketch_var_ty,
5774 node_path: None,
5776 meta: vec![],
5777 }),
5778 });
5779 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5780 support_x_id.to_constraint_id(range)?,
5781 support_y_id.to_constraint_id(range)?,
5782 );
5783
5784 let support_radius_id = sketch_block_state.next_sketch_var_id();
5785 let support_radius_value = n.n / 2.0;
5786 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5787 value: Box::new(crate::execution::SketchVar {
5788 id: support_radius_id,
5789 initial_value: support_radius_value,
5790 ty: sketch_var_ty,
5791 node_path: None,
5793 meta: vec![],
5794 }),
5795 });
5796 let support_radius =
5797 ezpz::datatypes::inputs::DatumDistance::new(support_radius_id.to_constraint_id(range)?);
5798 let support_circle = ezpz::datatypes::inputs::DatumCircle {
5799 center: support_point,
5800 radius: support_radius,
5801 };
5802 let center_line = ezpz::datatypes::inputs::DatumLineSegment::new(
5803 circular_target0.center,
5804 circular_target1.center,
5805 );
5806
5807 sketch_block_state
5808 .solver_constraints
5809 .push(Constraint::Fixed(support_radius.id, support_radius_value));
5810 sketch_block_state
5811 .solver_constraints
5812 .push(Constraint::PointLineDistance(support_point, center_line, 0.0));
5813 sketch_block_state
5814 .solver_constraints
5815 .push(Constraint::CircleTangentToCircle(
5816 circular_target0,
5817 support_circle,
5818 ezpz::CircleSide::Exterior,
5819 ));
5820 sketch_block_state
5821 .solver_constraints
5822 .push(Constraint::CircleTangentToCircle(
5823 support_circle,
5824 circular_target1,
5825 ezpz::CircleSide::Exterior,
5826 ));
5827
5828 use crate::execution::Artifact;
5829 use crate::execution::CodeRef;
5830 use crate::execution::SketchBlockConstraint;
5831 use crate::front::Distance;
5832 use crate::front::SourceRef;
5833 use crate::frontend::sketch::ConstraintSegment;
5834
5835 let Some(sketch_id) = sketch_block_state.sketch_id else {
5836 let message = "Sketch id missing for constraint artifact".to_owned();
5837 debug_assert!(false, "{}", &message);
5838 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5839 };
5840 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5841 segments: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
5842 distance: n.try_into().map_err(|_| {
5843 internal_err("Failed to convert distance units numeric suffix:", range)
5844 })?,
5845 label_position: label_position.clone(),
5846 source,
5847 });
5848 sketch_block_state.sketch_constraints.push(constraint_id);
5849 let artifact_id = exec_state.next_artifact_id();
5850 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5851 id: artifact_id,
5852 sketch_id,
5853 constraint_id,
5854 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5855 code_ref: CodeRef::placeholder(range),
5856 }));
5857 exec_state.add_scene_object(
5858 Object {
5859 id: constraint_id,
5860 kind: ObjectKind::Constraint {
5861 constraint: sketch_constraint,
5862 },
5863 label: Default::default(),
5864 comments: Default::default(),
5865 artifact_id,
5866 source: SourceRef::new(range, self.node_path.clone()),
5867 },
5868 range,
5869 );
5870 }
5871 SketchConstraintKind::Radius { .. } | SketchConstraintKind::Diameter { .. } => {
5872 #[derive(Clone, Copy)]
5873 enum CircularSegmentConstraintTarget {
5874 Arc {
5875 object_id: ObjectId,
5876 end: [crate::execution::SketchVarId; 2],
5877 direction: ArcDirection,
5878 },
5879 Circle {
5880 object_id: ObjectId,
5881 },
5882 }
5883
5884 fn sketch_var_initial_value(
5885 sketch_vars: &[KclValue],
5886 id: crate::execution::SketchVarId,
5887 exec_state: &mut ExecState,
5888 range: SourceRange,
5889 ) -> Result<f64, KclError> {
5890 sketch_vars
5891 .get(id.0)
5892 .and_then(KclValue::as_sketch_var)
5893 .map(|sketch_var| {
5894 sketch_var
5895 .initial_value_to_solver_units(
5896 exec_state,
5897 range,
5898 "circle radius initial value",
5899 )
5900 .map(|value| value.n)
5901 })
5902 .transpose()?
5903 .ok_or_else(|| {
5904 internal_err(
5905 format!("Missing sketch variable initial value for id {}", id.0),
5906 range,
5907 )
5908 })
5909 }
5910
5911 let (points, label_position) = match &constraint.kind {
5912 SketchConstraintKind::Radius { points, label_position } => {
5913 (points, label_position.clone())
5914 }
5915 SketchConstraintKind::Diameter { points, label_position } => {
5916 (points, label_position.clone())
5917 }
5918 _ => unreachable!(),
5919 };
5920 let range = self.as_source_range();
5921 let center = &points[0];
5922 let start = &points[1];
5923 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
5924 return Err(internal_err(
5925 "Being inside a sketch block should have already been checked above",
5926 self,
5927 ));
5928 };
5929 let (constraint_name, is_diameter) = match &constraint.kind {
5930 SketchConstraintKind::Radius { .. } => ("radius", false),
5931 SketchConstraintKind::Diameter { .. } => ("diameter", true),
5932 _ => unreachable!(),
5933 };
5934 let sketch_vars = sketch_block_state.sketch_vars.clone();
5935 let target_segment = sketch_block_state
5936 .needed_by_engine
5937 .iter()
5938 .find_map(|seg| match &seg.kind {
5939 UnsolvedSegmentKind::Arc {
5940 center_object_id,
5941 start_object_id,
5942 end,
5943 direction,
5944 ..
5945 } if *center_object_id == center.object_id
5946 && *start_object_id == start.object_id =>
5947 {
5948 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
5949 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
5950 (*end_x, *end_y)
5951 }
5952 _ => return None,
5953 };
5954 Some(CircularSegmentConstraintTarget::Arc {
5955 object_id: seg.object_id,
5956 end: [end_x_var, end_y_var],
5957 direction: *direction,
5958 })
5959 }
5960 UnsolvedSegmentKind::Circle {
5961 center_object_id,
5962 start_object_id,
5963 ..
5964 } if *center_object_id == center.object_id
5965 && *start_object_id == start.object_id =>
5966 {
5967 Some(CircularSegmentConstraintTarget::Circle {
5968 object_id: seg.object_id,
5969 })
5970 }
5971 _ => None,
5972 })
5973 .ok_or_else(|| {
5974 internal_err(
5975 format!("Could not find circular segment for {} constraint", constraint_name),
5976 range,
5977 )
5978 })?;
5979 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
5980 let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5981 center.vars.x.to_constraint_id(range)?,
5982 center.vars.y.to_constraint_id(range)?,
5983 );
5984 let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5985 start.vars.x.to_constraint_id(range)?,
5986 start.vars.y.to_constraint_id(range)?,
5987 );
5988 let solver_constraint = match target_segment {
5989 CircularSegmentConstraintTarget::Arc { end, direction, .. } => {
5990 let solver_arc = SolverArc::new(
5991 [center.vars.x, center.vars.y],
5992 [start.vars.x, start.vars.y],
5993 end,
5994 direction,
5995 range,
5996 )?;
5997 solver_arc.radius_constraint(radius_value)
5998 }
5999 CircularSegmentConstraintTarget::Circle { .. } => {
6000 let sketch_var_ty = solver_numeric_type(exec_state);
6001 let start_x =
6002 sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
6003 let start_y =
6004 sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
6005 let center_x =
6006 sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
6007 let center_y =
6008 sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
6009
6010 let radius_initial_value = libm::hypot(start_x - center_x, start_y - center_y);
6012
6013 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6014 let message =
6015 "Being inside a sketch block should have already been checked above"
6016 .to_owned();
6017 debug_assert!(false, "{}", &message);
6018 return Err(internal_err(message, self));
6019 };
6020 let radius_id = sketch_block_state.next_sketch_var_id();
6021 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
6022 value: Box::new(crate::execution::SketchVar {
6023 id: radius_id,
6024 initial_value: radius_initial_value,
6025 ty: sketch_var_ty,
6026 node_path: None,
6028 meta: vec![],
6029 }),
6030 });
6031 let radius =
6032 ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
6033 let solver_circle = ezpz::datatypes::inputs::DatumCircle {
6034 center: center_point,
6035 radius,
6036 };
6037 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
6038 start_point,
6039 center_point,
6040 radius,
6041 ));
6042 Constraint::CircleRadius(solver_circle, radius_value)
6043 }
6044 };
6045
6046 let constraint_id = exec_state.next_object_id();
6047 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6048 let message =
6049 "Being inside a sketch block should have already been checked above".to_owned();
6050 debug_assert!(false, "{}", &message);
6051 return Err(internal_err(message, self));
6052 };
6053 sketch_block_state.solver_constraints.push(solver_constraint);
6054 use crate::execution::Artifact;
6055 use crate::execution::CodeRef;
6056 use crate::execution::SketchBlockConstraint;
6057 use crate::front::SourceRef;
6058 let segment_object_id = match target_segment {
6059 CircularSegmentConstraintTarget::Arc { object_id, .. }
6060 | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
6061 };
6062
6063 let constraint = if is_diameter {
6064 use crate::frontend::sketch::Diameter;
6065 crate::front::Constraint::Diameter(Diameter {
6066 arc: segment_object_id,
6067 diameter: n.try_into().map_err(|_| {
6068 internal_err("Failed to convert diameter units numeric suffix:", range)
6069 })?,
6070 label_position,
6071 source,
6072 })
6073 } else {
6074 use crate::frontend::sketch::Radius;
6075 crate::front::Constraint::Radius(Radius {
6076 arc: segment_object_id,
6077 radius: n.try_into().map_err(|_| {
6078 internal_err("Failed to convert radius units numeric suffix:", range)
6079 })?,
6080 label_position,
6081 source,
6082 })
6083 };
6084 sketch_block_state.sketch_constraints.push(constraint_id);
6085 let Some(sketch_id) = sketch_block_state.sketch_id else {
6086 let message = "Sketch id missing for constraint artifact".to_owned();
6087 debug_assert!(false, "{}", &message);
6088 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
6089 };
6090 let artifact_id = exec_state.next_artifact_id();
6091 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
6092 id: artifact_id,
6093 sketch_id,
6094 constraint_id,
6095 constraint_type: super::artifact::sketch_block_constraint_type(&constraint),
6096 code_ref: CodeRef::placeholder(range),
6097 }));
6098 exec_state.add_scene_object(
6099 Object {
6100 id: constraint_id,
6101 kind: ObjectKind::Constraint { constraint },
6102 label: Default::default(),
6103 comments: Default::default(),
6104 artifact_id,
6105 source: SourceRef::new(range, self.node_path.clone()),
6106 },
6107 range,
6108 );
6109 }
6110 SketchConstraintKind::HorizontalDistance { points, label_position } => {
6111 let range = self.as_source_range();
6112 let p0 = &points[0];
6113 let p1 = &points[1];
6114 let constraint_id = exec_state.next_object_id();
6115 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6116 let message =
6117 "Being inside a sketch block should have already been checked above".to_owned();
6118 debug_assert!(false, "{}", &message);
6119 return Err(internal_err(message, self));
6120 };
6121 match (p0, p1) {
6122 (
6123 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
6124 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
6125 ) => {
6126 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6127 p0.vars.x.to_constraint_id(range)?,
6128 p0.vars.y.to_constraint_id(range)?,
6129 );
6130 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6131 p1.vars.x.to_constraint_id(range)?,
6132 p1.vars.y.to_constraint_id(range)?,
6133 );
6134 sketch_block_state
6135 .solver_constraints
6136 .push(ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n));
6137 }
6138 (
6139 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6140 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6141 ) => {
6142 sketch_block_state
6144 .solver_constraints
6145 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, -n.n));
6146 }
6147 (
6148 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6149 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6150 ) => {
6151 sketch_block_state
6153 .solver_constraints
6154 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, n.n));
6155 }
6156 (
6157 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6158 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6159 ) => {
6160 return Err(internal_err(
6161 "horizontalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
6162 range,
6163 ));
6164 }
6165 }
6166 use crate::execution::Artifact;
6167 use crate::execution::CodeRef;
6168 use crate::execution::SketchBlockConstraint;
6169 use crate::front::Distance;
6170 use crate::front::SourceRef;
6171 use crate::frontend::sketch::ConstraintSegment;
6172
6173 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
6174 segments: vec![
6175 match p0 {
6176 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6177 ConstraintSegment::from(point.object_id)
6178 }
6179 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6180 ConstraintSegment::ORIGIN
6181 }
6182 },
6183 match p1 {
6184 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6185 ConstraintSegment::from(point.object_id)
6186 }
6187 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6188 ConstraintSegment::ORIGIN
6189 }
6190 },
6191 ],
6192 distance: n.try_into().map_err(|_| {
6193 internal_err("Failed to convert distance units numeric suffix:", range)
6194 })?,
6195 label_position: label_position.clone(),
6196 source,
6197 });
6198 sketch_block_state.sketch_constraints.push(constraint_id);
6199 let Some(sketch_id) = sketch_block_state.sketch_id else {
6200 let message = "Sketch id missing for constraint artifact".to_owned();
6201 debug_assert!(false, "{}", &message);
6202 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
6203 };
6204 let artifact_id = exec_state.next_artifact_id();
6205 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
6206 id: artifact_id,
6207 sketch_id,
6208 constraint_id,
6209 constraint_type: super::artifact::sketch_block_constraint_type(&constraint),
6210 code_ref: CodeRef::placeholder(range),
6211 }));
6212 exec_state.add_scene_object(
6213 Object {
6214 id: constraint_id,
6215 kind: ObjectKind::Constraint { constraint },
6216 label: Default::default(),
6217 comments: Default::default(),
6218 artifact_id,
6219 source: SourceRef::new(range, self.node_path.clone()),
6220 },
6221 range,
6222 );
6223 }
6224 SketchConstraintKind::VerticalDistance { points, label_position } => {
6225 let range = self.as_source_range();
6226 let p0 = &points[0];
6227 let p1 = &points[1];
6228 let constraint_id = exec_state.next_object_id();
6229 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6230 let message =
6231 "Being inside a sketch block should have already been checked above".to_owned();
6232 debug_assert!(false, "{}", &message);
6233 return Err(internal_err(message, self));
6234 };
6235 match (p0, p1) {
6236 (
6237 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
6238 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
6239 ) => {
6240 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6241 p0.vars.x.to_constraint_id(range)?,
6242 p0.vars.y.to_constraint_id(range)?,
6243 );
6244 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6245 p1.vars.x.to_constraint_id(range)?,
6246 p1.vars.y.to_constraint_id(range)?,
6247 );
6248 sketch_block_state
6249 .solver_constraints
6250 .push(ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n));
6251 }
6252 (
6253 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6254 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6255 ) => {
6256 sketch_block_state
6257 .solver_constraints
6258 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, -n.n));
6259 }
6260 (
6261 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6262 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6263 ) => {
6264 sketch_block_state
6265 .solver_constraints
6266 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, n.n));
6267 }
6268 (
6269 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6270 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6271 ) => {
6272 return Err(internal_err(
6273 "verticalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
6274 range,
6275 ));
6276 }
6277 }
6278 use crate::execution::Artifact;
6279 use crate::execution::CodeRef;
6280 use crate::execution::SketchBlockConstraint;
6281 use crate::front::Distance;
6282 use crate::front::SourceRef;
6283 use crate::frontend::sketch::ConstraintSegment;
6284
6285 let constraint = crate::front::Constraint::VerticalDistance(Distance {
6286 segments: vec![
6287 match p0 {
6288 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6289 ConstraintSegment::from(point.object_id)
6290 }
6291 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6292 ConstraintSegment::ORIGIN
6293 }
6294 },
6295 match p1 {
6296 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6297 ConstraintSegment::from(point.object_id)
6298 }
6299 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6300 ConstraintSegment::ORIGIN
6301 }
6302 },
6303 ],
6304 distance: n.try_into().map_err(|_| {
6305 internal_err("Failed to convert distance units numeric suffix:", range)
6306 })?,
6307 label_position: label_position.clone(),
6308 source,
6309 });
6310 sketch_block_state.sketch_constraints.push(constraint_id);
6311 let Some(sketch_id) = sketch_block_state.sketch_id else {
6312 let message = "Sketch id missing for constraint artifact".to_owned();
6313 debug_assert!(false, "{}", &message);
6314 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
6315 };
6316 let artifact_id = exec_state.next_artifact_id();
6317 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
6318 id: artifact_id,
6319 sketch_id,
6320 constraint_id,
6321 constraint_type: super::artifact::sketch_block_constraint_type(&constraint),
6322 code_ref: CodeRef::placeholder(range),
6323 }));
6324 exec_state.add_scene_object(
6325 Object {
6326 id: constraint_id,
6327 kind: ObjectKind::Constraint { constraint },
6328 label: Default::default(),
6329 comments: Default::default(),
6330 artifact_id,
6331 source: SourceRef::new(range, self.node_path.clone()),
6332 },
6333 range,
6334 );
6335 }
6336 }
6337 return Ok(KclValue::none());
6338 }
6339 _ => {
6340 return Err(KclError::new_semantic(KclErrorDetails::new(
6341 format!(
6342 "Cannot create an equivalence constraint between values of these types: {} and {}",
6343 left_value.human_friendly_type(),
6344 right_value.human_friendly_type()
6345 ),
6346 vec![self.into()],
6347 )));
6348 }
6349 }
6350 }
6351
6352 if matches!(self.operator, BinaryOperator::Eq | BinaryOperator::Neq)
6355 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
6356 (&left_value, &right_value)
6357 {
6358 let is_equal = left == right;
6359 let value = if self.operator == BinaryOperator::Eq {
6360 is_equal
6361 } else {
6362 !is_equal
6363 };
6364 return Ok(KclValue::Bool { value, meta });
6365 }
6366
6367 if matches!(self.operator, BinaryOperator::Eq | BinaryOperator::Neq) {
6371 match (&left_value, &right_value) {
6372 (KclValue::Enum { value: left }, KclValue::Enum { value: right }) => {
6373 if left.enum_id() != right.enum_id() {
6374 return Err(different_enums_err(left, right, self.as_source_range()));
6375 }
6376
6377 let is_equal = left.variant() == right.variant();
6378 let value = if self.operator == BinaryOperator::Eq {
6379 is_equal
6380 } else {
6381 !is_equal
6382 };
6383 return Ok(KclValue::Bool { value, meta });
6384 }
6385 (KclValue::Enum { value }, other) | (other, KclValue::Enum { value }) => {
6386 return Err(KclError::new_semantic(KclErrorDetails::new(
6387 format!(
6388 "Cannot compare enum `{}` with {}.",
6389 value.qualified_name(),
6390 other.human_friendly_type()
6391 ),
6392 vec![self.as_source_range()],
6393 )));
6394 }
6395 _ => {}
6396 }
6397 }
6398
6399 let left = number_as_f64(&left_value, self.left.clone().into())?;
6400 let right = number_as_f64(&right_value, self.right.clone().into())?;
6401
6402 let value = match self.operator {
6403 BinaryOperator::Add => {
6404 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
6405 self.warn_on_unknown(&ty, "Adding", exec_state);
6406 KclValue::Number { value: l + r, meta, ty }
6407 }
6408 BinaryOperator::Sub => {
6409 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
6410 self.warn_on_unknown(&ty, "Subtracting", exec_state);
6411 KclValue::Number { value: l - r, meta, ty }
6412 }
6413 BinaryOperator::Mul => {
6414 let (l, r, ty) = NumericType::combine_mul(left, right);
6415 self.warn_on_unknown(&ty, "Multiplying", exec_state);
6416 KclValue::Number { value: l * r, meta, ty }
6417 }
6418 BinaryOperator::Div => {
6419 let (l, r, ty) = NumericType::combine_div(left, right);
6420 self.warn_on_unknown(&ty, "Dividing", exec_state);
6421 KclValue::Number { value: l / r, meta, ty }
6422 }
6423 BinaryOperator::Mod => {
6424 let (l, r, ty) = NumericType::combine_mod(left, right);
6425 self.warn_on_unknown(&ty, "Modulo of", exec_state);
6426 KclValue::Number { value: l % r, meta, ty }
6427 }
6428 BinaryOperator::Pow => KclValue::Number {
6429 value: libm::pow(left.n, right.n),
6430 meta,
6431 ty: exec_state.current_default_units(),
6432 },
6433 BinaryOperator::Neq => {
6434 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6435 self.warn_on_unknown(&ty, "Comparing", exec_state);
6436 KclValue::Bool { value: l != r, meta }
6437 }
6438 BinaryOperator::Gt => {
6439 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6440 self.warn_on_unknown(&ty, "Comparing", exec_state);
6441 KclValue::Bool { value: l > r, meta }
6442 }
6443 BinaryOperator::Gte => {
6444 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6445 self.warn_on_unknown(&ty, "Comparing", exec_state);
6446 KclValue::Bool { value: l >= r, meta }
6447 }
6448 BinaryOperator::Lt => {
6449 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6450 self.warn_on_unknown(&ty, "Comparing", exec_state);
6451 KclValue::Bool { value: l < r, meta }
6452 }
6453 BinaryOperator::Lte => {
6454 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6455 self.warn_on_unknown(&ty, "Comparing", exec_state);
6456 KclValue::Bool { value: l <= r, meta }
6457 }
6458 BinaryOperator::Eq => {
6459 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6460 self.warn_on_unknown(&ty, "Comparing", exec_state);
6461 KclValue::Bool { value: l == r, meta }
6462 }
6463 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
6464 };
6465
6466 Ok(value)
6467 }
6468
6469 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
6470 internal_err("missing result while evaluating binary expression", node)
6471 }
6472
6473 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
6474 if ty == &NumericType::Unknown {
6475 let sr = self.as_source_range();
6476 exec_state.clear_units_warnings(&sr);
6477 let mut err = CompilationIssue::err(
6478 sr,
6479 format!(
6480 "{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)`."
6481 ),
6482 );
6483 err.tag = crate::errors::Tag::UnknownNumericUnits;
6484 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
6485 }
6486 }
6487}
6488
6489impl Node<UnaryExpression> {
6490 pub(super) async fn get_result(
6491 &self,
6492 exec_state: &mut ExecState,
6493 ctx: &ExecutorContext,
6494 ) -> Result<KclValueControlFlow, KclError> {
6495 let value = self.argument.get_result(exec_state, ctx).await?;
6496 let value = control_continue!(value);
6497 self.apply_unary(value, exec_state).map(KclValue::continue_)
6498 }
6499
6500 pub(super) fn apply_unary(&self, value: KclValue, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
6503 match self.operator {
6504 UnaryOperator::Not => {
6505 let KclValue::Bool {
6506 value: bool_value,
6507 meta: _,
6508 } = value
6509 else {
6510 return Err(KclError::new_semantic(KclErrorDetails::new(
6511 format!(
6512 "Cannot apply unary operator ! to non-boolean value: {}",
6513 value.human_friendly_type()
6514 ),
6515 vec![self.into()],
6516 )));
6517 };
6518 let meta = vec![Metadata {
6519 source_range: self.into(),
6520 }];
6521 let negated = KclValue::Bool {
6522 value: !bool_value,
6523 meta,
6524 };
6525
6526 Ok(negated)
6527 }
6528 UnaryOperator::Neg => {
6529 let err = || {
6530 KclError::new_semantic(KclErrorDetails::new(
6531 format!(
6532 "You can only negate numbers, planes, or lines, but this is a {}",
6533 value.human_friendly_type()
6534 ),
6535 vec![self.into()],
6536 ))
6537 };
6538 match &value {
6539 KclValue::Number { value, ty, .. } => {
6540 let meta = vec![Metadata {
6541 source_range: self.into(),
6542 }];
6543 Ok(KclValue::Number {
6544 value: -value,
6545 meta,
6546 ty: *ty,
6547 })
6548 }
6549 KclValue::Plane { value } => {
6550 let mut plane = value.clone();
6551 if plane.info.x_axis.x != 0.0 {
6552 plane.info.x_axis.x *= -1.0;
6553 }
6554 if plane.info.x_axis.y != 0.0 {
6555 plane.info.x_axis.y *= -1.0;
6556 }
6557 if plane.info.x_axis.z != 0.0 {
6558 plane.info.x_axis.z *= -1.0;
6559 }
6560 plane.info.z_axis = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
6561 plane.info.z_axis.canonicalize_signed_zero();
6562
6563 plane.id = exec_state.next_uuid();
6564 plane.object_id = None;
6565 Ok(KclValue::Plane { value: plane })
6566 }
6567 KclValue::Object {
6568 value: values, meta, ..
6569 } => {
6570 let Some(direction) = values.get("direction") else {
6572 return Err(err());
6573 };
6574
6575 let direction = match direction {
6576 KclValue::Tuple { value: values, meta } => {
6577 let values = values
6578 .iter()
6579 .map(|v| match v {
6580 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
6581 value: *value * -1.0,
6582 ty: *ty,
6583 meta: meta.clone(),
6584 }),
6585 _ => Err(err()),
6586 })
6587 .collect::<Result<Vec<_>, _>>()?;
6588
6589 KclValue::Tuple {
6590 value: values,
6591 meta: meta.clone(),
6592 }
6593 }
6594 KclValue::HomArray {
6595 value: values,
6596 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
6597 } => {
6598 let values = values
6599 .iter()
6600 .map(|v| match v {
6601 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
6602 value: *value * -1.0,
6603 ty: *ty,
6604 meta: meta.clone(),
6605 }),
6606 _ => Err(err()),
6607 })
6608 .collect::<Result<Vec<_>, _>>()?;
6609
6610 KclValue::HomArray {
6611 value: values,
6612 ty: ty.clone(),
6613 }
6614 }
6615 _ => return Err(err()),
6616 };
6617
6618 let mut value = values.clone();
6619 value.insert("direction".to_owned(), direction);
6620 Ok(KclValue::Object {
6621 value,
6622 meta: meta.clone(),
6623 constrainable: false,
6624 object_kind: KclObjectKind::Default,
6625 })
6626 }
6627 _ => Err(err()),
6628 }
6629 }
6630 UnaryOperator::Plus => match value {
6631 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(value),
6632 _ => Err(KclError::new_semantic(KclErrorDetails::new(
6633 format!(
6634 "You can only apply unary + to numbers or planes, but this is a {}",
6635 value.human_friendly_type()
6636 ),
6637 vec![self.into()],
6638 ))),
6639 },
6640 }
6641 }
6642}
6643
6644pub(crate) async fn execute_pipe_body(
6645 exec_state: &mut ExecState,
6646 body: &[Expr],
6647 source_range: SourceRange,
6648 ctx: &ExecutorContext,
6649) -> Result<KclValueControlFlow, KclError> {
6650 let Some((first, body)) = body.split_first() else {
6651 return Err(KclError::new_semantic(KclErrorDetails::new(
6652 "Pipe expressions cannot be empty".to_owned(),
6653 vec![source_range],
6654 )));
6655 };
6656 let meta = Metadata {
6661 source_range: SourceRange::from(first),
6662 };
6663 let output = ctx
6664 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
6665 .await?;
6666 let output = control_continue!(output);
6667
6668 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
6672 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
6674 exec_state.mod_local.pipe_value = previous_pipe_value;
6676
6677 result
6678}
6679
6680#[async_recursion]
6683async fn inner_execute_pipe_body(
6684 exec_state: &mut ExecState,
6685 body: &[Expr],
6686 ctx: &ExecutorContext,
6687) -> Result<KclValueControlFlow, KclError> {
6688 for expression in body {
6689 if let Expr::TagDeclarator(_) = expression {
6690 return Err(KclError::new_semantic(KclErrorDetails::new(
6691 format!("This cannot be in a PipeExpression: {expression:?}"),
6692 vec![expression.into()],
6693 )));
6694 }
6695 let metadata = Metadata {
6696 source_range: SourceRange::from(expression),
6697 };
6698 let output = ctx
6699 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
6700 .await?;
6701 let output = control_continue!(output);
6702 exec_state.mod_local.pipe_value = Some(output);
6703 }
6704 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
6706 Ok(final_output.continue_())
6707}
6708
6709impl Node<TagDeclarator> {
6710 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
6711 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
6712 value: self.name.clone(),
6713 info: Vec::new(),
6714 meta: vec![Metadata {
6715 source_range: self.into(),
6716 }],
6717 }));
6718
6719 exec_state
6720 .mut_stack()
6721 .add(self.name.clone(), memory_item, self.into())?;
6722
6723 Ok(self.into())
6724 }
6725}
6726
6727impl Node<ArrayExpression> {
6728 #[async_recursion]
6729 pub(super) async fn execute(
6730 &self,
6731 exec_state: &mut ExecState,
6732 ctx: &ExecutorContext,
6733 ) -> Result<KclValueControlFlow, KclError> {
6734 let mut results = Vec::with_capacity(self.elements.len());
6735
6736 for element in &self.elements {
6737 let metadata = Metadata::from(element);
6738 let value = ctx
6741 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
6742 .await?;
6743 let value = control_continue!(value);
6744
6745 results.push(value);
6746 }
6747
6748 Ok(KclValue::HomArray {
6749 value: results,
6750 ty: RuntimeType::Primitive(PrimitiveType::Any),
6751 }
6752 .continue_())
6753 }
6754}
6755
6756impl Node<ArrayRangeExpression> {
6757 #[async_recursion]
6758 pub(super) async fn execute(
6759 &self,
6760 exec_state: &mut ExecState,
6761 ctx: &ExecutorContext,
6762 ) -> Result<KclValueControlFlow, KclError> {
6763 let metadata = Metadata::from(&self.start_element);
6764 let start_val = ctx
6765 .execute_expr(
6766 &self.start_element,
6767 exec_state,
6768 &metadata,
6769 &[],
6770 StatementKind::Expression,
6771 )
6772 .await?;
6773 let start_val_for_build = control_continue!(start_val);
6774 self.validate_range_start(&start_val_for_build)?;
6775 let metadata = Metadata::from(&self.end_element);
6776 let end_val = ctx
6777 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
6778 .await?;
6779 let end_val = control_continue!(end_val);
6780 self.build_range(start_val_for_build, end_val, exec_state)
6781 .map(KclValue::continue_)
6782 }
6783
6784 pub(super) fn validate_range_start(&self, start_val: &KclValue) -> Result<(), KclError> {
6789 if start_val.as_ty_f64().is_none() {
6790 return Err(KclError::new_semantic(KclErrorDetails::new(
6791 format!(
6792 "Expected number for range start but found {}",
6793 start_val.human_friendly_type()
6794 ),
6795 vec![self.into()],
6796 )));
6797 }
6798 Ok(())
6799 }
6800
6801 pub(super) fn build_range(
6804 &self,
6805 start_val: KclValue,
6806 end_val: KclValue,
6807 exec_state: &mut ExecState,
6808 ) -> Result<KclValue, KclError> {
6809 let start = start_val
6810 .as_ty_f64()
6811 .ok_or(KclError::new_semantic(KclErrorDetails::new(
6812 format!(
6813 "Expected number for range start but found {}",
6814 start_val.human_friendly_type()
6815 ),
6816 vec![self.into()],
6817 )))?;
6818 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
6819 format!(
6820 "Expected number for range end but found {}",
6821 end_val.human_friendly_type()
6822 ),
6823 vec![self.into()],
6824 )))?;
6825
6826 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
6827 let Some(start) = crate::try_f64_to_i64(start) else {
6828 return Err(KclError::new_semantic(KclErrorDetails::new(
6829 format!("Range start must be an integer, but found {start}"),
6830 vec![self.into()],
6831 )));
6832 };
6833 let Some(end) = crate::try_f64_to_i64(end) else {
6834 return Err(KclError::new_semantic(KclErrorDetails::new(
6835 format!("Range end must be an integer, but found {end}"),
6836 vec![self.into()],
6837 )));
6838 };
6839
6840 if end < start {
6841 return Err(KclError::new_semantic(KclErrorDetails::new(
6842 format!("Range start is greater than range end: {start} .. {end}"),
6843 vec![self.into()],
6844 )));
6845 }
6846
6847 let range: Vec<_> = if self.end_inclusive {
6848 (start..=end).collect()
6849 } else {
6850 (start..end).collect()
6851 };
6852
6853 let meta = vec![Metadata {
6854 source_range: self.into(),
6855 }];
6856
6857 Ok(KclValue::HomArray {
6858 value: range
6859 .into_iter()
6860 .map(|num| KclValue::Number {
6861 value: num as f64,
6862 ty,
6863 meta: meta.clone(),
6864 })
6865 .collect(),
6866 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
6867 })
6868 }
6869}
6870
6871impl Node<ObjectExpression> {
6872 #[async_recursion]
6873 pub(super) async fn execute(
6874 &self,
6875 exec_state: &mut ExecState,
6876 ctx: &ExecutorContext,
6877 ) -> Result<KclValueControlFlow, KclError> {
6878 let mut object = HashMap::with_capacity(self.properties.len());
6879 for property in &self.properties {
6880 let metadata = Metadata::from(&property.value);
6881 let result = ctx
6882 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
6883 .await?;
6884 let result = control_continue!(result);
6885 object.insert(property.key.name.clone(), result);
6886 }
6887
6888 Ok(KclValue::Object {
6889 value: object,
6890 meta: vec![Metadata {
6891 source_range: self.into(),
6892 }],
6893 constrainable: false,
6894 object_kind: KclObjectKind::Default,
6895 }
6896 .continue_())
6897 }
6898}
6899
6900fn article_for<S: AsRef<str>>(s: S) -> &'static str {
6901 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
6903 "an"
6904 } else {
6905 "a"
6906 }
6907}
6908
6909fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
6910 v.as_ty_f64().ok_or_else(|| {
6911 let actual_type = v.human_friendly_type();
6912 KclError::new_semantic(KclErrorDetails::new(
6913 format!("Expected a number, but found {actual_type}",),
6914 vec![source_range],
6915 ))
6916 })
6917}
6918
6919impl Node<IfExpression> {
6920 #[async_recursion]
6921 pub(super) async fn get_result(
6922 &self,
6923 exec_state: &mut ExecState,
6924 ctx: &ExecutorContext,
6925 ) -> Result<KclValueControlFlow, KclError> {
6926 let cond_value = ctx
6929 .execute_expr(
6930 &self.cond,
6931 exec_state,
6932 &Metadata::from(self),
6933 &[],
6934 StatementKind::Expression,
6935 )
6936 .await?;
6937 let cond_value = control_continue!(cond_value);
6938 if cond_value.get_bool()? {
6939 return exec_if_arm(ctx, &self.then_val, exec_state).await;
6940 }
6941
6942 for else_if in &self.else_ifs {
6944 let cond_value = ctx
6945 .execute_expr(
6946 &else_if.cond,
6947 exec_state,
6948 &Metadata::from(self),
6949 &[],
6950 StatementKind::Expression,
6951 )
6952 .await?;
6953 let cond_value = control_continue!(cond_value);
6954 if cond_value.get_bool()? {
6955 return exec_if_arm(ctx, &else_if.then_val, exec_state).await;
6956 }
6957 }
6958
6959 exec_if_arm(ctx, &self.final_else, exec_state).await
6961 }
6962}
6963
6964pub(super) fn if_arm_scope_begin(exec_state: &mut ExecState) -> Result<bool, KclError> {
6973 if !exec_state.entry_point_version_is_v3_or_higher() {
6974 return Ok(false);
6975 }
6976 exec_state.mut_stack().push_new_env_for_block()?;
6977 Ok(true)
6978}
6979
6980async fn exec_if_arm(
6985 ctx: &ExecutorContext,
6986 block: &Node<Program>,
6987 exec_state: &mut ExecState,
6988) -> Result<KclValueControlFlow, KclError> {
6989 let scoped = if_arm_scope_begin(exec_state)?;
6990 let result = ctx.exec_block(block, exec_state, BodyType::Block).await;
6991 if scoped {
6992 exec_state.mut_stack().pop_env()?;
6996 }
6997 let Some(cf) = result? else {
7001 let message = "if-expression arm produced no value";
7002 debug_assert!(false, "{message}");
7003 return Err(KclError::new_internal(KclErrorDetails::new(
7004 message.to_owned(),
7005 vec![block.to_source_range()],
7006 )));
7007 };
7008 Ok(cf)
7009}
7010
7011#[derive(Debug)]
7012pub(super) enum Property {
7013 UInt(usize),
7014 String(String),
7015}
7016
7017impl Property {
7018 #[allow(clippy::too_many_arguments)]
7019 async fn try_from<'a>(
7020 computed: bool,
7021 value: Expr,
7022 exec_state: &mut ExecState,
7023 sr: SourceRange,
7024 ctx: &ExecutorContext,
7025 metadata: &Metadata,
7026 annotations: &[Node<Annotation>],
7027 statement_kind: StatementKind<'a>,
7028 ) -> Result<Self, EarlyReturn> {
7029 if !computed {
7030 return Ok(Self::from_static_name(&value, sr)?);
7031 }
7032
7033 let prop_value = ctx
7034 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
7035 .await?;
7036 let prop_value = early_return!(prop_value);
7039 Ok(Self::from_value(prop_value, sr)?)
7040 }
7041
7042 pub(super) fn from_static_name(property: &Expr, sr: SourceRange) -> Result<Self, KclError> {
7045 let Expr::Name(identifier) = property else {
7046 return Err(KclError::new_semantic(KclErrorDetails::new(
7048 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
7049 .to_owned(),
7050 vec![sr],
7051 )));
7052 };
7053 Ok(Property::String(identifier.to_string()))
7054 }
7055
7056 pub(super) fn from_value(prop_value: KclValue, sr: SourceRange) -> Result<Self, KclError> {
7059 let property_sr = vec![sr];
7060 match prop_value {
7061 KclValue::Number { value, ty, meta: _ } => {
7062 if !matches!(
7063 ty,
7064 NumericType::Unknown
7065 | NumericType::Default { .. }
7066 | NumericType::Known(crate::exec::UnitType::Count)
7067 ) {
7068 return Err(KclError::new_semantic(KclErrorDetails::new(
7069 format!(
7070 "{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"
7071 ),
7072 property_sr,
7073 )));
7074 }
7075 if let Some(x) = crate::try_f64_to_usize(value) {
7076 Ok(Property::UInt(x))
7077 } else {
7078 Err(KclError::new_semantic(KclErrorDetails::new(
7079 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
7080 property_sr,
7081 )))
7082 }
7083 }
7084 _ => Err(KclError::new_semantic(KclErrorDetails::new(
7085 "Only numbers (>= 0) can be indexes".to_owned(),
7086 vec![sr],
7087 ))),
7088 }
7089 }
7090}
7091
7092impl Property {
7093 fn type_name(&self) -> &'static str {
7094 match self {
7095 Property::UInt(_) => "number",
7096 Property::String(_) => "string",
7097 }
7098 }
7099}
7100
7101impl Node<PipeExpression> {
7102 #[async_recursion]
7103 pub(super) async fn get_result(
7104 &self,
7105 exec_state: &mut ExecState,
7106 ctx: &ExecutorContext,
7107 ) -> Result<KclValueControlFlow, KclError> {
7108 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
7109 }
7110}
7111
7112#[cfg(test)]
7113mod test {
7114 use std::sync::Arc;
7115
7116 use kcl_api::UnitLength;
7117 use tokio::io::AsyncWriteExt;
7118
7119 use super::*;
7120 use crate::ExecutorSettings;
7121 use crate::engine::engine_manager;
7122 use crate::errors::Severity;
7123 use crate::exec::UnitType;
7124 use crate::execution::ContextType;
7125 use crate::execution::machine::ExecutorKind;
7126 use crate::execution::parse_execute;
7127
7128 fn assert_angle_degrees(actual: ezpz::datatypes::Angle, expected: f64) {
7129 assert!(
7130 (actual.to_degrees() - expected).abs() < 1e-9,
7131 "expected {expected}deg, got {}deg",
7132 actual.to_degrees()
7133 );
7134 }
7135
7136 #[test]
7137 fn remaps_sector_angles_to_existing_representative_endpoint_rays() {
7138 let representative_directions = [AngleRayDirection::Forward, AngleRayDirection::Forward];
7139
7140 assert_angle_degrees(
7141 remap_angle_for_representative_rays(
7142 angle_sector_rays(AngleSector::One, false),
7143 representative_directions,
7144 ezpz::datatypes::Angle::from_degrees(60.0),
7145 ),
7146 60.0,
7147 );
7148 assert_angle_degrees(
7149 remap_angle_for_representative_rays(
7150 angle_sector_rays(AngleSector::Two, false),
7151 representative_directions,
7152 ezpz::datatypes::Angle::from_degrees(120.0),
7153 ),
7154 60.0,
7155 );
7156 assert_angle_degrees(
7157 remap_angle_for_representative_rays(
7158 angle_sector_rays(AngleSector::Three, false),
7159 representative_directions,
7160 ezpz::datatypes::Angle::from_degrees(60.0),
7161 ),
7162 60.0,
7163 );
7164 assert_angle_degrees(
7165 remap_angle_for_representative_rays(
7166 angle_sector_rays(AngleSector::Four, false),
7167 representative_directions,
7168 ezpz::datatypes::Angle::from_degrees(120.0),
7169 ),
7170 60.0,
7171 );
7172 assert_angle_degrees(
7173 remap_angle_for_representative_rays(
7174 angle_sector_rays(AngleSector::One, true),
7175 representative_directions,
7176 ezpz::datatypes::Angle::from_degrees(300.0),
7177 ),
7178 60.0,
7179 );
7180 }
7181
7182 #[test]
7183 fn remaps_sector_angles_when_representative_endpoint_is_on_reverse_ray() {
7184 assert_angle_degrees(
7185 remap_angle_for_representative_rays(
7186 angle_sector_rays(AngleSector::One, false),
7187 [AngleRayDirection::Forward, AngleRayDirection::Reverse],
7188 ezpz::datatypes::Angle::from_degrees(60.0),
7189 ),
7190 240.0,
7191 );
7192 }
7193
7194 #[tokio::test(flavor = "multi_thread")]
7195 async fn angle_unlabeled_keeps_legacy_lines_at_angle() {
7196 let code = r#"
7197sketch(on = XY) {
7198 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7199 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7200 lines = [line1, line2]
7201 angle(lines) == 60deg
7202}
7203"#;
7204 let result = parse_execute(code).await.unwrap();
7205
7206 let metadata = result
7207 .exec_state
7208 .global
7209 .root_module_artifacts
7210 .legacy_angle_refactor_metadata();
7211 assert_eq!(metadata.len(), 1);
7212 assert_eq!(metadata[0].sector, 1);
7213 assert!(!metadata[0].inverse);
7214 let program = crate::Program::parse_no_errs(code).unwrap();
7215 let findings = program.lint(crate::lint::checks::lint_legacy_angle).unwrap();
7216 assert_eq!(metadata[0].source_range, findings[0].pos);
7217 }
7218
7219 #[tokio::test(flavor = "multi_thread")]
7220 async fn legacy_angle_refactor_metadata_matches_the_default_label_side() {
7221 let result = parse_execute(
7222 r#"
7223sketch(on = XY) {
7224 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7225 line2 = line(start = [var 0mm, var 0mm], end = [var -2mm, var -3.464mm])
7226 angle([line1, line2]) == 60deg
7227}
7228"#,
7229 )
7230 .await
7231 .unwrap();
7232
7233 let metadata = result
7234 .exec_state
7235 .global
7236 .root_module_artifacts
7237 .legacy_angle_refactor_metadata();
7238 assert_eq!(metadata.len(), 1);
7239 assert_eq!(metadata[0].sector, 4);
7240 assert!(metadata[0].inverse);
7241 }
7242
7243 #[tokio::test(flavor = "multi_thread")]
7244 async fn legacy_angle_refactor_metadata_uses_reverse_segment_rays() {
7245 let result = parse_execute(
7246 r#"
7247sketch(on = XY) {
7248 line1 = line(start = [var -4mm, var 0mm], end = [var 0mm, var 0mm])
7249 line2 = line(start = [var -2mm, var -3.464mm], end = [var 0mm, var 0mm])
7250 angle([line1, line2]) == 60deg
7251}
7252"#,
7253 )
7254 .await
7255 .unwrap();
7256
7257 let metadata = result
7258 .exec_state
7259 .global
7260 .root_module_artifacts
7261 .legacy_angle_refactor_metadata();
7262 assert_eq!(metadata.len(), 1);
7263 assert_eq!(metadata[0].sector, 3);
7264 assert!(!metadata[0].inverse);
7265 }
7266
7267 #[tokio::test(flavor = "multi_thread")]
7268 async fn legacy_angle_label_position_does_not_change_the_sector() {
7269 let result = parse_execute(
7270 r#"
7271sketch(on = XY) {
7272 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7273 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7274 angle([line1, line2], labelPosition = [-3mm, -1.7mm]) == 60deg
7275}
7276"#,
7277 )
7278 .await
7279 .unwrap();
7280
7281 let metadata = result
7282 .exec_state
7283 .global
7284 .root_module_artifacts
7285 .legacy_angle_refactor_metadata();
7286 assert_eq!(metadata.len(), 1);
7287 assert_eq!(metadata[0].sector, 1);
7288 assert!(!metadata[0].inverse);
7289 }
7290
7291 #[tokio::test(flavor = "multi_thread")]
7292 async fn parallel_legacy_angle_has_no_refactor_metadata() {
7293 let result = parse_execute(
7294 r#"
7295sketch(on = XY) {
7296 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7297 line2 = line(start = [var 0mm, var 1mm], end = [var 4mm, var 1mm])
7298 angle([line1, line2]) == 0deg
7299}
7300"#,
7301 )
7302 .await
7303 .unwrap();
7304
7305 assert!(
7306 result
7307 .exec_state
7308 .global
7309 .root_module_artifacts
7310 .legacy_angle_refactor_metadata()
7311 .is_empty()
7312 );
7313 }
7314
7315 #[tokio::test(flavor = "multi_thread")]
7316 async fn angle_dimension_with_sector_uses_named_lines() {
7317 parse_execute(
7318 r#"
7319sketch(on = XY) {
7320 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7321 line2 = line(start = [var 0mm, var 1mm], end = [var 2mm, var 3mm])
7322 angleDimension(lines = [line1, line2], sector = 2) == 60deg
7323}
7324"#,
7325 )
7326 .await
7327 .unwrap();
7328 }
7329
7330 #[tokio::test(flavor = "multi_thread")]
7331 async fn angle_dimension_requires_sector() {
7332 let err = parse_execute(
7333 r#"
7334sketch(on = XY) {
7335 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7336 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7337 angleDimension(lines = [line1, line2]) == 60deg
7338}
7339"#,
7340 )
7341 .await
7342 .unwrap_err();
7343
7344 assert!(
7345 err.to_string()
7346 .contains("The `angleDimension` function requires a keyword argument `sector`"),
7347 "unexpected error: {err:?}"
7348 );
7349 }
7350
7351 #[tokio::test(flavor = "multi_thread")]
7352 async fn angle_dimension_accepts_label_position() {
7353 let result = parse_execute(
7354 r#"
7355sketch(on = XY) {
7356 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7357 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7358 angleDimension(lines = [line1, line2], sector = 1, labelPosition = [10mm, 11mm]) == 60deg
7359}
7360"#,
7361 )
7362 .await
7363 .unwrap();
7364 let angle = result
7365 .exec_state
7366 .global
7367 .root_module_artifacts
7368 .scene_objects
7369 .iter()
7370 .find_map(|object| match &object.kind {
7371 ObjectKind::Constraint {
7372 constraint: crate::front::Constraint::Angle(angle),
7373 } => Some(angle),
7374 _ => None,
7375 })
7376 .unwrap();
7377 let label_position = angle.label_position.as_ref().unwrap();
7378 assert_eq!(label_position.x.value, 10.0);
7379 assert_eq!(label_position.y.value, 11.0);
7380 }
7381
7382 #[tokio::test(flavor = "multi_thread")]
7383 async fn angle_dimension_accepts_all_four_sectors() {
7384 parse_execute(
7385 r#"
7386sketch(on = XY) {
7387 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7388 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7389 angleDimension(lines = [line1, line2], sector = 1) == 60deg
7390 angleDimension(lines = [line1, line2], sector = 2) == 120deg
7391 angleDimension(lines = [line1, line2], sector = 3) == 60deg
7392 angleDimension(lines = [line1, line2], sector = 4) == 120deg
7393}
7394"#,
7395 )
7396 .await
7397 .unwrap();
7398 }
7399
7400 #[tokio::test(flavor = "multi_thread")]
7401 async fn angle_dimension_accepts_inverse_angle_for_sector() {
7402 let result = parse_execute(
7403 r#"
7404sketch(on = XY) {
7405 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7406 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7407 angleDimension(lines = [line1, line2], sector = 1, inverse = true) == 360deg - 60deg
7408}
7409"#,
7410 )
7411 .await
7412 .unwrap();
7413 let angle = result
7414 .exec_state
7415 .global
7416 .root_module_artifacts
7417 .scene_objects
7418 .iter()
7419 .find_map(|object| match &object.kind {
7420 ObjectKind::Constraint {
7421 constraint: crate::front::Constraint::Angle(angle),
7422 } => Some(angle),
7423 _ => None,
7424 })
7425 .unwrap();
7426 assert_eq!(angle.sector, Some(1));
7427 assert_eq!(angle.inverse, Some(true));
7428 }
7429
7430 #[tokio::test(flavor = "multi_thread")]
7431 async fn angle_dimension_rejects_invalid_sector() {
7432 let err = parse_execute(
7433 r#"
7434sketch(on = XY) {
7435 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7436 line2 = line(start = [var 0mm, var 1mm], end = [var 2mm, var 3mm])
7437 angleDimension(lines = [line1, line2], sector = 5) == 60deg
7438}
7439"#,
7440 )
7441 .await
7442 .unwrap_err();
7443
7444 assert!(
7445 err.to_string()
7446 .contains("angleDimension() sector must be 1, 2, 3, or 4"),
7447 "unexpected error: {err:?}"
7448 );
7449 }
7450
7451 #[tokio::test(flavor = "multi_thread")]
7452 async fn angle_dimension_rejects_parallel_lines() {
7453 let err = parse_execute(
7454 r#"
7455sketch(on = XY) {
7456 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7457 line2 = line(start = [var 0mm, var 1mm], end = [var 4mm, var 1mm])
7458 angleDimension(lines = [line1, line2], sector = 2) == 60deg
7459}
7460"#,
7461 )
7462 .await
7463 .unwrap_err();
7464
7465 assert!(
7466 err.to_string()
7467 .contains("angleDimension(lines = ..., sector = ...) requires non-parallel lines"),
7468 "unexpected error: {err:?}"
7469 );
7470 }
7471
7472 #[tokio::test(flavor = "multi_thread")]
7473 async fn angle_accepts_label_position() {
7474 let result = parse_execute(
7475 r#"
7476sketch(on = XY) {
7477 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7478 line2 = line(start = [var 0mm, var 1mm], end = [var 2mm, var 3mm])
7479 angle([line1, line2], labelPosition = [10mm, 11mm]) == 60deg
7480}
7481"#,
7482 )
7483 .await
7484 .unwrap();
7485 let angle = result
7486 .exec_state
7487 .global
7488 .root_module_artifacts
7489 .scene_objects
7490 .iter()
7491 .find_map(|object| match &object.kind {
7492 ObjectKind::Constraint {
7493 constraint: crate::front::Constraint::Angle(angle),
7494 } => Some(angle),
7495 _ => None,
7496 })
7497 .unwrap();
7498 let label_position = angle.label_position.as_ref().unwrap();
7499 assert_eq!(label_position.x.value, 10.0);
7500 assert_eq!(label_position.y.value, 11.0);
7501 }
7502
7503 #[tokio::test(flavor = "multi_thread")]
7504 async fn angle_requires_unlabeled_lines() {
7505 parse_execute(
7506 r#"
7507sketch(on = XY) {
7508 angle() == 60deg
7509}
7510"#,
7511 )
7512 .await
7513 .unwrap_err();
7514 }
7515
7516 #[tokio::test(flavor = "multi_thread")]
7517 async fn ascription() {
7518 let program = r#"
7519a = 42: number
7520b = a: number
7521p = {
7522 origin = { x = 0, y = 0, z = 0 },
7523 xAxis = { x = 1, y = 0, z = 0 },
7524 yAxis = { x = 0, y = 1, z = 0 },
7525 zAxis = { x = 0, y = 0, z = 1 }
7526}: Plane
7527arr1 = [42]: [number(cm)]
7528"#;
7529
7530 let result = parse_execute(program).await.unwrap();
7531 let mem = result.exec_state.stack();
7532 assert!(matches!(
7533 mem.memory
7534 .get_from_owned("p", result.mem_env, SourceRange::default(), 0)
7535 .unwrap(),
7536 KclValue::Plane { .. }
7537 ));
7538 let arr1 = mem
7539 .memory
7540 .get_from_owned("arr1", result.mem_env, SourceRange::default(), 0)
7541 .unwrap();
7542 if let KclValue::HomArray { value, ty } = arr1 {
7543 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
7544 assert_eq!(ty, RuntimeType::known_length(UnitLength::Centimeters));
7545 if let KclValue::Number { value, ty, .. } = &value[0] {
7547 assert_eq!(*value, 42.0);
7549 assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLength::Centimeters)));
7550 } else {
7551 panic!("Expected a number; found {:?}", value[0]);
7552 }
7553 } else {
7554 panic!("Expected HomArray; found {arr1:?}");
7555 }
7556
7557 let program = r#"
7558a = 42: string
7559"#;
7560 let result = parse_execute(program).await;
7561 let err = result.unwrap_err();
7562 assert!(
7563 err.to_string()
7564 .contains("could not coerce a number (with type `number`) to type `string`"),
7565 "Expected error but found {err:?}"
7566 );
7567
7568 let program = r#"
7569a = 42: Plane
7570"#;
7571 let result = parse_execute(program).await;
7572 let err = result.unwrap_err();
7573 assert!(
7574 err.to_string()
7575 .contains("could not coerce a number (with type `number`) to type `Plane`"),
7576 "Expected error but found {err:?}"
7577 );
7578
7579 let program = r#"
7580arr = [0]: [string]
7581"#;
7582 let result = parse_execute(program).await;
7583 let err = result.unwrap_err();
7584 assert!(
7585 err.to_string().contains(
7586 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
7587 ),
7588 "Expected error but found {err:?}"
7589 );
7590
7591 let program = r#"
7592mixedArr = [0, "a"]: [number(mm)]
7593"#;
7594 let result = parse_execute(program).await;
7595 let err = result.unwrap_err();
7596 assert!(
7597 err.to_string().contains(
7598 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
7599 ),
7600 "Expected error but found {err:?}"
7601 );
7602
7603 let program = r#"
7604mixedArr = [0, "a"]: [mm]
7605"#;
7606 let result = parse_execute(program).await;
7607 let err = result.unwrap_err();
7608 assert!(
7609 err.to_string().contains(
7610 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
7611 ),
7612 "Expected error but found {err:?}"
7613 );
7614 }
7615
7616 #[tokio::test(flavor = "multi_thread")]
7617 async fn neg_plane() {
7618 let program = r#"
7619p = {
7620 origin = { x = 0, y = 0, z = 0 },
7621 xAxis = { x = 1, y = 0, z = 0 },
7622 yAxis = { x = 0, y = 1, z = 0 },
7623}: Plane
7624p2 = -p
7625"#;
7626
7627 let result = parse_execute(program).await.unwrap();
7628 let mem = result.exec_state.stack();
7629 match mem
7630 .memory
7631 .get_from_owned("p2", result.mem_env, SourceRange::default(), 0)
7632 .unwrap()
7633 {
7634 KclValue::Plane { value } => {
7635 assert_eq!(value.info.x_axis.x, -1.0);
7636 assert_eq!(value.info.x_axis.y, 0.0);
7637 assert_eq!(value.info.x_axis.z, 0.0);
7638 }
7639 _ => unreachable!(),
7640 }
7641 }
7642
7643 #[tokio::test(flavor = "multi_thread")]
7644 async fn multiple_returns() {
7645 let program = r#"fn foo() {
7646 return 0
7647 return 42
7648}
7649
7650a = foo()
7651"#;
7652
7653 let result = parse_execute(program).await;
7654 assert!(result.unwrap_err().to_string().contains("return"));
7655 }
7656
7657 #[tokio::test(flavor = "multi_thread")]
7658 async fn load_all_modules() {
7659 let program_a_kcl = r#"
7661export a = 1
7662"#;
7663 let program_b_kcl = r#"
7665import a from 'a.kcl'
7666
7667export b = a + 1
7668"#;
7669 let program_c_kcl = r#"
7671import a from 'a.kcl'
7672
7673export c = a + 2
7674"#;
7675
7676 let main_kcl = r#"
7678import b from 'b.kcl'
7679import c from 'c.kcl'
7680
7681d = b + c
7682"#;
7683
7684 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
7685 .parse_errs_as_err()
7686 .unwrap();
7687
7688 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
7689
7690 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
7691 .await
7692 .unwrap()
7693 .write_all(main_kcl.as_bytes())
7694 .await
7695 .unwrap();
7696
7697 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
7698 .await
7699 .unwrap()
7700 .write_all(program_a_kcl.as_bytes())
7701 .await
7702 .unwrap();
7703
7704 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
7705 .await
7706 .unwrap()
7707 .write_all(program_b_kcl.as_bytes())
7708 .await
7709 .unwrap();
7710
7711 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
7712 .await
7713 .unwrap()
7714 .write_all(program_c_kcl.as_bytes())
7715 .await
7716 .unwrap();
7717
7718 let exec_ctxt = ExecutorContext {
7719 engine: Arc::new(engine_manager::EngineManager::new_mock()),
7720 engine_batch: crate::engine::EngineBatchContext::default(),
7721 fs: crate::fs::new_file_system_handle(crate::fs::FileManager::new()),
7722 settings: ExecutorSettings {
7723 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
7724 ..Default::default()
7725 },
7726 context_type: ContextType::Mock,
7727 execution_callbacks: Default::default(),
7728 executor_kind: ExecutorKind::resolve(),
7729 machine_call_depth_limit: crate::execution::machine::DEFAULT_MACHINE_CALL_DEPTH_LIMIT,
7730 };
7731 let mut exec_state = ExecState::new(&exec_ctxt);
7732
7733 exec_ctxt
7734 .run(
7735 &crate::Program {
7736 ast: main.clone(),
7737 original_file_contents: "".to_owned(),
7738 },
7739 &mut exec_state,
7740 )
7741 .await
7742 .unwrap();
7743 }
7744
7745 #[tokio::test(flavor = "multi_thread")]
7746 async fn user_coercion() {
7747 let program = r#"fn foo(x: Axis2d) {
7748 return 0
7749}
7750
7751foo(x = { direction = [0, 0], origin = [0, 0]})
7752"#;
7753
7754 parse_execute(program).await.unwrap();
7755
7756 let program = r#"fn foo(x: Axis3d) {
7757 return 0
7758}
7759
7760foo(x = { direction = [0, 0], origin = [0, 0]})
7761"#;
7762
7763 parse_execute(program).await.unwrap_err();
7764 }
7765
7766 #[tokio::test(flavor = "multi_thread")]
7767 async fn coerce_return() {
7768 let program = r#"fn foo(): number(mm) {
7769 return 42
7770}
7771
7772a = foo()
7773"#;
7774
7775 parse_execute(program).await.unwrap();
7776
7777 let program = r#"fn foo(): mm {
7778 return 42
7779}
7780
7781a = foo()
7782"#;
7783
7784 parse_execute(program).await.unwrap();
7785
7786 let program = r#"fn foo(): number(mm) {
7787 return { bar: 42 }
7788}
7789
7790a = foo()
7791"#;
7792
7793 parse_execute(program).await.unwrap_err();
7794
7795 let program = r#"fn foo(): mm {
7796 return { bar: 42 }
7797}
7798
7799a = foo()
7800"#;
7801
7802 parse_execute(program).await.unwrap_err();
7803 }
7804
7805 #[tokio::test(flavor = "multi_thread")]
7806 async fn test_sensible_error_when_missing_equals_in_kwarg() {
7807 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)"]
7808 .into_iter()
7809 .enumerate()
7810 {
7811 let program = format!(
7812 "fn foo() {{ return 0 }}
7813z = 0
7814fn f(x, y, z) {{ return 0 }}
7815{call}"
7816 );
7817 let err = parse_execute(&program).await.unwrap_err();
7818 let msg = err.message();
7819 assert!(
7820 msg.contains("This argument needs a label, but it doesn't have one"),
7821 "failed test {i}: {msg}"
7822 );
7823 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
7824 if i == 0 {
7825 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
7826 }
7827 }
7828 }
7829
7830 #[tokio::test(flavor = "multi_thread")]
7831 async fn default_param_for_unlabeled() {
7832 let ast = r#"fn myExtrude(@sk, length) {
7835 return extrude(sk, length)
7836}
7837sketch001 = startSketchOn(XY)
7838 |> circle(center = [0, 0], radius = 93.75)
7839 |> myExtrude(length = 40)
7840"#;
7841
7842 parse_execute(ast).await.unwrap();
7843 }
7844
7845 #[tokio::test(flavor = "multi_thread")]
7846 async fn dont_use_unlabelled_as_input() {
7847 let ast = r#"length = 10
7849startSketchOn(XY)
7850 |> circle(center = [0, 0], radius = 93.75)
7851 |> extrude(length)
7852"#;
7853
7854 parse_execute(ast).await.unwrap();
7855 }
7856
7857 #[tokio::test(flavor = "multi_thread")]
7858 async fn ascription_in_binop() {
7859 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
7860 parse_execute(ast).await.unwrap();
7861
7862 let ast = r#"foo = tan(0): rad - 4deg"#;
7863 parse_execute(ast).await.unwrap();
7864 }
7865
7866 #[tokio::test(flavor = "multi_thread")]
7867 async fn neg_sqrt() {
7868 let ast = r#"bad = sqrt(-2)"#;
7869
7870 let e = parse_execute(ast).await.unwrap_err();
7871 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
7873 }
7874
7875 #[tokio::test(flavor = "multi_thread")]
7876 async fn non_array_fns() {
7877 let ast = r#"push(1, item = 2)
7878pop(1)
7879map(1, f = fn(@x) { return x + 1 })
7880reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
7881
7882 parse_execute(ast).await.unwrap();
7883 }
7884
7885 #[tokio::test(flavor = "multi_thread")]
7886 async fn non_array_indexing() {
7887 let good = r#"a = 42
7888good = a[0]
7889"#;
7890 let result = parse_execute(good).await.unwrap();
7891 let mem = result.exec_state.stack();
7892 let num = mem
7893 .memory
7894 .get_from_owned("good", result.mem_env, SourceRange::default(), 0)
7895 .unwrap()
7896 .as_ty_f64()
7897 .unwrap();
7898 assert_eq!(num.n, 42.0);
7899
7900 let bad = r#"a = 42
7901bad = a[1]
7902"#;
7903
7904 parse_execute(bad).await.unwrap_err();
7905 }
7906
7907 #[tokio::test(flavor = "multi_thread")]
7908 async fn coerce_unknown_to_length() {
7909 let ast = r#"x = 2mm * 2mm
7910y = x: number(Length)"#;
7911 let e = parse_execute(ast).await.unwrap_err();
7912 assert!(
7913 e.message().contains("could not coerce"),
7914 "Error message: '{}'",
7915 e.message()
7916 );
7917
7918 let ast = r#"x = 2mm
7919y = x: number(Length)"#;
7920 let result = parse_execute(ast).await.unwrap();
7921 let mem = result.exec_state.stack();
7922 let num = mem
7923 .memory
7924 .get_from_owned("y", result.mem_env, SourceRange::default(), 0)
7925 .unwrap()
7926 .as_ty_f64()
7927 .unwrap();
7928 assert_eq!(num.n, 2.0);
7929 assert_eq!(num.ty, NumericType::mm());
7930 }
7931
7932 #[tokio::test(flavor = "multi_thread")]
7933 async fn one_warning_unknown() {
7934 let ast = r#"
7935// Should warn once
7936a = PI * 2
7937// Should warn once
7938b = (PI * 2) / 3
7939// Should not warn
7940c = ((PI * 2) / 3): number(deg)
7941"#;
7942
7943 let result = parse_execute(ast).await.unwrap();
7944 assert_eq!(result.exec_state.issues().len(), 2);
7945 }
7946
7947 #[tokio::test(flavor = "multi_thread")]
7948 async fn non_count_indexing() {
7949 let ast = r#"x = [0, 0]
7950y = x[1mm]
7951"#;
7952 parse_execute(ast).await.unwrap_err();
7953
7954 let ast = r#"x = [0, 0]
7955y = 1deg
7956z = x[y]
7957"#;
7958 parse_execute(ast).await.unwrap_err();
7959
7960 let ast = r#"x = [0, 0]
7961y = x[0mm + 1]
7962"#;
7963 parse_execute(ast).await.unwrap_err();
7964 }
7965
7966 #[tokio::test(flavor = "multi_thread")]
7967 async fn getting_property_of_plane() {
7968 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
7969 parse_execute(&ast).await.unwrap();
7970 }
7971
7972 #[tokio::test(flavor = "multi_thread")]
7973 async fn no_artifacts_from_within_hole_call() {
7974 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
7979 let out = parse_execute(&ast).await.unwrap();
7980
7981 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
7983
7984 let expected = 5;
7988 assert_eq!(
7989 actual_operations.len(),
7990 expected,
7991 "expected {expected} operations, received {}:\n{actual_operations:#?}",
7992 actual_operations.len(),
7993 );
7994 }
7995
7996 #[tokio::test(flavor = "multi_thread")]
7997 async fn feature_tree_annotation_on_user_defined_kcl() {
7998 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
8001 let out = parse_execute(&ast).await.unwrap();
8002
8003 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
8005
8006 let expected = 0;
8007 assert_eq!(
8008 actual_operations.len(),
8009 expected,
8010 "expected {expected} operations, received {}:\n{actual_operations:#?}",
8011 actual_operations.len(),
8012 );
8013 }
8014
8015 #[tokio::test(flavor = "multi_thread")]
8016 async fn no_feature_tree_annotation_on_user_defined_kcl() {
8017 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
8020 let out = parse_execute(&ast).await.unwrap();
8021
8022 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
8024
8025 let expected = 2;
8026 assert_eq!(
8027 actual_operations.len(),
8028 expected,
8029 "expected {expected} operations, received {}:\n{actual_operations:#?}",
8030 actual_operations.len(),
8031 );
8032 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
8033 assert!(matches!(actual_operations[1], Operation::GroupEnd));
8034 }
8035
8036 #[tokio::test(flavor = "multi_thread")]
8037 async fn custom_warning() {
8038 let warn = r#"
8039a = PI * 2
8040"#;
8041 let result = parse_execute(warn).await.unwrap();
8042 assert_eq!(result.exec_state.issues().len(), 1);
8043 assert_eq!(result.exec_state.issues()[0].severity, Severity::Warning);
8044
8045 let allow = r#"
8046@warnings(allow = unknownUnits)
8047a = PI * 2
8048"#;
8049 let result = parse_execute(allow).await.unwrap();
8050 assert_eq!(result.exec_state.issues().len(), 0);
8051
8052 let deny = r#"
8053@warnings(deny = [unknownUnits])
8054a = PI * 2
8055"#;
8056 let result = parse_execute(deny).await.unwrap();
8057 assert_eq!(result.exec_state.issues().len(), 1);
8058 assert_eq!(result.exec_state.issues()[0].severity, Severity::Error);
8059 }
8060
8061 #[tokio::test(flavor = "multi_thread")]
8065 async fn diagnostics_attribute_in_v3() {
8066 let warn = "@settings(kclVersion = \"3.0-preview\")\na = PI * 2\n";
8067 let result = parse_execute(warn).await.unwrap();
8068 let issues = result.exec_state.issues();
8069 assert_eq!(issues.len(), 1, "{issues:#?}");
8070 assert_eq!(issues[0].severity, Severity::Warning);
8071 assert_eq!(issues[0].tag, crate::errors::Tag::UnknownNumericUnits);
8072
8073 let allow = "@settings(kclVersion = \"3.0-preview\")\n@diagnostics(allow = unknownUnits)\na = PI * 2\n";
8074 let result = parse_execute(allow).await.unwrap();
8075 assert!(
8076 result.exec_state.issues().is_empty(),
8077 "{:#?}",
8078 result.exec_state.issues()
8079 );
8080
8081 let deny = "@settings(kclVersion = \"3.0-preview\")\n@diagnostics(deny = [unknownUnits])\na = PI * 2\n";
8082 let result = parse_execute(deny).await.unwrap();
8083 let issues = result.exec_state.issues();
8084 assert_eq!(issues.len(), 1, "{issues:#?}");
8085 assert_eq!(issues[0].severity, Severity::Error);
8086 assert_eq!(issues[0].tag, crate::errors::Tag::UnknownNumericUnits);
8087 }
8088
8089 #[tokio::test(flavor = "multi_thread")]
8093 async fn diagnostics_attribute_is_unknown_before_v3() {
8094 for version in ["1.0", "2.0"] {
8095 let code = format!("@settings(kclVersion = {version})\n@diagnostics(allow = unknownUnits)\na = PI * 2\n");
8096 let result = parse_execute(&code).await.unwrap();
8097 let issues = result.exec_state.issues();
8098 assert_eq!(issues.len(), 2, "code={code}, issues={issues:#?}");
8099 assert_eq!(issues[0].severity, Severity::Warning);
8100 assert_eq!(issues[0].message, "Unknown annotation");
8101 assert_eq!(
8102 &code[issues[0].source_range.start()..issues[0].source_range.end()],
8103 "@diagnostics(allow = unknownUnits)"
8104 );
8105 assert_eq!(issues[1].severity, Severity::Warning);
8106 assert_eq!(issues[1].tag, crate::errors::Tag::UnknownNumericUnits);
8107
8108 let code = format!("@settings(kclVersion = {version})\n@warnings(allow = unknownUnits)\na = PI * 2\n");
8109 let result = parse_execute(&code).await.unwrap();
8110 assert!(
8111 result.exec_state.issues().is_empty(),
8112 "code={code}, issues={:#?}",
8113 result.exec_state.issues()
8114 );
8115 }
8116 }
8117
8118 #[tokio::test(flavor = "multi_thread")]
8122 async fn diagnostics_attribute_errors_use_the_attribute_name() {
8123 for (version, attr, noun) in [
8124 ("1.0", "warnings", "warning"),
8125 ("2.0", "warnings", "warning"),
8126 ("\"3.0-preview\"", "diagnostics", "diagnostic"),
8127 ] {
8128 let settings = format!("@settings(kclVersion = {version})\n");
8129
8130 let code = format!("{settings}@{attr}\n");
8131 let error = parse_execute(&code).await.unwrap_err();
8132 assert_eq!(error.message(), format!("Empty `{attr}` annotation"), "code={code}");
8133
8134 let code = format!("{settings}@{attr}(warn = unknownUnits)\n");
8135 let error = parse_execute(&code).await.unwrap_err();
8136 assert_eq!(
8137 error.message(),
8138 format!("Unexpected {attr} key: `warn`; expected one of `allow`, `deny`"),
8139 "code={code}"
8140 );
8141
8142 let code = format!("{settings}@{attr}(allow = 1)\n");
8143 let error = parse_execute(&code).await.unwrap_err();
8144 assert_eq!(
8145 error.message(),
8146 format!(
8147 "Unexpected {attr} value, expected a name or array of names, e.g., `unknownUnits` or `[unknownUnits, deprecated]`"
8148 ),
8149 "code={code}"
8150 );
8151
8152 let code = format!("{settings}@{attr}(allow = bogus)\n");
8153 let error = parse_execute(&code).await.unwrap_err();
8154 let expected_prefix = format!("Unexpected {noun} value: `bogus`; accepted values: unknownUnits, ");
8155 assert!(
8156 error.message().starts_with(&expected_prefix),
8157 "code={code}, message={}",
8158 error.message()
8159 );
8160 }
8161 }
8162
8163 #[tokio::test(flavor = "multi_thread")]
8167 async fn warnings_attribute_is_renamed_in_v3() {
8168 let code = "@settings(kclVersion = \"3.0-preview\")\n@warnings(allow = unknownUnits)\na = PI * 2\n";
8169 let result = parse_execute(code).await.unwrap();
8170 let issues = result.exec_state.issues();
8171 assert_eq!(issues.len(), 2, "{issues:#?}");
8172
8173 let renamed = &issues[0];
8174 assert_eq!(renamed.severity, Severity::Error);
8175 assert_eq!(
8176 renamed.message,
8177 "The `@warnings` attribute was renamed to `@diagnostics` in KCL 3.0, so this attribute is ignored. Replace `@warnings` with `@diagnostics`; its `allow` and `deny` properties are unchanged."
8178 );
8179 assert_eq!(
8180 &code[renamed.source_range.start()..renamed.source_range.end()],
8181 "@warnings(allow = unknownUnits)"
8182 );
8183 let suggestion = renamed.suggestion.as_ref().unwrap();
8184 assert_eq!(suggestion.title, "Rename to `@diagnostics`");
8185 assert_eq!(
8186 suggestion.apply(code),
8187 "@settings(kclVersion = \"3.0-preview\")\n@diagnostics(allow = unknownUnits)\na = PI * 2\n"
8188 );
8189
8190 assert_eq!(issues[1].severity, Severity::Warning);
8191 assert_eq!(issues[1].tag, crate::errors::Tag::UnknownNumericUnits);
8192
8193 let code = "@settings(kclVersion = \"3.0-preview\")\n@warnings(allow = bogus)\n";
8195 let result = parse_execute(code).await.unwrap();
8196 let issues = result.exec_state.issues();
8197 assert_eq!(issues.len(), 1, "{issues:#?}");
8198 assert_eq!(issues[0].severity, Severity::Error);
8199 assert!(issues[0].message.starts_with("The `@warnings` attribute was renamed"));
8200 }
8201
8202 #[tokio::test(flavor = "multi_thread")]
8203 async fn sketch_block_unqualified_functions_use_sketch2() {
8204 let ast = r#"
8205s = sketch(on = XY) {
8206 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
8207 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
8208 coincident([line1.end, line2.start])
8209}
8210"#;
8211 let result = parse_execute(ast).await.unwrap();
8212 let mem = result.exec_state.stack();
8213 let sketch_value = mem
8214 .memory
8215 .get_from_owned("s", result.mem_env, SourceRange::default(), 0)
8216 .unwrap();
8217
8218 let KclValue::Object { value, .. } = sketch_value else {
8219 panic!("Expected sketch block to return an object, got {sketch_value:?}");
8220 };
8221
8222 assert!(value.contains_key("line1"));
8223 assert!(value.contains_key("line2"));
8224 assert!(!value.contains_key("line"));
8227 assert!(!value.contains_key("coincident"));
8228 }
8229
8230 #[tokio::test(flavor = "multi_thread")]
8231 async fn solver_module_is_not_available_outside_sketch_blocks() {
8232 let err = parse_execute("a = solver::ORIGIN").await.unwrap_err();
8233 assert!(err.message().contains("solver"), "Error message: '{}'", err.message());
8234
8235 let err = parse_execute(
8236 r#"@settings(experimentalFeatures = allow)
8237
8238import "std::solver""#,
8239 )
8240 .await
8241 .unwrap_err();
8242 assert!(
8243 err.message().contains("only available inside sketch blocks"),
8244 "Error message: '{}'",
8245 err.message()
8246 );
8247 }
8248
8249 #[tokio::test(flavor = "multi_thread")]
8250 async fn cannot_solid_extrude_an_open_profile() {
8251 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
8254 let program = crate::Program::parse_no_errs(&code).expect("should parse");
8255 let exec_ctxt = ExecutorContext::new_mock(None).await;
8256 let mut exec_state = ExecState::new(&exec_ctxt);
8257
8258 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
8259 assert!(matches!(err, KclError::Semantic { .. }));
8260 exec_ctxt.close().await;
8261 }
8262}