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