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