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