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