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