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