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_convergence_tolerance;
84use crate::execution::sketch_solve::solver_numeric_type;
85use crate::execution::sketch_solve::substitute_sketch_var_in_segment;
86use crate::execution::sketch_solve::substitute_sketch_vars;
87use crate::execution::state::ModuleState;
88use crate::execution::state::SketchBlockState;
89use crate::execution::types::CoercionMode;
90use crate::execution::types::NumericTypeExt;
91use crate::execution::types::PrimitiveType;
92use crate::execution::types::RuntimeType;
93use crate::execution::types::resolve_named_type_def;
94use crate::execution::types::type_value_named_by_segment;
95use crate::front::ArcDirection;
96use crate::front::LineCtor;
97use crate::front::Object;
98use crate::front::ObjectId;
99use crate::front::ObjectKind;
100use crate::front::PointCtor;
101use crate::modules::ModuleExecutionOutcome;
102use crate::modules::ModuleId;
103use crate::modules::ModuleItems;
104use crate::modules::ModulePath;
105use crate::modules::ModuleRepr;
106use crate::parsing::ast::types::ABSOLUTE_PATHS_NOT_SUPPORTED;
107use crate::parsing::ast::types::Annotation;
108use crate::parsing::ast::types::ArrayExpression;
109use crate::parsing::ast::types::ArrayRangeExpression;
110use crate::parsing::ast::types::AscribedExpression;
111use crate::parsing::ast::types::BinaryExpression;
112use crate::parsing::ast::types::BinaryOperator;
113use crate::parsing::ast::types::BinaryPart;
114use crate::parsing::ast::types::BodyItem;
115use crate::parsing::ast::types::CodeBlock;
116use crate::parsing::ast::types::Expr;
117use crate::parsing::ast::types::FunctionExpression;
118use crate::parsing::ast::types::Identifier;
119use crate::parsing::ast::types::IfExpression;
120use crate::parsing::ast::types::ImportPath;
121use crate::parsing::ast::types::ImportSelector;
122use crate::parsing::ast::types::ImportStatement;
123use crate::parsing::ast::types::ItemVisibility;
124use crate::parsing::ast::types::MemberExpression;
125use crate::parsing::ast::types::Name;
126use crate::parsing::ast::types::Node;
127use crate::parsing::ast::types::ObjectExpression;
128use crate::parsing::ast::types::PipeExpression;
129use crate::parsing::ast::types::Program;
130use crate::parsing::ast::types::ReturnStatement;
131use crate::parsing::ast::types::SketchBlock;
132use crate::parsing::ast::types::SketchVar;
133use crate::parsing::ast::types::TagDeclarator;
134use crate::parsing::ast::types::Type;
135use crate::parsing::ast::types::TypeDeclaration;
136use crate::parsing::ast::types::TypeDeclarationDefinition;
137use crate::parsing::ast::types::UnaryExpression;
138use crate::parsing::ast::types::UnaryOperator;
139use crate::parsing::ast::types::VariableDeclaration;
140use crate::std::StdFnProps;
141use crate::std::args::FromKclValue;
142use crate::std::args::TyF64;
143use crate::std::shapes::SketchOrSurface;
144use crate::std::sketch::ensure_sketch_plane_in_engine;
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) = match crate::parsing::parse_str_syntax(&source.source, id) {
1930 Ok(v) => v,
1931 Err(err) => {
1932 let message = format!("Failed to parse source for {resolved_path}; {err})");
1933 debug_assert!(false, "{message}");
1934 return Err(KclError::new_internal(KclErrorDetails::new(
1935 message,
1936 vec![source_range],
1937 )));
1938 }
1939 };
1940 if let Err(err) = crate::parsing::validate_never_type_ranges(
1941 &never_type_ranges,
1942 crate::parsing::SyntaxSource::BundledStdlib,
1943 ) {
1944 let message = format!("Failed to validate never type ranges for {resolved_path}; {err})");
1945 debug_assert!(false, "{message}");
1946 return Err(KclError::new_internal(KclErrorDetails::new(
1947 message,
1948 vec![source_range],
1949 )));
1950 }
1951 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
1952 Ok(id)
1953 }
1954 }
1955 }
1956
1957 pub(super) async fn exec_module_for_items(
1958 &self,
1959 module_id: ModuleId,
1960 exec_state: &mut ExecState,
1961 source_range: SourceRange,
1962 ) -> Result<ModuleItems, KclError> {
1963 let path = exec_state.global.module_infos[&module_id].path.clone();
1964 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1965 let result = match &mut repr {
1968 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
1969 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.items()),
1970 ModuleRepr::Kcl(program, cache) => self
1971 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
1972 .await
1973 .map(|outcome| {
1974 let items = outcome.items();
1975 *cache = Some(outcome);
1976 items
1977 }),
1978 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1979 "Cannot import items from foreign modules".to_owned(),
1980 vec![geom.source_range],
1981 ))),
1982 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
1983 };
1984
1985 exec_state.global.module_infos[&module_id].restore_repr(repr);
1986 result
1987 }
1988
1989 async fn exec_module_for_result(
1990 &self,
1991 module_id: ModuleId,
1992 exec_state: &mut ExecState,
1993 source_range: SourceRange,
1994 ) -> Result<Option<KclValue>, KclError> {
1995 let path = exec_state.global.module_infos[&module_id].path.clone();
1996 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1997 let result = match &mut repr {
2000 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
2001 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
2002 ModuleRepr::Kcl(program, cached_items) => {
2003 let result = self
2004 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
2005 .await;
2006 match result {
2007 Ok(outcome) => {
2008 let value = outcome.last_expr.clone();
2009 *cached_items = Some(outcome);
2010 Ok(value)
2011 }
2012 Err(e) => Err(e),
2013 }
2014 }
2015 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
2016 ModuleRepr::Foreign(geom, cached) => {
2017 let caller_artifacts = std::mem::take(&mut exec_state.mod_local.artifacts);
2018 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
2019 .await
2020 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
2021 let module_artifacts = std::mem::replace(&mut exec_state.mod_local.artifacts, caller_artifacts);
2022
2023 match result {
2024 Ok(val) => {
2025 *cached = Some((val.clone(), module_artifacts));
2026 Ok(val)
2027 }
2028 Err(e) => {
2029 exec_state.mod_local.artifacts.extend(module_artifacts);
2032 Err(e.add_import_location(&path.import_name(), source_range))
2035 }
2036 }
2037 }
2038 ModuleRepr::Dummy => unreachable!(),
2039 };
2040
2041 exec_state.global.module_infos[&module_id].restore_repr(repr);
2042
2043 result
2044 }
2045
2046 pub async fn exec_module_from_ast(
2047 &self,
2048 program: &Node<Program>,
2049 module_id: ModuleId,
2050 path: &ModulePath,
2051 exec_state: &mut ExecState,
2052 source_range: SourceRange,
2053 preserve_mem: PreserveMem,
2054 ) -> Result<ModuleExecutionOutcome, KclError> {
2055 exec_state.global.mod_loader.enter_module(path);
2056 let result = self
2057 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
2058 .await;
2059 exec_state.global.mod_loader.leave_module(path, source_range)?;
2060
2061 result.map_err(|(err, _, _)| {
2064 match err {
2065 KclError::ImportCycle { .. } => {
2066 err.override_source_ranges(vec![source_range])
2068 }
2069 _ => err.add_import_location(&path.import_name(), source_range),
2073 }
2074 })
2075 }
2076
2077 pub(super) async fn resolve_name_for_eval(
2081 &self,
2082 name: &Node<Name>,
2083 metadata: &Metadata,
2084 exec_state: &mut ExecState,
2085 ) -> Result<KclValue, KclError> {
2086 let value = name.get_result(exec_state, self).await?;
2087 if let KclValue::Module { value: module_id, meta } = value {
2088 Ok(self
2089 .exec_module_for_result(module_id, exec_state, metadata.source_range)
2090 .await?
2091 .unwrap_or_else(|| {
2092 exec_state.warn(
2093 CompilationIssue::err(
2094 metadata.source_range,
2095 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
2096 ),
2097 annotations::WARN_MOD_RETURN_VALUE,
2098 );
2099
2100 let mut new_meta = vec![metadata.to_owned()];
2101 new_meta.extend(meta);
2102 KclValue::KclNone {
2103 value: Default::default(),
2104 meta: new_meta,
2105 }
2106 }))
2107 } else {
2108 Ok(value)
2109 }
2110 }
2111
2112 #[async_recursion]
2113 pub(crate) async fn execute_expr<'a: 'async_recursion>(
2114 &self,
2115 init: &Expr,
2116 exec_state: &mut ExecState,
2117 metadata: &Metadata,
2118 annotations: &[Node<Annotation>],
2119 statement_kind: StatementKind<'a>,
2120 ) -> Result<KclValueControlFlow, KclError> {
2121 let item = match init {
2122 Expr::None(none) => KclValue::from(none).continue_(),
2123 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state).continue_(),
2124 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?.continue_(),
2125 Expr::Name(name) => self
2126 .resolve_name_for_eval(name, metadata, exec_state)
2127 .await?
2128 .continue_(),
2129 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
2130 Expr::FunctionExpression(function_expression) => self
2131 .create_function_closure(function_expression, annotations, metadata, statement_kind, exec_state)
2132 .await?
2133 .continue_(),
2134 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
2135 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
2136 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
2137 StatementKind::Declaration { name } => {
2138 let message = format!(
2139 "you cannot declare variable {name} as %, because % can only be used in function calls"
2140 );
2141
2142 return Err(KclError::new_semantic(KclErrorDetails::new(
2143 message,
2144 vec![pipe_substitution.into()],
2145 )));
2146 }
2147 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
2148 Some(x) => x.continue_(),
2149 None => {
2150 return Err(KclError::new_semantic(KclErrorDetails::new(
2151 "cannot use % outside a pipe expression".to_owned(),
2152 vec![pipe_substitution.into()],
2153 )));
2154 }
2155 },
2156 },
2157 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
2158 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
2159 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
2160 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
2161 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
2162 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
2163 Expr::LabelledExpression(expr) => {
2164 let value_cf = self
2165 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
2166 .await?;
2167 let value = control_continue!(value_cf);
2168 exec_state
2169 .mut_stack()
2170 .add(expr.label.name.clone(), value.clone(), init.into())?;
2171 value.continue_()
2173 }
2174 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
2175 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
2176 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
2177 };
2178 Ok(item)
2179 }
2180
2181 pub(crate) async fn eval_expr_fresh_root(
2185 &self,
2186 expr: &Expr,
2187 exec_state: &mut ExecState,
2188 metadata: &Metadata,
2189 ) -> Result<KclValueControlFlow, KclError> {
2190 if self.is_machine_executor() {
2191 return crate::execution::machine::run_expr(self, expr, exec_state, metadata).await;
2192 }
2193 self.execute_expr(expr, exec_state, metadata, &[], StatementKind::Expression)
2194 .await
2195 }
2196
2197 pub(super) async fn create_function_closure(
2201 &self,
2202 function_expression: &crate::parsing::ast::types::BoxNode<FunctionExpression>,
2203 annotations: &[Node<Annotation>],
2204 metadata: &Metadata,
2205 statement_kind: StatementKind<'_>,
2206 exec_state: &mut ExecState,
2207 ) -> Result<KclValue, KclError> {
2208 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
2209 let experimental = attrs
2210 .as_ref()
2211 .map(|a| a.experimental)
2212 .unwrap_or_else(|| FnAttrs::default().experimental);
2214
2215 let include_in_feature_tree = attrs
2217 .as_ref()
2218 .map(|a| a.include_in_feature_tree)
2219 .unwrap_or_else(|| FnAttrs::default().include_in_feature_tree);
2221 let (mut closure, placeholder_env_ref) = if let Some(attrs) = attrs
2222 && (attrs.impl_ == annotations::Impl::Rust
2223 || attrs.impl_ == annotations::Impl::RustConstrainable
2224 || attrs.impl_ == annotations::Impl::RustConstraint)
2225 {
2226 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
2227 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
2228 (
2229 KclValue::Function {
2230 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
2231 meta: vec![metadata.to_owned()],
2232 },
2233 None,
2234 )
2235 } else {
2236 return Err(KclError::new_semantic(KclErrorDetails::new(
2237 "Rust implementation of functions is restricted to the standard library".to_owned(),
2238 vec![metadata.source_range],
2239 )));
2240 }
2241 } else {
2242 let std_props = function_expression
2243 .name_str()
2244 .and_then(|name| exec_state.mod_local.path.build_std_fully_qualified_name(name))
2245 .map(|name| StdFnProps::default(&name));
2246 let (env_ref, placeholder_env_ref) = if function_expression.name.is_some() {
2250 let dummy = EnvironmentRef::dummy();
2253 (dummy, Some(dummy))
2254 } else {
2255 (exec_state.mut_stack().snapshot()?, None)
2256 };
2257 (
2258 KclValue::Function {
2259 value: Box::new(FunctionSource::kcl(
2260 function_expression.clone(),
2261 env_ref,
2262 KclFunctionSourceParams {
2263 std_props,
2264 experimental,
2265 include_in_feature_tree,
2266 },
2267 )),
2268 meta: vec![metadata.to_owned()],
2269 },
2270 placeholder_env_ref,
2271 )
2272 };
2273
2274 if let KclValue::Function { value, .. } = &mut closure {
2278 value.resolve_signature_types(exec_state, self).await?;
2279 }
2280
2281 if let Some(fn_name) = &function_expression.name {
2284 if let Some(placeholder_env_ref) = placeholder_env_ref {
2288 closure = exec_state.mut_stack().add_recursive_closure(
2289 fn_name.name.to_owned(),
2290 closure,
2291 placeholder_env_ref,
2292 metadata.source_range,
2293 )?;
2294 } else {
2295 exec_state
2297 .mut_stack()
2298 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
2299 }
2300 }
2301
2302 Ok(closure)
2303 }
2304}
2305
2306pub(super) fn skip_if_not_yet_added(
2310 annotations: &[Node<Annotation>],
2311 key: impl FnOnce() -> String,
2312 exported: bool,
2313 source_range: SourceRange,
2314 exec_state: &mut ExecState,
2315) -> Result<bool, KclError> {
2316 let Some(added_in) = annotations::added_in_version(annotations, source_range)? else {
2317 return Ok(false);
2318 };
2319 if annotations::version_ge(exec_state.entry_point_kcl_version().as_str(), &added_in) {
2320 return Ok(false);
2321 }
2322 exec_state.record_not_yet_added(key(), added_in, exported);
2323 Ok(true)
2324}
2325
2326fn module_enum_clash(name: &str, source_range: SourceRange) -> KclError {
2335 KclError::new_semantic(KclErrorDetails::new(
2336 format!(
2337 "An enum and a module cannot share the name `{name}` in the same scope, because `{name}::x` would be ambiguous. Rename one of them."
2338 ),
2339 vec![source_range],
2340 ))
2341}
2342
2343fn reject_enum_clashing_with_module(
2345 exec_state: &ExecState,
2346 name: &str,
2347 source_range: SourceRange,
2348) -> Result<(), KclError> {
2349 let key = format!("{}{}", memory::MODULE_PREFIX, name);
2350 if !exec_state.stack().cur_frame_contains(&key)? {
2351 return Ok(());
2352 }
2353
2354 Err(module_enum_clash(name, source_range))
2355}
2356
2357fn reject_module_clashing_with_enum(
2360 exec_state: &ExecState,
2361 name: &str,
2362 source_range: SourceRange,
2363) -> Result<(), KclError> {
2364 let key = format!("{}{}", memory::TYPE_PREFIX, name);
2365 if !exec_state.stack().cur_frame_contains(&key)? {
2366 return Ok(());
2367 }
2368
2369 let Ok(KclValue::Type {
2370 value: TypeDef::Enum(_),
2371 ..
2372 }) = exec_state.stack().get(&key, source_range)
2373 else {
2374 return Ok(());
2375 };
2376
2377 Err(module_enum_clash(name, source_range))
2378}
2379
2380fn different_enums_err(left: &EnumValue, right: &EnumValue, source_range: SourceRange) -> KclError {
2382 let left_name = left.enum_id().declared_name();
2383 let right_name = right.enum_id().declared_name();
2384
2385 let message = if left_name == right_name {
2386 format!(
2389 "Cannot compare two different enums that are both named `{left_name}`. They come from separate declarations."
2390 )
2391 } else {
2392 format!("Cannot compare enum `{left_name}` with enum `{right_name}`. They are different types.")
2393 };
2394
2395 KclError::new_semantic(KclErrorDetails::new(message, vec![source_range]))
2396}
2397
2398fn type_used_as_value(exec_state: &ExecState, name: &Node<Identifier>) -> Option<KclError> {
2404 let key = format!("{}{}", memory::TYPE_PREFIX, name.name);
2405 let KclValue::Type { value: def, .. } = exec_state.stack().get(&key, name.as_source_range()).ok()? else {
2406 return None;
2407 };
2408
2409 let suggestion = match &def {
2412 TypeDef::Enum(def) => def
2413 .variants()
2414 .first()
2415 .map(|variant| format!(" Use one of its variants, such as `{}::{variant}`.", name.name))
2416 .unwrap_or_default(),
2417 _ => String::new(),
2418 };
2419
2420 Some(KclError::new_semantic(KclErrorDetails::new(
2421 format!("`{}` is a type, not a value.{suggestion}", name.name),
2422 name.as_source_ranges(),
2423 )))
2424}
2425
2426enum EnumPathHead {
2427 Enum {
2428 def: Arc<EnumTypeDef>,
2429 binding_experimental: bool,
2430 },
2431 NonEnumType,
2432}
2433
2434fn enum_named_by_segment(
2441 exec_state: &ExecState,
2442 segment: &Node<Identifier>,
2443 within: Option<&ModuleItems>,
2444) -> Option<EnumPathHead> {
2445 match type_value_named_by_segment(exec_state, segment, within)? {
2446 KclValue::Type {
2447 value: TypeDef::Enum(def),
2448 experimental,
2449 ..
2450 } => Some(EnumPathHead::Enum {
2451 def,
2452 binding_experimental: experimental,
2453 }),
2454 KclValue::Type { .. } => Some(EnumPathHead::NonEnumType),
2455 _ => None,
2456 }
2457}
2458
2459fn non_enum_type_in_path(segment: &Node<Identifier>) -> KclError {
2460 KclError::new_semantic(KclErrorDetails::new(
2461 format!(
2462 "`{}` is a type that does not resolve to an enum, so it cannot be used as the head of a `::` path.",
2463 segment.name
2464 ),
2465 segment.as_source_ranges(),
2466 ))
2467}
2468
2469fn enum_variant_value(
2471 def: Arc<EnumTypeDef>,
2472 binding_experimental: bool,
2473 variant: &Node<Identifier>,
2474 exec_state: &mut ExecState,
2475) -> Result<KclValue, KclError> {
2476 let enum_name = def.id().declared_name();
2477
2478 if !def.has_variant(&variant.name) {
2479 let known = if def.variants().is_empty() {
2480 format!("Enum `{enum_name}` has no variants")
2481 } else {
2482 format!("Its variants are: {}", def.variants().join(", "))
2483 };
2484
2485 return Err(KclError::new_semantic(KclErrorDetails::new(
2486 format!("`{}` is not a variant of enum `{enum_name}`. {known}.", variant.name),
2487 variant.as_source_ranges(),
2488 )));
2489 }
2490
2491 if !exec_state.entry_point_version_is_v3_or_higher() || def.is_experimental() || binding_experimental {
2494 exec_state.warn_experimental(&format!("the enum `{enum_name}`"), variant.as_source_range());
2495 }
2496
2497 Ok(KclValue::Enum {
2501 value: Box::new(EnumValue::new(
2502 def,
2503 variant.name.clone(),
2504 vec![Metadata {
2505 source_range: variant.as_source_range(),
2506 }],
2507 )),
2508 })
2509}
2510
2511fn reject_glob_import_clash(
2514 exec_state: &ExecState,
2515 key: &str,
2516 item: &KclValue,
2517 source_range: SourceRange,
2518) -> Result<(), KclError> {
2519 if let Some(name) = key.strip_prefix(memory::MODULE_PREFIX) {
2520 return reject_module_clashing_with_enum(exec_state, name, source_range);
2521 }
2522
2523 if let Some(name) = key.strip_prefix(memory::TYPE_PREFIX)
2524 && matches!(
2525 item,
2526 KclValue::Type {
2527 value: TypeDef::Enum(_),
2528 ..
2529 }
2530 )
2531 {
2532 return reject_enum_clashing_with_module(exec_state, name, source_range);
2533 }
2534
2535 Ok(())
2536}
2537
2538pub(super) fn sketch_mode_should_skip(expr: &Expr) -> bool {
2541 fn contains_edited_sketch_block(node: crate::walk::Node<'_>) -> bool {
2542 if let crate::walk::Node::SketchBlock(sketch_block) = node {
2543 return sketch_block.is_being_edited;
2544 }
2545 node.children().into_iter().any(contains_edited_sketch_block)
2546 }
2547
2548 !contains_edited_sketch_block(expr.into())
2549}
2550
2551fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
2554 let KclError::UndefinedValue { name, mut details } = e else {
2555 return e;
2556 };
2557 if let (Some(name0), Some(name1)) = (&being_declared, &name)
2561 && name0 == name1
2562 {
2563 details.message = format!(
2564 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
2565 );
2566 }
2567 KclError::UndefinedValue { details, name }
2568}
2569
2570impl Node<AscribedExpression> {
2571 #[async_recursion]
2572 pub(super) async fn get_result(
2573 &self,
2574 exec_state: &mut ExecState,
2575 ctx: &ExecutorContext,
2576 ) -> Result<KclValueControlFlow, KclError> {
2577 let metadata = Metadata {
2578 source_range: SourceRange::from(self),
2579 };
2580 let result = ctx
2581 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
2582 .await?;
2583 let result = control_continue!(result);
2584 apply_ascription(&result, &self.ty, exec_state, ctx, self.into())
2585 .await
2586 .map(KclValue::continue_)
2587 }
2588}
2589
2590impl Node<SketchBlock> {
2591 pub(super) async fn get_result(
2592 &self,
2593 exec_state: &mut ExecState,
2594 ctx: &ExecutorContext,
2595 ) -> Result<KclValueControlFlow, KclError> {
2596 if exec_state.mod_local.sketch_block.is_some() {
2597 return Err(KclError::new_semantic(KclErrorDetails::new(
2599 "Cannot execute a sketch block from within another sketch block".to_owned(),
2600 vec![SourceRange::from(self)],
2601 )));
2602 }
2603
2604 let range = SourceRange::from(self);
2605
2606 let (sketch_id, sketch_surface) = match self.exec_arguments(exec_state, ctx).await {
2608 Ok(x) => x,
2609 Err(cf_error) => match cf_error {
2610 EarlyReturn::Value(cf_value) => return Ok(cf_value),
2612 EarlyReturn::Error(err) => return Err(err),
2613 },
2614 };
2615 let sketch_block_artifact_id = self.scene_setup(sketch_id, &sketch_surface, exec_state)?;
2616
2617 let (return_result, variables, sketch_block_state) = {
2618 self.prep_mem(exec_state.mut_stack().snapshot()?, exec_state)?;
2620
2621 let initial_sketch_block_state = {
2623 SketchBlockState {
2624 sketch_id: Some(sketch_id),
2625 ..Default::default()
2626 }
2627 };
2628
2629 let original_value = exec_state.mod_local.sketch_block.replace(initial_sketch_block_state);
2630
2631 let original_sketch_mode = std::mem::replace(&mut exec_state.mod_local.sketch_mode, false);
2634
2635 let (result, block_variables) = match self.load_sketch2_into_current_scope(exec_state, ctx, range).await {
2640 Ok(()) => {
2641 let parent = exec_state.mut_stack().snapshot()?;
2642 exec_state.mut_stack().push_new_env_for_call(parent)?;
2643 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
2644 let (result, block_variables) = match exec_state.stack().find_all_in_current_env() {
2645 Ok(block_variables) => (result, block_variables.into_iter().collect::<IndexMap<_, _>>()),
2646 Err(err) => (Err(err), IndexMap::new()),
2647 };
2648 let result = match exec_state.mut_stack().pop_env() {
2649 Ok(_) => result,
2650 Err(err) => Err(err),
2651 };
2652 (result, block_variables)
2653 }
2654 Err(err) => (Err(err), IndexMap::new()),
2655 };
2656
2657 exec_state.mod_local.sketch_mode = original_sketch_mode;
2658
2659 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
2660
2661 let result = match exec_state.mut_stack().pop_env() {
2663 Ok(_) => result,
2664 Err(err) => Err(err),
2665 };
2666
2667 (result, block_variables, sketch_block_state)
2668 };
2669
2670 let return_control_flow = return_result?;
2672 if let Some(control_flow) = return_control_flow
2677 && control_flow.is_some_return()
2678 {
2679 exec_state.push_op(Operation::GroupEnd);
2682 return Ok(control_flow);
2683 }
2684 let Some(sketch_block_state) = sketch_block_state else {
2685 debug_assert!(false, "Sketch block state should still be set to Some from just above");
2686 return Err(internal_err(
2687 "Sketch block state should still be set to Some from just above",
2688 self,
2689 ));
2690 };
2691 let return_value = self
2692 .finalize_sketch_block(
2693 sketch_id,
2694 &sketch_surface,
2695 sketch_block_artifact_id,
2696 variables,
2697 sketch_block_state,
2698 exec_state,
2699 ctx,
2700 )
2701 .await?;
2702 Ok(if self.is_being_edited {
2703 return_value.exit()
2706 } else {
2707 return_value.continue_()
2708 })
2709 }
2710
2711 async fn exec_arguments(
2721 &self,
2722 exec_state: &mut ExecState,
2723 ctx: &ExecutorContext,
2724 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2725 if !exec_state.sketch_mode() {
2726 let mut labeled = IndexMap::new();
2732 for labeled_arg in &self.arguments {
2733 let source_range = SourceRange::from(labeled_arg.arg.clone());
2734 let metadata = Metadata { source_range };
2735 let value_cf = ctx
2736 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
2737 .await?;
2738 let value = early_return!(value_cf);
2739 let arg = Arg::new(value, source_range);
2740 match &labeled_arg.label {
2741 Some(label) => {
2742 labeled.insert(label.name.clone(), arg);
2743 }
2744 None => {
2745 let name = labeled_arg.arg.ident_name();
2746 if let Some(name) = name {
2747 labeled.insert(name.to_owned(), arg);
2748 } else {
2749 return Err(KclError::new_semantic(KclErrorDetails::new(
2750 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
2751 vec![SourceRange::from(&labeled_arg.arg)],
2752 ))
2753 .into());
2754 }
2755 }
2756 }
2757 }
2758 self.finish_arguments_after_eval(labeled, exec_state, ctx).await
2759 } else {
2760 self.arguments_from_cache(exec_state)
2761 }
2762 }
2763
2764 pub(super) async fn finish_arguments_after_eval(
2768 &self,
2769 labeled: IndexMap<String, Arg>,
2770 exec_state: &mut ExecState,
2771 ctx: &ExecutorContext,
2772 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2773 let range = SourceRange::from(self);
2774 let mut args = Args::new_no_args(
2775 range,
2776 self.node_path.clone(),
2777 ctx.clone(),
2778 Some(SketchBlock::CALLEE_NAME.to_owned()),
2779 );
2780 args.labeled = labeled;
2781
2782 self.check_for_unexpected_arguments(&args, exec_state)?;
2790
2791 let arg_on_value: KclValue =
2792 args.get_kw_arg(SKETCH_BLOCK_PARAM_ON, &RuntimeType::sketch_or_surface(), exec_state)?;
2793
2794 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2795 let message = "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2796 debug_assert!(false, "{message}");
2797 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2798 };
2799 let mut sketch_surface = arg_on.into_sketch_surface();
2800
2801 match &mut sketch_surface {
2804 SketchSurface::Plane(plane) => {
2805 ensure_sketch_plane_in_engine(plane, exec_state, ctx, range, self.node_path.clone()).await?;
2807 }
2808 SketchSurface::Face(_) => {
2809 }
2811 }
2812
2813 let sketch_id = exec_state.next_object_id();
2819 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2820 let on_cache_name = sketch_on_cache_name(sketch_id);
2821 exec_state.mut_stack().add(on_cache_name, arg_on_value, range)?;
2823
2824 Ok((sketch_id, sketch_surface))
2825 }
2826
2827 pub(super) fn arguments_from_cache(
2830 &self,
2831 exec_state: &mut ExecState,
2832 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2833 let range = SourceRange::from(self);
2834 {
2835 let sketch_id = exec_state.next_object_id();
2842 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2843 let on_cache_name = sketch_on_cache_name(sketch_id);
2844 let arg_on_value = exec_state.stack().get_owned(&on_cache_name, range)?;
2845
2846 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2847 let message =
2848 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2849 debug_assert!(false, "{message}");
2850 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2851 };
2852 let mut sketch_surface = arg_on.into_sketch_surface();
2853
2854 if sketch_surface.object_id().is_none() {
2857 let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
2860 return Err(internal_err(
2861 "In sketch mode, the `on` plane argument must refer to an existing plane object.",
2862 range,
2863 )
2864 .into());
2865 };
2866 sketch_surface.set_object_id(last_object.id);
2867 }
2868
2869 Ok((sketch_id, sketch_surface))
2870 }
2871 }
2872
2873 pub(super) fn scene_setup(
2876 &self,
2877 sketch_id: ObjectId,
2878 sketch_surface: &SketchSurface,
2879 exec_state: &mut ExecState,
2880 ) -> Result<ArtifactId, KclError> {
2881 let range = SourceRange::from(self);
2882 let on_object_id = if let Some(object_id) = sketch_surface.object_id() {
2883 object_id
2884 } else {
2885 let message = "The `on` argument should have an object after ensure_sketch_plane_in_engine".to_owned();
2886 debug_assert!(false, "{message}");
2887 return Err(internal_err(message, range));
2888 };
2889 let sketch_ctor_on = sketch_on_frontend_plane(&self.arguments, on_object_id);
2890 let sketch_block_artifact_id = {
2891 use crate::execution::CodeRef;
2892 use crate::execution::SketchBlock;
2893 use crate::front::Plane;
2894 use crate::front::SourceRef;
2895
2896 let on_object = exec_state.mod_local.artifacts.scene_object_by_id(on_object_id);
2897
2898 let plane_artifact_id = on_object.map(|object| object.artifact_id);
2900 let plane_info = match &sketch_surface {
2901 SketchSurface::Plane(plane) => Some(super::artifact::artifact_plane_info(&plane.info)),
2902 SketchSurface::Face(_) => None,
2903 };
2904
2905 let standard_plane = match &sketch_ctor_on {
2906 Plane::Default(plane) => Some(*plane),
2907 Plane::Object(_) | Plane::PrimitiveFace(_) => None,
2908 };
2909
2910 let artifact_id = ArtifactId::from(exec_state.next_uuid());
2911 let label = exec_state.mod_local.being_declared.clone().unwrap_or_default();
2927 let sketch_scene_object = Object {
2929 id: sketch_id,
2930 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
2931 args: crate::front::SketchCtor { on: sketch_ctor_on },
2932 plane: on_object_id,
2933 segments: Default::default(),
2934 constraints: Default::default(),
2935 }),
2936 label,
2937 comments: Default::default(),
2938 artifact_id,
2939 source: SourceRef::new(self.into(), self.node_path.clone()),
2940 };
2941 exec_state.set_scene_object(sketch_scene_object);
2942
2943 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
2945 id: artifact_id,
2946 standard_plane,
2947 plane_id: plane_artifact_id,
2948 plane_info,
2949 path_id: None,
2953 code_ref: CodeRef::placeholder(range),
2954 sketch_id,
2955 }));
2956
2957 exec_state.push_op(Operation::GroupBegin {
2958 group: Group::SketchBlock { sketch_id },
2959 node_path: NodePath::placeholder(),
2960 source_range: range,
2961 });
2962 artifact_id
2963 };
2964 Ok(sketch_block_artifact_id)
2965 }
2966
2967 #[allow(clippy::too_many_arguments)]
2972 pub(super) async fn finalize_sketch_block(
2973 &self,
2974 sketch_id: ObjectId,
2975 sketch_surface: &SketchSurface,
2976 sketch_block_artifact_id: ArtifactId,
2977 variables: IndexMap<String, KclValue>,
2978 mut sketch_block_state: SketchBlockState,
2979 exec_state: &mut ExecState,
2980 ctx: &ExecutorContext,
2981 ) -> Result<KclValue, KclError> {
2982 let range = SourceRange::from(self);
2983 let constraints = sketch_block_state
2985 .solver_constraints
2986 .iter()
2987 .cloned()
2988 .map(ezpz::ConstraintRequest::highest_priority)
2989 .chain(
2990 sketch_block_state
2992 .solver_optional_constraints
2993 .iter()
2994 .cloned()
2995 .map(|c| ezpz::ConstraintRequest::new(c, 1)),
2996 )
2997 .collect::<Vec<_>>();
2998 let initial_guesses = sketch_block_state
2999 .sketch_vars
3000 .iter()
3001 .map(|v| {
3002 let Some(sketch_var) = v.as_sketch_var() else {
3003 return Err(internal_err("Expected sketch variable", self));
3004 };
3005 let constraint_id = sketch_var.id.to_constraint_id(range)?;
3006 let number_value = KclValue::Number {
3008 value: sketch_var.initial_value,
3009 ty: sketch_var.ty,
3010 meta: sketch_var.meta.clone(),
3011 };
3012 let initial_guess_value = normalize_to_solver_distance_unit(
3013 &number_value,
3014 v.into(),
3015 exec_state,
3016 "sketch variable initial value",
3017 )?;
3018 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
3019 n.n
3020 } else {
3021 let message = format!(
3022 "Expected number after coercion, but found {}",
3023 initial_guess_value.human_friendly_type()
3024 );
3025 debug_assert!(false, "{}", &message);
3026 return Err(internal_err(message, self));
3027 };
3028 Ok((constraint_id, initial_guess))
3029 })
3030 .collect::<Result<Vec<_>, KclError>>()?;
3031 let config = ezpz::Config::default()
3033 .with_max_iterations(50)
3034 .with_convergence_tolerance(solver_convergence_tolerance(exec_state));
3035 let solve_result = if exec_state.mod_local.freedom_analysis {
3036 ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
3037 let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
3038 (outcome.outcome, Some(freedom_analysis))
3039 })
3040 } else {
3041 ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
3042 };
3043 let num_required_constraints = sketch_block_state.solver_constraints.len();
3045 let all_constraints: Vec<ezpz::Constraint> = sketch_block_state
3046 .solver_constraints
3047 .iter()
3048 .cloned()
3049 .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
3050 .collect();
3051
3052 let (solve_outcome, solve_analysis) = match solve_result {
3053 Ok((solved, freedom)) => {
3054 if solved
3055 .final_values()
3056 .iter()
3057 .any(|number| number.is_infinite() || number.is_nan())
3058 {
3059 return Err(KclError::new_internal(KclErrorDetails::new(
3060 "KCL's 2D constraint solver returned an invalid number".to_owned(),
3061 vec![SourceRange::from(self)],
3062 )));
3063 }
3064 let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
3065 if !outcome.converged {
3066 exec_state.warn(
3067 CompilationIssue::err(range, "Constraint solver failed to find a solution".to_owned()),
3068 annotations::WARN_SOLVER,
3069 );
3070 }
3071 (outcome, freedom)
3072 }
3073 Err(failure) => {
3074 match &failure.error {
3075 NonLinearSystemError::FaerMatrix { .. }
3076 | NonLinearSystemError::Faer { .. }
3077 | NonLinearSystemError::FaerSolve { .. }
3078 | NonLinearSystemError::FaerSvd(..) => {
3079 exec_state.warn(
3082 CompilationIssue::err(range, "Internal error in constraint solver".to_owned()),
3083 annotations::WARN_SOLVER,
3084 );
3085 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
3086 (
3087 Solved {
3088 final_values,
3089 iterations: Default::default(),
3090 warnings: failure.warnings,
3091 priority_solved: Default::default(),
3092 variables_in_conflicts: Default::default(),
3093 unsatisfied_directional_constraints: Default::default(),
3094 converged: false,
3095 },
3096 None,
3097 )
3098 }
3099 NonLinearSystemError::EmptySystemNotAllowed
3100 | NonLinearSystemError::WrongNumberGuesses { .. }
3101 | NonLinearSystemError::MissingGuess { .. }
3102 | NonLinearSystemError::NotFound(..) => {
3103 #[cfg(target_arch = "wasm32")]
3106 web_sys::console::error_1(
3107 &format!("Internal error from constraint solver: {}", failure.error).into(),
3108 );
3109 return Err(internal_err(
3110 format!("Internal error from constraint solver: {}", failure.error),
3111 self,
3112 ));
3113 }
3114 _ => {
3115 return Err(internal_err(
3117 format!("Error from constraint solver: {}", failure.error),
3118 self,
3119 ));
3120 }
3121 }
3122 }
3123 };
3124 for warning in &solve_outcome.warnings {
3126 let message = if let Some(index) = warning.about_constraint.as_ref() {
3127 format!("{}; constraint index {}", warning.content, index)
3128 } else {
3129 format!("{}", warning.content)
3130 };
3131 exec_state.warn(CompilationIssue::err(range, message), annotations::WARN_SOLVER);
3132 }
3133 if solve_outcome.converged {
3134 exec_state.mod_local.artifacts.refactor_metadata.extend(
3135 sketch_block_state
3136 .pending_legacy_angle_refactor_metadata
3137 .iter()
3138 .filter_map(|pending| {
3139 finalize_legacy_angle_refactor_meta(pending, &solve_outcome.final_values)
3140 .map(RefactorMetadata::LegacyAngle)
3141 }),
3142 );
3143 }
3144 let sketch_engine_id = exec_state.next_uuid();
3146 let solution_ty = solver_numeric_type(exec_state);
3147 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
3148 for unsolved_segment in &sketch_block_state.needed_by_engine {
3149 solved_segments.push(substitute_sketch_var_in_segment(
3150 unsolved_segment.clone(),
3151 sketch_surface,
3152 sketch_engine_id,
3153 None,
3154 &solve_outcome,
3155 solver_numeric_type(exec_state),
3156 solve_analysis.as_ref(),
3157 )?);
3158 }
3159 exec_state.mod_local.artifacts.var_solutions =
3165 sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
3166
3167 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
3169
3170 let sketch = create_segments_in_engine(
3172 sketch_surface,
3173 sketch_engine_id,
3174 &mut solved_segments,
3175 &sketch_block_state.segment_tags,
3176 ctx,
3177 exec_state,
3178 range,
3179 )
3180 .await?;
3181
3182 if let Some(sketch_artifact_id) = sketch.as_ref().map(|s| s.artifact_id) {
3184 if let Some(Artifact::SketchBlock(sketch_block_artifact)) =
3185 exec_state.artifact_mut(sketch_block_artifact_id)
3186 {
3187 sketch_block_artifact.path_id = Some(sketch_artifact_id);
3188 } else {
3189 let message = "Sketch block artifact not found, so path couldn't be linked to it".to_owned();
3190 debug_assert!(false, "{message}");
3191 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3192 }
3193 }
3194
3195 let variables = substitute_sketch_vars(
3200 variables,
3201 sketch_surface,
3202 sketch_engine_id,
3203 sketch.as_ref(),
3204 &solve_outcome,
3205 solution_ty,
3206 solve_analysis.as_ref(),
3207 )?;
3208
3209 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
3210 for scene_object in scene_objects {
3211 segment_object_ids.push(scene_object.id);
3212 exec_state.set_scene_object(scene_object);
3214 }
3215 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
3217 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
3218 debug_assert!(false, "{}", &message);
3219 return Err(internal_err(message, range));
3220 };
3221 let ObjectKind::Sketch(front_sketch) = &mut sketch_object.kind else {
3222 let message = format!(
3223 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
3224 sketch_id, sketch_object
3225 );
3226 debug_assert!(
3227 false,
3228 "{}; scene_objects={:#?}",
3229 message, exec_state.mod_local.artifacts.scene_objects
3230 );
3231 return Err(internal_err(message, range));
3232 };
3233 front_sketch.segments.extend(segment_object_ids);
3234 front_sketch
3236 .constraints
3237 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
3238
3239 exec_state.push_op(Operation::GroupEnd);
3241
3242 if exec_state.mod_local.freedom_analysis {
3246 let status = {
3247 let scene_objects = &exec_state.mod_local.artifacts.scene_objects;
3248 scene_objects
3249 .get(sketch_id.0)
3250 .and_then(|obj| sketch_constraint_status_for_sketch(scene_objects, obj))
3251 };
3252 if let Some(status) = status
3253 && status.status == ConstraintKind::OverConstrained
3254 {
3255 let description = if status.conflict_count == 1 {
3256 "segment has"
3257 } else {
3258 "segments have"
3259 };
3260 let message = format!(
3261 "Sketch is over-constrained: {} {description} conflicting constraints.{}",
3262 status.conflict_count,
3263 signed_distance_conflict_hint(&solve_outcome),
3264 );
3265 exec_state.warn(
3266 CompilationIssue::err(range, message),
3267 annotations::WARN_OVER_CONSTRAINED_SKETCH,
3268 );
3269 }
3270 }
3271
3272 let properties = self.sketch_properties(sketch, variables);
3273 let metadata = Metadata {
3274 source_range: SourceRange::from(self),
3275 };
3276 let return_value = KclValue::Object {
3277 value: properties,
3278 constrainable: Default::default(),
3279 object_kind: KclObjectKind::Default,
3280 meta: vec![metadata],
3281 };
3282 Ok(return_value)
3283 }
3284
3285 fn check_for_unexpected_arguments(&self, args: &Args, exec_state: &mut ExecState) -> Result<(), KclError> {
3288 if !args.unlabeled.is_empty() {
3289 let message = "Sketch block doesn't support unlabeled arguments; argument shorthand should have already been desugared";
3290 debug_assert!(false, "{message}");
3291 return Err(KclError::new_internal(KclErrorDetails::new(
3292 message.to_owned(),
3293 vec![args.source_range],
3294 )));
3295 }
3296 for (label, arg) in &args.labeled {
3297 if label == SKETCH_BLOCK_PARAM_ON {
3298 continue;
3299 }
3300 exec_state.err(CompilationIssue::err(
3301 arg.source_range,
3302 unexpected_kw_arg_message(label, Some(SketchBlock::CALLEE_NAME)),
3303 ));
3304 }
3305 Ok(())
3306 }
3307
3308 pub(super) async fn load_sketch2_into_current_scope(
3309 &self,
3310 exec_state: &mut ExecState,
3311 ctx: &ExecutorContext,
3312 source_range: SourceRange,
3313 ) -> Result<(), KclError> {
3314 let path = vec!["std".to_owned(), "solver".to_owned()];
3315 let resolved_path = ModulePath::from_std_import_path(&path)?;
3316 let module_id = ctx
3317 .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
3318 .await?;
3319 let ModuleItems {
3320 environment: env_ref,
3321 exports,
3322 ..
3323 } = ctx.exec_module_for_items(module_id, exec_state, source_range).await?;
3324
3325 for name in exports {
3326 let value = exec_state
3327 .stack()
3328 .memory
3329 .get_from_owned(&name, env_ref, source_range, 0)?;
3330 exec_state.mut_stack().add(name, value, source_range)?;
3331 }
3332 Ok(())
3333 }
3334
3335 pub(crate) fn sketch_properties(
3339 &self,
3340 sketch: Option<Sketch>,
3341 variables: HashMap<String, KclValue>,
3342 ) -> HashMap<String, KclValue> {
3343 let Some(sketch) = sketch else {
3344 return variables;
3347 };
3348
3349 let mut properties = variables;
3350
3351 let sketch_value = KclValue::Sketch {
3352 value: Box::new(sketch),
3353 };
3354 let mut meta_map = HashMap::with_capacity(1);
3355 meta_map.insert(SKETCH_OBJECT_META_SKETCH.to_owned(), sketch_value);
3356 let meta_value = KclValue::Object {
3357 value: meta_map,
3358 constrainable: false,
3359 object_kind: KclObjectKind::Default,
3360 meta: vec![Metadata {
3361 source_range: SourceRange::from(self),
3362 }],
3363 };
3364
3365 properties.insert(SKETCH_OBJECT_META.to_owned(), meta_value);
3366
3367 properties
3368 }
3369}
3370
3371impl SketchBlock {
3372 pub(super) fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) -> Result<(), KclError> {
3373 exec_state.mut_stack().push_new_env_for_call(parent)
3374 }
3375}
3376
3377impl Node<SketchVar> {
3378 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
3379 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
3380 return Err(KclError::new_semantic(KclErrorDetails::new(
3381 "Cannot use a sketch variable outside of a sketch block".to_owned(),
3382 vec![SourceRange::from(self)],
3383 )));
3384 };
3385 let id = sketch_block_state.next_sketch_var_id();
3386 let sketch_var = if let Some(initial) = &self.initial {
3387 KclValue::from_sketch_var_literal(initial, id, self.node_path.clone(), exec_state)
3388 } else {
3389 let metadata = Metadata {
3390 source_range: SourceRange::from(self),
3391 };
3392
3393 KclValue::SketchVar {
3394 value: Box::new(super::SketchVar {
3395 id,
3396 initial_value: 0.0,
3397 ty: NumericType::default(),
3398 node_path: self.node_path.clone(),
3399 meta: vec![metadata],
3400 }),
3401 }
3402 };
3403
3404 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3405 return Err(KclError::new_semantic(KclErrorDetails::new(
3406 "Cannot use a sketch variable outside of a sketch block".to_owned(),
3407 vec![SourceRange::from(self)],
3408 )));
3409 };
3410 sketch_block_state.sketch_vars.push(sketch_var.clone());
3411
3412 Ok(sketch_var)
3413 }
3414}
3415
3416pub(super) async fn apply_ascription(
3417 value: &KclValue,
3418 ty: &Node<Type>,
3419 exec_state: &mut ExecState,
3420 ctx: &ExecutorContext,
3421 source_range: SourceRange,
3422) -> Result<KclValue, KclError> {
3423 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, ctx, value.into(), false, false).await?;
3424
3425 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
3426 exec_state.clear_units_warnings(&source_range);
3427 }
3428
3429 value.coerce(&ty, CoercionMode::explicit(), exec_state).map_err(|e| {
3430 if let Some(message) = e.message {
3433 return KclError::new_semantic(KclErrorDetails::new(message, vec![source_range]));
3434 }
3435
3436 let suggestion = if ty == RuntimeType::length() {
3437 ", you might try coercing to a fully specified numeric type such as `mm`"
3438 } else if ty == RuntimeType::angle() {
3439 ", you might try coercing to a fully specified numeric type such as `deg`"
3440 } else {
3441 ""
3442 };
3443 let ty_str = if let Some(ty) = value.principal_type() {
3444 format!("(with type `{ty}`) ")
3445 } else {
3446 String::new()
3447 };
3448 KclError::new_semantic(KclErrorDetails::new(
3449 format!(
3450 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
3451 value.human_friendly_type()
3452 ),
3453 vec![source_range],
3454 ))
3455 })
3456}
3457
3458impl BinaryPart {
3459 #[async_recursion]
3460 pub(super) async fn get_result(
3461 &self,
3462 exec_state: &mut ExecState,
3463 ctx: &ExecutorContext,
3464 ) -> Result<KclValueControlFlow, KclError> {
3465 match self {
3466 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
3467 BinaryPart::Name(name) => {
3468 let metadata = Metadata {
3469 source_range: SourceRange::from(&**name),
3470 };
3471 ctx.resolve_name_for_eval(name, &metadata, exec_state)
3472 .await
3473 .map(KclValue::continue_)
3474 }
3475 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
3476 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
3477 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
3478 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
3479 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
3480 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
3481 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
3482 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
3483 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
3484 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
3485 }
3486 }
3487}
3488
3489impl Node<Name> {
3490 pub(super) async fn get_result(
3491 &self,
3492 exec_state: &mut ExecState,
3493 ctx: &ExecutorContext,
3494 ) -> Result<KclValue, KclError> {
3495 let result = self.get_result_inner(exec_state, ctx).await;
3498 result.map_err(|e| var_in_own_ref_err(e, &exec_state.mod_local.being_declared))
3499 }
3500
3501 async fn get_result_inner(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
3502 if self.abs_path {
3503 return Err(KclError::new_semantic(KclErrorDetails::new(
3504 ABSOLUTE_PATHS_NOT_SUPPORTED.to_owned(),
3505 self.as_source_ranges(),
3506 )));
3507 }
3508
3509 if self.path.is_empty() {
3510 if let Ok(item_value) = exec_state.stack().get(&self.name.name, self.into()) {
3511 return Ok(item_value);
3512 }
3513
3514 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
3515 let not_defined = match exec_state.stack().get(&mod_name, self.into()) {
3516 Ok(module) => return Ok(module),
3517 Err(err) => err,
3518 };
3519
3520 return Err(type_used_as_value(exec_state, &self.name)
3524 .unwrap_or_else(|| exec_state.with_not_yet_added_hint(&[&self.name.name, &mod_name], not_defined)));
3525 }
3526
3527 let mut mem_spec: Option<ModuleItems> = None;
3528 for (index, p) in self.path.iter().enumerate() {
3529 let non_enum_type = match enum_named_by_segment(exec_state, p, mem_spec.as_ref()) {
3532 Some(EnumPathHead::Enum {
3533 def,
3534 binding_experimental,
3535 }) => {
3536 if let Some(next) = self.path.get(index + 1) {
3537 return Err(KclError::new_semantic(KclErrorDetails::new(
3538 format!(
3539 "`{}` is an enum, so only a variant name can follow it. There is nothing to reach through `{}::{}`.",
3540 p.name, p.name, next.name
3541 ),
3542 p.as_source_ranges(),
3543 )));
3544 }
3545
3546 return enum_variant_value(def, binding_experimental, &self.name, exec_state);
3547 }
3548 Some(EnumPathHead::NonEnumType) => true,
3549 None => false,
3550 };
3551
3552 let value = match &mem_spec {
3553 Some(items) => {
3554 if !items.exports.contains(&p.name) {
3555 if non_enum_type {
3556 return Err(non_enum_type_in_path(p));
3557 }
3558 return Err(KclError::new_semantic(KclErrorDetails::new(
3559 format!("Item {} not found in module's exported items", p.name),
3560 p.as_source_ranges(),
3561 )));
3562 }
3563
3564 exec_state
3565 .stack()
3566 .memory
3567 .get_from_owned(&p.name, items.environment, p.as_source_range(), 0)?
3568 }
3569 None => {
3570 let module_key = format!("{}{}", memory::MODULE_PREFIX, p.name);
3571 match exec_state.stack().get(&module_key, self.into()) {
3572 Ok(value) => value,
3573 Err(_) if non_enum_type => return Err(non_enum_type_in_path(p)),
3574 Err(err) => return Err(exec_state.with_not_yet_added_hint(&[&module_key], err)),
3575 }
3576 }
3577 };
3578
3579 let module_id = match value {
3580 KclValue::Module { value, .. } => value,
3581 value => {
3582 return Err(KclError::new_semantic(KclErrorDetails::new(
3583 format!(
3584 "Identifier in path must refer to a module, found {}",
3585 value.human_friendly_type()
3586 ),
3587 p.as_source_ranges(),
3588 )));
3589 }
3590 };
3591
3592 mem_spec = Some(
3593 ctx.exec_module_for_items(module_id, exec_state, p.as_source_range())
3594 .await?,
3595 );
3596 }
3597
3598 let ModuleItems {
3599 environment: env,
3600 exports,
3601 not_yet_added,
3602 } = mem_spec.unwrap();
3603
3604 let item_exported = exports.contains(&self.name.name);
3605 let item_value = exec_state
3606 .stack()
3607 .memory
3608 .get_from_owned(&self.name.name, env, self.name.as_source_range(), 0);
3609
3610 if item_exported && item_value.is_ok() {
3612 return item_value;
3613 }
3614
3615 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
3616 let mod_exported = exports.contains(&mod_name);
3617 let mod_value = exec_state
3618 .stack()
3619 .memory
3620 .get_from_owned(&mod_name, env, self.name.as_source_range(), 0);
3621
3622 if mod_exported && mod_value.is_ok() {
3624 return mod_value;
3625 }
3626
3627 if item_value.is_err() && mod_value.is_err() {
3630 return item_value
3631 .map_err(|err| exec_state.with_not_yet_added_hint_from(¬_yet_added, &[&self.name.name], err));
3632 }
3633
3634 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
3636 Err(KclError::new_semantic(KclErrorDetails::new(
3637 format!("Item {} not found in module's exported items", self.name.name),
3638 self.name.as_source_ranges(),
3639 )))
3640 }
3641}
3642
3643fn mock_array_may_have_engine_dependent_cardinality(ty: &RuntimeType) -> bool {
3644 match ty {
3645 RuntimeType::Primitive(
3646 PrimitiveType::Sketch
3647 | PrimitiveType::Solid
3648 | PrimitiveType::Face
3649 | PrimitiveType::Edge
3650 | PrimitiveType::BoundedEdge
3651 | PrimitiveType::ImportedGeometry,
3652 ) => true,
3653 RuntimeType::Union(types) => types.iter().any(mock_array_may_have_engine_dependent_cardinality),
3654 _ => false,
3655 }
3656}
3657
3658impl Node<MemberExpression> {
3659 async fn get_result(
3660 &self,
3661 exec_state: &mut ExecState,
3662 ctx: &ExecutorContext,
3663 ) -> Result<KclValueControlFlow, KclError> {
3664 if exec_state.entry_point_version_is_v3_or_higher() {
3665 let object = control_continue!(self.eval_object(exec_state, ctx).await?);
3668 let property = match self.eval_property(exec_state, ctx).await {
3669 Ok(property) => property,
3670 Err(EarlyReturn::Value(cf)) => return Ok(cf),
3673 Err(EarlyReturn::Error(err)) => return Err(err),
3674 };
3675 return self.apply_member(object, property, exec_state, ctx).await;
3676 }
3677
3678 let property = match self.eval_property(exec_state, ctx).await {
3682 Ok(property) => property,
3683 Err(EarlyReturn::Value(cf)) => return Ok(cf),
3686 Err(EarlyReturn::Error(err)) => return Err(err),
3687 };
3688 let object = control_continue!(self.eval_object(exec_state, ctx).await?);
3689 self.apply_member(object, property, exec_state, ctx).await
3690 }
3691
3692 async fn eval_object(
3697 &self,
3698 exec_state: &mut ExecState,
3699 ctx: &ExecutorContext,
3700 ) -> Result<KclValueControlFlow, KclError> {
3701 let object_meta = Metadata {
3702 source_range: SourceRange::from(&self.object),
3703 };
3704 ctx.execute_expr(&self.object, exec_state, &object_meta, &[], StatementKind::Expression)
3705 .await
3706 }
3707
3708 async fn eval_property(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<Property, EarlyReturn> {
3712 let property_meta = Metadata {
3713 source_range: SourceRange::from(&self.property),
3714 };
3715 Property::try_from(
3716 self.computed,
3717 self.property.clone(),
3718 exec_state,
3719 self.into(),
3720 ctx,
3721 &property_meta,
3722 &[],
3723 StatementKind::Expression,
3724 )
3725 .await
3726 }
3727
3728 pub(super) async fn apply_member(
3732 &self,
3733 object: KclValue,
3734 property: Property,
3735 exec_state: &mut ExecState,
3736 ctx: &ExecutorContext,
3737 ) -> Result<KclValueControlFlow, KclError> {
3738 let meta = Metadata {
3741 source_range: SourceRange::from(self),
3742 };
3743
3744 match (object, property, self.computed) {
3746 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
3747 "at" => match &segment.repr {
3748 SegmentRepr::Unsolved { segment } => {
3749 match &segment.kind {
3750 UnsolvedSegmentKind::Point { position, .. } => {
3751 Ok(KclValue::HomArray {
3753 value: vec![
3754 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
3755 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
3756 ],
3757 ty: RuntimeType::any(),
3758 }
3759 .continue_())
3760 }
3761 _ => Err(KclError::new_undefined_value(
3762 KclErrorDetails::new(
3763 format!("Property '{property}' not found in segment"),
3764 vec![self.clone().into()],
3765 ),
3766 None,
3767 )),
3768 }
3769 }
3770 SegmentRepr::Solved { segment } => {
3771 match &segment.kind {
3772 SegmentKind::Point { position, .. } => {
3773 Ok(KclValue::array_from_point2d(
3775 [position[0].n, position[1].n],
3776 position[0].ty,
3777 segment.meta.clone(),
3778 )
3779 .continue_())
3780 }
3781 _ => Err(KclError::new_undefined_value(
3782 KclErrorDetails::new(
3783 format!("Property '{property}' not found in segment"),
3784 vec![self.clone().into()],
3785 ),
3786 None,
3787 )),
3788 }
3789 }
3790 },
3791 "start" => match &segment.repr {
3792 SegmentRepr::Unsolved { segment } => match &segment.kind {
3793 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
3794 KclErrorDetails::new(
3795 format!("Property '{property}' not found in point segment"),
3796 vec![self.clone().into()],
3797 ),
3798 None,
3799 )),
3800 UnsolvedSegmentKind::Line {
3801 start,
3802 ctor,
3803 start_object_id,
3804 ..
3805 } => Ok(KclValue::Segment {
3806 value: Box::new(AbstractSegment {
3807 repr: SegmentRepr::Unsolved {
3808 segment: Box::new(UnsolvedSegment {
3809 id: segment.id,
3810 object_id: *start_object_id,
3811 kind: UnsolvedSegmentKind::Point {
3812 position: start.clone(),
3813 ctor: Box::new(PointCtor {
3814 position: ctor.start.clone(),
3815 }),
3816 },
3817 tag: segment.tag.clone(),
3818 node_path: segment.node_path.clone(),
3819 meta: segment.meta.clone(),
3820 }),
3821 },
3822 meta: segment.meta.clone(),
3823 }),
3824 }
3825 .continue_()),
3826 UnsolvedSegmentKind::Arc {
3827 start,
3828 ctor,
3829 start_object_id,
3830 ..
3831 } => Ok(KclValue::Segment {
3832 value: Box::new(AbstractSegment {
3833 repr: SegmentRepr::Unsolved {
3834 segment: Box::new(UnsolvedSegment {
3835 id: segment.id,
3836 object_id: *start_object_id,
3837 kind: UnsolvedSegmentKind::Point {
3838 position: start.clone(),
3839 ctor: Box::new(PointCtor {
3840 position: ctor.start.clone(),
3841 }),
3842 },
3843 tag: segment.tag.clone(),
3844 node_path: segment.node_path.clone(),
3845 meta: segment.meta.clone(),
3846 }),
3847 },
3848 meta: segment.meta.clone(),
3849 }),
3850 }
3851 .continue_()),
3852 UnsolvedSegmentKind::Circle {
3853 start,
3854 ctor,
3855 start_object_id,
3856 ..
3857 } => Ok(KclValue::Segment {
3858 value: Box::new(AbstractSegment {
3859 repr: SegmentRepr::Unsolved {
3860 segment: Box::new(UnsolvedSegment {
3861 id: segment.id,
3862 object_id: *start_object_id,
3863 kind: UnsolvedSegmentKind::Point {
3864 position: start.clone(),
3865 ctor: Box::new(PointCtor {
3866 position: ctor.start.clone(),
3867 }),
3868 },
3869 tag: segment.tag.clone(),
3870 node_path: segment.node_path.clone(),
3871 meta: segment.meta.clone(),
3872 }),
3873 },
3874 meta: segment.meta.clone(),
3875 }),
3876 }
3877 .continue_()),
3878 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
3879 KclErrorDetails::new(
3880 format!("Property '{property}' not found in segment"),
3881 vec![self.clone().into()],
3882 ),
3883 None,
3884 )),
3885 },
3886 SegmentRepr::Solved { segment } => match &segment.kind {
3887 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
3888 KclErrorDetails::new(
3889 format!("Property '{property}' not found in point segment"),
3890 vec![self.clone().into()],
3891 ),
3892 None,
3893 )),
3894 SegmentKind::Line {
3895 start,
3896 ctor,
3897 start_object_id,
3898 start_freedom,
3899 ..
3900 } => Ok(KclValue::Segment {
3901 value: Box::new(AbstractSegment {
3902 repr: SegmentRepr::Solved {
3903 segment: Box::new(Segment {
3904 id: segment.id,
3905 object_id: *start_object_id,
3906 kind: SegmentKind::Point {
3907 position: start.clone(),
3908 ctor: Box::new(PointCtor {
3909 position: ctor.start.clone(),
3910 }),
3911 freedom: *start_freedom,
3912 },
3913 surface: segment.surface.clone(),
3914 sketch_id: segment.sketch_id,
3915 sketch: segment.sketch.clone(),
3916 tag: segment.tag.clone(),
3917 node_path: segment.node_path.clone(),
3918 meta: segment.meta.clone(),
3919 }),
3920 },
3921 meta: segment.meta.clone(),
3922 }),
3923 }
3924 .continue_()),
3925 SegmentKind::Arc {
3926 start,
3927 ctor,
3928 start_object_id,
3929 start_freedom,
3930 ..
3931 } => Ok(KclValue::Segment {
3932 value: Box::new(AbstractSegment {
3933 repr: SegmentRepr::Solved {
3934 segment: Box::new(Segment {
3935 id: segment.id,
3936 object_id: *start_object_id,
3937 kind: SegmentKind::Point {
3938 position: start.clone(),
3939 ctor: Box::new(PointCtor {
3940 position: ctor.start.clone(),
3941 }),
3942 freedom: *start_freedom,
3943 },
3944 surface: segment.surface.clone(),
3945 sketch_id: segment.sketch_id,
3946 sketch: segment.sketch.clone(),
3947 tag: segment.tag.clone(),
3948 node_path: segment.node_path.clone(),
3949 meta: segment.meta.clone(),
3950 }),
3951 },
3952 meta: segment.meta.clone(),
3953 }),
3954 }
3955 .continue_()),
3956 SegmentKind::Circle {
3957 start,
3958 ctor,
3959 start_object_id,
3960 start_freedom,
3961 ..
3962 } => Ok(KclValue::Segment {
3963 value: Box::new(AbstractSegment {
3964 repr: SegmentRepr::Solved {
3965 segment: Box::new(Segment {
3966 id: segment.id,
3967 object_id: *start_object_id,
3968 kind: SegmentKind::Point {
3969 position: start.clone(),
3970 ctor: Box::new(PointCtor {
3971 position: ctor.start.clone(),
3972 }),
3973 freedom: *start_freedom,
3974 },
3975 surface: segment.surface.clone(),
3976 sketch_id: segment.sketch_id,
3977 sketch: segment.sketch.clone(),
3978 tag: segment.tag.clone(),
3979 node_path: segment.node_path.clone(),
3980 meta: segment.meta.clone(),
3981 }),
3982 },
3983 meta: segment.meta.clone(),
3984 }),
3985 }
3986 .continue_()),
3987 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
3988 KclErrorDetails::new(
3989 format!("Property '{property}' not found in segment"),
3990 vec![self.clone().into()],
3991 ),
3992 None,
3993 )),
3994 },
3995 },
3996 "end" => match &segment.repr {
3997 SegmentRepr::Unsolved { segment } => match &segment.kind {
3998 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
3999 KclErrorDetails::new(
4000 format!("Property '{property}' not found in point segment"),
4001 vec![self.clone().into()],
4002 ),
4003 None,
4004 )),
4005 UnsolvedSegmentKind::Line {
4006 end,
4007 ctor,
4008 end_object_id,
4009 ..
4010 } => Ok(KclValue::Segment {
4011 value: Box::new(AbstractSegment {
4012 repr: SegmentRepr::Unsolved {
4013 segment: Box::new(UnsolvedSegment {
4014 id: segment.id,
4015 object_id: *end_object_id,
4016 kind: UnsolvedSegmentKind::Point {
4017 position: end.clone(),
4018 ctor: Box::new(PointCtor {
4019 position: ctor.end.clone(),
4020 }),
4021 },
4022 tag: segment.tag.clone(),
4023 node_path: segment.node_path.clone(),
4024 meta: segment.meta.clone(),
4025 }),
4026 },
4027 meta: segment.meta.clone(),
4028 }),
4029 }
4030 .continue_()),
4031 UnsolvedSegmentKind::Arc {
4032 end,
4033 ctor,
4034 end_object_id,
4035 ..
4036 } => Ok(KclValue::Segment {
4037 value: Box::new(AbstractSegment {
4038 repr: SegmentRepr::Unsolved {
4039 segment: Box::new(UnsolvedSegment {
4040 id: segment.id,
4041 object_id: *end_object_id,
4042 kind: UnsolvedSegmentKind::Point {
4043 position: end.clone(),
4044 ctor: Box::new(PointCtor {
4045 position: ctor.end.clone(),
4046 }),
4047 },
4048 tag: segment.tag.clone(),
4049 node_path: segment.node_path.clone(),
4050 meta: segment.meta.clone(),
4051 }),
4052 },
4053 meta: segment.meta.clone(),
4054 }),
4055 }
4056 .continue_()),
4057 UnsolvedSegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
4058 KclErrorDetails::new(
4059 format!("Property '{property}' not found in segment"),
4060 vec![self.into()],
4061 ),
4062 None,
4063 )),
4064 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
4065 KclErrorDetails::new(
4066 format!("Property '{property}' not found in segment"),
4067 vec![self.clone().into()],
4068 ),
4069 None,
4070 )),
4071 },
4072 SegmentRepr::Solved { segment } => match &segment.kind {
4073 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
4074 KclErrorDetails::new(
4075 format!("Property '{property}' not found in point segment"),
4076 vec![self.clone().into()],
4077 ),
4078 None,
4079 )),
4080 SegmentKind::Line {
4081 end,
4082 ctor,
4083 end_object_id,
4084 end_freedom,
4085 ..
4086 } => Ok(KclValue::Segment {
4087 value: Box::new(AbstractSegment {
4088 repr: SegmentRepr::Solved {
4089 segment: Box::new(Segment {
4090 id: segment.id,
4091 object_id: *end_object_id,
4092 kind: SegmentKind::Point {
4093 position: end.clone(),
4094 ctor: Box::new(PointCtor {
4095 position: ctor.end.clone(),
4096 }),
4097 freedom: *end_freedom,
4098 },
4099 surface: segment.surface.clone(),
4100 sketch_id: segment.sketch_id,
4101 sketch: segment.sketch.clone(),
4102 tag: segment.tag.clone(),
4103 node_path: segment.node_path.clone(),
4104 meta: segment.meta.clone(),
4105 }),
4106 },
4107 meta: segment.meta.clone(),
4108 }),
4109 }
4110 .continue_()),
4111 SegmentKind::Arc {
4112 end,
4113 ctor,
4114 end_object_id,
4115 end_freedom,
4116 ..
4117 } => Ok(KclValue::Segment {
4118 value: Box::new(AbstractSegment {
4119 repr: SegmentRepr::Solved {
4120 segment: Box::new(Segment {
4121 id: segment.id,
4122 object_id: *end_object_id,
4123 kind: SegmentKind::Point {
4124 position: end.clone(),
4125 ctor: Box::new(PointCtor {
4126 position: ctor.end.clone(),
4127 }),
4128 freedom: *end_freedom,
4129 },
4130 surface: segment.surface.clone(),
4131 sketch_id: segment.sketch_id,
4132 sketch: segment.sketch.clone(),
4133 tag: segment.tag.clone(),
4134 node_path: segment.node_path.clone(),
4135 meta: segment.meta.clone(),
4136 }),
4137 },
4138 meta: segment.meta.clone(),
4139 }),
4140 }
4141 .continue_()),
4142 SegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
4143 KclErrorDetails::new(
4144 format!("Property '{property}' not found in segment"),
4145 vec![self.into()],
4146 ),
4147 None,
4148 )),
4149 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
4150 KclErrorDetails::new(
4151 format!("Property '{property}' not found in segment"),
4152 vec![self.clone().into()],
4153 ),
4154 None,
4155 )),
4156 },
4157 },
4158 "center" => match &segment.repr {
4159 SegmentRepr::Unsolved { segment } => match &segment.kind {
4160 UnsolvedSegmentKind::Arc {
4161 center,
4162 ctor,
4163 center_object_id,
4164 ..
4165 } => Ok(KclValue::Segment {
4166 value: Box::new(AbstractSegment {
4167 repr: SegmentRepr::Unsolved {
4168 segment: Box::new(UnsolvedSegment {
4169 id: segment.id,
4170 object_id: *center_object_id,
4171 kind: UnsolvedSegmentKind::Point {
4172 position: center.clone(),
4173 ctor: Box::new(PointCtor {
4174 position: ctor.center.clone(),
4175 }),
4176 },
4177 tag: segment.tag.clone(),
4178 node_path: segment.node_path.clone(),
4179 meta: segment.meta.clone(),
4180 }),
4181 },
4182 meta: segment.meta.clone(),
4183 }),
4184 }
4185 .continue_()),
4186 UnsolvedSegmentKind::Circle {
4187 center,
4188 ctor,
4189 center_object_id,
4190 ..
4191 } => Ok(KclValue::Segment {
4192 value: Box::new(AbstractSegment {
4193 repr: SegmentRepr::Unsolved {
4194 segment: Box::new(UnsolvedSegment {
4195 id: segment.id,
4196 object_id: *center_object_id,
4197 kind: UnsolvedSegmentKind::Point {
4198 position: center.clone(),
4199 ctor: Box::new(PointCtor {
4200 position: ctor.center.clone(),
4201 }),
4202 },
4203 tag: segment.tag.clone(),
4204 node_path: segment.node_path.clone(),
4205 meta: segment.meta.clone(),
4206 }),
4207 },
4208 meta: segment.meta.clone(),
4209 }),
4210 }
4211 .continue_()),
4212 _ => Err(KclError::new_undefined_value(
4213 KclErrorDetails::new(
4214 format!("Property '{property}' not found in segment"),
4215 vec![self.clone().into()],
4216 ),
4217 None,
4218 )),
4219 },
4220 SegmentRepr::Solved { segment } => match &segment.kind {
4221 SegmentKind::Arc {
4222 center,
4223 ctor,
4224 center_object_id,
4225 center_freedom,
4226 ..
4227 } => Ok(KclValue::Segment {
4228 value: Box::new(AbstractSegment {
4229 repr: SegmentRepr::Solved {
4230 segment: Box::new(Segment {
4231 id: segment.id,
4232 object_id: *center_object_id,
4233 kind: SegmentKind::Point {
4234 position: center.clone(),
4235 ctor: Box::new(PointCtor {
4236 position: ctor.center.clone(),
4237 }),
4238 freedom: *center_freedom,
4239 },
4240 surface: segment.surface.clone(),
4241 sketch_id: segment.sketch_id,
4242 sketch: segment.sketch.clone(),
4243 tag: segment.tag.clone(),
4244 node_path: segment.node_path.clone(),
4245 meta: segment.meta.clone(),
4246 }),
4247 },
4248 meta: segment.meta.clone(),
4249 }),
4250 }
4251 .continue_()),
4252 SegmentKind::Circle {
4253 center,
4254 ctor,
4255 center_object_id,
4256 center_freedom,
4257 ..
4258 } => Ok(KclValue::Segment {
4259 value: Box::new(AbstractSegment {
4260 repr: SegmentRepr::Solved {
4261 segment: Box::new(Segment {
4262 id: segment.id,
4263 object_id: *center_object_id,
4264 kind: SegmentKind::Point {
4265 position: center.clone(),
4266 ctor: Box::new(PointCtor {
4267 position: ctor.center.clone(),
4268 }),
4269 freedom: *center_freedom,
4270 },
4271 surface: segment.surface.clone(),
4272 sketch_id: segment.sketch_id,
4273 sketch: segment.sketch.clone(),
4274 tag: segment.tag.clone(),
4275 node_path: segment.node_path.clone(),
4276 meta: segment.meta.clone(),
4277 }),
4278 },
4279 meta: segment.meta.clone(),
4280 }),
4281 }
4282 .continue_()),
4283 _ => Err(KclError::new_undefined_value(
4284 KclErrorDetails::new(
4285 format!("Property '{property}' not found in segment"),
4286 vec![self.clone().into()],
4287 ),
4288 None,
4289 )),
4290 },
4291 },
4292 "controls" => match &segment.repr {
4293 SegmentRepr::Unsolved { segment } => match &segment.kind {
4294 UnsolvedSegmentKind::ControlPointSpline {
4295 controls,
4296 ctor,
4297 control_object_ids,
4298 ..
4299 } => Ok(KclValue::HomArray {
4300 value: controls
4301 .iter()
4302 .zip(control_object_ids.iter())
4303 .zip(ctor.points.iter())
4304 .map(|((position, object_id), ctor_point)| KclValue::Segment {
4305 value: Box::new(AbstractSegment {
4306 repr: SegmentRepr::Unsolved {
4307 segment: Box::new(UnsolvedSegment {
4308 id: segment.id,
4309 object_id: *object_id,
4310 kind: UnsolvedSegmentKind::Point {
4311 position: position.clone(),
4312 ctor: Box::new(PointCtor {
4313 position: ctor_point.clone(),
4314 }),
4315 },
4316 tag: segment.tag.clone(),
4317 node_path: segment.node_path.clone(),
4318 meta: segment.meta.clone(),
4319 }),
4320 },
4321 meta: segment.meta.clone(),
4322 }),
4323 })
4324 .collect(),
4325 ty: RuntimeType::segment(),
4326 }
4327 .continue_()),
4328 _ => Err(KclError::new_undefined_value(
4329 KclErrorDetails::new(
4330 format!("Property '{property}' not found in segment"),
4331 vec![self.clone().into()],
4332 ),
4333 None,
4334 )),
4335 },
4336 SegmentRepr::Solved { segment } => match &segment.kind {
4337 SegmentKind::ControlPointSpline {
4338 controls,
4339 ctor,
4340 control_object_ids,
4341 control_freedoms,
4342 ..
4343 } => Ok(KclValue::HomArray {
4344 value: controls
4345 .iter()
4346 .zip(control_object_ids.iter())
4347 .zip(control_freedoms.iter())
4348 .zip(ctor.points.iter())
4349 .map(|(((position, object_id), freedom), ctor_point)| KclValue::Segment {
4350 value: Box::new(AbstractSegment {
4351 repr: SegmentRepr::Solved {
4352 segment: Box::new(Segment {
4353 id: segment.id,
4354 object_id: *object_id,
4355 kind: SegmentKind::Point {
4356 position: position.clone(),
4357 ctor: Box::new(PointCtor {
4358 position: ctor_point.clone(),
4359 }),
4360 freedom: *freedom,
4361 },
4362 surface: segment.surface.clone(),
4363 sketch_id: segment.sketch_id,
4364 sketch: segment.sketch.clone(),
4365 tag: segment.tag.clone(),
4366 node_path: segment.node_path.clone(),
4367 meta: segment.meta.clone(),
4368 }),
4369 },
4370 meta: segment.meta.clone(),
4371 }),
4372 })
4373 .collect(),
4374 ty: RuntimeType::segment(),
4375 }
4376 .continue_()),
4377 _ => Err(KclError::new_undefined_value(
4378 KclErrorDetails::new(
4379 format!("Property '{property}' not found in segment"),
4380 vec![self.clone().into()],
4381 ),
4382 None,
4383 )),
4384 },
4385 },
4386 "edges" => match &segment.repr {
4387 SegmentRepr::Unsolved { segment } => match &segment.kind {
4388 UnsolvedSegmentKind::ControlPointSpline {
4389 controls,
4390 ctor,
4391 control_object_ids,
4392 control_polygon_edge_object_ids,
4393 construction,
4394 ..
4395 } => Ok(KclValue::HomArray {
4396 value: control_polygon_edge_object_ids
4397 .iter()
4398 .enumerate()
4399 .map(|(index, object_id)| KclValue::Segment {
4400 value: Box::new(AbstractSegment {
4401 repr: SegmentRepr::Unsolved {
4402 segment: Box::new(UnsolvedSegment {
4403 id: segment.id,
4404 object_id: *object_id,
4405 kind: UnsolvedSegmentKind::Line {
4406 start: controls[index].clone(),
4407 end: controls[index + 1].clone(),
4408 ctor: Box::new(LineCtor {
4409 start: ctor.points[index].clone(),
4410 end: ctor.points[index + 1].clone(),
4411 construction: Some(*construction),
4412 }),
4413 start_object_id: control_object_ids[index],
4414 end_object_id: control_object_ids[index + 1],
4415 construction: *construction,
4416 },
4417 tag: segment.tag.clone(),
4418 node_path: segment.node_path.clone(),
4419 meta: segment.meta.clone(),
4420 }),
4421 },
4422 meta: segment.meta.clone(),
4423 }),
4424 })
4425 .collect(),
4426 ty: RuntimeType::segment(),
4427 }
4428 .continue_()),
4429 _ => Err(KclError::new_undefined_value(
4430 KclErrorDetails::new(
4431 format!("Property '{property}' not found in segment"),
4432 vec![self.clone().into()],
4433 ),
4434 None,
4435 )),
4436 },
4437 SegmentRepr::Solved { segment } => match &segment.kind {
4438 SegmentKind::ControlPointSpline {
4439 controls,
4440 ctor,
4441 control_object_ids,
4442 control_polygon_edge_object_ids,
4443 control_freedoms,
4444 construction,
4445 ..
4446 } => Ok(KclValue::HomArray {
4447 value: control_polygon_edge_object_ids
4448 .iter()
4449 .enumerate()
4450 .map(|(index, object_id)| KclValue::Segment {
4451 value: Box::new(AbstractSegment {
4452 repr: SegmentRepr::Solved {
4453 segment: Box::new(Segment {
4454 id: segment.id,
4455 object_id: *object_id,
4456 kind: SegmentKind::Line {
4457 start: controls[index].clone(),
4458 end: controls[index + 1].clone(),
4459 ctor: Box::new(LineCtor {
4460 start: ctor.points[index].clone(),
4461 end: ctor.points[index + 1].clone(),
4462 construction: Some(*construction),
4463 }),
4464 start_object_id: control_object_ids[index],
4465 end_object_id: control_object_ids[index + 1],
4466 start_freedom: control_freedoms[index],
4467 end_freedom: control_freedoms[index + 1],
4468 construction: *construction,
4469 },
4470 surface: segment.surface.clone(),
4471 sketch_id: segment.sketch_id,
4472 sketch: segment.sketch.clone(),
4473 tag: segment.tag.clone(),
4474 node_path: segment.node_path.clone(),
4475 meta: segment.meta.clone(),
4476 }),
4477 },
4478 meta: segment.meta.clone(),
4479 }),
4480 })
4481 .collect(),
4482 ty: RuntimeType::segment(),
4483 }
4484 .continue_()),
4485 _ => Err(KclError::new_undefined_value(
4486 KclErrorDetails::new(
4487 format!("Property '{property}' not found in segment"),
4488 vec![self.clone().into()],
4489 ),
4490 None,
4491 )),
4492 },
4493 },
4494 other => Err(KclError::new_undefined_value(
4495 KclErrorDetails::new(
4496 format!("Property '{other}' not found in segment"),
4497 vec![self.clone().into()],
4498 ),
4499 None,
4500 )),
4501 },
4502 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
4503 "zAxis" => {
4504 let (p, u) = plane.info.z_axis.as_3_dims();
4505 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4506 }
4507 "yAxis" => {
4508 let (p, u) = plane.info.y_axis.as_3_dims();
4509 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4510 }
4511 "xAxis" => {
4512 let (p, u) = plane.info.x_axis.as_3_dims();
4513 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4514 }
4515 "origin" => {
4516 let (p, u) = plane.info.origin.as_3_dims();
4517 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
4518 }
4519 other => Err(KclError::new_undefined_value(
4520 KclErrorDetails::new(
4521 format!("Property '{other}' not found in plane"),
4522 vec![self.clone().into()],
4523 ),
4524 None,
4525 )),
4526 },
4527 (
4528 KclValue::Object {
4529 value: map,
4530 object_kind,
4531 ..
4532 },
4533 Property::String(property),
4534 false,
4535 ) => {
4536 if let Some(value) = map.get(&property) {
4537 if object_kind
4538 .deprecated_solid_tag_names()
4539 .iter()
4540 .any(|tag_name| tag_name == &property)
4541 {
4542 exec_state.warn(
4543 CompilationIssue::err(
4544 SourceRange::from(self),
4545 format!(
4546 "Accessing solid-created face `{property}` through sketch tags is deprecated. Use the body's faces instead, e.g. `body.faces.{property}`."
4547 ),
4548 ),
4549 annotations::WARN_DEPRECATED,
4550 );
4551 }
4552 Ok(value.to_owned().continue_())
4553 } else {
4554 Err(KclError::new_undefined_value(
4555 KclErrorDetails::new(
4556 format!("Property '{property}' not found in object"),
4557 vec![self.clone().into()],
4558 ),
4559 None,
4560 ))
4561 }
4562 }
4563 (KclValue::Object { .. }, Property::String(property), true) => {
4564 Err(KclError::new_semantic(KclErrorDetails::new(
4565 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
4566 vec![self.clone().into()],
4567 )))
4568 }
4569 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
4570 if i == 0
4571 && let Some(value) = map.get("x")
4572 {
4573 return Ok(value.to_owned().continue_());
4574 }
4575 if i == 1
4576 && let Some(value) = map.get("y")
4577 {
4578 return Ok(value.to_owned().continue_());
4579 }
4580 if i == 2
4581 && let Some(value) = map.get("z")
4582 {
4583 return Ok(value.to_owned().continue_());
4584 }
4585 let t = p.type_name();
4586 let article = article_for(t);
4587 Err(KclError::new_semantic(KclErrorDetails::new(
4588 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
4589 vec![self.clone().into()],
4590 )))
4591 }
4592 (KclValue::HomArray { value: arr, ty }, Property::UInt(index), _) => {
4593 let value_of_arr = arr.get(index);
4594 let oob_error = KclError::new_undefined_value(
4596 KclErrorDetails::new(
4597 format!("The array doesn't have any item at index {index}"),
4598 vec![self.clone().into()],
4599 ),
4600 None,
4601 );
4602 if let Some(value) = value_of_arr {
4603 Ok(value.to_owned().continue_())
4605 } else if ctx.no_engine_commands().await
4606 && !exec_state.is_sketch_mode_execution()
4607 && mock_array_may_have_engine_dependent_cardinality(&ty)
4608 {
4609 let value = arr.first();
4619 value.map(|value| value.to_owned().continue_()).ok_or(oob_error)
4620 } else {
4621 Err(oob_error)
4622 }
4623 }
4624 (obj, Property::UInt(0), _) => Ok(obj.continue_()),
4627 (KclValue::HomArray { .. }, p, _) => {
4628 let t = p.type_name();
4629 let article = article_for(t);
4630 Err(KclError::new_semantic(KclErrorDetails::new(
4631 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
4632 vec![self.clone().into()],
4633 )))
4634 }
4635 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
4636 let Some(sketch) = value.sketch() else {
4637 return Err(KclError::new_semantic(KclErrorDetails::new(
4638 "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
4639 vec![self.clone().into()],
4640 )));
4641 };
4642 Ok(KclValue::Sketch {
4643 value: Box::new(sketch.clone()),
4644 }
4645 .continue_())
4646 }
4647 (KclValue::Solid { value: solid }, Property::String(prop), false) if prop == "faces" => {
4648 Ok(KclValue::Object {
4649 meta: vec![Metadata {
4650 source_range: SourceRange::from(self.clone()),
4651 }],
4652 value: solid
4653 .faces
4654 .iter()
4655 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
4656 .collect(),
4657 constrainable: false,
4658 object_kind: KclObjectKind::Default,
4659 }
4660 .continue_())
4661 }
4662 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
4663 Err(KclError::new_semantic(KclErrorDetails::new(
4665 format!(
4666 "Property `{prop}` not found on {}. You can get a solid's faces through `exampleSolid.faces`, or its sketch tags through `exampleSolid.sketch.tags`.",
4667 geometry.human_friendly_type()
4668 ),
4669 vec![self.clone().into()],
4670 )))
4671 }
4672 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
4673 meta: vec![Metadata {
4674 source_range: SourceRange::from(self.clone()),
4675 }],
4676 value: sk
4677 .tags
4678 .iter()
4679 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
4680 .collect(),
4681 constrainable: false,
4682 object_kind: KclObjectKind::SketchTags {
4683 deprecated_solid_tag_names: sk
4684 .tags
4685 .iter()
4686 .filter(|(_, tag)| tag.is_body_created_tag())
4687 .map(|(name, _)| name.to_owned())
4688 .collect(),
4689 },
4690 }
4691 .continue_()),
4692 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
4693 Err(KclError::new_semantic(KclErrorDetails::new(
4694 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
4695 vec![self.clone().into()],
4696 )))
4697 }
4698 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
4699 format!(
4700 "Only objects can have members accessed with dot notation, but you're trying to access {}",
4701 being_indexed.human_friendly_type()
4702 ),
4703 vec![self.clone().into()],
4704 ))),
4705 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
4706 format!(
4707 "Only arrays can be indexed, but you're trying to index {}",
4708 being_indexed.human_friendly_type()
4709 ),
4710 vec![self.clone().into()],
4711 ))),
4712 }
4713 }
4714}
4715
4716impl Node<BinaryExpression> {
4717 pub(super) async fn get_result(
4718 &self,
4719 exec_state: &mut ExecState,
4720 ctx: &ExecutorContext,
4721 ) -> Result<KclValueControlFlow, KclError> {
4722 enum State {
4723 EvaluateLeft(Node<BinaryExpression>),
4724 FromLeft {
4725 node: Node<BinaryExpression>,
4726 },
4727 EvaluateRight {
4728 node: Node<BinaryExpression>,
4729 left: KclValue,
4730 },
4731 FromRight {
4732 node: Node<BinaryExpression>,
4733 left: KclValue,
4734 },
4735 }
4736
4737 let mut stack = vec![State::EvaluateLeft(self.clone())];
4738 let mut last_result: Option<KclValue> = None;
4739
4740 while let Some(state) = stack.pop() {
4741 match state {
4742 State::EvaluateLeft(node) => {
4743 let left_part = node.left.clone();
4744 match left_part {
4745 BinaryPart::BinaryExpression(child) => {
4746 stack.push(State::FromLeft { node });
4747 stack.push(State::EvaluateLeft(child.into_node()));
4748 }
4749 part => {
4750 let left_value = part.get_result(exec_state, ctx).await?;
4751 let left_value = control_continue!(left_value);
4752 stack.push(State::EvaluateRight { node, left: left_value });
4753 }
4754 }
4755 }
4756 State::FromLeft { node } => {
4757 let Some(left_value) = last_result.take() else {
4758 return Err(Self::missing_result_error(&node));
4759 };
4760 stack.push(State::EvaluateRight { node, left: left_value });
4761 }
4762 State::EvaluateRight { node, left } => {
4763 let right_part = node.right.clone();
4764 match right_part {
4765 BinaryPart::BinaryExpression(child) => {
4766 stack.push(State::FromRight { node, left });
4767 stack.push(State::EvaluateLeft(child.into_node()));
4768 }
4769 part => {
4770 let right_value = part.get_result(exec_state, ctx).await?;
4771 let right_value = control_continue!(right_value);
4772 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
4773 last_result = Some(result);
4774 }
4775 }
4776 }
4777 State::FromRight { node, left } => {
4778 let Some(right_value) = last_result.take() else {
4779 return Err(Self::missing_result_error(&node));
4780 };
4781 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
4782 last_result = Some(result);
4783 }
4784 }
4785 }
4786
4787 last_result
4788 .map(KclValue::continue_)
4789 .ok_or_else(|| Self::missing_result_error(self))
4790 }
4791
4792 pub(super) async fn apply_operator(
4793 &self,
4794 exec_state: &mut ExecState,
4795 ctx: &ExecutorContext,
4796 left_value: KclValue,
4797 right_value: KclValue,
4798 ) -> Result<KclValue, KclError> {
4799 let mut meta = left_value.metadata();
4800 meta.extend(right_value.metadata());
4801 let mut seen = HashSet::new();
4803 meta.retain(|metadata| seen.insert(metadata.source_range));
4804
4805 if self.operator == BinaryOperator::Add
4807 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
4808 (&left_value, &right_value)
4809 {
4810 return Ok(KclValue::String {
4811 value: format!("{left}{right}"),
4812 meta,
4813 });
4814 }
4815
4816 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
4818 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
4819 let args = Args::new_no_args(
4820 self.into(),
4821 self.node_path.clone(),
4822 ctx.clone(),
4823 Some("union".to_owned()),
4824 );
4825 let result = crate::std::csg::inner_union(
4826 vec![*left.clone(), *right.clone()],
4827 Default::default(),
4828 crate::std::csg::CsgAlgorithm::Latest,
4829 exec_state,
4830 args,
4831 )
4832 .await?;
4833 return Ok(result.into());
4834 }
4835 } else if self.operator == BinaryOperator::Sub {
4836 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
4838 let args = Args::new_no_args(
4839 self.into(),
4840 self.node_path.clone(),
4841 ctx.clone(),
4842 Some("subtract".to_owned()),
4843 );
4844 let result = crate::std::csg::inner_subtract(
4845 vec![*left.clone()],
4846 vec![*right.clone()],
4847 Default::default(),
4848 crate::std::csg::CsgAlgorithm::Latest,
4849 exec_state,
4850 args,
4851 )
4852 .await?;
4853 return Ok(result.into());
4854 }
4855 } else if self.operator == BinaryOperator::And
4856 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
4857 {
4858 let args = Args::new_no_args(
4860 self.into(),
4861 self.node_path.clone(),
4862 ctx.clone(),
4863 Some("intersect".to_owned()),
4864 );
4865 let result = crate::std::csg::inner_intersect(
4866 vec![*left.clone(), *right.clone()],
4867 Default::default(),
4868 crate::std::csg::CsgAlgorithm::Latest,
4869 exec_state,
4870 args,
4871 )
4872 .await?;
4873 return Ok(result.into());
4874 }
4875
4876 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
4878 let KclValue::Bool { value: left_value, .. } = left_value else {
4879 return Err(KclError::new_semantic(KclErrorDetails::new(
4880 format!(
4881 "Cannot apply logical operator to non-boolean value: {}",
4882 left_value.human_friendly_type()
4883 ),
4884 vec![self.left.clone().into()],
4885 )));
4886 };
4887 let KclValue::Bool { value: right_value, .. } = right_value else {
4888 return Err(KclError::new_semantic(KclErrorDetails::new(
4889 format!(
4890 "Cannot apply logical operator to non-boolean value: {}",
4891 right_value.human_friendly_type()
4892 ),
4893 vec![self.right.clone().into()],
4894 )));
4895 };
4896 let raw_value = match self.operator {
4897 BinaryOperator::Or => left_value || right_value,
4898 BinaryOperator::And => left_value && right_value,
4899 _ => unreachable!(),
4900 };
4901 return Ok(KclValue::Bool { value: raw_value, meta });
4902 }
4903
4904 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
4906 match (&left_value, &right_value) {
4907 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
4909 if left_value.id == right_value.id =>
4910 {
4911 return Ok(KclValue::none());
4912 }
4913 (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
4915 let constraint = Constraint::ScalarEqual(
4916 var0.id.to_constraint_id(self.as_source_range())?,
4917 var1.id.to_constraint_id(self.as_source_range())?,
4918 );
4919 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4920 let message = "Being inside a sketch block should have already been checked above".to_owned();
4921 debug_assert!(false, "{}", &message);
4922 return Err(internal_err(message, self));
4923 };
4924 sketch_block_state.solver_constraints.push(constraint);
4925 return Ok(KclValue::none());
4926 }
4927 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
4929 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
4930 let number_value = normalize_to_solver_distance_unit(
4931 input_number,
4932 input_number.into(),
4933 exec_state,
4934 "fixed constraint value",
4935 )?;
4936 let Some(n) = number_value.as_ty_f64() else {
4937 let message = format!(
4938 "Expected number after coercion, but found {}",
4939 number_value.human_friendly_type()
4940 );
4941 debug_assert!(false, "{}", &message);
4942 return Err(internal_err(message, self));
4943 };
4944 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
4945 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4946 let message = "Being inside a sketch block should have already been checked above".to_owned();
4947 debug_assert!(false, "{}", &message);
4948 return Err(internal_err(message, self));
4949 };
4950 sketch_block_state.solver_constraints.push(constraint);
4951 exec_state.warn_experimental("scalar fixed constraint", self.as_source_range());
4952 return Ok(KclValue::none());
4953 }
4954 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
4956 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
4957 let number_value = match constraint.kind {
4958 SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
4960 input_number,
4961 input_number.into(),
4962 exec_state,
4963 "fixed constraint value",
4964 )?,
4965 SketchConstraintKind::Distance { .. }
4967 | SketchConstraintKind::PointLineDistance { .. }
4968 | SketchConstraintKind::LineLineDistance { .. }
4969 | SketchConstraintKind::PointCircularDistance { .. }
4970 | SketchConstraintKind::LineCircularDistance { .. }
4971 | SketchConstraintKind::CircularCircularDistance { .. }
4972 | SketchConstraintKind::Radius { .. }
4973 | SketchConstraintKind::Diameter { .. }
4974 | SketchConstraintKind::HorizontalDistance { .. }
4975 | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
4976 input_number,
4977 input_number.into(),
4978 exec_state,
4979 "fixed constraint value",
4980 )?,
4981 };
4982 let Some(n) = number_value.as_ty_f64() else {
4983 let message = format!(
4984 "Expected number after coercion, but found {}",
4985 number_value.human_friendly_type()
4986 );
4987 debug_assert!(false, "{}", &message);
4988 return Err(internal_err(message, self));
4989 };
4990 let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
4992 &self.right
4993 } else {
4994 &self.left
4995 };
4996 let source = {
4997 use crate::unparser::ExprContext;
4998 let mut buf = String::new();
4999 number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
5000 crate::frontend::sketch::ConstraintSource {
5001 expr: buf,
5002 is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
5003 }
5004 };
5005
5006 match &constraint.kind {
5007 SketchConstraintKind::Angle {
5008 line0,
5009 line1,
5010 mode,
5011 label_position,
5012 } => {
5013 let range = self.as_source_range();
5014 let desired_angle = match n.ty {
5015 NumericType::Known(crate::exec::UnitType::Angle(crate::exec::UnitAngle::Degrees))
5016 | NumericType::Default {
5017 len: _,
5018 angle: UnitAngle::Degrees,
5019 } => ezpz::datatypes::Angle::from_degrees(n.n),
5020 NumericType::Known(crate::exec::UnitType::Angle(crate::exec::UnitAngle::Radians))
5021 | NumericType::Default {
5022 len: _,
5023 angle: UnitAngle::Radians,
5024 } => ezpz::datatypes::Angle::from_radians(n.n),
5025 NumericType::Known(crate::exec::UnitType::Count)
5026 | NumericType::Known(crate::exec::UnitType::GenericLength)
5027 | NumericType::Known(crate::exec::UnitType::GenericAngle)
5028 | NumericType::Known(crate::exec::UnitType::Length(_))
5029 | NumericType::Unknown
5030 | NumericType::Any => {
5031 let message = format!("Expected angle but found {:?}", n);
5032 debug_assert!(false, "{}", &message);
5033 return Err(internal_err(message, self));
5034 }
5035 };
5036 let angle_lowering = match *mode {
5037 AngleConstraintMode::LinesAtAngle => {
5038 AngleConstraintLowering::LinesAtAngle(Box::new(PendingLegacyAngleRefactorMeta {
5039 source_range: constraint
5040 .meta
5041 .first()
5042 .map(|meta| meta.source_range)
5043 .unwrap_or(range),
5044 lines: [line0.clone(), line1.clone()],
5045 desired_angle_radians: desired_angle.to_radians(),
5046 }))
5047 }
5048 AngleConstraintMode::PointsAtAngle { sector, inverse } => {
5049 let sketch_vars = exec_state
5050 .mod_local
5051 .sketch_block
5052 .as_ref()
5053 .ok_or_else(|| {
5054 internal_err(
5055 "Being inside a sketch block should have already been checked above",
5056 self,
5057 )
5058 })?
5059 .sketch_vars
5060 .clone();
5061 let initial_line0 = constrainable_line_initial_positions(
5062 &sketch_vars,
5063 line0,
5064 exec_state,
5065 range,
5066 "angle line0",
5067 )?;
5068 let initial_line1 = constrainable_line_initial_positions(
5069 &sketch_vars,
5070 line1,
5071 exec_state,
5072 range,
5073 "angle line1",
5074 )?;
5075 let Some(initial_vertex) = intersect_lines_2d(initial_line0, initial_line1) else {
5076 return Err(KclError::new_semantic(KclErrorDetails::new(
5077 "angleDimension(lines = ..., sector = ...) requires non-parallel lines"
5078 .to_owned(),
5079 vec![range],
5080 )));
5081 };
5082 let (line0_representative, line0_direction) =
5083 representative_angle_endpoint(line0, initial_line0, initial_vertex, range)?;
5084 let (line1_representative, line1_direction) =
5085 representative_angle_endpoint(line1, initial_line1, initial_vertex, range)?;
5086 let sector_rays = angle_sector_rays(sector, inverse);
5087 let angle_kind =
5088 ezpz::datatypes::AngleKind::Other(remap_angle_for_representative_rays(
5089 sector_rays,
5090 [line0_direction, line1_direction],
5091 desired_angle,
5092 ));
5093 AngleConstraintLowering::PointsAtAngle(PointsAtAngleLineData {
5094 initial_vertex,
5095 representative_points: [line0_representative, line1_representative],
5096 angle_kind,
5097 })
5098 }
5099 };
5100 let sketch_var_ty = solver_numeric_type(exec_state);
5101 let constraint_id = exec_state.next_object_id();
5102 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5103 let message =
5104 "Being inside a sketch block should have already been checked above".to_owned();
5105 debug_assert!(false, "{}", &message);
5106 return Err(internal_err(message, self));
5107 };
5108 match angle_lowering {
5109 AngleConstraintLowering::LinesAtAngle(refactor_meta) => {
5110 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5111 datum_line_from_constrainable(line0, range)?,
5112 datum_line_from_constrainable(line1, range)?,
5113 ezpz::datatypes::AngleKind::Other(desired_angle),
5114 ));
5115 sketch_block_state
5116 .pending_legacy_angle_refactor_metadata
5117 .push(*refactor_meta);
5118 }
5119 AngleConstraintLowering::PointsAtAngle(points_at_angle_data) => {
5120 push_points_at_angle_for_lines(
5121 sketch_block_state,
5122 sketch_var_ty,
5123 [line0, line1],
5124 points_at_angle_data,
5125 range,
5126 )?
5127 }
5128 }
5129 use crate::execution::Artifact;
5130 use crate::execution::CodeRef;
5131 use crate::execution::SketchBlockConstraint;
5132 use crate::front::Angle;
5133 use crate::front::SourceRef;
5134
5135 let Some(sketch_id) = sketch_block_state.sketch_id else {
5136 let message = "Sketch id missing for constraint artifact".to_owned();
5137 debug_assert!(false, "{}", &message);
5138 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5139 };
5140 let (sector, inverse) = match *mode {
5141 AngleConstraintMode::LinesAtAngle => (None, None),
5142 AngleConstraintMode::PointsAtAngle { sector, inverse } => {
5143 (Some(front_angle_sector(sector)), Some(inverse))
5144 }
5145 };
5146 let sketch_constraint = crate::front::Constraint::Angle(Angle {
5147 lines: vec![line0.object_id, line1.object_id],
5148 angle: n.try_into().map_err(|_| {
5149 internal_err("Failed to convert angle units numeric suffix:", range)
5150 })?,
5151 sector,
5152 inverse,
5153 label_position: label_position.clone(),
5154 source,
5155 });
5156 sketch_block_state.sketch_constraints.push(constraint_id);
5157 let artifact_id = exec_state.next_artifact_id();
5158 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5159 id: artifact_id,
5160 sketch_id,
5161 constraint_id,
5162 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5163 code_ref: CodeRef::placeholder(range),
5164 }));
5165 exec_state.add_scene_object(
5166 Object {
5167 id: constraint_id,
5168 kind: ObjectKind::Constraint {
5169 constraint: sketch_constraint,
5170 },
5171 label: Default::default(),
5172 comments: Default::default(),
5173 artifact_id,
5174 source: SourceRef::new(range, self.node_path.clone()),
5175 },
5176 range,
5177 );
5178 }
5179 SketchConstraintKind::Distance { points, label_position } => {
5180 let range = self.as_source_range();
5181 let p0 = &points[0];
5182 let p1 = &points[1];
5183 let sketch_var_ty = solver_numeric_type(exec_state);
5184 let constraint_id = exec_state.next_object_id();
5185 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5186 let message =
5187 "Being inside a sketch block should have already been checked above".to_owned();
5188 debug_assert!(false, "{}", &message);
5189 return Err(internal_err(message, self));
5190 };
5191 match (p0, p1) {
5192 (
5193 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
5194 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
5195 ) => {
5196 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5197 p0.vars.x.to_constraint_id(range)?,
5198 p0.vars.y.to_constraint_id(range)?,
5199 );
5200 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5201 p1.vars.x.to_constraint_id(range)?,
5202 p1.vars.y.to_constraint_id(range)?,
5203 );
5204 sketch_block_state
5205 .solver_constraints
5206 .push(Constraint::Distance(solver_pt0, solver_pt1, n.n));
5207 }
5208 (
5209 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
5210 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5211 )
5212 | (
5213 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5214 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
5215 ) => {
5216 let origin_x_id = sketch_block_state.next_sketch_var_id();
5217 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5218 value: Box::new(crate::execution::SketchVar {
5219 id: origin_x_id,
5220 initial_value: 0.0,
5221 ty: sketch_var_ty,
5222 node_path: None,
5224 meta: vec![],
5225 }),
5226 });
5227 let origin_y_id = sketch_block_state.next_sketch_var_id();
5228 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5229 value: Box::new(crate::execution::SketchVar {
5230 id: origin_y_id,
5231 initial_value: 0.0,
5232 ty: sketch_var_ty,
5233 node_path: None,
5235 meta: vec![],
5236 }),
5237 });
5238 let origin_x = origin_x_id.to_constraint_id(range)?;
5239 let origin_y = origin_y_id.to_constraint_id(range)?;
5240 sketch_block_state
5241 .solver_constraints
5242 .push(Constraint::Fixed(origin_x, 0.0));
5243 sketch_block_state
5244 .solver_constraints
5245 .push(Constraint::Fixed(origin_y, 0.0));
5246 let solver_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5247 point.vars.x.to_constraint_id(range)?,
5248 point.vars.y.to_constraint_id(range)?,
5249 );
5250 let origin_point = ezpz::datatypes::inputs::DatumPoint::new_xy(origin_x, origin_y);
5251 sketch_block_state.solver_constraints.push(Constraint::Distance(
5252 solver_point,
5253 origin_point,
5254 n.n,
5255 ));
5256 }
5257 (
5258 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5259 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5260 ) => {
5261 return Err(internal_err(
5262 "distance() cannot constrain ORIGIN against ORIGIN".to_owned(),
5263 range,
5264 ));
5265 }
5266 }
5267 use crate::execution::Artifact;
5268 use crate::execution::CodeRef;
5269 use crate::execution::SketchBlockConstraint;
5270 use crate::front::Distance;
5271 use crate::front::SourceRef;
5272 use crate::frontend::sketch::ConstraintSegment;
5273
5274 let Some(sketch_id) = sketch_block_state.sketch_id else {
5275 let message = "Sketch id missing for constraint artifact".to_owned();
5276 debug_assert!(false, "{}", &message);
5277 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5278 };
5279 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5280 segments: vec![
5281 match p0 {
5282 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5283 ConstraintSegment::from(point.object_id)
5284 }
5285 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5286 ConstraintSegment::ORIGIN
5287 }
5288 },
5289 match p1 {
5290 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5291 ConstraintSegment::from(point.object_id)
5292 }
5293 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5294 ConstraintSegment::ORIGIN
5295 }
5296 },
5297 ],
5298 distance: n.try_into().map_err(|_| {
5299 internal_err("Failed to convert distance units numeric suffix:", range)
5300 })?,
5301 label_position: label_position.clone(),
5302 source,
5303 });
5304 sketch_block_state.sketch_constraints.push(constraint_id);
5305 let artifact_id = exec_state.next_artifact_id();
5306 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5307 id: artifact_id,
5308 sketch_id,
5309 constraint_id,
5310 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5311 code_ref: CodeRef::placeholder(range),
5312 }));
5313 exec_state.add_scene_object(
5314 Object {
5315 id: constraint_id,
5316 kind: ObjectKind::Constraint {
5317 constraint: sketch_constraint,
5318 },
5319 label: Default::default(),
5320 comments: Default::default(),
5321 artifact_id,
5322 source: SourceRef::new(range, self.node_path.clone()),
5323 },
5324 range,
5325 );
5326 }
5327 SketchConstraintKind::PointLineDistance {
5328 point,
5329 line,
5330 input_object_ids,
5331 label_position,
5332 } => {
5333 let range = self.as_source_range();
5334 let sketch_var_ty = solver_numeric_type(exec_state);
5335 let sketch_vars = exec_state
5336 .mod_local
5337 .sketch_block
5338 .as_ref()
5339 .ok_or_else(|| {
5340 internal_err(
5341 "Being inside a sketch block should have already been checked above",
5342 self,
5343 )
5344 })?
5345 .sketch_vars
5346 .clone();
5347 let support_initial =
5348 projected_point_on_line_initial_position(&sketch_vars, point, line, exec_state, range)?;
5349 let solver_line = datum_line_from_constrainable(line, range)?;
5350
5351 let constraint_id = exec_state.next_object_id();
5352 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5353 let message =
5354 "Being inside a sketch block should have already been checked above".to_owned();
5355 debug_assert!(false, "{}", &message);
5356 return Err(internal_err(message, self));
5357 };
5358
5359 let solver_point = datum_point_from_constrainable_or_origin(
5365 sketch_block_state,
5366 sketch_var_ty,
5367 point,
5368 range,
5369 )?;
5370 let support_x_id = sketch_block_state.next_sketch_var_id();
5371 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5372 value: Box::new(crate::execution::SketchVar {
5373 id: support_x_id,
5374 initial_value: support_initial[0],
5375 ty: sketch_var_ty,
5376 node_path: None,
5378 meta: vec![],
5379 }),
5380 });
5381 let support_y_id = sketch_block_state.next_sketch_var_id();
5382 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5383 value: Box::new(crate::execution::SketchVar {
5384 id: support_y_id,
5385 initial_value: support_initial[1],
5386 ty: sketch_var_ty,
5387 node_path: None,
5389 meta: vec![],
5390 }),
5391 });
5392 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5393 support_x_id.to_constraint_id(range)?,
5394 support_y_id.to_constraint_id(range)?,
5395 );
5396 let support_line =
5397 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
5398
5399 sketch_block_state
5400 .solver_constraints
5401 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
5402 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5403 support_line,
5404 solver_line,
5405 ezpz::datatypes::AngleKind::Perpendicular,
5406 ));
5407 sketch_block_state.solver_constraints.push(Constraint::Distance(
5408 solver_point,
5409 support_point,
5410 n.n,
5411 ));
5412
5413 use crate::execution::Artifact;
5414 use crate::execution::CodeRef;
5415 use crate::execution::SketchBlockConstraint;
5416 use crate::front::Distance;
5417 use crate::front::SourceRef;
5418 use crate::frontend::sketch::ConstraintSegment;
5419
5420 let Some(sketch_id) = sketch_block_state.sketch_id else {
5421 let message = "Sketch id missing for constraint artifact".to_owned();
5422 debug_assert!(false, "{}", &message);
5423 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5424 };
5425 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5426 segments: input_object_ids
5427 .iter()
5428 .copied()
5429 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
5430 .collect(),
5431 distance: n.try_into().map_err(|_| {
5432 internal_err("Failed to convert distance units numeric suffix:", range)
5433 })?,
5434 label_position: label_position.clone(),
5435 source,
5436 });
5437 sketch_block_state.sketch_constraints.push(constraint_id);
5438 let artifact_id = exec_state.next_artifact_id();
5439 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5440 id: artifact_id,
5441 sketch_id,
5442 constraint_id,
5443 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5444 code_ref: CodeRef::placeholder(range),
5445 }));
5446 exec_state.add_scene_object(
5447 Object {
5448 id: constraint_id,
5449 kind: ObjectKind::Constraint {
5450 constraint: sketch_constraint,
5451 },
5452 label: Default::default(),
5453 comments: Default::default(),
5454 artifact_id,
5455 source: SourceRef::new(range, self.node_path.clone()),
5456 },
5457 range,
5458 );
5459 }
5460 SketchConstraintKind::LineLineDistance {
5461 line0,
5462 line1,
5463 input_object_ids,
5464 label_position,
5465 } => {
5466 let range = self.as_source_range();
5467 let reference_point = crate::execution::ConstrainablePoint2d {
5468 vars: line0.vars[0].clone(),
5469 object_id: line0.object_id,
5470 };
5471 let sketch_var_ty = solver_numeric_type(exec_state);
5472 let sketch_vars = exec_state
5473 .mod_local
5474 .sketch_block
5475 .as_ref()
5476 .ok_or_else(|| {
5477 internal_err(
5478 "Being inside a sketch block should have already been checked above",
5479 self,
5480 )
5481 })?
5482 .sketch_vars
5483 .clone();
5484 let support_initial = projected_point_on_line_initial_position(
5485 &sketch_vars,
5486 &crate::execution::ConstrainablePoint2dOrOrigin::Point(reference_point.clone()),
5487 line1,
5488 exec_state,
5489 range,
5490 )?;
5491 let solver_point = datum_point_from_constrainable(&reference_point, range)?;
5492 let solver_line0 = datum_line_from_constrainable(line0, range)?;
5493 let solver_line1 = datum_line_from_constrainable(line1, range)?;
5494
5495 let constraint_id = exec_state.next_object_id();
5496 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5497 let message =
5498 "Being inside a sketch block should have already been checked above".to_owned();
5499 debug_assert!(false, "{}", &message);
5500 return Err(internal_err(message, self));
5501 };
5502
5503 let support_x_id = sketch_block_state.next_sketch_var_id();
5509 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5510 value: Box::new(crate::execution::SketchVar {
5511 id: support_x_id,
5512 initial_value: support_initial[0],
5513 ty: sketch_var_ty,
5514 node_path: None,
5516 meta: vec![],
5517 }),
5518 });
5519 let support_y_id = sketch_block_state.next_sketch_var_id();
5520 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5521 value: Box::new(crate::execution::SketchVar {
5522 id: support_y_id,
5523 initial_value: support_initial[1],
5524 ty: sketch_var_ty,
5525 node_path: None,
5527 meta: vec![],
5528 }),
5529 });
5530 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5531 support_x_id.to_constraint_id(range)?,
5532 support_y_id.to_constraint_id(range)?,
5533 );
5534 let support_line =
5535 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
5536
5537 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5538 solver_line0,
5539 solver_line1,
5540 ezpz::datatypes::AngleKind::Parallel,
5541 ));
5542 sketch_block_state
5543 .solver_constraints
5544 .push(Constraint::PointLineDistance(support_point, solver_line1, 0.0));
5545 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5546 support_line,
5547 solver_line1,
5548 ezpz::datatypes::AngleKind::Perpendicular,
5549 ));
5550 sketch_block_state.solver_constraints.push(Constraint::Distance(
5551 solver_point,
5552 support_point,
5553 n.n,
5554 ));
5555
5556 use crate::execution::Artifact;
5557 use crate::execution::CodeRef;
5558 use crate::execution::SketchBlockConstraint;
5559 use crate::front::Distance;
5560 use crate::front::SourceRef;
5561 use crate::frontend::sketch::ConstraintSegment;
5562
5563 let Some(sketch_id) = sketch_block_state.sketch_id else {
5564 let message = "Sketch id missing for constraint artifact".to_owned();
5565 debug_assert!(false, "{}", &message);
5566 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5567 };
5568 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5569 segments: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
5570 distance: n.try_into().map_err(|_| {
5571 internal_err("Failed to convert distance units numeric suffix:", range)
5572 })?,
5573 label_position: label_position.clone(),
5574 source,
5575 });
5576 sketch_block_state.sketch_constraints.push(constraint_id);
5577 let artifact_id = exec_state.next_artifact_id();
5578 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5579 id: artifact_id,
5580 sketch_id,
5581 constraint_id,
5582 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5583 code_ref: CodeRef::placeholder(range),
5584 }));
5585 exec_state.add_scene_object(
5586 Object {
5587 id: constraint_id,
5588 kind: ObjectKind::Constraint {
5589 constraint: sketch_constraint,
5590 },
5591 label: Default::default(),
5592 comments: Default::default(),
5593 artifact_id,
5594 source: SourceRef::new(range, self.node_path.clone()),
5595 },
5596 range,
5597 );
5598 }
5599 SketchConstraintKind::PointCircularDistance {
5600 point,
5601 center,
5602 start,
5603 end,
5604 input_object_ids,
5605 label_position,
5606 } => {
5607 let range = self.as_source_range();
5608 let sketch_var_ty = solver_numeric_type(exec_state);
5609 let sketch_vars = exec_state
5610 .mod_local
5611 .sketch_block
5612 .as_ref()
5613 .ok_or_else(|| {
5614 internal_err(
5615 "Being inside a sketch block should have already been checked above",
5616 self,
5617 )
5618 })?
5619 .sketch_vars
5620 .clone();
5621 let circular =
5622 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
5623
5624 let constraint_id = exec_state.next_object_id();
5625 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5626 let message =
5627 "Being inside a sketch block should have already been checked above".to_owned();
5628 debug_assert!(false, "{}", &message);
5629 return Err(internal_err(message, self));
5630 };
5631
5632 let target_point = datum_point_from_constrainable_or_origin(
5637 sketch_block_state,
5638 sketch_var_ty,
5639 point,
5640 range,
5641 )?;
5642 push_circular_distance_constraints(
5643 sketch_block_state,
5644 sketch_var_ty,
5645 target_point,
5646 circular,
5647 n.n,
5648 range,
5649 )?;
5650
5651 use crate::execution::Artifact;
5652 use crate::execution::CodeRef;
5653 use crate::execution::SketchBlockConstraint;
5654 use crate::front::Distance;
5655 use crate::front::SourceRef;
5656 use crate::frontend::sketch::ConstraintSegment;
5657
5658 let Some(sketch_id) = sketch_block_state.sketch_id else {
5659 let message = "Sketch id missing for constraint artifact".to_owned();
5660 debug_assert!(false, "{}", &message);
5661 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5662 };
5663 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5664 segments: input_object_ids
5665 .iter()
5666 .copied()
5667 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
5668 .collect(),
5669 distance: n.try_into().map_err(|_| {
5670 internal_err("Failed to convert distance units numeric suffix:", range)
5671 })?,
5672 label_position: label_position.clone(),
5673 source,
5674 });
5675 sketch_block_state.sketch_constraints.push(constraint_id);
5676 let artifact_id = exec_state.next_artifact_id();
5677 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5678 id: artifact_id,
5679 sketch_id,
5680 constraint_id,
5681 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5682 code_ref: CodeRef::placeholder(range),
5683 }));
5684 exec_state.add_scene_object(
5685 Object {
5686 id: constraint_id,
5687 kind: ObjectKind::Constraint {
5688 constraint: sketch_constraint,
5689 },
5690 label: Default::default(),
5691 comments: Default::default(),
5692 artifact_id,
5693 source: SourceRef::new(range, self.node_path.clone()),
5694 },
5695 range,
5696 );
5697 }
5698 SketchConstraintKind::LineCircularDistance {
5699 line,
5700 center,
5701 start,
5702 end,
5703 input_object_ids,
5704 label_position,
5705 } => {
5706 let range = self.as_source_range();
5707 let sketch_var_ty = solver_numeric_type(exec_state);
5708 let sketch_vars = exec_state
5709 .mod_local
5710 .sketch_block
5711 .as_ref()
5712 .ok_or_else(|| {
5713 internal_err(
5714 "Being inside a sketch block should have already been checked above",
5715 self,
5716 )
5717 })?
5718 .sketch_vars
5719 .clone();
5720 let support_initial = projected_point_on_line_initial_position(
5721 &sketch_vars,
5722 &crate::execution::ConstrainablePoint2dOrOrigin::Point(center.clone()),
5723 line,
5724 exec_state,
5725 range,
5726 )?;
5727 let solver_line = datum_line_from_constrainable(line, range)?;
5728 let circular =
5729 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
5730
5731 let constraint_id = exec_state.next_object_id();
5732 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5733 let message =
5734 "Being inside a sketch block should have already been checked above".to_owned();
5735 debug_assert!(false, "{}", &message);
5736 return Err(internal_err(message, self));
5737 };
5738
5739 let support_x_id = sketch_block_state.next_sketch_var_id();
5745 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5746 value: Box::new(crate::execution::SketchVar {
5747 id: support_x_id,
5748 initial_value: support_initial[0],
5749 ty: sketch_var_ty,
5750 node_path: None,
5752 meta: vec![],
5753 }),
5754 });
5755 let support_y_id = sketch_block_state.next_sketch_var_id();
5756 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5757 value: Box::new(crate::execution::SketchVar {
5758 id: support_y_id,
5759 initial_value: support_initial[1],
5760 ty: sketch_var_ty,
5761 node_path: None,
5763 meta: vec![],
5764 }),
5765 });
5766 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5767 support_x_id.to_constraint_id(range)?,
5768 support_y_id.to_constraint_id(range)?,
5769 );
5770 let support_line =
5771 ezpz::datatypes::inputs::DatumLineSegment::new(circular.center, support_point);
5772
5773 sketch_block_state
5774 .solver_constraints
5775 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
5776 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
5777 support_line,
5778 solver_line,
5779 ezpz::datatypes::AngleKind::Perpendicular,
5780 ));
5781 push_circular_distance_constraints(
5782 sketch_block_state,
5783 sketch_var_ty,
5784 support_point,
5785 circular,
5786 n.n,
5787 range,
5788 )?;
5789
5790 use crate::execution::Artifact;
5791 use crate::execution::CodeRef;
5792 use crate::execution::SketchBlockConstraint;
5793 use crate::front::Distance;
5794 use crate::front::SourceRef;
5795 use crate::frontend::sketch::ConstraintSegment;
5796
5797 let Some(sketch_id) = sketch_block_state.sketch_id else {
5798 let message = "Sketch id missing for constraint artifact".to_owned();
5799 debug_assert!(false, "{}", &message);
5800 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5801 };
5802 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5803 segments: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
5804 distance: n.try_into().map_err(|_| {
5805 internal_err("Failed to convert distance units numeric suffix:", range)
5806 })?,
5807 label_position: label_position.clone(),
5808 source,
5809 });
5810 sketch_block_state.sketch_constraints.push(constraint_id);
5811 let artifact_id = exec_state.next_artifact_id();
5812 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5813 id: artifact_id,
5814 sketch_id,
5815 constraint_id,
5816 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5817 code_ref: CodeRef::placeholder(range),
5818 }));
5819 exec_state.add_scene_object(
5820 Object {
5821 id: constraint_id,
5822 kind: ObjectKind::Constraint {
5823 constraint: sketch_constraint,
5824 },
5825 label: Default::default(),
5826 comments: Default::default(),
5827 artifact_id,
5828 source: SourceRef::new(range, self.node_path.clone()),
5829 },
5830 range,
5831 );
5832 }
5833 SketchConstraintKind::CircularCircularDistance {
5834 center0,
5835 start0,
5836 end0,
5837 center1,
5838 start1,
5839 end1,
5840 input_object_ids,
5841 label_position,
5842 } => {
5843 let range = self.as_source_range();
5844 let sketch_var_ty = solver_numeric_type(exec_state);
5845 let sketch_vars = exec_state
5846 .mod_local
5847 .sketch_block
5848 .as_ref()
5849 .ok_or_else(|| {
5850 internal_err(
5851 "Being inside a sketch block should have already been checked above",
5852 self,
5853 )
5854 })?
5855 .sketch_vars
5856 .clone();
5857 let circular0 = circular_distance_datums(
5858 &sketch_vars,
5859 center0,
5860 start0,
5861 end0.as_ref(),
5862 exec_state,
5863 range,
5864 )?;
5865 let circular1 = circular_distance_datums(
5866 &sketch_vars,
5867 center1,
5868 start1,
5869 end1.as_ref(),
5870 exec_state,
5871 range,
5872 )?;
5873 let support_initial = circular_circular_support_initial_position(
5874 &sketch_vars,
5875 center0,
5876 center1,
5877 circular0.radius_initial_value,
5878 n.n,
5879 exec_state,
5880 range,
5881 )?;
5882
5883 let constraint_id = exec_state.next_object_id();
5884 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5885 let message =
5886 "Being inside a sketch block should have already been checked above".to_owned();
5887 debug_assert!(false, "{}", &message);
5888 return Err(internal_err(message, self));
5889 };
5890
5891 let circular_target0 =
5897 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular0, range)?;
5898 let circular_target1 =
5899 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular1, range)?;
5900
5901 let support_x_id = sketch_block_state.next_sketch_var_id();
5902 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5903 value: Box::new(crate::execution::SketchVar {
5904 id: support_x_id,
5905 initial_value: support_initial[0],
5906 ty: sketch_var_ty,
5907 node_path: None,
5909 meta: vec![],
5910 }),
5911 });
5912 let support_y_id = sketch_block_state.next_sketch_var_id();
5913 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5914 value: Box::new(crate::execution::SketchVar {
5915 id: support_y_id,
5916 initial_value: support_initial[1],
5917 ty: sketch_var_ty,
5918 node_path: None,
5920 meta: vec![],
5921 }),
5922 });
5923 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
5924 support_x_id.to_constraint_id(range)?,
5925 support_y_id.to_constraint_id(range)?,
5926 );
5927
5928 let support_radius_id = sketch_block_state.next_sketch_var_id();
5929 let support_radius_value = n.n / 2.0;
5930 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
5931 value: Box::new(crate::execution::SketchVar {
5932 id: support_radius_id,
5933 initial_value: support_radius_value,
5934 ty: sketch_var_ty,
5935 node_path: None,
5937 meta: vec![],
5938 }),
5939 });
5940 let support_radius =
5941 ezpz::datatypes::inputs::DatumDistance::new(support_radius_id.to_constraint_id(range)?);
5942 let support_circle = ezpz::datatypes::inputs::DatumCircle {
5943 center: support_point,
5944 radius: support_radius,
5945 };
5946 let center_line = ezpz::datatypes::inputs::DatumLineSegment::new(
5947 circular_target0.center,
5948 circular_target1.center,
5949 );
5950
5951 sketch_block_state
5952 .solver_constraints
5953 .push(Constraint::Fixed(support_radius.id, support_radius_value));
5954 sketch_block_state
5955 .solver_constraints
5956 .push(Constraint::PointLineDistance(support_point, center_line, 0.0));
5957 sketch_block_state
5958 .solver_constraints
5959 .push(Constraint::CircleTangentToCircle(
5960 circular_target0,
5961 support_circle,
5962 ezpz::CircleSide::Exterior,
5963 ));
5964 sketch_block_state
5965 .solver_constraints
5966 .push(Constraint::CircleTangentToCircle(
5967 support_circle,
5968 circular_target1,
5969 ezpz::CircleSide::Exterior,
5970 ));
5971
5972 use crate::execution::Artifact;
5973 use crate::execution::CodeRef;
5974 use crate::execution::SketchBlockConstraint;
5975 use crate::front::Distance;
5976 use crate::front::SourceRef;
5977 use crate::frontend::sketch::ConstraintSegment;
5978
5979 let Some(sketch_id) = sketch_block_state.sketch_id else {
5980 let message = "Sketch id missing for constraint artifact".to_owned();
5981 debug_assert!(false, "{}", &message);
5982 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5983 };
5984 let sketch_constraint = crate::front::Constraint::Distance(Distance {
5985 segments: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
5986 distance: n.try_into().map_err(|_| {
5987 internal_err("Failed to convert distance units numeric suffix:", range)
5988 })?,
5989 label_position: label_position.clone(),
5990 source,
5991 });
5992 sketch_block_state.sketch_constraints.push(constraint_id);
5993 let artifact_id = exec_state.next_artifact_id();
5994 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5995 id: artifact_id,
5996 sketch_id,
5997 constraint_id,
5998 constraint_type: super::artifact::sketch_block_constraint_type(&sketch_constraint),
5999 code_ref: CodeRef::placeholder(range),
6000 }));
6001 exec_state.add_scene_object(
6002 Object {
6003 id: constraint_id,
6004 kind: ObjectKind::Constraint {
6005 constraint: sketch_constraint,
6006 },
6007 label: Default::default(),
6008 comments: Default::default(),
6009 artifact_id,
6010 source: SourceRef::new(range, self.node_path.clone()),
6011 },
6012 range,
6013 );
6014 }
6015 SketchConstraintKind::Radius { .. } | SketchConstraintKind::Diameter { .. } => {
6016 #[derive(Clone, Copy)]
6017 enum CircularSegmentConstraintTarget {
6018 Arc {
6019 object_id: ObjectId,
6020 end: [crate::execution::SketchVarId; 2],
6021 direction: ArcDirection,
6022 },
6023 Circle {
6024 object_id: ObjectId,
6025 },
6026 }
6027
6028 fn sketch_var_initial_value(
6029 sketch_vars: &[KclValue],
6030 id: crate::execution::SketchVarId,
6031 exec_state: &mut ExecState,
6032 range: SourceRange,
6033 ) -> Result<f64, KclError> {
6034 sketch_vars
6035 .get(id.0)
6036 .and_then(KclValue::as_sketch_var)
6037 .map(|sketch_var| {
6038 sketch_var
6039 .initial_value_to_solver_units(
6040 exec_state,
6041 range,
6042 "circle radius initial value",
6043 )
6044 .map(|value| value.n)
6045 })
6046 .transpose()?
6047 .ok_or_else(|| {
6048 internal_err(
6049 format!("Missing sketch variable initial value for id {}", id.0),
6050 range,
6051 )
6052 })
6053 }
6054
6055 let (points, label_position) = match &constraint.kind {
6056 SketchConstraintKind::Radius { points, label_position } => {
6057 (points, label_position.clone())
6058 }
6059 SketchConstraintKind::Diameter { points, label_position } => {
6060 (points, label_position.clone())
6061 }
6062 _ => unreachable!(),
6063 };
6064 let range = self.as_source_range();
6065 let center = &points[0];
6066 let start = &points[1];
6067 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
6068 return Err(internal_err(
6069 "Being inside a sketch block should have already been checked above",
6070 self,
6071 ));
6072 };
6073 let (constraint_name, is_diameter) = match &constraint.kind {
6074 SketchConstraintKind::Radius { .. } => ("radius", false),
6075 SketchConstraintKind::Diameter { .. } => ("diameter", true),
6076 _ => unreachable!(),
6077 };
6078 let sketch_vars = sketch_block_state.sketch_vars.clone();
6079 let target_segment = sketch_block_state
6080 .needed_by_engine
6081 .iter()
6082 .find_map(|seg| match &seg.kind {
6083 UnsolvedSegmentKind::Arc {
6084 center_object_id,
6085 start_object_id,
6086 end,
6087 direction,
6088 ..
6089 } if *center_object_id == center.object_id
6090 && *start_object_id == start.object_id =>
6091 {
6092 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
6093 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
6094 (*end_x, *end_y)
6095 }
6096 _ => return None,
6097 };
6098 Some(CircularSegmentConstraintTarget::Arc {
6099 object_id: seg.object_id,
6100 end: [end_x_var, end_y_var],
6101 direction: *direction,
6102 })
6103 }
6104 UnsolvedSegmentKind::Circle {
6105 center_object_id,
6106 start_object_id,
6107 ..
6108 } if *center_object_id == center.object_id
6109 && *start_object_id == start.object_id =>
6110 {
6111 Some(CircularSegmentConstraintTarget::Circle {
6112 object_id: seg.object_id,
6113 })
6114 }
6115 _ => None,
6116 })
6117 .ok_or_else(|| {
6118 internal_err(
6119 format!("Could not find circular segment for {} constraint", constraint_name),
6120 range,
6121 )
6122 })?;
6123 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
6124 let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
6125 center.vars.x.to_constraint_id(range)?,
6126 center.vars.y.to_constraint_id(range)?,
6127 );
6128 let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
6129 start.vars.x.to_constraint_id(range)?,
6130 start.vars.y.to_constraint_id(range)?,
6131 );
6132 let solver_constraint = match target_segment {
6133 CircularSegmentConstraintTarget::Arc { end, direction, .. } => {
6134 let solver_arc = SolverArc::new(
6135 [center.vars.x, center.vars.y],
6136 [start.vars.x, start.vars.y],
6137 end,
6138 direction,
6139 range,
6140 )?;
6141 solver_arc.radius_constraint(radius_value)
6142 }
6143 CircularSegmentConstraintTarget::Circle { .. } => {
6144 let sketch_var_ty = solver_numeric_type(exec_state);
6145 let start_x =
6146 sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
6147 let start_y =
6148 sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
6149 let center_x =
6150 sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
6151 let center_y =
6152 sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
6153
6154 let radius_initial_value = libm::hypot(start_x - center_x, start_y - center_y);
6156
6157 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6158 let message =
6159 "Being inside a sketch block should have already been checked above"
6160 .to_owned();
6161 debug_assert!(false, "{}", &message);
6162 return Err(internal_err(message, self));
6163 };
6164 let radius_id = sketch_block_state.next_sketch_var_id();
6165 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
6166 value: Box::new(crate::execution::SketchVar {
6167 id: radius_id,
6168 initial_value: radius_initial_value,
6169 ty: sketch_var_ty,
6170 node_path: None,
6172 meta: vec![],
6173 }),
6174 });
6175 let radius =
6176 ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
6177 let solver_circle = ezpz::datatypes::inputs::DatumCircle {
6178 center: center_point,
6179 radius,
6180 };
6181 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
6182 start_point,
6183 center_point,
6184 radius,
6185 ));
6186 Constraint::CircleRadius(solver_circle, radius_value)
6187 }
6188 };
6189
6190 let constraint_id = exec_state.next_object_id();
6191 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6192 let message =
6193 "Being inside a sketch block should have already been checked above".to_owned();
6194 debug_assert!(false, "{}", &message);
6195 return Err(internal_err(message, self));
6196 };
6197 sketch_block_state.solver_constraints.push(solver_constraint);
6198 use crate::execution::Artifact;
6199 use crate::execution::CodeRef;
6200 use crate::execution::SketchBlockConstraint;
6201 use crate::front::SourceRef;
6202 let segment_object_id = match target_segment {
6203 CircularSegmentConstraintTarget::Arc { object_id, .. }
6204 | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
6205 };
6206
6207 let constraint = if is_diameter {
6208 use crate::frontend::sketch::Diameter;
6209 crate::front::Constraint::Diameter(Diameter {
6210 arc: segment_object_id,
6211 diameter: n.try_into().map_err(|_| {
6212 internal_err("Failed to convert diameter units numeric suffix:", range)
6213 })?,
6214 label_position,
6215 source,
6216 })
6217 } else {
6218 use crate::frontend::sketch::Radius;
6219 crate::front::Constraint::Radius(Radius {
6220 arc: segment_object_id,
6221 radius: n.try_into().map_err(|_| {
6222 internal_err("Failed to convert radius units numeric suffix:", range)
6223 })?,
6224 label_position,
6225 source,
6226 })
6227 };
6228 sketch_block_state.sketch_constraints.push(constraint_id);
6229 let Some(sketch_id) = sketch_block_state.sketch_id else {
6230 let message = "Sketch id missing for constraint artifact".to_owned();
6231 debug_assert!(false, "{}", &message);
6232 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
6233 };
6234 let artifact_id = exec_state.next_artifact_id();
6235 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
6236 id: artifact_id,
6237 sketch_id,
6238 constraint_id,
6239 constraint_type: super::artifact::sketch_block_constraint_type(&constraint),
6240 code_ref: CodeRef::placeholder(range),
6241 }));
6242 exec_state.add_scene_object(
6243 Object {
6244 id: constraint_id,
6245 kind: ObjectKind::Constraint { constraint },
6246 label: Default::default(),
6247 comments: Default::default(),
6248 artifact_id,
6249 source: SourceRef::new(range, self.node_path.clone()),
6250 },
6251 range,
6252 );
6253 }
6254 SketchConstraintKind::HorizontalDistance { points, label_position } => {
6255 let range = self.as_source_range();
6256 let p0 = &points[0];
6257 let p1 = &points[1];
6258 let constraint_id = exec_state.next_object_id();
6259 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6260 let message =
6261 "Being inside a sketch block should have already been checked above".to_owned();
6262 debug_assert!(false, "{}", &message);
6263 return Err(internal_err(message, self));
6264 };
6265 match (p0, p1) {
6266 (
6267 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
6268 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
6269 ) => {
6270 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6271 p0.vars.x.to_constraint_id(range)?,
6272 p0.vars.y.to_constraint_id(range)?,
6273 );
6274 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6275 p1.vars.x.to_constraint_id(range)?,
6276 p1.vars.y.to_constraint_id(range)?,
6277 );
6278 sketch_block_state
6279 .solver_constraints
6280 .push(ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n));
6281 }
6282 (
6283 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6284 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6285 ) => {
6286 sketch_block_state
6288 .solver_constraints
6289 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, -n.n));
6290 }
6291 (
6292 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6293 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6294 ) => {
6295 sketch_block_state
6297 .solver_constraints
6298 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, n.n));
6299 }
6300 (
6301 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6302 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6303 ) => {
6304 return Err(internal_err(
6305 "horizontalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
6306 range,
6307 ));
6308 }
6309 }
6310 use crate::execution::Artifact;
6311 use crate::execution::CodeRef;
6312 use crate::execution::SketchBlockConstraint;
6313 use crate::front::Distance;
6314 use crate::front::SourceRef;
6315 use crate::frontend::sketch::ConstraintSegment;
6316
6317 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
6318 segments: vec![
6319 match p0 {
6320 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6321 ConstraintSegment::from(point.object_id)
6322 }
6323 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6324 ConstraintSegment::ORIGIN
6325 }
6326 },
6327 match p1 {
6328 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6329 ConstraintSegment::from(point.object_id)
6330 }
6331 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6332 ConstraintSegment::ORIGIN
6333 }
6334 },
6335 ],
6336 distance: n.try_into().map_err(|_| {
6337 internal_err("Failed to convert distance units numeric suffix:", range)
6338 })?,
6339 label_position: label_position.clone(),
6340 source,
6341 });
6342 sketch_block_state.sketch_constraints.push(constraint_id);
6343 let Some(sketch_id) = sketch_block_state.sketch_id else {
6344 let message = "Sketch id missing for constraint artifact".to_owned();
6345 debug_assert!(false, "{}", &message);
6346 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
6347 };
6348 let artifact_id = exec_state.next_artifact_id();
6349 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
6350 id: artifact_id,
6351 sketch_id,
6352 constraint_id,
6353 constraint_type: super::artifact::sketch_block_constraint_type(&constraint),
6354 code_ref: CodeRef::placeholder(range),
6355 }));
6356 exec_state.add_scene_object(
6357 Object {
6358 id: constraint_id,
6359 kind: ObjectKind::Constraint { constraint },
6360 label: Default::default(),
6361 comments: Default::default(),
6362 artifact_id,
6363 source: SourceRef::new(range, self.node_path.clone()),
6364 },
6365 range,
6366 );
6367 }
6368 SketchConstraintKind::VerticalDistance { points, label_position } => {
6369 let range = self.as_source_range();
6370 let p0 = &points[0];
6371 let p1 = &points[1];
6372 let constraint_id = exec_state.next_object_id();
6373 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
6374 let message =
6375 "Being inside a sketch block should have already been checked above".to_owned();
6376 debug_assert!(false, "{}", &message);
6377 return Err(internal_err(message, self));
6378 };
6379 match (p0, p1) {
6380 (
6381 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
6382 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
6383 ) => {
6384 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6385 p0.vars.x.to_constraint_id(range)?,
6386 p0.vars.y.to_constraint_id(range)?,
6387 );
6388 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
6389 p1.vars.x.to_constraint_id(range)?,
6390 p1.vars.y.to_constraint_id(range)?,
6391 );
6392 sketch_block_state
6393 .solver_constraints
6394 .push(ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n));
6395 }
6396 (
6397 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6398 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6399 ) => {
6400 sketch_block_state
6401 .solver_constraints
6402 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, -n.n));
6403 }
6404 (
6405 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6406 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
6407 ) => {
6408 sketch_block_state
6409 .solver_constraints
6410 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, n.n));
6411 }
6412 (
6413 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6414 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
6415 ) => {
6416 return Err(internal_err(
6417 "verticalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
6418 range,
6419 ));
6420 }
6421 }
6422 use crate::execution::Artifact;
6423 use crate::execution::CodeRef;
6424 use crate::execution::SketchBlockConstraint;
6425 use crate::front::Distance;
6426 use crate::front::SourceRef;
6427 use crate::frontend::sketch::ConstraintSegment;
6428
6429 let constraint = crate::front::Constraint::VerticalDistance(Distance {
6430 segments: vec![
6431 match p0 {
6432 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6433 ConstraintSegment::from(point.object_id)
6434 }
6435 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6436 ConstraintSegment::ORIGIN
6437 }
6438 },
6439 match p1 {
6440 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
6441 ConstraintSegment::from(point.object_id)
6442 }
6443 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
6444 ConstraintSegment::ORIGIN
6445 }
6446 },
6447 ],
6448 distance: n.try_into().map_err(|_| {
6449 internal_err("Failed to convert distance units numeric suffix:", range)
6450 })?,
6451 label_position: label_position.clone(),
6452 source,
6453 });
6454 sketch_block_state.sketch_constraints.push(constraint_id);
6455 let Some(sketch_id) = sketch_block_state.sketch_id else {
6456 let message = "Sketch id missing for constraint artifact".to_owned();
6457 debug_assert!(false, "{}", &message);
6458 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
6459 };
6460 let artifact_id = exec_state.next_artifact_id();
6461 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
6462 id: artifact_id,
6463 sketch_id,
6464 constraint_id,
6465 constraint_type: super::artifact::sketch_block_constraint_type(&constraint),
6466 code_ref: CodeRef::placeholder(range),
6467 }));
6468 exec_state.add_scene_object(
6469 Object {
6470 id: constraint_id,
6471 kind: ObjectKind::Constraint { constraint },
6472 label: Default::default(),
6473 comments: Default::default(),
6474 artifact_id,
6475 source: SourceRef::new(range, self.node_path.clone()),
6476 },
6477 range,
6478 );
6479 }
6480 }
6481 return Ok(KclValue::none());
6482 }
6483 _ => {
6484 return Err(KclError::new_semantic(KclErrorDetails::new(
6485 format!(
6486 "Cannot create an equivalence constraint between values of these types: {} and {}",
6487 left_value.human_friendly_type(),
6488 right_value.human_friendly_type()
6489 ),
6490 vec![self.into()],
6491 )));
6492 }
6493 }
6494 }
6495
6496 if matches!(self.operator, BinaryOperator::Eq | BinaryOperator::Neq)
6499 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
6500 (&left_value, &right_value)
6501 {
6502 let is_equal = left == right;
6503 let value = if self.operator == BinaryOperator::Eq {
6504 is_equal
6505 } else {
6506 !is_equal
6507 };
6508 return Ok(KclValue::Bool { value, meta });
6509 }
6510
6511 if matches!(self.operator, BinaryOperator::Eq | BinaryOperator::Neq) {
6515 match (&left_value, &right_value) {
6516 (KclValue::Enum { value: left }, KclValue::Enum { value: right }) => {
6517 if left.enum_id() != right.enum_id() {
6518 return Err(different_enums_err(left, right, self.as_source_range()));
6519 }
6520
6521 let is_equal = left.variant() == right.variant();
6522 let value = if self.operator == BinaryOperator::Eq {
6523 is_equal
6524 } else {
6525 !is_equal
6526 };
6527 return Ok(KclValue::Bool { value, meta });
6528 }
6529 (KclValue::Enum { value }, other) | (other, KclValue::Enum { value }) => {
6530 return Err(KclError::new_semantic(KclErrorDetails::new(
6531 format!(
6532 "Cannot compare enum `{}` with {}.",
6533 value.qualified_name(),
6534 other.human_friendly_type()
6535 ),
6536 vec![self.as_source_range()],
6537 )));
6538 }
6539 _ => {}
6540 }
6541 }
6542
6543 let left = number_as_f64(&left_value, self.left.clone().into())?;
6544 let right = number_as_f64(&right_value, self.right.clone().into())?;
6545
6546 let value = match self.operator {
6547 BinaryOperator::Add => {
6548 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
6549 self.warn_on_unknown(&ty, "Adding", exec_state);
6550 KclValue::Number { value: l + r, meta, ty }
6551 }
6552 BinaryOperator::Sub => {
6553 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
6554 self.warn_on_unknown(&ty, "Subtracting", exec_state);
6555 KclValue::Number { value: l - r, meta, ty }
6556 }
6557 BinaryOperator::Mul => {
6558 let (l, r, ty) = NumericType::combine_mul(left, right);
6559 self.warn_on_unknown(&ty, "Multiplying", exec_state);
6560 KclValue::Number { value: l * r, meta, ty }
6561 }
6562 BinaryOperator::Div => {
6563 let (l, r, ty) = NumericType::combine_div(left, right);
6564 self.warn_on_unknown(&ty, "Dividing", exec_state);
6565 KclValue::Number { value: l / r, meta, ty }
6566 }
6567 BinaryOperator::Mod => {
6568 let (l, r, ty) = NumericType::combine_mod(left, right);
6569 self.warn_on_unknown(&ty, "Modulo of", exec_state);
6570 KclValue::Number { value: l % r, meta, ty }
6571 }
6572 BinaryOperator::Pow => KclValue::Number {
6573 value: libm::pow(left.n, right.n),
6574 meta,
6575 ty: exec_state.current_default_units(),
6576 },
6577 BinaryOperator::Neq => {
6578 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6579 self.warn_on_unknown(&ty, "Comparing", exec_state);
6580 KclValue::Bool { value: l != r, meta }
6581 }
6582 BinaryOperator::Gt => {
6583 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6584 self.warn_on_unknown(&ty, "Comparing", exec_state);
6585 KclValue::Bool { value: l > r, meta }
6586 }
6587 BinaryOperator::Gte => {
6588 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6589 self.warn_on_unknown(&ty, "Comparing", exec_state);
6590 KclValue::Bool { value: l >= r, meta }
6591 }
6592 BinaryOperator::Lt => {
6593 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6594 self.warn_on_unknown(&ty, "Comparing", exec_state);
6595 KclValue::Bool { value: l < r, meta }
6596 }
6597 BinaryOperator::Lte => {
6598 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6599 self.warn_on_unknown(&ty, "Comparing", exec_state);
6600 KclValue::Bool { value: l <= r, meta }
6601 }
6602 BinaryOperator::Eq => {
6603 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
6604 self.warn_on_unknown(&ty, "Comparing", exec_state);
6605 KclValue::Bool { value: l == r, meta }
6606 }
6607 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
6608 };
6609
6610 Ok(value)
6611 }
6612
6613 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
6614 internal_err("missing result while evaluating binary expression", node)
6615 }
6616
6617 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
6618 if ty == &NumericType::Unknown {
6619 let sr = self.as_source_range();
6620 exec_state.clear_units_warnings(&sr);
6621 let mut err = CompilationIssue::err(
6622 sr,
6623 format!(
6624 "{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)`."
6625 ),
6626 );
6627 err.tag = crate::errors::Tag::UnknownNumericUnits;
6628 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
6629 }
6630 }
6631}
6632
6633impl Node<UnaryExpression> {
6634 pub(super) async fn get_result(
6635 &self,
6636 exec_state: &mut ExecState,
6637 ctx: &ExecutorContext,
6638 ) -> Result<KclValueControlFlow, KclError> {
6639 let value = self.argument.get_result(exec_state, ctx).await?;
6640 let value = control_continue!(value);
6641 self.apply_unary(value, exec_state).map(KclValue::continue_)
6642 }
6643
6644 pub(super) fn apply_unary(&self, value: KclValue, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
6647 match self.operator {
6648 UnaryOperator::Not => {
6649 let KclValue::Bool {
6650 value: bool_value,
6651 meta: _,
6652 } = value
6653 else {
6654 return Err(KclError::new_semantic(KclErrorDetails::new(
6655 format!(
6656 "Cannot apply unary operator ! to non-boolean value: {}",
6657 value.human_friendly_type()
6658 ),
6659 vec![self.into()],
6660 )));
6661 };
6662 let meta = vec![Metadata {
6663 source_range: self.into(),
6664 }];
6665 let negated = KclValue::Bool {
6666 value: !bool_value,
6667 meta,
6668 };
6669
6670 Ok(negated)
6671 }
6672 UnaryOperator::Neg => {
6673 let err = || {
6674 KclError::new_semantic(KclErrorDetails::new(
6675 format!(
6676 "You can only negate numbers, planes, or lines, but this is a {}",
6677 value.human_friendly_type()
6678 ),
6679 vec![self.into()],
6680 ))
6681 };
6682 match &value {
6683 KclValue::Number { value, ty, .. } => {
6684 let meta = vec![Metadata {
6685 source_range: self.into(),
6686 }];
6687 Ok(KclValue::Number {
6688 value: -value,
6689 meta,
6690 ty: *ty,
6691 })
6692 }
6693 KclValue::Plane { value } => {
6694 let mut plane = value.clone();
6695 if plane.info.x_axis.x != 0.0 {
6696 plane.info.x_axis.x *= -1.0;
6697 }
6698 if plane.info.x_axis.y != 0.0 {
6699 plane.info.x_axis.y *= -1.0;
6700 }
6701 if plane.info.x_axis.z != 0.0 {
6702 plane.info.x_axis.z *= -1.0;
6703 }
6704 plane.info.z_axis = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
6705 plane.info.z_axis.canonicalize_signed_zero();
6706
6707 plane.id = exec_state.next_uuid();
6708 plane.object_id = None;
6709 Ok(KclValue::Plane { value: plane })
6710 }
6711 KclValue::Object {
6712 value: values, meta, ..
6713 } => {
6714 let Some(direction) = values.get("direction") else {
6716 return Err(err());
6717 };
6718
6719 let direction = match direction {
6720 KclValue::Tuple { value: values, meta } => {
6721 let values = values
6722 .iter()
6723 .map(|v| match v {
6724 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
6725 value: *value * -1.0,
6726 ty: *ty,
6727 meta: meta.clone(),
6728 }),
6729 _ => Err(err()),
6730 })
6731 .collect::<Result<Vec<_>, _>>()?;
6732
6733 KclValue::Tuple {
6734 value: values,
6735 meta: meta.clone(),
6736 }
6737 }
6738 KclValue::HomArray {
6739 value: values,
6740 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
6741 } => {
6742 let values = values
6743 .iter()
6744 .map(|v| match v {
6745 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
6746 value: *value * -1.0,
6747 ty: *ty,
6748 meta: meta.clone(),
6749 }),
6750 _ => Err(err()),
6751 })
6752 .collect::<Result<Vec<_>, _>>()?;
6753
6754 KclValue::HomArray {
6755 value: values,
6756 ty: ty.clone(),
6757 }
6758 }
6759 _ => return Err(err()),
6760 };
6761
6762 let mut value = values.clone();
6763 value.insert("direction".to_owned(), direction);
6764 Ok(KclValue::Object {
6765 value,
6766 meta: meta.clone(),
6767 constrainable: false,
6768 object_kind: KclObjectKind::Default,
6769 })
6770 }
6771 _ => Err(err()),
6772 }
6773 }
6774 UnaryOperator::Plus => match value {
6775 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(value),
6776 _ => Err(KclError::new_semantic(KclErrorDetails::new(
6777 format!(
6778 "You can only apply unary + to numbers or planes, but this is a {}",
6779 value.human_friendly_type()
6780 ),
6781 vec![self.into()],
6782 ))),
6783 },
6784 }
6785 }
6786}
6787
6788pub(crate) async fn execute_pipe_body(
6789 exec_state: &mut ExecState,
6790 body: &[Expr],
6791 source_range: SourceRange,
6792 ctx: &ExecutorContext,
6793) -> Result<KclValueControlFlow, KclError> {
6794 let Some((first, body)) = body.split_first() else {
6795 return Err(KclError::new_semantic(KclErrorDetails::new(
6796 "Pipe expressions cannot be empty".to_owned(),
6797 vec![source_range],
6798 )));
6799 };
6800 let meta = Metadata {
6805 source_range: SourceRange::from(first),
6806 };
6807 let output = ctx
6808 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
6809 .await?;
6810 let output = control_continue!(output);
6811
6812 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
6816 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
6818 exec_state.mod_local.pipe_value = previous_pipe_value;
6820
6821 result
6822}
6823
6824#[async_recursion]
6827async fn inner_execute_pipe_body(
6828 exec_state: &mut ExecState,
6829 body: &[Expr],
6830 ctx: &ExecutorContext,
6831) -> Result<KclValueControlFlow, KclError> {
6832 for expression in body {
6833 if let Expr::TagDeclarator(_) = expression {
6834 return Err(KclError::new_semantic(KclErrorDetails::new(
6835 format!("This cannot be in a PipeExpression: {expression:?}"),
6836 vec![expression.into()],
6837 )));
6838 }
6839 let metadata = Metadata {
6840 source_range: SourceRange::from(expression),
6841 };
6842 let output = ctx
6843 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
6844 .await?;
6845 let output = control_continue!(output);
6846 exec_state.mod_local.pipe_value = Some(output);
6847 }
6848 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
6850 Ok(final_output.continue_())
6851}
6852
6853impl Node<TagDeclarator> {
6854 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
6855 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
6856 value: self.name.clone(),
6857 info: Vec::new(),
6858 meta: vec![Metadata {
6859 source_range: self.into(),
6860 }],
6861 }));
6862
6863 exec_state
6864 .mut_stack()
6865 .add(self.name.clone(), memory_item, self.into())?;
6866
6867 Ok(self.into())
6868 }
6869}
6870
6871impl Node<ArrayExpression> {
6872 #[async_recursion]
6873 pub(super) async fn execute(
6874 &self,
6875 exec_state: &mut ExecState,
6876 ctx: &ExecutorContext,
6877 ) -> Result<KclValueControlFlow, KclError> {
6878 let mut results = Vec::with_capacity(self.elements.len());
6879
6880 for element in &self.elements {
6881 let metadata = Metadata::from(element);
6882 let value = ctx
6885 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
6886 .await?;
6887 let value = control_continue!(value);
6888
6889 results.push(value);
6890 }
6891
6892 Ok(KclValue::HomArray {
6893 value: results,
6894 ty: RuntimeType::Primitive(PrimitiveType::Any),
6895 }
6896 .continue_())
6897 }
6898}
6899
6900impl Node<ArrayRangeExpression> {
6901 #[async_recursion]
6902 pub(super) async fn execute(
6903 &self,
6904 exec_state: &mut ExecState,
6905 ctx: &ExecutorContext,
6906 ) -> Result<KclValueControlFlow, KclError> {
6907 let metadata = Metadata::from(&self.start_element);
6908 let start_val = ctx
6909 .execute_expr(
6910 &self.start_element,
6911 exec_state,
6912 &metadata,
6913 &[],
6914 StatementKind::Expression,
6915 )
6916 .await?;
6917 let start_val_for_build = control_continue!(start_val);
6918 self.validate_range_start(&start_val_for_build)?;
6919 let metadata = Metadata::from(&self.end_element);
6920 let end_val = ctx
6921 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
6922 .await?;
6923 let end_val = control_continue!(end_val);
6924 self.build_range(start_val_for_build, end_val, exec_state)
6925 .map(KclValue::continue_)
6926 }
6927
6928 pub(super) fn validate_range_start(&self, start_val: &KclValue) -> Result<(), KclError> {
6933 if start_val.as_ty_f64().is_none() {
6934 return Err(KclError::new_semantic(KclErrorDetails::new(
6935 format!(
6936 "Expected number for range start but found {}",
6937 start_val.human_friendly_type()
6938 ),
6939 vec![self.into()],
6940 )));
6941 }
6942 Ok(())
6943 }
6944
6945 pub(super) fn build_range(
6948 &self,
6949 start_val: KclValue,
6950 end_val: KclValue,
6951 exec_state: &mut ExecState,
6952 ) -> Result<KclValue, KclError> {
6953 let start = start_val
6954 .as_ty_f64()
6955 .ok_or(KclError::new_semantic(KclErrorDetails::new(
6956 format!(
6957 "Expected number for range start but found {}",
6958 start_val.human_friendly_type()
6959 ),
6960 vec![self.into()],
6961 )))?;
6962 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
6963 format!(
6964 "Expected number for range end but found {}",
6965 end_val.human_friendly_type()
6966 ),
6967 vec![self.into()],
6968 )))?;
6969
6970 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
6971 let Some(start) = crate::try_f64_to_i64(start) else {
6972 return Err(KclError::new_semantic(KclErrorDetails::new(
6973 format!("Range start must be an integer, but found {start}"),
6974 vec![self.into()],
6975 )));
6976 };
6977 let Some(end) = crate::try_f64_to_i64(end) else {
6978 return Err(KclError::new_semantic(KclErrorDetails::new(
6979 format!("Range end must be an integer, but found {end}"),
6980 vec![self.into()],
6981 )));
6982 };
6983
6984 if end < start {
6985 return Err(KclError::new_semantic(KclErrorDetails::new(
6986 format!("Range start is greater than range end: {start} .. {end}"),
6987 vec![self.into()],
6988 )));
6989 }
6990
6991 let range: Vec<_> = if self.end_inclusive {
6992 (start..=end).collect()
6993 } else {
6994 (start..end).collect()
6995 };
6996
6997 let meta = vec![Metadata {
6998 source_range: self.into(),
6999 }];
7000
7001 Ok(KclValue::HomArray {
7002 value: range
7003 .into_iter()
7004 .map(|num| KclValue::Number {
7005 value: num as f64,
7006 ty,
7007 meta: meta.clone(),
7008 })
7009 .collect(),
7010 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
7011 })
7012 }
7013}
7014
7015impl Node<ObjectExpression> {
7016 #[async_recursion]
7017 pub(super) async fn execute(
7018 &self,
7019 exec_state: &mut ExecState,
7020 ctx: &ExecutorContext,
7021 ) -> Result<KclValueControlFlow, KclError> {
7022 let mut object = HashMap::with_capacity(self.properties.len());
7023 for property in &self.properties {
7024 let metadata = Metadata::from(&property.value);
7025 let result = ctx
7026 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
7027 .await?;
7028 let result = control_continue!(result);
7029 object.insert(property.key.name.clone(), result);
7030 }
7031
7032 Ok(KclValue::Object {
7033 value: object,
7034 meta: vec![Metadata {
7035 source_range: self.into(),
7036 }],
7037 constrainable: false,
7038 object_kind: KclObjectKind::Default,
7039 }
7040 .continue_())
7041 }
7042}
7043
7044fn article_for<S: AsRef<str>>(s: S) -> &'static str {
7045 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
7047 "an"
7048 } else {
7049 "a"
7050 }
7051}
7052
7053fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
7054 v.as_ty_f64().ok_or_else(|| {
7055 let actual_type = v.human_friendly_type();
7056 KclError::new_semantic(KclErrorDetails::new(
7057 format!("Expected a number, but found {actual_type}",),
7058 vec![source_range],
7059 ))
7060 })
7061}
7062
7063impl Node<IfExpression> {
7064 #[async_recursion]
7065 pub(super) async fn get_result(
7066 &self,
7067 exec_state: &mut ExecState,
7068 ctx: &ExecutorContext,
7069 ) -> Result<KclValueControlFlow, KclError> {
7070 let cond_value = ctx
7073 .execute_expr(
7074 &self.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, &self.then_val, exec_state).await;
7084 }
7085
7086 for else_if in &self.else_ifs {
7088 let cond_value = ctx
7089 .execute_expr(
7090 &else_if.cond,
7091 exec_state,
7092 &Metadata::from(self),
7093 &[],
7094 StatementKind::Expression,
7095 )
7096 .await?;
7097 let cond_value = control_continue!(cond_value);
7098 if cond_value.get_bool()? {
7099 return exec_if_arm(ctx, &else_if.then_val, exec_state).await;
7100 }
7101 }
7102
7103 exec_if_arm(ctx, &self.final_else, exec_state).await
7105 }
7106}
7107
7108pub(super) fn if_arm_scope_begin(exec_state: &mut ExecState) -> Result<bool, KclError> {
7117 if !exec_state.entry_point_version_is_v3_or_higher() {
7118 return Ok(false);
7119 }
7120 exec_state.mut_stack().push_new_env_for_block()?;
7121 Ok(true)
7122}
7123
7124async fn exec_if_arm(
7129 ctx: &ExecutorContext,
7130 block: &Node<Program>,
7131 exec_state: &mut ExecState,
7132) -> Result<KclValueControlFlow, KclError> {
7133 let scoped = if_arm_scope_begin(exec_state)?;
7134 let result = ctx.exec_block(block, exec_state, BodyType::Block).await;
7135 if scoped {
7136 exec_state.mut_stack().pop_env()?;
7140 }
7141 let Some(cf) = result? else {
7145 let message = "if-expression arm produced no value";
7146 debug_assert!(false, "{message}");
7147 return Err(KclError::new_internal(KclErrorDetails::new(
7148 message.to_owned(),
7149 vec![block.to_source_range()],
7150 )));
7151 };
7152 Ok(cf)
7153}
7154
7155#[derive(Debug)]
7156pub(super) enum Property {
7157 UInt(usize),
7158 String(String),
7159}
7160
7161impl Property {
7162 #[allow(clippy::too_many_arguments)]
7163 async fn try_from<'a>(
7164 computed: bool,
7165 value: Expr,
7166 exec_state: &mut ExecState,
7167 sr: SourceRange,
7168 ctx: &ExecutorContext,
7169 metadata: &Metadata,
7170 annotations: &[Node<Annotation>],
7171 statement_kind: StatementKind<'a>,
7172 ) -> Result<Self, EarlyReturn> {
7173 if !computed {
7174 return Ok(Self::from_static_name(&value, sr)?);
7175 }
7176
7177 let prop_value = ctx
7178 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
7179 .await?;
7180 let prop_value = early_return!(prop_value);
7183 Ok(Self::from_value(prop_value, sr)?)
7184 }
7185
7186 pub(super) fn from_static_name(property: &Expr, sr: SourceRange) -> Result<Self, KclError> {
7189 let Expr::Name(identifier) = property else {
7190 return Err(KclError::new_semantic(KclErrorDetails::new(
7192 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
7193 .to_owned(),
7194 vec![sr],
7195 )));
7196 };
7197 Ok(Property::String(identifier.to_string()))
7198 }
7199
7200 pub(super) fn from_value(prop_value: KclValue, sr: SourceRange) -> Result<Self, KclError> {
7203 let property_sr = vec![sr];
7204 match prop_value {
7205 KclValue::Number { value, ty, meta: _ } => {
7206 if !matches!(
7207 ty,
7208 NumericType::Unknown
7209 | NumericType::Default { .. }
7210 | NumericType::Known(crate::exec::UnitType::Count)
7211 ) {
7212 return Err(KclError::new_semantic(KclErrorDetails::new(
7213 format!(
7214 "{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"
7215 ),
7216 property_sr,
7217 )));
7218 }
7219 if let Some(x) = crate::try_f64_to_usize(value) {
7220 Ok(Property::UInt(x))
7221 } else {
7222 Err(KclError::new_semantic(KclErrorDetails::new(
7223 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
7224 property_sr,
7225 )))
7226 }
7227 }
7228 _ => Err(KclError::new_semantic(KclErrorDetails::new(
7229 "Only numbers (>= 0) can be indexes".to_owned(),
7230 vec![sr],
7231 ))),
7232 }
7233 }
7234}
7235
7236impl Property {
7237 fn type_name(&self) -> &'static str {
7238 match self {
7239 Property::UInt(_) => "number",
7240 Property::String(_) => "string",
7241 }
7242 }
7243}
7244
7245impl Node<PipeExpression> {
7246 #[async_recursion]
7247 pub(super) async fn get_result(
7248 &self,
7249 exec_state: &mut ExecState,
7250 ctx: &ExecutorContext,
7251 ) -> Result<KclValueControlFlow, KclError> {
7252 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
7253 }
7254}
7255
7256#[cfg(test)]
7257mod test {
7258 use std::sync::Arc;
7259
7260 use kcl_api::UnitLength;
7261 use tokio::io::AsyncWriteExt;
7262
7263 use super::*;
7264 use crate::ExecutorSettings;
7265 use crate::engine::engine_manager;
7266 use crate::errors::Severity;
7267 use crate::exec::UnitType;
7268 use crate::execution::ContextType;
7269 use crate::execution::machine::ExecutorKind;
7270 use crate::execution::parse_execute;
7271
7272 fn assert_angle_degrees(actual: ezpz::datatypes::Angle, expected: f64) {
7273 assert!(
7274 (actual.to_degrees() - expected).abs() < 1e-9,
7275 "expected {expected}deg, got {}deg",
7276 actual.to_degrees()
7277 );
7278 }
7279
7280 #[test]
7281 fn remaps_sector_angles_to_existing_representative_endpoint_rays() {
7282 let representative_directions = [AngleRayDirection::Forward, AngleRayDirection::Forward];
7283
7284 assert_angle_degrees(
7285 remap_angle_for_representative_rays(
7286 angle_sector_rays(AngleSector::One, 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::Two, 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::Three, false),
7303 representative_directions,
7304 ezpz::datatypes::Angle::from_degrees(60.0),
7305 ),
7306 60.0,
7307 );
7308 assert_angle_degrees(
7309 remap_angle_for_representative_rays(
7310 angle_sector_rays(AngleSector::Four, false),
7311 representative_directions,
7312 ezpz::datatypes::Angle::from_degrees(120.0),
7313 ),
7314 60.0,
7315 );
7316 assert_angle_degrees(
7317 remap_angle_for_representative_rays(
7318 angle_sector_rays(AngleSector::One, true),
7319 representative_directions,
7320 ezpz::datatypes::Angle::from_degrees(300.0),
7321 ),
7322 60.0,
7323 );
7324 }
7325
7326 #[test]
7327 fn remaps_sector_angles_when_representative_endpoint_is_on_reverse_ray() {
7328 assert_angle_degrees(
7329 remap_angle_for_representative_rays(
7330 angle_sector_rays(AngleSector::One, false),
7331 [AngleRayDirection::Forward, AngleRayDirection::Reverse],
7332 ezpz::datatypes::Angle::from_degrees(60.0),
7333 ),
7334 240.0,
7335 );
7336 }
7337
7338 #[tokio::test(flavor = "multi_thread")]
7339 async fn angle_unlabeled_keeps_legacy_lines_at_angle() {
7340 let code = r#"
7341sketch(on = XY) {
7342 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7343 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7344 lines = [line1, line2]
7345 angle(lines) == 60deg
7346}
7347"#;
7348 let result = parse_execute(code).await.unwrap();
7349
7350 let metadata = result
7351 .exec_state
7352 .global
7353 .root_module_artifacts
7354 .legacy_angle_refactor_metadata();
7355 assert_eq!(metadata.len(), 1);
7356 assert_eq!(metadata[0].sector, 1);
7357 assert!(!metadata[0].inverse);
7358 let program = crate::Program::parse_no_errs(code).unwrap();
7359 let findings = program.lint(crate::lint::checks::lint_legacy_angle).unwrap();
7360 assert_eq!(metadata[0].source_range, findings[0].pos);
7361 }
7362
7363 #[tokio::test(flavor = "multi_thread")]
7364 async fn legacy_angle_refactor_metadata_matches_the_default_label_side() {
7365 let result = parse_execute(
7366 r#"
7367sketch(on = XY) {
7368 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7369 line2 = line(start = [var 0mm, var 0mm], end = [var -2mm, var -3.464mm])
7370 angle([line1, line2]) == 60deg
7371}
7372"#,
7373 )
7374 .await
7375 .unwrap();
7376
7377 let metadata = result
7378 .exec_state
7379 .global
7380 .root_module_artifacts
7381 .legacy_angle_refactor_metadata();
7382 assert_eq!(metadata.len(), 1);
7383 assert_eq!(metadata[0].sector, 4);
7384 assert!(metadata[0].inverse);
7385 }
7386
7387 #[tokio::test(flavor = "multi_thread")]
7388 async fn legacy_angle_refactor_metadata_uses_reverse_segment_rays() {
7389 let result = parse_execute(
7390 r#"
7391sketch(on = XY) {
7392 line1 = line(start = [var -4mm, var 0mm], end = [var 0mm, var 0mm])
7393 line2 = line(start = [var -2mm, var -3.464mm], end = [var 0mm, var 0mm])
7394 angle([line1, line2]) == 60deg
7395}
7396"#,
7397 )
7398 .await
7399 .unwrap();
7400
7401 let metadata = result
7402 .exec_state
7403 .global
7404 .root_module_artifacts
7405 .legacy_angle_refactor_metadata();
7406 assert_eq!(metadata.len(), 1);
7407 assert_eq!(metadata[0].sector, 3);
7408 assert!(!metadata[0].inverse);
7409 }
7410
7411 #[tokio::test(flavor = "multi_thread")]
7412 async fn legacy_angle_label_position_does_not_change_the_sector() {
7413 let result = parse_execute(
7414 r#"
7415sketch(on = XY) {
7416 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7417 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7418 angle([line1, line2], labelPosition = [-3mm, -1.7mm]) == 60deg
7419}
7420"#,
7421 )
7422 .await
7423 .unwrap();
7424
7425 let metadata = result
7426 .exec_state
7427 .global
7428 .root_module_artifacts
7429 .legacy_angle_refactor_metadata();
7430 assert_eq!(metadata.len(), 1);
7431 assert_eq!(metadata[0].sector, 1);
7432 assert!(!metadata[0].inverse);
7433 }
7434
7435 #[tokio::test(flavor = "multi_thread")]
7436 async fn parallel_legacy_angle_has_no_refactor_metadata() {
7437 let result = parse_execute(
7438 r#"
7439sketch(on = XY) {
7440 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7441 line2 = line(start = [var 0mm, var 1mm], end = [var 4mm, var 1mm])
7442 angle([line1, line2]) == 0deg
7443}
7444"#,
7445 )
7446 .await
7447 .unwrap();
7448
7449 assert!(
7450 result
7451 .exec_state
7452 .global
7453 .root_module_artifacts
7454 .legacy_angle_refactor_metadata()
7455 .is_empty()
7456 );
7457 }
7458
7459 #[tokio::test(flavor = "multi_thread")]
7460 async fn angle_dimension_with_sector_uses_named_lines() {
7461 parse_execute(
7462 r#"
7463sketch(on = XY) {
7464 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7465 line2 = line(start = [var 0mm, var 1mm], end = [var 2mm, var 3mm])
7466 angleDimension(lines = [line1, line2], sector = 2) == 60deg
7467}
7468"#,
7469 )
7470 .await
7471 .unwrap();
7472 }
7473
7474 #[tokio::test(flavor = "multi_thread")]
7475 async fn angle_dimension_requires_sector() {
7476 let err = parse_execute(
7477 r#"
7478sketch(on = XY) {
7479 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7480 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7481 angleDimension(lines = [line1, line2]) == 60deg
7482}
7483"#,
7484 )
7485 .await
7486 .unwrap_err();
7487
7488 assert!(
7489 err.to_string()
7490 .contains("The `angleDimension` function requires a keyword argument `sector`"),
7491 "unexpected error: {err:?}"
7492 );
7493 }
7494
7495 #[tokio::test(flavor = "multi_thread")]
7496 async fn angle_dimension_accepts_label_position() {
7497 let result = parse_execute(
7498 r#"
7499sketch(on = XY) {
7500 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7501 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7502 angleDimension(lines = [line1, line2], sector = 1, labelPosition = [10mm, 11mm]) == 60deg
7503}
7504"#,
7505 )
7506 .await
7507 .unwrap();
7508 let angle = result
7509 .exec_state
7510 .global
7511 .root_module_artifacts
7512 .scene_objects
7513 .iter()
7514 .find_map(|object| match &object.kind {
7515 ObjectKind::Constraint {
7516 constraint: crate::front::Constraint::Angle(angle),
7517 } => Some(angle),
7518 _ => None,
7519 })
7520 .unwrap();
7521 let label_position = angle.label_position.as_ref().unwrap();
7522 assert_eq!(label_position.x.value, 10.0);
7523 assert_eq!(label_position.y.value, 11.0);
7524 }
7525
7526 #[tokio::test(flavor = "multi_thread")]
7527 async fn angle_dimension_accepts_all_four_sectors() {
7528 parse_execute(
7529 r#"
7530sketch(on = XY) {
7531 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7532 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7533 angleDimension(lines = [line1, line2], sector = 1) == 60deg
7534 angleDimension(lines = [line1, line2], sector = 2) == 120deg
7535 angleDimension(lines = [line1, line2], sector = 3) == 60deg
7536 angleDimension(lines = [line1, line2], sector = 4) == 120deg
7537}
7538"#,
7539 )
7540 .await
7541 .unwrap();
7542 }
7543
7544 #[tokio::test(flavor = "multi_thread")]
7545 async fn angle_dimension_accepts_inverse_angle_for_sector() {
7546 let result = parse_execute(
7547 r#"
7548sketch(on = XY) {
7549 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7550 line2 = line(start = [var 0mm, var 0mm], end = [var 2mm, var 3.464mm])
7551 angleDimension(lines = [line1, line2], sector = 1, inverse = true) == 360deg - 60deg
7552}
7553"#,
7554 )
7555 .await
7556 .unwrap();
7557 let angle = result
7558 .exec_state
7559 .global
7560 .root_module_artifacts
7561 .scene_objects
7562 .iter()
7563 .find_map(|object| match &object.kind {
7564 ObjectKind::Constraint {
7565 constraint: crate::front::Constraint::Angle(angle),
7566 } => Some(angle),
7567 _ => None,
7568 })
7569 .unwrap();
7570 assert_eq!(angle.sector, Some(1));
7571 assert_eq!(angle.inverse, Some(true));
7572 }
7573
7574 #[tokio::test(flavor = "multi_thread")]
7575 async fn angle_dimension_rejects_invalid_sector() {
7576 let err = parse_execute(
7577 r#"
7578sketch(on = XY) {
7579 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7580 line2 = line(start = [var 0mm, var 1mm], end = [var 2mm, var 3mm])
7581 angleDimension(lines = [line1, line2], sector = 5) == 60deg
7582}
7583"#,
7584 )
7585 .await
7586 .unwrap_err();
7587
7588 assert!(
7589 err.to_string()
7590 .contains("angleDimension() sector must be 1, 2, 3, or 4"),
7591 "unexpected error: {err:?}"
7592 );
7593 }
7594
7595 #[tokio::test(flavor = "multi_thread")]
7596 async fn angle_dimension_rejects_parallel_lines() {
7597 let err = parse_execute(
7598 r#"
7599sketch(on = XY) {
7600 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7601 line2 = line(start = [var 0mm, var 1mm], end = [var 4mm, var 1mm])
7602 angleDimension(lines = [line1, line2], sector = 2) == 60deg
7603}
7604"#,
7605 )
7606 .await
7607 .unwrap_err();
7608
7609 assert!(
7610 err.to_string()
7611 .contains("angleDimension(lines = ..., sector = ...) requires non-parallel lines"),
7612 "unexpected error: {err:?}"
7613 );
7614 }
7615
7616 #[tokio::test(flavor = "multi_thread")]
7617 async fn angle_accepts_label_position() {
7618 let result = parse_execute(
7619 r#"
7620sketch(on = XY) {
7621 line1 = line(start = [var 0mm, var 0mm], end = [var 4mm, var 0mm])
7622 line2 = line(start = [var 0mm, var 1mm], end = [var 2mm, var 3mm])
7623 angle([line1, line2], labelPosition = [10mm, 11mm]) == 60deg
7624}
7625"#,
7626 )
7627 .await
7628 .unwrap();
7629 let angle = result
7630 .exec_state
7631 .global
7632 .root_module_artifacts
7633 .scene_objects
7634 .iter()
7635 .find_map(|object| match &object.kind {
7636 ObjectKind::Constraint {
7637 constraint: crate::front::Constraint::Angle(angle),
7638 } => Some(angle),
7639 _ => None,
7640 })
7641 .unwrap();
7642 let label_position = angle.label_position.as_ref().unwrap();
7643 assert_eq!(label_position.x.value, 10.0);
7644 assert_eq!(label_position.y.value, 11.0);
7645 }
7646
7647 #[tokio::test(flavor = "multi_thread")]
7648 async fn angle_requires_unlabeled_lines() {
7649 parse_execute(
7650 r#"
7651sketch(on = XY) {
7652 angle() == 60deg
7653}
7654"#,
7655 )
7656 .await
7657 .unwrap_err();
7658 }
7659
7660 #[tokio::test(flavor = "multi_thread")]
7661 async fn ascription() {
7662 let program = r#"
7663a = 42: number
7664b = a: number
7665p = {
7666 origin = { x = 0, y = 0, z = 0 },
7667 xAxis = { x = 1, y = 0, z = 0 },
7668 yAxis = { x = 0, y = 1, z = 0 },
7669 zAxis = { x = 0, y = 0, z = 1 }
7670}: Plane
7671arr1 = [42]: [number(cm)]
7672"#;
7673
7674 let result = parse_execute(program).await.unwrap();
7675 let mem = result.exec_state.stack();
7676 assert!(matches!(
7677 mem.memory
7678 .get_from_owned("p", result.mem_env, SourceRange::default(), 0)
7679 .unwrap(),
7680 KclValue::Plane { .. }
7681 ));
7682 let arr1 = mem
7683 .memory
7684 .get_from_owned("arr1", result.mem_env, SourceRange::default(), 0)
7685 .unwrap();
7686 if let KclValue::HomArray { value, ty } = arr1 {
7687 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
7688 assert_eq!(ty, RuntimeType::known_length(UnitLength::Centimeters));
7689 if let KclValue::Number { value, ty, .. } = &value[0] {
7691 assert_eq!(*value, 42.0);
7693 assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLength::Centimeters)));
7694 } else {
7695 panic!("Expected a number; found {:?}", value[0]);
7696 }
7697 } else {
7698 panic!("Expected HomArray; found {arr1:?}");
7699 }
7700
7701 let program = r#"
7702a = 42: string
7703"#;
7704 let result = parse_execute(program).await;
7705 let err = result.unwrap_err();
7706 assert!(
7707 err.to_string()
7708 .contains("could not coerce a number (with type `number`) to type `string`"),
7709 "Expected error but found {err:?}"
7710 );
7711
7712 let program = r#"
7713a = 42: Plane
7714"#;
7715 let result = parse_execute(program).await;
7716 let err = result.unwrap_err();
7717 assert!(
7718 err.to_string()
7719 .contains("could not coerce a number (with type `number`) to type `Plane`"),
7720 "Expected error but found {err:?}"
7721 );
7722
7723 let program = r#"
7724arr = [0]: [string]
7725"#;
7726 let result = parse_execute(program).await;
7727 let err = result.unwrap_err();
7728 assert!(
7729 err.to_string().contains(
7730 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
7731 ),
7732 "Expected error but found {err:?}"
7733 );
7734
7735 let program = r#"
7736mixedArr = [0, "a"]: [number(mm)]
7737"#;
7738 let result = parse_execute(program).await;
7739 let err = result.unwrap_err();
7740 assert!(
7741 err.to_string().contains(
7742 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
7743 ),
7744 "Expected error but found {err:?}"
7745 );
7746
7747 let program = r#"
7748mixedArr = [0, "a"]: [mm]
7749"#;
7750 let result = parse_execute(program).await;
7751 let err = result.unwrap_err();
7752 assert!(
7753 err.to_string().contains(
7754 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
7755 ),
7756 "Expected error but found {err:?}"
7757 );
7758 }
7759
7760 #[tokio::test(flavor = "multi_thread")]
7761 async fn neg_plane() {
7762 let program = r#"
7763p = {
7764 origin = { x = 0, y = 0, z = 0 },
7765 xAxis = { x = 1, y = 0, z = 0 },
7766 yAxis = { x = 0, y = 1, z = 0 },
7767}: Plane
7768p2 = -p
7769"#;
7770
7771 let result = parse_execute(program).await.unwrap();
7772 let mem = result.exec_state.stack();
7773 match mem
7774 .memory
7775 .get_from_owned("p2", result.mem_env, SourceRange::default(), 0)
7776 .unwrap()
7777 {
7778 KclValue::Plane { value } => {
7779 assert_eq!(value.info.x_axis.x, -1.0);
7780 assert_eq!(value.info.x_axis.y, 0.0);
7781 assert_eq!(value.info.x_axis.z, 0.0);
7782 }
7783 _ => unreachable!(),
7784 }
7785 }
7786
7787 #[tokio::test(flavor = "multi_thread")]
7788 async fn multiple_returns() {
7789 let program = r#"fn foo() {
7790 return 0
7791 return 42
7792}
7793
7794a = foo()
7795"#;
7796
7797 let result = parse_execute(program).await;
7798 assert!(result.unwrap_err().to_string().contains("return"));
7799 }
7800
7801 #[tokio::test(flavor = "multi_thread")]
7802 async fn load_all_modules() {
7803 let program_a_kcl = r#"
7805export a = 1
7806"#;
7807 let program_b_kcl = r#"
7809import a from 'a.kcl'
7810
7811export b = a + 1
7812"#;
7813 let program_c_kcl = r#"
7815import a from 'a.kcl'
7816
7817export c = a + 2
7818"#;
7819
7820 let main_kcl = r#"
7822import b from 'b.kcl'
7823import c from 'c.kcl'
7824
7825d = b + c
7826"#;
7827
7828 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
7829 .parse_errs_as_err()
7830 .unwrap();
7831
7832 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
7833
7834 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
7835 .await
7836 .unwrap()
7837 .write_all(main_kcl.as_bytes())
7838 .await
7839 .unwrap();
7840
7841 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
7842 .await
7843 .unwrap()
7844 .write_all(program_a_kcl.as_bytes())
7845 .await
7846 .unwrap();
7847
7848 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
7849 .await
7850 .unwrap()
7851 .write_all(program_b_kcl.as_bytes())
7852 .await
7853 .unwrap();
7854
7855 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
7856 .await
7857 .unwrap()
7858 .write_all(program_c_kcl.as_bytes())
7859 .await
7860 .unwrap();
7861
7862 let exec_ctxt = ExecutorContext {
7863 engine: Arc::new(engine_manager::EngineManager::new_mock()),
7864 engine_batch: crate::engine::EngineBatchContext::default(),
7865 fs: crate::fs::new_file_system_handle(crate::fs::FileManager::new()),
7866 settings: ExecutorSettings {
7867 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
7868 ..Default::default()
7869 },
7870 context_type: ContextType::Mock,
7871 execution_callbacks: Default::default(),
7872 executor_kind: ExecutorKind::resolve(),
7873 machine_call_depth_limit: crate::execution::machine::DEFAULT_MACHINE_CALL_DEPTH_LIMIT,
7874 configure_engine_render: true,
7875 };
7876 let mut exec_state = ExecState::new(&exec_ctxt);
7877
7878 exec_ctxt
7879 .run(
7880 &crate::Program {
7881 kcl_version: main.kcl_version,
7882 ast: main.ast.clone(),
7883 original_file_contents: "".to_owned(),
7884 },
7885 &mut exec_state,
7886 )
7887 .await
7888 .unwrap();
7889 }
7890
7891 #[tokio::test(flavor = "multi_thread")]
7892 async fn user_coercion() {
7893 let program = r#"fn foo(x: Axis2d) {
7894 return 0
7895}
7896
7897foo(x = { direction = [0, 0], origin = [0, 0]})
7898"#;
7899
7900 parse_execute(program).await.unwrap();
7901
7902 let program = r#"fn foo(x: Axis3d) {
7903 return 0
7904}
7905
7906foo(x = { direction = [0, 0], origin = [0, 0]})
7907"#;
7908
7909 parse_execute(program).await.unwrap_err();
7910 }
7911
7912 #[tokio::test(flavor = "multi_thread")]
7913 async fn coerce_return() {
7914 let program = r#"fn foo(): number(mm) {
7915 return 42
7916}
7917
7918a = foo()
7919"#;
7920
7921 parse_execute(program).await.unwrap();
7922
7923 let program = r#"fn foo(): mm {
7924 return 42
7925}
7926
7927a = foo()
7928"#;
7929
7930 parse_execute(program).await.unwrap();
7931
7932 let program = r#"fn foo(): number(mm) {
7933 return { bar: 42 }
7934}
7935
7936a = foo()
7937"#;
7938
7939 parse_execute(program).await.unwrap_err();
7940
7941 let program = r#"fn foo(): mm {
7942 return { bar: 42 }
7943}
7944
7945a = foo()
7946"#;
7947
7948 parse_execute(program).await.unwrap_err();
7949 }
7950
7951 #[tokio::test(flavor = "multi_thread")]
7952 async fn test_sensible_error_when_missing_equals_in_kwarg() {
7953 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)"]
7954 .into_iter()
7955 .enumerate()
7956 {
7957 let program = format!(
7958 "fn foo() {{ return 0 }}
7959z = 0
7960fn f(x, y, z) {{ return 0 }}
7961{call}"
7962 );
7963 let err = parse_execute(&program).await.unwrap_err();
7964 let msg = err.message();
7965 assert!(
7966 msg.contains("This argument needs a label, but it doesn't have one"),
7967 "failed test {i}: {msg}"
7968 );
7969 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
7970 if i == 0 {
7971 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
7972 }
7973 }
7974 }
7975
7976 #[tokio::test(flavor = "multi_thread")]
7977 async fn default_param_for_unlabeled() {
7978 let ast = r#"fn myExtrude(@sk, length) {
7981 return extrude(sk, length)
7982}
7983sketch001 = startSketchOn(XY)
7984 |> circle(center = [0, 0], radius = 93.75)
7985 |> myExtrude(length = 40)
7986"#;
7987
7988 parse_execute(ast).await.unwrap();
7989 }
7990
7991 #[tokio::test(flavor = "multi_thread")]
7992 async fn dont_use_unlabelled_as_input() {
7993 let ast = r#"length = 10
7995startSketchOn(XY)
7996 |> circle(center = [0, 0], radius = 93.75)
7997 |> extrude(length)
7998"#;
7999
8000 parse_execute(ast).await.unwrap();
8001 }
8002
8003 #[tokio::test(flavor = "multi_thread")]
8004 async fn ascription_in_binop() {
8005 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
8006 parse_execute(ast).await.unwrap();
8007
8008 let ast = r#"foo = tan(0): rad - 4deg"#;
8009 parse_execute(ast).await.unwrap();
8010 }
8011
8012 #[tokio::test(flavor = "multi_thread")]
8013 async fn neg_sqrt() {
8014 let ast = r#"bad = sqrt(-2)"#;
8015
8016 let e = parse_execute(ast).await.unwrap_err();
8017 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
8019 }
8020
8021 #[tokio::test(flavor = "multi_thread")]
8022 async fn non_array_fns() {
8023 let ast = r#"push(1, item = 2)
8024pop(1)
8025map(1, f = fn(@x) { return x + 1 })
8026reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
8027
8028 parse_execute(ast).await.unwrap();
8029 }
8030
8031 #[tokio::test(flavor = "multi_thread")]
8032 async fn non_array_indexing() {
8033 let good = r#"a = 42
8034good = a[0]
8035"#;
8036 let result = parse_execute(good).await.unwrap();
8037 let mem = result.exec_state.stack();
8038 let num = mem
8039 .memory
8040 .get_from_owned("good", result.mem_env, SourceRange::default(), 0)
8041 .unwrap()
8042 .as_ty_f64()
8043 .unwrap();
8044 assert_eq!(num.n, 42.0);
8045
8046 let bad = r#"a = 42
8047bad = a[1]
8048"#;
8049
8050 parse_execute(bad).await.unwrap_err();
8051 }
8052
8053 #[tokio::test(flavor = "multi_thread")]
8054 async fn coerce_unknown_to_length() {
8055 let ast = r#"x = 2mm * 2mm
8056y = x: number(Length)"#;
8057 let e = parse_execute(ast).await.unwrap_err();
8058 assert!(
8059 e.message().contains("could not coerce"),
8060 "Error message: '{}'",
8061 e.message()
8062 );
8063
8064 let ast = r#"x = 2mm
8065y = x: number(Length)"#;
8066 let result = parse_execute(ast).await.unwrap();
8067 let mem = result.exec_state.stack();
8068 let num = mem
8069 .memory
8070 .get_from_owned("y", result.mem_env, SourceRange::default(), 0)
8071 .unwrap()
8072 .as_ty_f64()
8073 .unwrap();
8074 assert_eq!(num.n, 2.0);
8075 assert_eq!(num.ty, NumericType::mm());
8076 }
8077
8078 #[tokio::test(flavor = "multi_thread")]
8079 async fn one_warning_unknown() {
8080 let ast = r#"
8081// Should warn once
8082a = PI * 2
8083// Should warn once
8084b = (PI * 2) / 3
8085// Should not warn
8086c = ((PI * 2) / 3): number(deg)
8087"#;
8088
8089 let result = parse_execute(ast).await.unwrap();
8090 assert_eq!(result.exec_state.issues().len(), 2);
8091 }
8092
8093 #[tokio::test(flavor = "multi_thread")]
8094 async fn non_count_indexing() {
8095 let ast = r#"x = [0, 0]
8096y = x[1mm]
8097"#;
8098 parse_execute(ast).await.unwrap_err();
8099
8100 let ast = r#"x = [0, 0]
8101y = 1deg
8102z = x[y]
8103"#;
8104 parse_execute(ast).await.unwrap_err();
8105
8106 let ast = r#"x = [0, 0]
8107y = x[0mm + 1]
8108"#;
8109 parse_execute(ast).await.unwrap_err();
8110 }
8111
8112 #[tokio::test(flavor = "multi_thread")]
8113 async fn getting_property_of_plane() {
8114 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
8115 parse_execute(&ast).await.unwrap();
8116 }
8117
8118 #[tokio::test(flavor = "multi_thread")]
8119 async fn no_artifacts_from_within_hole_call() {
8120 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
8125 let out = parse_execute(&ast).await.unwrap();
8126
8127 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
8129
8130 let expected = 5;
8134 assert_eq!(
8135 actual_operations.len(),
8136 expected,
8137 "expected {expected} operations, received {}:\n{actual_operations:#?}",
8138 actual_operations.len(),
8139 );
8140 }
8141
8142 #[tokio::test(flavor = "multi_thread")]
8143 async fn feature_tree_annotation_on_user_defined_kcl() {
8144 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
8147 let out = parse_execute(&ast).await.unwrap();
8148
8149 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
8151
8152 let expected = 0;
8153 assert_eq!(
8154 actual_operations.len(),
8155 expected,
8156 "expected {expected} operations, received {}:\n{actual_operations:#?}",
8157 actual_operations.len(),
8158 );
8159 }
8160
8161 #[tokio::test(flavor = "multi_thread")]
8162 async fn no_feature_tree_annotation_on_user_defined_kcl() {
8163 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
8166 let out = parse_execute(&ast).await.unwrap();
8167
8168 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
8170
8171 let expected = 2;
8172 assert_eq!(
8173 actual_operations.len(),
8174 expected,
8175 "expected {expected} operations, received {}:\n{actual_operations:#?}",
8176 actual_operations.len(),
8177 );
8178 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
8179 assert!(matches!(actual_operations[1], Operation::GroupEnd));
8180 }
8181
8182 #[tokio::test(flavor = "multi_thread")]
8183 async fn custom_warning() {
8184 let warn = r#"
8185a = PI * 2
8186"#;
8187 let result = parse_execute(warn).await.unwrap();
8188 assert_eq!(result.exec_state.issues().len(), 1);
8189 assert_eq!(result.exec_state.issues()[0].severity, Severity::Warning);
8190
8191 let allow = r#"
8192@warnings(allow = unknownUnits)
8193a = PI * 2
8194"#;
8195 let result = parse_execute(allow).await.unwrap();
8196 assert_eq!(result.exec_state.issues().len(), 0);
8197
8198 let deny = r#"
8199@warnings(deny = [unknownUnits])
8200a = PI * 2
8201"#;
8202 let result = parse_execute(deny).await.unwrap();
8203 assert_eq!(result.exec_state.issues().len(), 1);
8204 assert_eq!(result.exec_state.issues()[0].severity, Severity::Error);
8205 }
8206
8207 #[tokio::test(flavor = "multi_thread")]
8211 async fn diagnostics_attribute_in_v3() {
8212 let warn = "@settings(kclVersion = \"3.0-preview\")\na = PI * 2\n";
8213 let result = parse_execute(warn).await.unwrap();
8214 let issues = result.exec_state.issues();
8215 assert_eq!(issues.len(), 1, "{issues:#?}");
8216 assert_eq!(issues[0].severity, Severity::Warning);
8217 assert_eq!(issues[0].tag, crate::errors::Tag::UnknownNumericUnits);
8218
8219 let allow = "@settings(kclVersion = \"3.0-preview\")\n@diagnostics(allow = unknownUnits)\na = PI * 2\n";
8220 let result = parse_execute(allow).await.unwrap();
8221 assert!(
8222 result.exec_state.issues().is_empty(),
8223 "{:#?}",
8224 result.exec_state.issues()
8225 );
8226
8227 let deny = "@settings(kclVersion = \"3.0-preview\")\n@diagnostics(deny = [unknownUnits])\na = PI * 2\n";
8228 let result = parse_execute(deny).await.unwrap();
8229 let issues = result.exec_state.issues();
8230 assert_eq!(issues.len(), 1, "{issues:#?}");
8231 assert_eq!(issues[0].severity, Severity::Error);
8232 assert_eq!(issues[0].tag, crate::errors::Tag::UnknownNumericUnits);
8233 }
8234
8235 #[tokio::test(flavor = "multi_thread")]
8239 async fn diagnostics_attribute_is_unknown_before_v3() {
8240 for version in ["1.0", "2.0"] {
8241 let code = format!("@settings(kclVersion = {version})\n@diagnostics(allow = unknownUnits)\na = PI * 2\n");
8242 let result = parse_execute(&code).await.unwrap();
8243 let issues = result.exec_state.issues();
8244 assert_eq!(issues.len(), 2, "code={code}, issues={issues:#?}");
8245 assert_eq!(issues[0].severity, Severity::Warning);
8246 assert_eq!(issues[0].message, "Unknown annotation");
8247 assert_eq!(
8248 &code[issues[0].source_range.start()..issues[0].source_range.end()],
8249 "@diagnostics(allow = unknownUnits)"
8250 );
8251 assert_eq!(issues[1].severity, Severity::Warning);
8252 assert_eq!(issues[1].tag, crate::errors::Tag::UnknownNumericUnits);
8253
8254 let code = format!("@settings(kclVersion = {version})\n@warnings(allow = unknownUnits)\na = PI * 2\n");
8255 let result = parse_execute(&code).await.unwrap();
8256 assert!(
8257 result.exec_state.issues().is_empty(),
8258 "code={code}, issues={:#?}",
8259 result.exec_state.issues()
8260 );
8261 }
8262 }
8263
8264 #[tokio::test(flavor = "multi_thread")]
8268 async fn diagnostics_attribute_errors_use_the_attribute_name() {
8269 for (version, attr, noun) in [
8270 ("1.0", "warnings", "warning"),
8271 ("2.0", "warnings", "warning"),
8272 ("\"3.0-preview\"", "diagnostics", "diagnostic"),
8273 ] {
8274 let settings = format!("@settings(kclVersion = {version})\n");
8275
8276 let code = format!("{settings}@{attr}\n");
8277 let error = parse_execute(&code).await.unwrap_err();
8278 assert_eq!(error.message(), format!("Empty `{attr}` annotation"), "code={code}");
8279
8280 let code = format!("{settings}@{attr}(warn = unknownUnits)\n");
8281 let error = parse_execute(&code).await.unwrap_err();
8282 assert_eq!(
8283 error.message(),
8284 format!("Unexpected {attr} key: `warn`; expected one of `allow`, `deny`"),
8285 "code={code}"
8286 );
8287
8288 let code = format!("{settings}@{attr}(allow = 1)\n");
8289 let error = parse_execute(&code).await.unwrap_err();
8290 assert_eq!(
8291 error.message(),
8292 format!(
8293 "Unexpected {attr} value, expected a name or array of names, e.g., `unknownUnits` or `[unknownUnits, deprecated]`"
8294 ),
8295 "code={code}"
8296 );
8297
8298 let code = format!("{settings}@{attr}(allow = bogus)\n");
8299 let error = parse_execute(&code).await.unwrap_err();
8300 let expected_prefix = format!("Unexpected {noun} value: `bogus`; accepted values: unknownUnits, ");
8301 assert!(
8302 error.message().starts_with(&expected_prefix),
8303 "code={code}, message={}",
8304 error.message()
8305 );
8306 }
8307 }
8308
8309 #[tokio::test(flavor = "multi_thread")]
8313 async fn warnings_attribute_is_renamed_in_v3() {
8314 let code = "@settings(kclVersion = \"3.0-preview\")\n@warnings(allow = unknownUnits)\na = PI * 2\n";
8315 let result = parse_execute(code).await.unwrap();
8316 let issues = result.exec_state.issues();
8317 assert_eq!(issues.len(), 2, "{issues:#?}");
8318
8319 let renamed = &issues[0];
8320 assert_eq!(renamed.severity, Severity::Error);
8321 assert_eq!(
8322 renamed.message,
8323 "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."
8324 );
8325 assert_eq!(
8326 &code[renamed.source_range.start()..renamed.source_range.end()],
8327 "@warnings(allow = unknownUnits)"
8328 );
8329 let suggestion = renamed.suggestion.as_ref().unwrap();
8330 assert_eq!(suggestion.title, "Rename to `@diagnostics`");
8331 assert_eq!(
8332 suggestion.apply(code),
8333 "@settings(kclVersion = \"3.0-preview\")\n@diagnostics(allow = unknownUnits)\na = PI * 2\n"
8334 );
8335
8336 assert_eq!(issues[1].severity, Severity::Warning);
8337 assert_eq!(issues[1].tag, crate::errors::Tag::UnknownNumericUnits);
8338
8339 let code = "@settings(kclVersion = \"3.0-preview\")\n@warnings(allow = bogus)\n";
8341 let result = parse_execute(code).await.unwrap();
8342 let issues = result.exec_state.issues();
8343 assert_eq!(issues.len(), 1, "{issues:#?}");
8344 assert_eq!(issues[0].severity, Severity::Error);
8345 assert!(issues[0].message.starts_with("The `@warnings` attribute was renamed"));
8346 }
8347
8348 #[tokio::test(flavor = "multi_thread")]
8349 async fn sketch_block_unqualified_functions_use_sketch2() {
8350 let ast = r#"
8351s = sketch(on = XY) {
8352 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
8353 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
8354 coincident([line1.end, line2.start])
8355}
8356"#;
8357 let result = parse_execute(ast).await.unwrap();
8358 let mem = result.exec_state.stack();
8359 let sketch_value = mem
8360 .memory
8361 .get_from_owned("s", result.mem_env, SourceRange::default(), 0)
8362 .unwrap();
8363
8364 let KclValue::Object { value, .. } = sketch_value else {
8365 panic!("Expected sketch block to return an object, got {sketch_value:?}");
8366 };
8367
8368 assert!(value.contains_key("line1"));
8369 assert!(value.contains_key("line2"));
8370 assert!(!value.contains_key("line"));
8373 assert!(!value.contains_key("coincident"));
8374 }
8375
8376 #[tokio::test(flavor = "multi_thread")]
8377 async fn solver_module_is_not_available_outside_sketch_blocks() {
8378 let err = parse_execute("a = solver::ORIGIN").await.unwrap_err();
8379 assert!(err.message().contains("solver"), "Error message: '{}'", err.message());
8380
8381 let err = parse_execute(
8382 r#"@settings(experimentalFeatures = allow)
8383
8384import "std::solver""#,
8385 )
8386 .await
8387 .unwrap_err();
8388 assert!(
8389 err.message().contains("only available inside sketch blocks"),
8390 "Error message: '{}'",
8391 err.message()
8392 );
8393 }
8394
8395 #[tokio::test(flavor = "multi_thread")]
8396 async fn cannot_solid_extrude_an_open_profile() {
8397 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
8400 let program = crate::Program::parse_no_errs(&code).expect("should parse");
8401 let exec_ctxt = ExecutorContext::new_mock(None).await;
8402 let mut exec_state = ExecState::new(&exec_ctxt);
8403
8404 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
8405 assert!(matches!(err, KclError::Semantic { .. }));
8406 exec_ctxt.close().await;
8407 }
8408}