1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use indexmap::IndexMap;
5use kcl_ezpz::{Constraint, NonLinearSystemError};
6
7#[cfg(feature = "artifact-graph")]
8use crate::front::{Object, ObjectKind};
9use crate::{
10 CompilationError, NodePath, SourceRange,
11 errors::{KclError, KclErrorDetails},
12 execution::{
13 AbstractSegment, BodyType, ControlFlowKind, EnvironmentRef, ExecState, ExecutorContext, KclValue,
14 KclValueControlFlow, Metadata, ModelingCmdMeta, ModuleArtifactState, Operation, PreserveMem, Segment,
15 SegmentKind, SegmentRepr, SketchConstraintKind, SketchSurface, StatementKind, TagIdentifier, UnsolvedExpr,
16 UnsolvedSegment, UnsolvedSegmentKind, annotations,
17 cad_op::OpKclValue,
18 control_continue,
19 fn_call::{Arg, Args},
20 kcl_value::{FunctionSource, KclFunctionSourceParams, TypeDef},
21 memory,
22 sketch_solve::{
23 FreedomAnalysis, Solved, create_segment_scene_objects, normalize_to_solver_unit, solver_numeric_type,
24 substitute_sketch_var_in_segment, substitute_sketch_vars,
25 },
26 state::{ModuleState, SketchBlockState},
27 types::{NumericType, PrimitiveType, RuntimeType},
28 },
29 front::PointCtor,
30 modules::{ModuleExecutionOutcome, ModuleId, ModulePath, ModuleRepr},
31 parsing::ast::types::{
32 Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
33 BinaryPart, BodyItem, CodeBlock, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility,
34 MemberExpression, Name, Node, ObjectExpression, PipeExpression, Program, SketchBlock, SketchVar, TagDeclarator,
35 Type, UnaryExpression, UnaryOperator,
36 },
37 std::{
38 args::TyF64, shapes::SketchOrSurface, sketch::ensure_sketch_plane_in_engine, sketch2::create_segments_in_engine,
39 },
40};
41
42fn internal_err(message: impl Into<String>, range: impl Into<SourceRange>) -> KclError {
43 KclError::new_internal(KclErrorDetails::new(message.into(), vec![range.into()]))
44}
45
46impl<'a> StatementKind<'a> {
47 fn expect_name(&self) -> &'a str {
48 match self {
49 StatementKind::Declaration { name } => name,
50 StatementKind::Expression => unreachable!(),
51 }
52 }
53}
54
55impl ExecutorContext {
56 async fn handle_annotations(
58 &self,
59 annotations: impl Iterator<Item = &Node<Annotation>>,
60 body_type: BodyType,
61 exec_state: &mut ExecState,
62 ) -> Result<bool, KclError> {
63 let mut no_prelude = false;
64 for annotation in annotations {
65 if annotation.name() == Some(annotations::SETTINGS) {
66 if matches!(body_type, BodyType::Root) {
67 let (updated_len, updated_angle) =
68 exec_state.mod_local.settings.update_from_annotation(annotation)?;
69 if updated_len {
70 exec_state.mod_local.explicit_length_units = true;
71 }
72 if updated_angle {
73 exec_state.warn(
74 CompilationError::err(
75 annotation.as_source_range(),
76 "Prefer to use explicit units for angles",
77 ),
78 annotations::WARN_ANGLE_UNITS,
79 );
80 }
81 } else {
82 exec_state.err(CompilationError::err(
83 annotation.as_source_range(),
84 "Settings can only be modified at the top level scope of a file",
85 ));
86 }
87 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
88 if matches!(body_type, BodyType::Root) {
89 no_prelude = true;
90 } else {
91 exec_state.err(CompilationError::err(
92 annotation.as_source_range(),
93 "The standard library can only be skipped at the top level scope of a file",
94 ));
95 }
96 } else if annotation.name() == Some(annotations::WARNINGS) {
97 if matches!(body_type, BodyType::Root) {
99 let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
100 for p in props {
101 match &*p.inner.key.name {
102 annotations::WARN_ALLOW => {
103 let allowed = annotations::many_of(
104 &p.inner.value,
105 &annotations::WARN_VALUES,
106 annotation.as_source_range(),
107 )?;
108 exec_state.mod_local.allowed_warnings = allowed;
109 }
110 annotations::WARN_DENY => {
111 let denied = annotations::many_of(
112 &p.inner.value,
113 &annotations::WARN_VALUES,
114 annotation.as_source_range(),
115 )?;
116 exec_state.mod_local.denied_warnings = denied;
117 }
118 name => {
119 return Err(KclError::new_semantic(KclErrorDetails::new(
120 format!(
121 "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
122 annotations::WARN_ALLOW,
123 annotations::WARN_DENY,
124 ),
125 vec![annotation.as_source_range()],
126 )));
127 }
128 }
129 }
130 } else {
131 exec_state.err(CompilationError::err(
132 annotation.as_source_range(),
133 "Warnings can only be customized at the top level scope of a file",
134 ));
135 }
136 } else {
137 exec_state.warn(
138 CompilationError::err(annotation.as_source_range(), "Unknown annotation"),
139 annotations::WARN_UNKNOWN_ATTR,
140 );
141 }
142 }
143 Ok(no_prelude)
144 }
145
146 pub(super) async fn exec_module_body(
147 &self,
148 program: &Node<Program>,
149 exec_state: &mut ExecState,
150 preserve_mem: PreserveMem,
151 module_id: ModuleId,
152 path: &ModulePath,
153 ) -> Result<ModuleExecutionOutcome, (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>)> {
154 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
155
156 let mut local_state = ModuleState::new(
165 path.clone(),
166 exec_state.stack().memory.clone(),
167 Some(module_id),
168 exec_state.mod_local.sketch_mode,
169 exec_state.mod_local.freedom_analysis,
170 );
171 match preserve_mem {
172 PreserveMem::Always => {
173 #[cfg(feature = "artifact-graph")]
174 {
175 use crate::id::IncIdGenerator;
176 exec_state
177 .mod_local
178 .artifacts
179 .scene_objects
180 .clone_from(&exec_state.global.root_module_artifacts.scene_objects);
181 exec_state.mod_local.artifacts.object_id_generator =
182 IncIdGenerator::new(exec_state.global.root_module_artifacts.scene_objects.len());
183 }
184 }
185 PreserveMem::Normal => {
186 #[cfg(feature = "artifact-graph")]
187 {
188 local_state
189 .artifacts
190 .scene_objects
191 .clone_from(&exec_state.mod_local.artifacts.scene_objects);
192 }
193 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
194 }
195 }
196
197 let no_prelude = self
198 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
199 .await
200 .map_err(|err| (err, None, None))?;
201
202 if preserve_mem.normal() {
203 exec_state.mut_stack().push_new_root_env(!no_prelude);
204 }
205
206 let result = self
207 .exec_block(program, exec_state, crate::execution::BodyType::Root)
208 .await;
209
210 let env_ref = match preserve_mem {
211 PreserveMem::Always => exec_state.mut_stack().pop_and_preserve_env(),
212 PreserveMem::Normal => exec_state.mut_stack().pop_env(),
213 };
214 let module_artifacts = match preserve_mem {
215 PreserveMem::Always => std::mem::take(&mut exec_state.mod_local.artifacts),
216 PreserveMem::Normal => {
217 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
218 local_state.artifacts
219 }
220 };
221
222 crate::log::log(format!("leave {path}"));
223
224 result
225 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
226 .map(|last_expr| ModuleExecutionOutcome {
227 last_expr: last_expr.map(|value_cf| value_cf.into_value()),
228 environment: env_ref,
229 exports: local_state.module_exports,
230 artifacts: module_artifacts,
231 })
232 }
233
234 #[async_recursion]
236 pub(super) async fn exec_block<'a, B>(
237 &'a self,
238 block: &'a B,
239 exec_state: &mut ExecState,
240 body_type: BodyType,
241 ) -> Result<Option<KclValueControlFlow>, KclError>
242 where
243 B: CodeBlock + Sync,
244 {
245 let mut last_expr = None;
246 for statement in block.body() {
248 match statement {
249 BodyItem::ImportStatement(import_stmt) => {
250 if exec_state.sketch_mode() {
251 continue;
252 }
253 if !matches!(body_type, BodyType::Root) {
254 return Err(KclError::new_semantic(KclErrorDetails::new(
255 "Imports are only supported at the top-level of a file.".to_owned(),
256 vec![import_stmt.into()],
257 )));
258 }
259
260 let source_range = SourceRange::from(import_stmt);
261 let attrs = &import_stmt.outer_attrs;
262 let module_path = ModulePath::from_import_path(
263 &import_stmt.path,
264 &self.settings.project_directory,
265 &exec_state.mod_local.path,
266 )?;
267 let module_id = self
268 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
269 .await?;
270
271 match &import_stmt.selector {
272 ImportSelector::List { items } => {
273 let (env_ref, module_exports) =
274 self.exec_module_for_items(module_id, exec_state, source_range).await?;
275 for import_item in items {
276 let mem = &exec_state.stack().memory;
278 let mut value = mem
279 .get_from(&import_item.name.name, env_ref, import_item.into(), 0)
280 .cloned();
281 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
282 let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
283 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
284 let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
285
286 if value.is_err() && ty.is_err() && mod_value.is_err() {
287 return Err(KclError::new_undefined_value(
288 KclErrorDetails::new(
289 format!("{} is not defined in module", import_item.name.name),
290 vec![SourceRange::from(&import_item.name)],
291 ),
292 None,
293 ));
294 }
295
296 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
298 value = Err(KclError::new_semantic(KclErrorDetails::new(
299 format!(
300 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
301 import_item.name.name
302 ),
303 vec![SourceRange::from(&import_item.name)],
304 )));
305 }
306
307 if ty.is_ok() && !module_exports.contains(&ty_name) {
308 ty = Err(KclError::new_semantic(KclErrorDetails::new(
309 format!(
310 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
311 import_item.name.name
312 ),
313 vec![SourceRange::from(&import_item.name)],
314 )));
315 }
316
317 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
318 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
319 format!(
320 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
321 import_item.name.name
322 ),
323 vec![SourceRange::from(&import_item.name)],
324 )));
325 }
326
327 if value.is_err() && ty.is_err() && mod_value.is_err() {
328 return value.map(|v| Some(v.continue_()));
329 }
330
331 if let Ok(value) = value {
333 exec_state.mut_stack().add(
334 import_item.identifier().to_owned(),
335 value,
336 SourceRange::from(&import_item.name),
337 )?;
338
339 if let ItemVisibility::Export = import_stmt.visibility {
340 exec_state
341 .mod_local
342 .module_exports
343 .push(import_item.identifier().to_owned());
344 }
345 }
346
347 if let Ok(ty) = ty {
348 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
349 exec_state.mut_stack().add(
350 ty_name.clone(),
351 ty,
352 SourceRange::from(&import_item.name),
353 )?;
354
355 if let ItemVisibility::Export = import_stmt.visibility {
356 exec_state.mod_local.module_exports.push(ty_name);
357 }
358 }
359
360 if let Ok(mod_value) = mod_value {
361 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
362 exec_state.mut_stack().add(
363 mod_name.clone(),
364 mod_value,
365 SourceRange::from(&import_item.name),
366 )?;
367
368 if let ItemVisibility::Export = import_stmt.visibility {
369 exec_state.mod_local.module_exports.push(mod_name);
370 }
371 }
372 }
373 }
374 ImportSelector::Glob(_) => {
375 let (env_ref, module_exports) =
376 self.exec_module_for_items(module_id, exec_state, source_range).await?;
377 for name in module_exports.iter() {
378 let item = exec_state
379 .stack()
380 .memory
381 .get_from(name, env_ref, source_range, 0)
382 .map_err(|_err| {
383 internal_err(
384 format!("{name} is not defined in module (but was exported?)"),
385 source_range,
386 )
387 })?
388 .clone();
389 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
390
391 if let ItemVisibility::Export = import_stmt.visibility {
392 exec_state.mod_local.module_exports.push(name.clone());
393 }
394 }
395 }
396 ImportSelector::None { .. } => {
397 let name = import_stmt.module_name().unwrap();
398 let item = KclValue::Module {
399 value: module_id,
400 meta: vec![source_range.into()],
401 };
402 exec_state.mut_stack().add(
403 format!("{}{}", memory::MODULE_PREFIX, name),
404 item,
405 source_range,
406 )?;
407 }
408 }
409 last_expr = None;
410 }
411 BodyItem::ExpressionStatement(expression_statement) => {
412 if exec_state.sketch_mode() && sketch_mode_should_skip(&expression_statement.expression) {
413 continue;
414 }
415
416 let metadata = Metadata::from(expression_statement);
417 let value = self
418 .execute_expr(
419 &expression_statement.expression,
420 exec_state,
421 &metadata,
422 &[],
423 StatementKind::Expression,
424 )
425 .await?;
426
427 let is_return = value.is_some_return();
428 last_expr = Some(value);
429
430 if is_return {
431 break;
432 }
433 }
434 BodyItem::VariableDeclaration(variable_declaration) => {
435 if exec_state.sketch_mode() && sketch_mode_should_skip(&variable_declaration.declaration.init) {
436 continue;
437 }
438
439 let var_name = variable_declaration.declaration.id.name.to_string();
440 let source_range = SourceRange::from(&variable_declaration.declaration.init);
441 let metadata = Metadata { source_range };
442
443 let annotations = &variable_declaration.outer_attrs;
444
445 let lhs = variable_declaration.inner.name().to_owned();
448 let prev_being_declared = exec_state.mod_local.being_declared.take();
449 exec_state.mod_local.being_declared = Some(lhs);
450 let rhs_result = self
451 .execute_expr(
452 &variable_declaration.declaration.init,
453 exec_state,
454 &metadata,
455 annotations,
456 StatementKind::Declaration { name: &var_name },
457 )
458 .await;
459 exec_state.mod_local.being_declared = prev_being_declared;
461 let rhs = rhs_result?;
462
463 if rhs.is_some_return() {
464 last_expr = Some(rhs);
465 break;
466 }
467 let rhs = rhs.into_value();
468
469 let should_bind_name =
470 if let Some(fn_name) = variable_declaration.declaration.init.fn_declaring_name() {
471 var_name != fn_name
475 } else {
476 true
479 };
480 if should_bind_name {
481 exec_state
482 .mut_stack()
483 .add(var_name.clone(), rhs.clone(), source_range)?;
484 }
485
486 let should_show_in_feature_tree =
490 !exec_state.mod_local.inside_stdlib && rhs.show_variable_in_feature_tree();
491 if should_show_in_feature_tree {
492 exec_state.push_op(Operation::VariableDeclaration {
493 name: var_name.clone(),
494 value: OpKclValue::from(&rhs),
495 visibility: variable_declaration.visibility,
496 node_path: NodePath::placeholder(),
497 source_range,
498 });
499 }
500
501 if let ItemVisibility::Export = variable_declaration.visibility {
503 if matches!(body_type, BodyType::Root) {
504 exec_state.mod_local.module_exports.push(var_name);
505 } else {
506 exec_state.err(CompilationError::err(
507 variable_declaration.as_source_range(),
508 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
509 ));
510 }
511 }
512 last_expr = matches!(body_type, BodyType::Root).then_some(rhs.continue_());
514 }
515 BodyItem::TypeDeclaration(ty) => {
516 if exec_state.sketch_mode() {
517 continue;
518 }
519
520 let metadata = Metadata::from(&**ty);
521 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
522 match attrs.impl_ {
523 annotations::Impl::Rust
524 | annotations::Impl::RustConstrainable
525 | annotations::Impl::RustConstraint => {
526 let std_path = match &exec_state.mod_local.path {
527 ModulePath::Std { value } => value,
528 ModulePath::Local { .. } | ModulePath::Main => {
529 return Err(KclError::new_semantic(KclErrorDetails::new(
530 "User-defined types are not yet supported.".to_owned(),
531 vec![metadata.source_range],
532 )));
533 }
534 };
535 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
536 let value = KclValue::Type {
537 value: TypeDef::RustRepr(t, props),
538 meta: vec![metadata],
539 experimental: attrs.experimental,
540 };
541 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
542 exec_state
543 .mut_stack()
544 .add(name_in_mem.clone(), value, metadata.source_range)
545 .map_err(|_| {
546 KclError::new_semantic(KclErrorDetails::new(
547 format!("Redefinition of type {}.", ty.name.name),
548 vec![metadata.source_range],
549 ))
550 })?;
551
552 if let ItemVisibility::Export = ty.visibility {
553 exec_state.mod_local.module_exports.push(name_in_mem);
554 }
555 }
556 annotations::Impl::Primitive => {}
558 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
559 Some(alias) => {
560 let value = KclValue::Type {
561 value: TypeDef::Alias(
562 RuntimeType::from_parsed(
563 alias.inner.clone(),
564 exec_state,
565 metadata.source_range,
566 attrs.impl_ == annotations::Impl::KclConstrainable,
567 )
568 .map_err(|e| KclError::new_semantic(e.into()))?,
569 ),
570 meta: vec![metadata],
571 experimental: attrs.experimental,
572 };
573 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
574 exec_state
575 .mut_stack()
576 .add(name_in_mem.clone(), value, metadata.source_range)
577 .map_err(|_| {
578 KclError::new_semantic(KclErrorDetails::new(
579 format!("Redefinition of type {}.", ty.name.name),
580 vec![metadata.source_range],
581 ))
582 })?;
583
584 if let ItemVisibility::Export = ty.visibility {
585 exec_state.mod_local.module_exports.push(name_in_mem);
586 }
587 }
588 None => {
589 return Err(KclError::new_semantic(KclErrorDetails::new(
590 "User-defined types are not yet supported.".to_owned(),
591 vec![metadata.source_range],
592 )));
593 }
594 },
595 }
596
597 last_expr = None;
598 }
599 BodyItem::ReturnStatement(return_statement) => {
600 if exec_state.sketch_mode() && sketch_mode_should_skip(&return_statement.argument) {
601 continue;
602 }
603
604 let metadata = Metadata::from(return_statement);
605
606 if matches!(body_type, BodyType::Root) {
607 return Err(KclError::new_semantic(KclErrorDetails::new(
608 "Cannot return from outside a function.".to_owned(),
609 vec![metadata.source_range],
610 )));
611 }
612
613 let value_cf = self
614 .execute_expr(
615 &return_statement.argument,
616 exec_state,
617 &metadata,
618 &[],
619 StatementKind::Expression,
620 )
621 .await?;
622 if value_cf.is_some_return() {
623 last_expr = Some(value_cf);
624 break;
625 }
626 let value = value_cf.into_value();
627 exec_state
628 .mut_stack()
629 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
630 .map_err(|_| {
631 KclError::new_semantic(KclErrorDetails::new(
632 "Multiple returns from a single function.".to_owned(),
633 vec![metadata.source_range],
634 ))
635 })?;
636 last_expr = None;
637 }
638 }
639 }
640
641 if matches!(body_type, BodyType::Root) {
642 exec_state
644 .flush_batch(
645 ModelingCmdMeta::new(exec_state, self, block.to_source_range()),
646 true,
649 )
650 .await?;
651 }
652
653 Ok(last_expr)
654 }
655
656 pub async fn open_module(
657 &self,
658 path: &ImportPath,
659 attrs: &[Node<Annotation>],
660 resolved_path: &ModulePath,
661 exec_state: &mut ExecState,
662 source_range: SourceRange,
663 ) -> Result<ModuleId, KclError> {
664 match path {
665 ImportPath::Kcl { .. } => {
666 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
667
668 if let Some(id) = exec_state.id_for_module(resolved_path) {
669 return Ok(id);
670 }
671
672 let id = exec_state.next_module_id();
673 exec_state.add_path_to_source_id(resolved_path.clone(), id);
675 let source = resolved_path.source(&self.fs, source_range).await?;
676 exec_state.add_id_to_source(id, source.clone());
677 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
679 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
680
681 Ok(id)
682 }
683 ImportPath::Foreign { .. } => {
684 if let Some(id) = exec_state.id_for_module(resolved_path) {
685 return Ok(id);
686 }
687
688 let id = exec_state.next_module_id();
689 let path = resolved_path.expect_path();
690 exec_state.add_path_to_source_id(resolved_path.clone(), id);
692 let format = super::import::format_from_annotations(attrs, path, source_range)?;
693 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
694 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
695 Ok(id)
696 }
697 ImportPath::Std { .. } => {
698 if let Some(id) = exec_state.id_for_module(resolved_path) {
699 return Ok(id);
700 }
701
702 let id = exec_state.next_module_id();
703 exec_state.add_path_to_source_id(resolved_path.clone(), id);
705 let source = resolved_path.source(&self.fs, source_range).await?;
706 exec_state.add_id_to_source(id, source.clone());
707 let parsed = crate::parsing::parse_str(&source.source, id)
708 .parse_errs_as_err()
709 .unwrap();
710 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
711 Ok(id)
712 }
713 }
714 }
715
716 pub(super) async fn exec_module_for_items(
717 &self,
718 module_id: ModuleId,
719 exec_state: &mut ExecState,
720 source_range: SourceRange,
721 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
722 let path = exec_state.global.module_infos[&module_id].path.clone();
723 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
724 let result = match &mut repr {
727 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
728 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
729 ModuleRepr::Kcl(program, cache) => self
730 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
731 .await
732 .map(|outcome| {
733 *cache = Some(outcome.clone());
734 (outcome.environment, outcome.exports)
735 }),
736 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
737 "Cannot import items from foreign modules".to_owned(),
738 vec![geom.source_range],
739 ))),
740 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
741 };
742
743 exec_state.global.module_infos[&module_id].restore_repr(repr);
744 result
745 }
746
747 async fn exec_module_for_result(
748 &self,
749 module_id: ModuleId,
750 exec_state: &mut ExecState,
751 source_range: SourceRange,
752 ) -> Result<Option<KclValue>, KclError> {
753 let path = exec_state.global.module_infos[&module_id].path.clone();
754 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
755 let result = match &mut repr {
758 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
759 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
760 ModuleRepr::Kcl(program, cached_items) => {
761 let result = self
762 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
763 .await;
764 match result {
765 Ok(outcome) => {
766 let value = outcome.last_expr.clone();
767 *cached_items = Some(outcome);
768 Ok(value)
769 }
770 Err(e) => Err(e),
771 }
772 }
773 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
774 ModuleRepr::Foreign(geom, cached) => {
775 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
776 .await
777 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
778
779 match result {
780 Ok(val) => {
781 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
782 Ok(val)
783 }
784 Err(e) => Err(e),
785 }
786 }
787 ModuleRepr::Dummy => unreachable!(),
788 };
789
790 exec_state.global.module_infos[&module_id].restore_repr(repr);
791
792 result
793 }
794
795 pub async fn exec_module_from_ast(
796 &self,
797 program: &Node<Program>,
798 module_id: ModuleId,
799 path: &ModulePath,
800 exec_state: &mut ExecState,
801 source_range: SourceRange,
802 preserve_mem: PreserveMem,
803 ) -> Result<ModuleExecutionOutcome, KclError> {
804 exec_state.global.mod_loader.enter_module(path);
805 let result = self
806 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
807 .await;
808 exec_state.global.mod_loader.leave_module(path, source_range)?;
809
810 result.map_err(|(err, _, _)| {
813 if let KclError::ImportCycle { .. } = err {
814 err.override_source_ranges(vec![source_range])
816 } else {
817 KclError::new_semantic(KclErrorDetails::new(
819 format!(
820 "Error loading imported file ({path}). Open it to view more details.\n {}",
821 err.message()
822 ),
823 vec![source_range],
824 ))
825 }
826 })
827 }
828
829 #[async_recursion]
830 pub(crate) async fn execute_expr<'a: 'async_recursion>(
831 &self,
832 init: &Expr,
833 exec_state: &mut ExecState,
834 metadata: &Metadata,
835 annotations: &[Node<Annotation>],
836 statement_kind: StatementKind<'a>,
837 ) -> Result<KclValueControlFlow, KclError> {
838 let item = match init {
839 Expr::None(none) => KclValue::from(none).continue_(),
840 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state).continue_(),
841 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?.continue_(),
842 Expr::Name(name) => {
843 let being_declared = exec_state.mod_local.being_declared.clone();
844 let value = name
845 .get_result(exec_state, self)
846 .await
847 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
848 .clone();
849 if let KclValue::Module { value: module_id, meta } = value {
850 self.exec_module_for_result(
851 module_id,
852 exec_state,
853 metadata.source_range
854 ).await?.map(|v| v.continue_())
855 .unwrap_or_else(|| {
856 exec_state.warn(CompilationError::err(
857 metadata.source_range,
858 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
859 ),
860 annotations::WARN_MOD_RETURN_VALUE);
861
862 let mut new_meta = vec![metadata.to_owned()];
863 new_meta.extend(meta);
864 KclValue::KclNone {
865 value: Default::default(),
866 meta: new_meta,
867 }.continue_()
868 })
869 } else {
870 value.continue_()
871 }
872 }
873 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
874 Expr::FunctionExpression(function_expression) => {
875 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
876 let experimental = attrs.map(|a| a.experimental).unwrap_or_default();
877 let is_std = matches!(&exec_state.mod_local.path, ModulePath::Std { .. });
878
879 let include_in_feature_tree = attrs.unwrap_or_default().include_in_feature_tree;
881 let (mut closure, placeholder_env_ref) = if let Some(attrs) = attrs
882 && (attrs.impl_ == annotations::Impl::Rust
883 || attrs.impl_ == annotations::Impl::RustConstrainable
884 || attrs.impl_ == annotations::Impl::RustConstraint)
885 {
886 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
887 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
888 (
889 KclValue::Function {
890 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
891 meta: vec![metadata.to_owned()],
892 },
893 None,
894 )
895 } else {
896 return Err(KclError::new_semantic(KclErrorDetails::new(
897 "Rust implementation of functions is restricted to the standard library".to_owned(),
898 vec![metadata.source_range],
899 )));
900 }
901 } else {
902 let (env_ref, placeholder_env_ref) = if function_expression.name.is_some() {
906 let dummy = EnvironmentRef::dummy();
909 (dummy, Some(dummy))
910 } else {
911 (exec_state.mut_stack().snapshot(), None)
912 };
913 (
914 KclValue::Function {
915 value: Box::new(FunctionSource::kcl(
916 function_expression.clone(),
917 env_ref,
918 KclFunctionSourceParams {
919 is_std,
920 experimental,
921 include_in_feature_tree,
922 },
923 )),
924 meta: vec![metadata.to_owned()],
925 },
926 placeholder_env_ref,
927 )
928 };
929
930 if let Some(fn_name) = &function_expression.name {
933 if let Some(placeholder_env_ref) = placeholder_env_ref {
937 closure = exec_state.mut_stack().add_recursive_closure(
938 fn_name.name.to_owned(),
939 closure,
940 placeholder_env_ref,
941 metadata.source_range,
942 )?;
943 } else {
944 exec_state
946 .mut_stack()
947 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
948 }
949 }
950
951 closure.continue_()
952 }
953 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
954 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
955 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
956 StatementKind::Declaration { name } => {
957 let message = format!(
958 "you cannot declare variable {name} as %, because % can only be used in function calls"
959 );
960
961 return Err(KclError::new_semantic(KclErrorDetails::new(
962 message,
963 vec![pipe_substitution.into()],
964 )));
965 }
966 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
967 Some(x) => x.continue_(),
968 None => {
969 return Err(KclError::new_semantic(KclErrorDetails::new(
970 "cannot use % outside a pipe expression".to_owned(),
971 vec![pipe_substitution.into()],
972 )));
973 }
974 },
975 },
976 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
977 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
978 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
979 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
980 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
981 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
982 Expr::LabelledExpression(expr) => {
983 let value_cf = self
984 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
985 .await?;
986 let value = control_continue!(value_cf);
987 exec_state
988 .mut_stack()
989 .add(expr.label.name.clone(), value.clone(), init.into())?;
990 value.continue_()
992 }
993 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
994 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
995 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
996 };
997 Ok(item)
998 }
999}
1000
1001fn sketch_mode_should_skip(expr: &Expr) -> bool {
1004 match expr {
1005 Expr::SketchBlock(sketch_block) => !sketch_block.is_being_edited,
1006 _ => true,
1007 }
1008}
1009
1010fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
1013 let KclError::UndefinedValue { name, mut details } = e else {
1014 return e;
1015 };
1016 if let (Some(name0), Some(name1)) = (&being_declared, &name)
1020 && name0 == name1
1021 {
1022 details.message = format!(
1023 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
1024 );
1025 }
1026 KclError::UndefinedValue { details, name }
1027}
1028
1029impl Node<AscribedExpression> {
1030 #[async_recursion]
1031 pub(super) async fn get_result(
1032 &self,
1033 exec_state: &mut ExecState,
1034 ctx: &ExecutorContext,
1035 ) -> Result<KclValueControlFlow, KclError> {
1036 let metadata = Metadata {
1037 source_range: SourceRange::from(self),
1038 };
1039 let result = ctx
1040 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
1041 .await?;
1042 let result = control_continue!(result);
1043 apply_ascription(&result, &self.ty, exec_state, self.into()).map(KclValue::continue_)
1044 }
1045}
1046
1047impl Node<SketchBlock> {
1048 pub(super) async fn get_result(
1049 &self,
1050 exec_state: &mut ExecState,
1051 ctx: &ExecutorContext,
1052 ) -> Result<KclValueControlFlow, KclError> {
1053 if exec_state.mod_local.sketch_block.is_some() {
1054 return Err(KclError::new_semantic(KclErrorDetails::new(
1056 "Cannot execute a sketch block from within another sketch block".to_owned(),
1057 vec![SourceRange::from(self)],
1058 )));
1059 }
1060
1061 let range = SourceRange::from(self);
1062
1063 let mut labeled = IndexMap::new();
1065 for labeled_arg in &self.arguments {
1066 let source_range = SourceRange::from(labeled_arg.arg.clone());
1067 let metadata = Metadata { source_range };
1068 let value_cf = ctx
1069 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
1070 .await?;
1071 let value = control_continue!(value_cf);
1072 let arg = Arg::new(value, source_range);
1073 match &labeled_arg.label {
1074 Some(label) => {
1075 labeled.insert(label.name.clone(), arg);
1076 }
1077 None => {
1078 let name = labeled_arg.arg.ident_name();
1079 if let Some(name) = name {
1080 labeled.insert(name.to_owned(), arg);
1081 } else {
1082 return Err(KclError::new_semantic(KclErrorDetails::new(
1083 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
1084 vec![SourceRange::from(&labeled_arg.arg)],
1085 )));
1086 }
1087 }
1088 }
1089 }
1090 let mut args = Args::new_no_args(range, ctx.clone(), Some("sketch block".to_owned()));
1091 args.labeled = labeled;
1092
1093 let arg_on: SketchOrSurface = args.get_kw_arg("on", &RuntimeType::sketch_or_surface(), exec_state)?;
1098 let mut sketch_surface = arg_on.into_sketch_surface();
1099 if exec_state.sketch_mode() {
1102 if sketch_surface.object_id().is_none() {
1103 #[cfg(not(feature = "artifact-graph"))]
1104 {
1105 sketch_surface.set_object_id(exec_state.next_object_id());
1108 }
1109 #[cfg(feature = "artifact-graph")]
1110 {
1111 let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
1114 return Err(internal_err(
1115 "In sketch mode, the `on` plane argument must refer to an existing plane object.",
1116 range,
1117 ));
1118 };
1119 sketch_surface.set_object_id(last_object.id);
1120 }
1121 }
1122 } else {
1123 match &mut sketch_surface {
1124 SketchSurface::Plane(plane) => {
1125 ensure_sketch_plane_in_engine(plane, exec_state, &args).await?;
1127 }
1128 SketchSurface::Face(_) => {
1129 }
1131 }
1132 }
1133 let on_object_id = if let Some(object_id) = sketch_surface.object_id() {
1134 object_id
1135 } else {
1136 let message = "The `on` argument should have an object after ensure_sketch_plane_in_engine".to_owned();
1137 debug_assert!(false, "{message}");
1138 return Err(internal_err(message, range));
1139 };
1140 let arg_on_expr_name = self
1141 .arguments
1142 .iter()
1143 .find_map(|labeled_arg| {
1144 if let Some(label) = &labeled_arg.label
1145 && label.name == "on"
1146 {
1147 if let Some(name) = labeled_arg.arg.ident_name() {
1149 Some(Ok(name))
1150 } else {
1151 let message = "A sketch block's `on` parameter must be a variable or identifier, not an arbitrary expression. The parser should have enforced this."
1152 .to_owned();
1153 debug_assert!(false, "{message}");
1154 Some(Err(internal_err(message, &labeled_arg.arg)))
1155 }
1156 } else {
1157 None
1158 }
1159 })
1160 .unwrap_or_else(|| {
1161 Err(KclError::new_invalid_expression(KclErrorDetails::new(
1162 "sketch block requires an `on` parameter".to_owned(),
1163 vec![SourceRange::from(self)],
1164 )))
1165 })?
1166 .to_owned();
1168 #[cfg(not(feature = "artifact-graph"))]
1169 {
1170 let _ = on_object_id;
1171 drop(arg_on_expr_name);
1172 }
1173 #[cfg(feature = "artifact-graph")]
1174 let sketch_id = {
1175 use crate::execution::{Artifact, ArtifactId, CodeRef, SketchBlock};
1176
1177 let on_object = exec_state.mod_local.artifacts.scene_object_by_id(on_object_id);
1178
1179 let plane_artifact_id = on_object.map(|object| object.artifact_id);
1181
1182 let sketch_id = exec_state.next_object_id();
1183
1184 let artifact_id = ArtifactId::from(exec_state.next_uuid());
1185 let sketch_scene_object = Object {
1186 id: sketch_id,
1187 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
1188 args: crate::front::SketchCtor { on: arg_on_expr_name },
1189 plane: on_object_id,
1190 segments: Default::default(),
1191 constraints: Default::default(),
1192 }),
1193 label: Default::default(),
1194 comments: Default::default(),
1195 artifact_id,
1196 source: range.into(),
1197 };
1198 exec_state.add_scene_object(sketch_scene_object, range);
1199
1200 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
1202 id: artifact_id,
1203 plane_id: plane_artifact_id,
1204 code_ref: CodeRef::placeholder(range),
1205 sketch_id,
1206 }));
1207
1208 sketch_id
1209 };
1210
1211 let (return_result, variables, sketch_block_state) = {
1212 self.prep_mem(exec_state.mut_stack().snapshot(), exec_state);
1214
1215 let original_value = exec_state.mod_local.sketch_block.replace(SketchBlockState::default());
1217
1218 let original_sketch_mode = std::mem::replace(&mut exec_state.mod_local.sketch_mode, false);
1221
1222 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1223
1224 exec_state.mod_local.sketch_mode = original_sketch_mode;
1225
1226 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1227
1228 let block_variables = exec_state
1229 .stack()
1230 .find_all_in_current_env()
1231 .map(|(name, value)| (name.clone(), value.clone()))
1232 .collect::<IndexMap<_, _>>();
1233
1234 exec_state.mut_stack().pop_env();
1235
1236 (result, block_variables, sketch_block_state)
1237 };
1238
1239 return_result?;
1241 let Some(sketch_block_state) = sketch_block_state else {
1242 debug_assert!(false, "Sketch block state should still be set to Some from just above");
1243 return Err(internal_err(
1244 "Sketch block state should still be set to Some from just above",
1245 self,
1246 ));
1247 };
1248
1249 let constraints = sketch_block_state
1251 .solver_constraints
1252 .iter()
1253 .cloned()
1254 .map(kcl_ezpz::ConstraintRequest::highest_priority)
1255 .chain(
1256 sketch_block_state
1258 .solver_optional_constraints
1259 .iter()
1260 .cloned()
1261 .map(|c| kcl_ezpz::ConstraintRequest::new(c, 1)),
1262 )
1263 .collect::<Vec<_>>();
1264 let initial_guesses = sketch_block_state
1265 .sketch_vars
1266 .iter()
1267 .map(|v| {
1268 let Some(sketch_var) = v.as_sketch_var() else {
1269 return Err(internal_err("Expected sketch variable", self));
1270 };
1271 let constraint_id = sketch_var.id.to_constraint_id(range)?;
1272 let number_value = KclValue::Number {
1274 value: sketch_var.initial_value,
1275 ty: sketch_var.ty,
1276 meta: sketch_var.meta.clone(),
1277 };
1278 let initial_guess_value =
1279 normalize_to_solver_unit(&number_value, v.into(), exec_state, "sketch variable initial value")?;
1280 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1281 n.n
1282 } else {
1283 let message = format!(
1284 "Expected number after coercion, but found {}",
1285 initial_guess_value.human_friendly_type()
1286 );
1287 debug_assert!(false, "{}", &message);
1288 return Err(internal_err(message, self));
1289 };
1290 Ok((constraint_id, initial_guess))
1291 })
1292 .collect::<Result<Vec<_>, KclError>>()?;
1293 let config = kcl_ezpz::Config::default().with_max_iterations(50);
1295 let solve_result = if exec_state.mod_local.freedom_analysis {
1296 kcl_ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
1297 let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
1298 (outcome.outcome, Some(freedom_analysis))
1299 })
1300 } else {
1301 kcl_ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1302 };
1303 let num_required_constraints = sketch_block_state.solver_constraints.len();
1305 let all_constraints: Vec<kcl_ezpz::Constraint> = sketch_block_state
1306 .solver_constraints
1307 .iter()
1308 .cloned()
1309 .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
1310 .collect();
1311
1312 let (solve_outcome, solve_analysis) = match solve_result {
1313 Ok((solved, freedom)) => {
1314 let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
1315 (outcome, freedom)
1316 }
1317 Err(failure) => {
1318 match &failure.error {
1319 NonLinearSystemError::FaerMatrix { .. }
1320 | NonLinearSystemError::Faer { .. }
1321 | NonLinearSystemError::FaerSolve { .. }
1322 | NonLinearSystemError::FaerSvd(..)
1323 | NonLinearSystemError::DidNotConverge => {
1324 exec_state.warn(
1327 CompilationError::err(range, "Constraint solver failed to find a solution".to_owned()),
1328 annotations::WARN_SOLVER,
1329 );
1330 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1331 (
1332 Solved {
1333 final_values,
1334 iterations: Default::default(),
1335 warnings: failure.warnings,
1336 priority_solved: Default::default(),
1337 variables_in_conflicts: Default::default(),
1338 },
1339 None,
1340 )
1341 }
1342 NonLinearSystemError::EmptySystemNotAllowed
1343 | NonLinearSystemError::WrongNumberGuesses { .. }
1344 | NonLinearSystemError::MissingGuess { .. }
1345 | NonLinearSystemError::NotFound(..) => {
1346 #[cfg(target_arch = "wasm32")]
1349 web_sys::console::error_1(
1350 &format!("Internal error from constraint solver: {}", &failure.error).into(),
1351 );
1352 return Err(internal_err(
1353 format!("Internal error from constraint solver: {}", &failure.error),
1354 self,
1355 ));
1356 }
1357 _ => {
1358 return Err(internal_err(
1360 format!("Error from constraint solver: {}", &failure.error),
1361 self,
1362 ));
1363 }
1364 }
1365 }
1366 };
1367 #[cfg(not(feature = "artifact-graph"))]
1368 let _ = solve_analysis;
1369 for warning in &solve_outcome.warnings {
1371 let message = if let Some(index) = warning.about_constraint.as_ref() {
1372 format!("{}; constraint index {}", &warning.content, index)
1373 } else {
1374 format!("{}", &warning.content)
1375 };
1376 exec_state.warn(CompilationError::err(range, message), annotations::WARN_SOLVER);
1377 }
1378 let solution_ty = solver_numeric_type(exec_state);
1380 let variables = substitute_sketch_vars(variables, &solve_outcome, solution_ty, solve_analysis.as_ref())?;
1381 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1382 for unsolved_segment in &sketch_block_state.needed_by_engine {
1383 solved_segments.push(substitute_sketch_var_in_segment(
1384 unsolved_segment.clone(),
1385 &solve_outcome,
1386 solver_numeric_type(exec_state),
1387 solve_analysis.as_ref(),
1388 )?);
1389 }
1390 let solved_segments = solved_segments; #[cfg(feature = "artifact-graph")]
1392 {
1393 exec_state.mod_local.artifacts.var_solutions =
1399 sketch_block_state.var_solutions(solve_outcome, solution_ty, SourceRange::from(self))?;
1400 }
1401
1402 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1404
1405 #[cfg(not(feature = "artifact-graph"))]
1406 drop(scene_objects);
1407 #[cfg(feature = "artifact-graph")]
1408 {
1409 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1410 for scene_object in scene_objects {
1411 segment_object_ids.push(scene_object.id);
1412 exec_state.set_scene_object(scene_object);
1414 }
1415 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
1417 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1418 debug_assert!(false, "{}", &message);
1419 return Err(internal_err(message, range));
1420 };
1421 let ObjectKind::Sketch(sketch) = &mut sketch_object.kind else {
1422 let message = format!(
1423 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
1424 sketch_id, sketch_object
1425 );
1426 debug_assert!(
1427 false,
1428 "{}; scene_objects={:#?}",
1429 &message, &exec_state.mod_local.artifacts.scene_objects
1430 );
1431 return Err(internal_err(message, range));
1432 };
1433 sketch.segments.extend(segment_object_ids);
1434 let mut sketch_block_state = sketch_block_state;
1436 sketch
1437 .constraints
1438 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
1439
1440 exec_state.push_op(Operation::SketchSolve {
1442 sketch_id,
1443 node_path: NodePath::placeholder(),
1444 source_range: range,
1445 });
1446 }
1447
1448 if !exec_state.sketch_mode() {
1450 create_segments_in_engine(&sketch_surface, &solved_segments, ctx, exec_state, range).await?;
1451 }
1452
1453 let metadata = Metadata {
1454 source_range: SourceRange::from(self),
1455 };
1456 let return_value = KclValue::Object {
1457 value: variables,
1458 constrainable: Default::default(),
1459 meta: vec![metadata],
1460 };
1461 Ok(if self.is_being_edited {
1462 return_value.exit()
1465 } else {
1466 return_value.continue_()
1467 })
1468 }
1469}
1470
1471impl SketchBlock {
1472 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) {
1473 exec_state.mut_stack().push_new_env_for_call(parent);
1474 }
1475}
1476
1477impl Node<SketchVar> {
1478 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
1479 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
1480 return Err(KclError::new_semantic(KclErrorDetails::new(
1481 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1482 vec![SourceRange::from(self)],
1483 )));
1484 };
1485 let id = sketch_block_state.next_sketch_var_id();
1486 let sketch_var = if let Some(initial) = &self.initial {
1487 KclValue::from_sketch_var_literal(initial, id, exec_state)
1488 } else {
1489 let metadata = Metadata {
1490 source_range: SourceRange::from(self),
1491 };
1492
1493 KclValue::SketchVar {
1494 value: Box::new(super::SketchVar {
1495 id,
1496 initial_value: 0.0,
1497 ty: NumericType::default(),
1498 meta: vec![metadata],
1499 }),
1500 }
1501 };
1502
1503 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
1504 return Err(KclError::new_semantic(KclErrorDetails::new(
1505 "Cannot use a sketch variable outside of a sketch block".to_owned(),
1506 vec![SourceRange::from(self)],
1507 )));
1508 };
1509 sketch_block_state.sketch_vars.push(sketch_var.clone());
1510
1511 Ok(sketch_var)
1512 }
1513}
1514
1515fn apply_ascription(
1516 value: &KclValue,
1517 ty: &Node<Type>,
1518 exec_state: &mut ExecState,
1519 source_range: SourceRange,
1520) -> Result<KclValue, KclError> {
1521 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false)
1522 .map_err(|e| KclError::new_semantic(e.into()))?;
1523
1524 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
1525 exec_state.clear_units_warnings(&source_range);
1526 }
1527
1528 value.coerce(&ty, false, exec_state).map_err(|_| {
1529 let suggestion = if ty == RuntimeType::length() {
1530 ", you might try coercing to a fully specified numeric type such as `mm`"
1531 } else if ty == RuntimeType::angle() {
1532 ", you might try coercing to a fully specified numeric type such as `deg`"
1533 } else {
1534 ""
1535 };
1536 let ty_str = if let Some(ty) = value.principal_type() {
1537 format!("(with type `{ty}`) ")
1538 } else {
1539 String::new()
1540 };
1541 KclError::new_semantic(KclErrorDetails::new(
1542 format!(
1543 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
1544 value.human_friendly_type()
1545 ),
1546 vec![source_range],
1547 ))
1548 })
1549}
1550
1551impl BinaryPart {
1552 #[async_recursion]
1553 pub(super) async fn get_result(
1554 &self,
1555 exec_state: &mut ExecState,
1556 ctx: &ExecutorContext,
1557 ) -> Result<KclValueControlFlow, KclError> {
1558 match self {
1559 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
1560 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.cloned().map(KclValue::continue_),
1561 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
1562 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
1563 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
1564 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
1565 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
1566 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
1567 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
1568 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
1569 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
1570 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
1571 }
1572 }
1573}
1574
1575impl Node<Name> {
1576 pub(super) async fn get_result<'a>(
1577 &self,
1578 exec_state: &'a mut ExecState,
1579 ctx: &ExecutorContext,
1580 ) -> Result<&'a KclValue, KclError> {
1581 let being_declared = exec_state.mod_local.being_declared.clone();
1582 self.get_result_inner(exec_state, ctx)
1583 .await
1584 .map_err(|e| var_in_own_ref_err(e, &being_declared))
1585 }
1586
1587 async fn get_result_inner<'a>(
1588 &self,
1589 exec_state: &'a mut ExecState,
1590 ctx: &ExecutorContext,
1591 ) -> Result<&'a KclValue, KclError> {
1592 if self.abs_path {
1593 return Err(KclError::new_semantic(KclErrorDetails::new(
1594 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
1595 self.as_source_ranges(),
1596 )));
1597 }
1598
1599 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
1600
1601 if self.path.is_empty() {
1602 let item_value = exec_state.stack().get(&self.name.name, self.into());
1603 if item_value.is_ok() {
1604 return item_value;
1605 }
1606 return exec_state.stack().get(&mod_name, self.into());
1607 }
1608
1609 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
1610 for p in &self.path {
1611 let value = match mem_spec {
1612 Some((env, exports)) => {
1613 if !exports.contains(&p.name) {
1614 return Err(KclError::new_semantic(KclErrorDetails::new(
1615 format!("Item {} not found in module's exported items", p.name),
1616 p.as_source_ranges(),
1617 )));
1618 }
1619
1620 exec_state
1621 .stack()
1622 .memory
1623 .get_from(&p.name, env, p.as_source_range(), 0)?
1624 }
1625 None => exec_state
1626 .stack()
1627 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
1628 };
1629
1630 let KclValue::Module { value: module_id, .. } = value else {
1631 return Err(KclError::new_semantic(KclErrorDetails::new(
1632 format!(
1633 "Identifier in path must refer to a module, found {}",
1634 value.human_friendly_type()
1635 ),
1636 p.as_source_ranges(),
1637 )));
1638 };
1639
1640 mem_spec = Some(
1641 ctx.exec_module_for_items(*module_id, exec_state, p.as_source_range())
1642 .await?,
1643 );
1644 }
1645
1646 let (env, exports) = mem_spec.unwrap();
1647
1648 let item_exported = exports.contains(&self.name.name);
1649 let item_value = exec_state
1650 .stack()
1651 .memory
1652 .get_from(&self.name.name, env, self.name.as_source_range(), 0);
1653
1654 if item_exported && item_value.is_ok() {
1656 return item_value;
1657 }
1658
1659 let mod_exported = exports.contains(&mod_name);
1660 let mod_value = exec_state
1661 .stack()
1662 .memory
1663 .get_from(&mod_name, env, self.name.as_source_range(), 0);
1664
1665 if mod_exported && mod_value.is_ok() {
1667 return mod_value;
1668 }
1669
1670 if item_value.is_err() && mod_value.is_err() {
1672 return item_value;
1673 }
1674
1675 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
1677 Err(KclError::new_semantic(KclErrorDetails::new(
1678 format!("Item {} not found in module's exported items", self.name.name),
1679 self.name.as_source_ranges(),
1680 )))
1681 }
1682}
1683
1684impl Node<MemberExpression> {
1685 async fn get_result(
1686 &self,
1687 exec_state: &mut ExecState,
1688 ctx: &ExecutorContext,
1689 ) -> Result<KclValueControlFlow, KclError> {
1690 let meta = Metadata {
1691 source_range: SourceRange::from(self),
1692 };
1693 let property = Property::try_from(
1696 self.computed,
1697 self.property.clone(),
1698 exec_state,
1699 self.into(),
1700 ctx,
1701 &meta,
1702 &[],
1703 StatementKind::Expression,
1704 )
1705 .await?;
1706 let object_cf = ctx
1707 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
1708 .await?;
1709 let object = control_continue!(object_cf);
1710
1711 match (object, property, self.computed) {
1713 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
1714 "at" => match &segment.repr {
1715 SegmentRepr::Unsolved { segment } => {
1716 match &segment.kind {
1717 UnsolvedSegmentKind::Point { position, .. } => {
1718 Ok(KclValue::HomArray {
1720 value: vec![
1721 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
1722 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
1723 ],
1724 ty: RuntimeType::any(),
1725 }
1726 .continue_())
1727 }
1728 _ => Err(KclError::new_undefined_value(
1729 KclErrorDetails::new(
1730 format!("Property '{property}' not found in segment"),
1731 vec![self.clone().into()],
1732 ),
1733 None,
1734 )),
1735 }
1736 }
1737 SegmentRepr::Solved { segment } => {
1738 match &segment.kind {
1739 SegmentKind::Point { position, .. } => {
1740 Ok(KclValue::array_from_point2d(
1742 [position[0].n, position[1].n],
1743 position[0].ty,
1744 segment.meta.clone(),
1745 )
1746 .continue_())
1747 }
1748 _ => Err(KclError::new_undefined_value(
1749 KclErrorDetails::new(
1750 format!("Property '{property}' not found in segment"),
1751 vec![self.clone().into()],
1752 ),
1753 None,
1754 )),
1755 }
1756 }
1757 },
1758 "start" => match &segment.repr {
1759 SegmentRepr::Unsolved { segment } => match &segment.kind {
1760 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1761 KclErrorDetails::new(
1762 format!("Property '{property}' not found in point segment"),
1763 vec![self.clone().into()],
1764 ),
1765 None,
1766 )),
1767 UnsolvedSegmentKind::Line {
1768 start,
1769 ctor,
1770 start_object_id,
1771 ..
1772 } => Ok(KclValue::Segment {
1773 value: Box::new(AbstractSegment {
1774 repr: SegmentRepr::Unsolved {
1775 segment: UnsolvedSegment {
1776 id: segment.id,
1777 object_id: *start_object_id,
1778 kind: UnsolvedSegmentKind::Point {
1779 position: start.clone(),
1780 ctor: Box::new(PointCtor {
1781 position: ctor.start.clone(),
1782 }),
1783 },
1784 meta: segment.meta.clone(),
1785 },
1786 },
1787 meta: segment.meta.clone(),
1788 }),
1789 }
1790 .continue_()),
1791 UnsolvedSegmentKind::Arc {
1792 start,
1793 ctor,
1794 start_object_id,
1795 ..
1796 } => Ok(KclValue::Segment {
1797 value: Box::new(AbstractSegment {
1798 repr: SegmentRepr::Unsolved {
1799 segment: UnsolvedSegment {
1800 id: segment.id,
1801 object_id: *start_object_id,
1802 kind: UnsolvedSegmentKind::Point {
1803 position: start.clone(),
1804 ctor: Box::new(PointCtor {
1805 position: ctor.start.clone(),
1806 }),
1807 },
1808 meta: segment.meta.clone(),
1809 },
1810 },
1811 meta: segment.meta.clone(),
1812 }),
1813 }
1814 .continue_()),
1815 },
1816 SegmentRepr::Solved { segment } => match &segment.kind {
1817 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1818 KclErrorDetails::new(
1819 format!("Property '{property}' not found in point segment"),
1820 vec![self.clone().into()],
1821 ),
1822 None,
1823 )),
1824 SegmentKind::Line {
1825 start,
1826 ctor,
1827 start_object_id,
1828 start_freedom,
1829 ..
1830 } => Ok(KclValue::Segment {
1831 value: Box::new(AbstractSegment {
1832 repr: SegmentRepr::Solved {
1833 segment: Segment {
1834 id: segment.id,
1835 object_id: *start_object_id,
1836 kind: SegmentKind::Point {
1837 position: start.clone(),
1838 ctor: Box::new(PointCtor {
1839 position: ctor.start.clone(),
1840 }),
1841 freedom: *start_freedom,
1842 },
1843 meta: segment.meta.clone(),
1844 },
1845 },
1846 meta: segment.meta.clone(),
1847 }),
1848 }
1849 .continue_()),
1850 SegmentKind::Arc {
1851 start,
1852 ctor,
1853 start_object_id,
1854 start_freedom,
1855 ..
1856 } => Ok(KclValue::Segment {
1857 value: Box::new(AbstractSegment {
1858 repr: SegmentRepr::Solved {
1859 segment: Segment {
1860 id: segment.id,
1861 object_id: *start_object_id,
1862 kind: SegmentKind::Point {
1863 position: start.clone(),
1864 ctor: Box::new(PointCtor {
1865 position: ctor.start.clone(),
1866 }),
1867 freedom: *start_freedom,
1868 },
1869 meta: segment.meta.clone(),
1870 },
1871 },
1872 meta: segment.meta.clone(),
1873 }),
1874 }
1875 .continue_()),
1876 },
1877 },
1878 "end" => match &segment.repr {
1879 SegmentRepr::Unsolved { segment } => match &segment.kind {
1880 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1881 KclErrorDetails::new(
1882 format!("Property '{property}' not found in point segment"),
1883 vec![self.clone().into()],
1884 ),
1885 None,
1886 )),
1887 UnsolvedSegmentKind::Line {
1888 end,
1889 ctor,
1890 end_object_id,
1891 ..
1892 } => Ok(KclValue::Segment {
1893 value: Box::new(AbstractSegment {
1894 repr: SegmentRepr::Unsolved {
1895 segment: UnsolvedSegment {
1896 id: segment.id,
1897 object_id: *end_object_id,
1898 kind: UnsolvedSegmentKind::Point {
1899 position: end.clone(),
1900 ctor: Box::new(PointCtor {
1901 position: ctor.end.clone(),
1902 }),
1903 },
1904 meta: segment.meta.clone(),
1905 },
1906 },
1907 meta: segment.meta.clone(),
1908 }),
1909 }
1910 .continue_()),
1911 UnsolvedSegmentKind::Arc {
1912 end,
1913 ctor,
1914 end_object_id,
1915 ..
1916 } => Ok(KclValue::Segment {
1917 value: Box::new(AbstractSegment {
1918 repr: SegmentRepr::Unsolved {
1919 segment: UnsolvedSegment {
1920 id: segment.id,
1921 object_id: *end_object_id,
1922 kind: UnsolvedSegmentKind::Point {
1923 position: end.clone(),
1924 ctor: Box::new(PointCtor {
1925 position: ctor.end.clone(),
1926 }),
1927 },
1928 meta: segment.meta.clone(),
1929 },
1930 },
1931 meta: segment.meta.clone(),
1932 }),
1933 }
1934 .continue_()),
1935 },
1936 SegmentRepr::Solved { segment } => match &segment.kind {
1937 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
1938 KclErrorDetails::new(
1939 format!("Property '{property}' not found in point segment"),
1940 vec![self.clone().into()],
1941 ),
1942 None,
1943 )),
1944 SegmentKind::Line {
1945 end,
1946 ctor,
1947 end_object_id,
1948 end_freedom,
1949 ..
1950 } => Ok(KclValue::Segment {
1951 value: Box::new(AbstractSegment {
1952 repr: SegmentRepr::Solved {
1953 segment: Segment {
1954 id: segment.id,
1955 object_id: *end_object_id,
1956 kind: SegmentKind::Point {
1957 position: end.clone(),
1958 ctor: Box::new(PointCtor {
1959 position: ctor.end.clone(),
1960 }),
1961 freedom: *end_freedom,
1962 },
1963 meta: segment.meta.clone(),
1964 },
1965 },
1966 meta: segment.meta.clone(),
1967 }),
1968 }
1969 .continue_()),
1970 SegmentKind::Arc {
1971 end,
1972 ctor,
1973 end_object_id,
1974 end_freedom,
1975 ..
1976 } => Ok(KclValue::Segment {
1977 value: Box::new(AbstractSegment {
1978 repr: SegmentRepr::Solved {
1979 segment: Segment {
1980 id: segment.id,
1981 object_id: *end_object_id,
1982 kind: SegmentKind::Point {
1983 position: end.clone(),
1984 ctor: Box::new(PointCtor {
1985 position: ctor.end.clone(),
1986 }),
1987 freedom: *end_freedom,
1988 },
1989 meta: segment.meta.clone(),
1990 },
1991 },
1992 meta: segment.meta.clone(),
1993 }),
1994 }
1995 .continue_()),
1996 },
1997 },
1998 "center" => match &segment.repr {
1999 SegmentRepr::Unsolved { segment } => match &segment.kind {
2000 UnsolvedSegmentKind::Arc {
2001 center,
2002 ctor,
2003 center_object_id,
2004 ..
2005 } => Ok(KclValue::Segment {
2006 value: Box::new(AbstractSegment {
2007 repr: SegmentRepr::Unsolved {
2008 segment: UnsolvedSegment {
2009 id: segment.id,
2010 object_id: *center_object_id,
2011 kind: UnsolvedSegmentKind::Point {
2012 position: center.clone(),
2013 ctor: Box::new(PointCtor {
2014 position: ctor.center.clone(),
2015 }),
2016 },
2017 meta: segment.meta.clone(),
2018 },
2019 },
2020 meta: segment.meta.clone(),
2021 }),
2022 }
2023 .continue_()),
2024 _ => Err(KclError::new_undefined_value(
2025 KclErrorDetails::new(
2026 format!("Property '{property}' not found in segment"),
2027 vec![self.clone().into()],
2028 ),
2029 None,
2030 )),
2031 },
2032 SegmentRepr::Solved { segment } => match &segment.kind {
2033 SegmentKind::Arc {
2034 center,
2035 ctor,
2036 center_object_id,
2037 center_freedom,
2038 ..
2039 } => Ok(KclValue::Segment {
2040 value: Box::new(AbstractSegment {
2041 repr: SegmentRepr::Solved {
2042 segment: Segment {
2043 id: segment.id,
2044 object_id: *center_object_id,
2045 kind: SegmentKind::Point {
2046 position: center.clone(),
2047 ctor: Box::new(PointCtor {
2048 position: ctor.center.clone(),
2049 }),
2050 freedom: *center_freedom,
2051 },
2052 meta: segment.meta.clone(),
2053 },
2054 },
2055 meta: segment.meta.clone(),
2056 }),
2057 }
2058 .continue_()),
2059 _ => Err(KclError::new_undefined_value(
2060 KclErrorDetails::new(
2061 format!("Property '{property}' not found in segment"),
2062 vec![self.clone().into()],
2063 ),
2064 None,
2065 )),
2066 },
2067 },
2068 other => Err(KclError::new_undefined_value(
2069 KclErrorDetails::new(
2070 format!("Property '{other}' not found in segment"),
2071 vec![self.clone().into()],
2072 ),
2073 None,
2074 )),
2075 },
2076 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
2077 "zAxis" => {
2078 let (p, u) = plane.info.z_axis.as_3_dims();
2079 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2080 }
2081 "yAxis" => {
2082 let (p, u) = plane.info.y_axis.as_3_dims();
2083 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2084 }
2085 "xAxis" => {
2086 let (p, u) = plane.info.x_axis.as_3_dims();
2087 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2088 }
2089 "origin" => {
2090 let (p, u) = plane.info.origin.as_3_dims();
2091 Ok(KclValue::array_from_point3d(p, u.into(), vec![meta]).continue_())
2092 }
2093 other => Err(KclError::new_undefined_value(
2094 KclErrorDetails::new(
2095 format!("Property '{other}' not found in plane"),
2096 vec![self.clone().into()],
2097 ),
2098 None,
2099 )),
2100 },
2101 (KclValue::Object { value: map, .. }, Property::String(property), false) => {
2102 if let Some(value) = map.get(&property) {
2103 Ok(value.to_owned().continue_())
2104 } else {
2105 Err(KclError::new_undefined_value(
2106 KclErrorDetails::new(
2107 format!("Property '{property}' not found in object"),
2108 vec![self.clone().into()],
2109 ),
2110 None,
2111 ))
2112 }
2113 }
2114 (KclValue::Object { .. }, Property::String(property), true) => {
2115 Err(KclError::new_semantic(KclErrorDetails::new(
2116 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
2117 vec![self.clone().into()],
2118 )))
2119 }
2120 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
2121 if i == 0
2122 && let Some(value) = map.get("x")
2123 {
2124 return Ok(value.to_owned().continue_());
2125 }
2126 if i == 1
2127 && let Some(value) = map.get("y")
2128 {
2129 return Ok(value.to_owned().continue_());
2130 }
2131 if i == 2
2132 && let Some(value) = map.get("z")
2133 {
2134 return Ok(value.to_owned().continue_());
2135 }
2136 let t = p.type_name();
2137 let article = article_for(t);
2138 Err(KclError::new_semantic(KclErrorDetails::new(
2139 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
2140 vec![self.clone().into()],
2141 )))
2142 }
2143 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
2144 let value_of_arr = arr.get(index);
2145 if let Some(value) = value_of_arr {
2146 Ok(value.to_owned().continue_())
2147 } else {
2148 Err(KclError::new_undefined_value(
2149 KclErrorDetails::new(
2150 format!("The array doesn't have any item at index {index}"),
2151 vec![self.clone().into()],
2152 ),
2153 None,
2154 ))
2155 }
2156 }
2157 (obj, Property::UInt(0), _) => Ok(obj.continue_()),
2160 (KclValue::HomArray { .. }, p, _) => {
2161 let t = p.type_name();
2162 let article = article_for(t);
2163 Err(KclError::new_semantic(KclErrorDetails::new(
2164 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
2165 vec![self.clone().into()],
2166 )))
2167 }
2168 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
2169 value: Box::new(value.sketch),
2170 }
2171 .continue_()),
2172 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
2173 Err(KclError::new_semantic(KclErrorDetails::new(
2175 format!(
2176 "Property `{prop}` not found on {}. You can get a solid's tags through its sketch, as in, `exampleSolid.sketch.tags`.",
2177 geometry.human_friendly_type()
2178 ),
2179 vec![self.clone().into()],
2180 )))
2181 }
2182 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
2183 meta: vec![Metadata {
2184 source_range: SourceRange::from(self.clone()),
2185 }],
2186 value: sk
2187 .tags
2188 .iter()
2189 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
2190 .collect(),
2191 constrainable: false,
2192 }
2193 .continue_()),
2194 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
2195 Err(KclError::new_semantic(KclErrorDetails::new(
2196 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
2197 vec![self.clone().into()],
2198 )))
2199 }
2200 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
2201 format!(
2202 "Only objects can have members accessed with dot notation, but you're trying to access {}",
2203 being_indexed.human_friendly_type()
2204 ),
2205 vec![self.clone().into()],
2206 ))),
2207 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
2208 format!(
2209 "Only arrays can be indexed, but you're trying to index {}",
2210 being_indexed.human_friendly_type()
2211 ),
2212 vec![self.clone().into()],
2213 ))),
2214 }
2215 }
2216}
2217
2218impl Node<BinaryExpression> {
2219 pub(super) async fn get_result(
2220 &self,
2221 exec_state: &mut ExecState,
2222 ctx: &ExecutorContext,
2223 ) -> Result<KclValueControlFlow, KclError> {
2224 enum State {
2225 EvaluateLeft(Node<BinaryExpression>),
2226 FromLeft {
2227 node: Node<BinaryExpression>,
2228 },
2229 EvaluateRight {
2230 node: Node<BinaryExpression>,
2231 left: KclValue,
2232 },
2233 FromRight {
2234 node: Node<BinaryExpression>,
2235 left: KclValue,
2236 },
2237 }
2238
2239 let mut stack = vec![State::EvaluateLeft(self.clone())];
2240 let mut last_result: Option<KclValue> = None;
2241
2242 while let Some(state) = stack.pop() {
2243 match state {
2244 State::EvaluateLeft(node) => {
2245 let left_part = node.left.clone();
2246 match left_part {
2247 BinaryPart::BinaryExpression(child) => {
2248 stack.push(State::FromLeft { node });
2249 stack.push(State::EvaluateLeft(*child));
2250 }
2251 part => {
2252 let left_value = part.get_result(exec_state, ctx).await?;
2253 let left_value = control_continue!(left_value);
2254 stack.push(State::EvaluateRight { node, left: left_value });
2255 }
2256 }
2257 }
2258 State::FromLeft { node } => {
2259 let Some(left_value) = last_result.take() else {
2260 return Err(Self::missing_result_error(&node));
2261 };
2262 stack.push(State::EvaluateRight { node, left: left_value });
2263 }
2264 State::EvaluateRight { node, left } => {
2265 let right_part = node.right.clone();
2266 match right_part {
2267 BinaryPart::BinaryExpression(child) => {
2268 stack.push(State::FromRight { node, left });
2269 stack.push(State::EvaluateLeft(*child));
2270 }
2271 part => {
2272 let right_value = part.get_result(exec_state, ctx).await?;
2273 let right_value = control_continue!(right_value);
2274 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2275 last_result = Some(result);
2276 }
2277 }
2278 }
2279 State::FromRight { node, left } => {
2280 let Some(right_value) = last_result.take() else {
2281 return Err(Self::missing_result_error(&node));
2282 };
2283 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
2284 last_result = Some(result);
2285 }
2286 }
2287 }
2288
2289 last_result
2290 .map(KclValue::continue_)
2291 .ok_or_else(|| Self::missing_result_error(self))
2292 }
2293
2294 async fn apply_operator(
2295 &self,
2296 exec_state: &mut ExecState,
2297 ctx: &ExecutorContext,
2298 left_value: KclValue,
2299 right_value: KclValue,
2300 ) -> Result<KclValue, KclError> {
2301 let mut meta = left_value.metadata();
2302 meta.extend(right_value.metadata());
2303
2304 if self.operator == BinaryOperator::Add
2306 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
2307 (&left_value, &right_value)
2308 {
2309 return Ok(KclValue::String {
2310 value: format!("{left}{right}"),
2311 meta,
2312 });
2313 }
2314
2315 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
2317 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2318 let args = Args::new_no_args(self.into(), ctx.clone(), Some("union".to_owned()));
2319 let result = crate::std::csg::inner_union(
2320 vec![*left.clone(), *right.clone()],
2321 Default::default(),
2322 exec_state,
2323 args,
2324 )
2325 .await?;
2326 return Ok(result.into());
2327 }
2328 } else if self.operator == BinaryOperator::Sub {
2329 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
2331 let args = Args::new_no_args(self.into(), ctx.clone(), Some("subtract".to_owned()));
2332 let result = crate::std::csg::inner_subtract(
2333 vec![*left.clone()],
2334 vec![*right.clone()],
2335 Default::default(),
2336 exec_state,
2337 args,
2338 )
2339 .await?;
2340 return Ok(result.into());
2341 }
2342 } else if self.operator == BinaryOperator::And
2343 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
2344 {
2345 let args = Args::new_no_args(self.into(), ctx.clone(), Some("intersect".to_owned()));
2347 let result = crate::std::csg::inner_intersect(
2348 vec![*left.clone(), *right.clone()],
2349 Default::default(),
2350 exec_state,
2351 args,
2352 )
2353 .await?;
2354 return Ok(result.into());
2355 }
2356
2357 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
2359 let KclValue::Bool { value: left_value, .. } = left_value else {
2360 return Err(KclError::new_semantic(KclErrorDetails::new(
2361 format!(
2362 "Cannot apply logical operator to non-boolean value: {}",
2363 left_value.human_friendly_type()
2364 ),
2365 vec![self.left.clone().into()],
2366 )));
2367 };
2368 let KclValue::Bool { value: right_value, .. } = right_value else {
2369 return Err(KclError::new_semantic(KclErrorDetails::new(
2370 format!(
2371 "Cannot apply logical operator to non-boolean value: {}",
2372 right_value.human_friendly_type()
2373 ),
2374 vec![self.right.clone().into()],
2375 )));
2376 };
2377 let raw_value = match self.operator {
2378 BinaryOperator::Or => left_value || right_value,
2379 BinaryOperator::And => left_value && right_value,
2380 _ => unreachable!(),
2381 };
2382 return Ok(KclValue::Bool { value: raw_value, meta });
2383 }
2384
2385 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
2387 match (&left_value, &right_value) {
2388 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
2390 if left_value.id == right_value.id =>
2391 {
2392 return Ok(KclValue::Bool { value: true, meta });
2393 }
2394 (KclValue::SketchVar { .. }, KclValue::SketchVar { .. }) => {
2396 return Err(KclError::new_semantic(KclErrorDetails::new(
2399 "TODO: Different sketch variables".to_owned(),
2400 vec![self.into()],
2401 )));
2402 }
2403 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
2405 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
2406 let number_value = normalize_to_solver_unit(
2407 input_number,
2408 input_number.into(),
2409 exec_state,
2410 "fixed constraint value",
2411 )?;
2412 let Some(n) = number_value.as_ty_f64() else {
2413 let message = format!(
2414 "Expected number after coercion, but found {}",
2415 number_value.human_friendly_type()
2416 );
2417 debug_assert!(false, "{}", &message);
2418 return Err(internal_err(message, self));
2419 };
2420 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
2421 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2422 let message = "Being inside a sketch block should have already been checked above".to_owned();
2423 debug_assert!(false, "{}", &message);
2424 return Err(internal_err(message, self));
2425 };
2426 sketch_block_state.solver_constraints.push(constraint);
2427 return Ok(KclValue::Bool { value: true, meta });
2428 }
2429 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
2431 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
2432 let number_value = normalize_to_solver_unit(
2433 input_number,
2434 input_number.into(),
2435 exec_state,
2436 "fixed constraint value",
2437 )?;
2438 let Some(n) = number_value.as_ty_f64() else {
2439 let message = format!(
2440 "Expected number after coercion, but found {}",
2441 number_value.human_friendly_type()
2442 );
2443 debug_assert!(false, "{}", &message);
2444 return Err(internal_err(message, self));
2445 };
2446 match &constraint.kind {
2447 SketchConstraintKind::Distance { points } => {
2448 let range = self.as_source_range();
2449 let p0 = &points[0];
2450 let p1 = &points[1];
2451 let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2452 p0.vars.x.to_constraint_id(range)?,
2453 p0.vars.y.to_constraint_id(range)?,
2454 );
2455 let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2456 p1.vars.x.to_constraint_id(range)?,
2457 p1.vars.y.to_constraint_id(range)?,
2458 );
2459 let solver_constraint = Constraint::Distance(solver_pt0, solver_pt1, n.n);
2460
2461 #[cfg(feature = "artifact-graph")]
2462 let constraint_id = exec_state.next_object_id();
2463 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2464 let message =
2465 "Being inside a sketch block should have already been checked above".to_owned();
2466 debug_assert!(false, "{}", &message);
2467 return Err(internal_err(message, self));
2468 };
2469 sketch_block_state.solver_constraints.push(solver_constraint);
2470 #[cfg(feature = "artifact-graph")]
2471 {
2472 use crate::{execution::ArtifactId, front::Distance};
2473
2474 let constraint = crate::front::Constraint::Distance(Distance {
2475 points: vec![p0.object_id, p1.object_id],
2476 distance: n.try_into().map_err(|_| {
2477 internal_err("Failed to convert distance units numeric suffix:", range)
2478 })?,
2479 });
2480 sketch_block_state.sketch_constraints.push(constraint_id);
2481 exec_state.add_scene_object(
2482 Object {
2483 id: constraint_id,
2484 kind: ObjectKind::Constraint { constraint },
2485 label: Default::default(),
2486 comments: Default::default(),
2487 artifact_id: ArtifactId::constraint(),
2488 source: range.into(),
2489 },
2490 range,
2491 );
2492 }
2493 }
2494 SketchConstraintKind::Radius { points } | SketchConstraintKind::Diameter { points } => {
2495 let range = self.as_source_range();
2496 let center = &points[0];
2497 let start = &points[1];
2498 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
2500 return Err(internal_err(
2501 "Being inside a sketch block should have already been checked above",
2502 self,
2503 ));
2504 };
2505 let (constraint_name, is_diameter) = match &constraint.kind {
2507 SketchConstraintKind::Radius { .. } => ("radius", false),
2508 SketchConstraintKind::Diameter { .. } => ("diameter", true),
2509 _ => unreachable!(),
2510 };
2511 let arc_segment = sketch_block_state
2512 .needed_by_engine
2513 .iter()
2514 .find(|seg| {
2515 matches!(&seg.kind, UnsolvedSegmentKind::Arc {
2516 center_object_id,
2517 start_object_id,
2518 ..
2519 } if *center_object_id == center.object_id && *start_object_id == start.object_id)
2520 })
2521 .ok_or_else(|| {
2522 internal_err(
2523 format!("Could not find arc segment for {} constraint", constraint_name),
2524 range,
2525 )
2526 })?;
2527 let UnsolvedSegmentKind::Arc { end, .. } = &arc_segment.kind else {
2528 return Err(internal_err("Expected arc segment", range));
2529 };
2530 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
2532 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => (*end_x, *end_y),
2533 _ => {
2534 return Err(internal_err(
2535 "Arc end point must have sketch vars in all coordinates",
2536 range,
2537 ));
2538 }
2539 };
2540 let solver_arc = kcl_ezpz::datatypes::inputs::DatumCircularArc {
2541 center: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2542 center.vars.x.to_constraint_id(range)?,
2543 center.vars.y.to_constraint_id(range)?,
2544 ),
2545 start: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2546 start.vars.x.to_constraint_id(range)?,
2547 start.vars.y.to_constraint_id(range)?,
2548 ),
2549 end: kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2550 end_x_var.to_constraint_id(range)?,
2551 end_y_var.to_constraint_id(range)?,
2552 ),
2553 };
2554 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
2557 let solver_constraint = Constraint::ArcRadius(solver_arc, radius_value);
2558
2559 #[cfg(feature = "artifact-graph")]
2560 let constraint_id = exec_state.next_object_id();
2561 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2562 let message =
2563 "Being inside a sketch block should have already been checked above".to_owned();
2564 debug_assert!(false, "{}", &message);
2565 return Err(internal_err(message, self));
2566 };
2567 sketch_block_state.solver_constraints.push(solver_constraint);
2568 #[cfg(feature = "artifact-graph")]
2569 {
2570 use crate::execution::ArtifactId;
2571
2572 let arc_object_id = sketch_block_state
2574 .needed_by_engine
2575 .iter()
2576 .find(|seg| {
2577 matches!(&seg.kind, UnsolvedSegmentKind::Arc {
2578 center_object_id,
2579 start_object_id,
2580 ..
2581 } if *center_object_id == center.object_id && *start_object_id == start.object_id)
2582 })
2583 .map(|seg| seg.object_id)
2584 .ok_or_else(|| {
2585 internal_err(
2586 format!(
2587 "Could not find arc segment object ID for {} constraint",
2588 constraint_name
2589 ),
2590 range,
2591 )
2592 })?;
2593
2594 let constraint = if is_diameter {
2595 use crate::frontend::sketch::Diameter;
2596 crate::front::Constraint::Diameter(Diameter {
2597 arc: arc_object_id,
2598 diameter: n.try_into().map_err(|_| {
2599 internal_err("Failed to convert diameter units numeric suffix:", range)
2600 })?,
2601 })
2602 } else {
2603 use crate::frontend::sketch::Radius;
2604 crate::front::Constraint::Radius(Radius {
2605 arc: arc_object_id,
2606 radius: n.try_into().map_err(|_| {
2607 internal_err("Failed to convert radius units numeric suffix:", range)
2608 })?,
2609 })
2610 };
2611 sketch_block_state.sketch_constraints.push(constraint_id);
2612 exec_state.add_scene_object(
2613 Object {
2614 id: constraint_id,
2615 kind: ObjectKind::Constraint { constraint },
2616 label: Default::default(),
2617 comments: Default::default(),
2618 artifact_id: ArtifactId::constraint(),
2619 source: range.into(),
2620 },
2621 range,
2622 );
2623 }
2624 }
2625 SketchConstraintKind::HorizontalDistance { points } => {
2626 let range = self.as_source_range();
2627 let p0 = &points[0];
2628 let p1 = &points[1];
2629 let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2630 p0.vars.x.to_constraint_id(range)?,
2631 p0.vars.y.to_constraint_id(range)?,
2632 );
2633 let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2634 p1.vars.x.to_constraint_id(range)?,
2635 p1.vars.y.to_constraint_id(range)?,
2636 );
2637 let solver_constraint =
2641 kcl_ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n);
2642
2643 #[cfg(feature = "artifact-graph")]
2644 let constraint_id = exec_state.next_object_id();
2645 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2646 let message =
2647 "Being inside a sketch block should have already been checked above".to_owned();
2648 debug_assert!(false, "{}", &message);
2649 return Err(internal_err(message, self));
2650 };
2651 sketch_block_state.solver_constraints.push(solver_constraint);
2652 #[cfg(feature = "artifact-graph")]
2653 {
2654 use crate::{execution::ArtifactId, front::Distance};
2655
2656 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
2657 points: vec![p0.object_id, p1.object_id],
2658 distance: n.try_into().map_err(|_| {
2659 internal_err("Failed to convert distance units numeric suffix:", range)
2660 })?,
2661 });
2662 sketch_block_state.sketch_constraints.push(constraint_id);
2663 exec_state.add_scene_object(
2664 Object {
2665 id: constraint_id,
2666 kind: ObjectKind::Constraint { constraint },
2667 label: Default::default(),
2668 comments: Default::default(),
2669 artifact_id: ArtifactId::constraint(),
2670 source: range.into(),
2671 },
2672 range,
2673 );
2674 }
2675 }
2676 SketchConstraintKind::VerticalDistance { points } => {
2677 let range = self.as_source_range();
2678 let p0 = &points[0];
2679 let p1 = &points[1];
2680 let solver_pt0 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2681 p0.vars.x.to_constraint_id(range)?,
2682 p0.vars.y.to_constraint_id(range)?,
2683 );
2684 let solver_pt1 = kcl_ezpz::datatypes::inputs::DatumPoint::new_xy(
2685 p1.vars.x.to_constraint_id(range)?,
2686 p1.vars.y.to_constraint_id(range)?,
2687 );
2688 let solver_constraint = kcl_ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n);
2692
2693 #[cfg(feature = "artifact-graph")]
2694 let constraint_id = exec_state.next_object_id();
2695 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2696 let message =
2697 "Being inside a sketch block should have already been checked above".to_owned();
2698 debug_assert!(false, "{}", &message);
2699 return Err(internal_err(message, self));
2700 };
2701 sketch_block_state.solver_constraints.push(solver_constraint);
2702 #[cfg(feature = "artifact-graph")]
2703 {
2704 use crate::{execution::ArtifactId, front::Distance};
2705
2706 let constraint = crate::front::Constraint::VerticalDistance(Distance {
2707 points: vec![p0.object_id, p1.object_id],
2708 distance: n.try_into().map_err(|_| {
2709 internal_err("Failed to convert distance units numeric suffix:", range)
2710 })?,
2711 });
2712 sketch_block_state.sketch_constraints.push(constraint_id);
2713 exec_state.add_scene_object(
2714 Object {
2715 id: constraint_id,
2716 kind: ObjectKind::Constraint { constraint },
2717 label: Default::default(),
2718 comments: Default::default(),
2719 artifact_id: ArtifactId::constraint(),
2720 source: range.into(),
2721 },
2722 range,
2723 );
2724 }
2725 }
2726 }
2727 return Ok(KclValue::Bool { value: true, meta });
2728 }
2729 _ => {
2730 return Err(KclError::new_semantic(KclErrorDetails::new(
2731 format!(
2732 "Cannot create an equivalence constraint between values of these types: {} and {}",
2733 left_value.human_friendly_type(),
2734 right_value.human_friendly_type()
2735 ),
2736 vec![self.into()],
2737 )));
2738 }
2739 }
2740 }
2741
2742 let left = number_as_f64(&left_value, self.left.clone().into())?;
2743 let right = number_as_f64(&right_value, self.right.clone().into())?;
2744
2745 let value = match self.operator {
2746 BinaryOperator::Add => {
2747 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2748 self.warn_on_unknown(&ty, "Adding", exec_state);
2749 KclValue::Number { value: l + r, meta, ty }
2750 }
2751 BinaryOperator::Sub => {
2752 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
2753 self.warn_on_unknown(&ty, "Subtracting", exec_state);
2754 KclValue::Number { value: l - r, meta, ty }
2755 }
2756 BinaryOperator::Mul => {
2757 let (l, r, ty) = NumericType::combine_mul(left, right);
2758 self.warn_on_unknown(&ty, "Multiplying", exec_state);
2759 KclValue::Number { value: l * r, meta, ty }
2760 }
2761 BinaryOperator::Div => {
2762 let (l, r, ty) = NumericType::combine_div(left, right);
2763 self.warn_on_unknown(&ty, "Dividing", exec_state);
2764 KclValue::Number { value: l / r, meta, ty }
2765 }
2766 BinaryOperator::Mod => {
2767 let (l, r, ty) = NumericType::combine_mod(left, right);
2768 self.warn_on_unknown(&ty, "Modulo of", exec_state);
2769 KclValue::Number { value: l % r, meta, ty }
2770 }
2771 BinaryOperator::Pow => KclValue::Number {
2772 value: left.n.powf(right.n),
2773 meta,
2774 ty: exec_state.current_default_units(),
2775 },
2776 BinaryOperator::Neq => {
2777 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2778 self.warn_on_unknown(&ty, "Comparing", exec_state);
2779 KclValue::Bool { value: l != r, meta }
2780 }
2781 BinaryOperator::Gt => {
2782 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2783 self.warn_on_unknown(&ty, "Comparing", exec_state);
2784 KclValue::Bool { value: l > r, meta }
2785 }
2786 BinaryOperator::Gte => {
2787 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2788 self.warn_on_unknown(&ty, "Comparing", exec_state);
2789 KclValue::Bool { value: l >= r, meta }
2790 }
2791 BinaryOperator::Lt => {
2792 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2793 self.warn_on_unknown(&ty, "Comparing", exec_state);
2794 KclValue::Bool { value: l < r, meta }
2795 }
2796 BinaryOperator::Lte => {
2797 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2798 self.warn_on_unknown(&ty, "Comparing", exec_state);
2799 KclValue::Bool { value: l <= r, meta }
2800 }
2801 BinaryOperator::Eq => {
2802 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
2803 self.warn_on_unknown(&ty, "Comparing", exec_state);
2804 KclValue::Bool { value: l == r, meta }
2805 }
2806 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
2807 };
2808
2809 Ok(value)
2810 }
2811
2812 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
2813 internal_err("missing result while evaluating binary expression", node)
2814 }
2815
2816 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
2817 if ty == &NumericType::Unknown {
2818 let sr = self.as_source_range();
2819 exec_state.clear_units_warnings(&sr);
2820 let mut err = CompilationError::err(
2821 sr,
2822 format!(
2823 "{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)`."
2824 ),
2825 );
2826 err.tag = crate::errors::Tag::UnknownNumericUnits;
2827 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
2828 }
2829 }
2830}
2831
2832impl Node<UnaryExpression> {
2833 pub(super) async fn get_result(
2834 &self,
2835 exec_state: &mut ExecState,
2836 ctx: &ExecutorContext,
2837 ) -> Result<KclValueControlFlow, KclError> {
2838 match self.operator {
2839 UnaryOperator::Not => {
2840 let value = self.argument.get_result(exec_state, ctx).await?;
2841 let value = control_continue!(value);
2842 let KclValue::Bool {
2843 value: bool_value,
2844 meta: _,
2845 } = value
2846 else {
2847 return Err(KclError::new_semantic(KclErrorDetails::new(
2848 format!(
2849 "Cannot apply unary operator ! to non-boolean value: {}",
2850 value.human_friendly_type()
2851 ),
2852 vec![self.into()],
2853 )));
2854 };
2855 let meta = vec![Metadata {
2856 source_range: self.into(),
2857 }];
2858 let negated = KclValue::Bool {
2859 value: !bool_value,
2860 meta,
2861 };
2862
2863 Ok(negated.continue_())
2864 }
2865 UnaryOperator::Neg => {
2866 let value = self.argument.get_result(exec_state, ctx).await?;
2867 let value = control_continue!(value);
2868 let err = || {
2869 KclError::new_semantic(KclErrorDetails::new(
2870 format!(
2871 "You can only negate numbers, planes, or lines, but this is a {}",
2872 value.human_friendly_type()
2873 ),
2874 vec![self.into()],
2875 ))
2876 };
2877 match &value {
2878 KclValue::Number { value, ty, .. } => {
2879 let meta = vec![Metadata {
2880 source_range: self.into(),
2881 }];
2882 Ok(KclValue::Number {
2883 value: -value,
2884 meta,
2885 ty: *ty,
2886 }
2887 .continue_())
2888 }
2889 KclValue::Plane { value } => {
2890 let mut plane = value.clone();
2891 if plane.info.x_axis.x != 0.0 {
2892 plane.info.x_axis.x *= -1.0;
2893 }
2894 if plane.info.x_axis.y != 0.0 {
2895 plane.info.x_axis.y *= -1.0;
2896 }
2897 if plane.info.x_axis.z != 0.0 {
2898 plane.info.x_axis.z *= -1.0;
2899 }
2900
2901 plane.id = exec_state.next_uuid();
2902 plane.object_id = None;
2903 Ok(KclValue::Plane { value: plane }.continue_())
2904 }
2905 KclValue::Object {
2906 value: values, meta, ..
2907 } => {
2908 let Some(direction) = values.get("direction") else {
2910 return Err(err());
2911 };
2912
2913 let direction = match direction {
2914 KclValue::Tuple { value: values, meta } => {
2915 let values = values
2916 .iter()
2917 .map(|v| match v {
2918 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
2919 value: *value * -1.0,
2920 ty: *ty,
2921 meta: meta.clone(),
2922 }),
2923 _ => Err(err()),
2924 })
2925 .collect::<Result<Vec<_>, _>>()?;
2926
2927 KclValue::Tuple {
2928 value: values,
2929 meta: meta.clone(),
2930 }
2931 }
2932 KclValue::HomArray {
2933 value: values,
2934 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
2935 } => {
2936 let values = values
2937 .iter()
2938 .map(|v| match v {
2939 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
2940 value: *value * -1.0,
2941 ty: *ty,
2942 meta: meta.clone(),
2943 }),
2944 _ => Err(err()),
2945 })
2946 .collect::<Result<Vec<_>, _>>()?;
2947
2948 KclValue::HomArray {
2949 value: values,
2950 ty: ty.clone(),
2951 }
2952 }
2953 _ => return Err(err()),
2954 };
2955
2956 let mut value = values.clone();
2957 value.insert("direction".to_owned(), direction);
2958 Ok(KclValue::Object {
2959 value,
2960 meta: meta.clone(),
2961 constrainable: false,
2962 }
2963 .continue_())
2964 }
2965 _ => Err(err()),
2966 }
2967 }
2968 UnaryOperator::Plus => {
2969 let operand = self.argument.get_result(exec_state, ctx).await?;
2970 let operand = control_continue!(operand);
2971 match operand {
2972 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
2973 _ => Err(KclError::new_semantic(KclErrorDetails::new(
2974 format!(
2975 "You can only apply unary + to numbers or planes, but this is a {}",
2976 operand.human_friendly_type()
2977 ),
2978 vec![self.into()],
2979 ))),
2980 }
2981 }
2982 }
2983 }
2984}
2985
2986pub(crate) async fn execute_pipe_body(
2987 exec_state: &mut ExecState,
2988 body: &[Expr],
2989 source_range: SourceRange,
2990 ctx: &ExecutorContext,
2991) -> Result<KclValueControlFlow, KclError> {
2992 let Some((first, body)) = body.split_first() else {
2993 return Err(KclError::new_semantic(KclErrorDetails::new(
2994 "Pipe expressions cannot be empty".to_owned(),
2995 vec![source_range],
2996 )));
2997 };
2998 let meta = Metadata {
3003 source_range: SourceRange::from(first),
3004 };
3005 let output = ctx
3006 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
3007 .await?;
3008 let output = control_continue!(output);
3009
3010 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
3014 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
3016 exec_state.mod_local.pipe_value = previous_pipe_value;
3018
3019 result
3020}
3021
3022#[async_recursion]
3025async fn inner_execute_pipe_body(
3026 exec_state: &mut ExecState,
3027 body: &[Expr],
3028 ctx: &ExecutorContext,
3029) -> Result<KclValueControlFlow, KclError> {
3030 for expression in body {
3031 if let Expr::TagDeclarator(_) = expression {
3032 return Err(KclError::new_semantic(KclErrorDetails::new(
3033 format!("This cannot be in a PipeExpression: {expression:?}"),
3034 vec![expression.into()],
3035 )));
3036 }
3037 let metadata = Metadata {
3038 source_range: SourceRange::from(expression),
3039 };
3040 let output = ctx
3041 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
3042 .await?;
3043 let output = control_continue!(output);
3044 exec_state.mod_local.pipe_value = Some(output);
3045 }
3046 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
3048 Ok(final_output.continue_())
3049}
3050
3051impl Node<TagDeclarator> {
3052 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
3053 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
3054 value: self.name.clone(),
3055 info: Vec::new(),
3056 meta: vec![Metadata {
3057 source_range: self.into(),
3058 }],
3059 }));
3060
3061 exec_state
3062 .mut_stack()
3063 .add(self.name.clone(), memory_item, self.into())?;
3064
3065 Ok(self.into())
3066 }
3067}
3068
3069impl Node<ArrayExpression> {
3070 #[async_recursion]
3071 pub(super) async fn execute(
3072 &self,
3073 exec_state: &mut ExecState,
3074 ctx: &ExecutorContext,
3075 ) -> Result<KclValueControlFlow, KclError> {
3076 let mut results = Vec::with_capacity(self.elements.len());
3077
3078 for element in &self.elements {
3079 let metadata = Metadata::from(element);
3080 let value = ctx
3083 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
3084 .await?;
3085 let value = control_continue!(value);
3086
3087 results.push(value);
3088 }
3089
3090 Ok(KclValue::HomArray {
3091 value: results,
3092 ty: RuntimeType::Primitive(PrimitiveType::Any),
3093 }
3094 .continue_())
3095 }
3096}
3097
3098impl Node<ArrayRangeExpression> {
3099 #[async_recursion]
3100 pub(super) async fn execute(
3101 &self,
3102 exec_state: &mut ExecState,
3103 ctx: &ExecutorContext,
3104 ) -> Result<KclValueControlFlow, KclError> {
3105 let metadata = Metadata::from(&self.start_element);
3106 let start_val = ctx
3107 .execute_expr(
3108 &self.start_element,
3109 exec_state,
3110 &metadata,
3111 &[],
3112 StatementKind::Expression,
3113 )
3114 .await?;
3115 let start_val = control_continue!(start_val);
3116 let start = start_val
3117 .as_ty_f64()
3118 .ok_or(KclError::new_semantic(KclErrorDetails::new(
3119 format!(
3120 "Expected number for range start but found {}",
3121 start_val.human_friendly_type()
3122 ),
3123 vec![self.into()],
3124 )))?;
3125 let metadata = Metadata::from(&self.end_element);
3126 let end_val = ctx
3127 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
3128 .await?;
3129 let end_val = control_continue!(end_val);
3130 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
3131 format!(
3132 "Expected number for range end but found {}",
3133 end_val.human_friendly_type()
3134 ),
3135 vec![self.into()],
3136 )))?;
3137
3138 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
3139 let Some(start) = crate::try_f64_to_i64(start) else {
3140 return Err(KclError::new_semantic(KclErrorDetails::new(
3141 format!("Range start must be an integer, but found {start}"),
3142 vec![self.into()],
3143 )));
3144 };
3145 let Some(end) = crate::try_f64_to_i64(end) else {
3146 return Err(KclError::new_semantic(KclErrorDetails::new(
3147 format!("Range end must be an integer, but found {end}"),
3148 vec![self.into()],
3149 )));
3150 };
3151
3152 if end < start {
3153 return Err(KclError::new_semantic(KclErrorDetails::new(
3154 format!("Range start is greater than range end: {start} .. {end}"),
3155 vec![self.into()],
3156 )));
3157 }
3158
3159 let range: Vec<_> = if self.end_inclusive {
3160 (start..=end).collect()
3161 } else {
3162 (start..end).collect()
3163 };
3164
3165 let meta = vec![Metadata {
3166 source_range: self.into(),
3167 }];
3168
3169 Ok(KclValue::HomArray {
3170 value: range
3171 .into_iter()
3172 .map(|num| KclValue::Number {
3173 value: num as f64,
3174 ty,
3175 meta: meta.clone(),
3176 })
3177 .collect(),
3178 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
3179 }
3180 .continue_())
3181 }
3182}
3183
3184impl Node<ObjectExpression> {
3185 #[async_recursion]
3186 pub(super) async fn execute(
3187 &self,
3188 exec_state: &mut ExecState,
3189 ctx: &ExecutorContext,
3190 ) -> Result<KclValueControlFlow, KclError> {
3191 let mut object = HashMap::with_capacity(self.properties.len());
3192 for property in &self.properties {
3193 let metadata = Metadata::from(&property.value);
3194 let result = ctx
3195 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
3196 .await?;
3197 let result = control_continue!(result);
3198 object.insert(property.key.name.clone(), result);
3199 }
3200
3201 Ok(KclValue::Object {
3202 value: object,
3203 meta: vec![Metadata {
3204 source_range: self.into(),
3205 }],
3206 constrainable: false,
3207 }
3208 .continue_())
3209 }
3210}
3211
3212fn article_for<S: AsRef<str>>(s: S) -> &'static str {
3213 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
3215 "an"
3216 } else {
3217 "a"
3218 }
3219}
3220
3221fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
3222 v.as_ty_f64().ok_or_else(|| {
3223 let actual_type = v.human_friendly_type();
3224 KclError::new_semantic(KclErrorDetails::new(
3225 format!("Expected a number, but found {actual_type}",),
3226 vec![source_range],
3227 ))
3228 })
3229}
3230
3231impl Node<IfExpression> {
3232 #[async_recursion]
3233 pub(super) async fn get_result(
3234 &self,
3235 exec_state: &mut ExecState,
3236 ctx: &ExecutorContext,
3237 ) -> Result<KclValueControlFlow, KclError> {
3238 let cond_value = ctx
3240 .execute_expr(
3241 &self.cond,
3242 exec_state,
3243 &Metadata::from(self),
3244 &[],
3245 StatementKind::Expression,
3246 )
3247 .await?;
3248 let cond_value = control_continue!(cond_value);
3249 if cond_value.get_bool()? {
3250 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
3251 return Ok(block_result.unwrap());
3255 }
3256
3257 for else_if in &self.else_ifs {
3259 let cond_value = ctx
3260 .execute_expr(
3261 &else_if.cond,
3262 exec_state,
3263 &Metadata::from(self),
3264 &[],
3265 StatementKind::Expression,
3266 )
3267 .await?;
3268 let cond_value = control_continue!(cond_value);
3269 if cond_value.get_bool()? {
3270 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
3271 return Ok(block_result.unwrap());
3275 }
3276 }
3277
3278 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
3280 .await
3281 .map(|expr| expr.unwrap())
3282 }
3283}
3284
3285#[derive(Debug)]
3286enum Property {
3287 UInt(usize),
3288 String(String),
3289}
3290
3291impl Property {
3292 #[allow(clippy::too_many_arguments)]
3293 async fn try_from<'a>(
3294 computed: bool,
3295 value: Expr,
3296 exec_state: &mut ExecState,
3297 sr: SourceRange,
3298 ctx: &ExecutorContext,
3299 metadata: &Metadata,
3300 annotations: &[Node<Annotation>],
3301 statement_kind: StatementKind<'a>,
3302 ) -> Result<Self, KclError> {
3303 let property_sr = vec![sr];
3304 if !computed {
3305 let Expr::Name(identifier) = value else {
3306 return Err(KclError::new_semantic(KclErrorDetails::new(
3308 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
3309 .to_owned(),
3310 property_sr,
3311 )));
3312 };
3313 return Ok(Property::String(identifier.to_string()));
3314 }
3315
3316 let prop_value = ctx
3317 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
3318 .await?;
3319 let prop_value = match prop_value.control {
3320 ControlFlowKind::Continue => prop_value.into_value(),
3321 ControlFlowKind::Exit => {
3322 let message = "Early return inside array brackets is currently not supported".to_owned();
3323 debug_assert!(false, "{}", &message);
3324 return Err(internal_err(message, sr));
3325 }
3326 };
3327 match prop_value {
3328 KclValue::Number { value, ty, meta: _ } => {
3329 if !matches!(
3330 ty,
3331 NumericType::Unknown
3332 | NumericType::Default { .. }
3333 | NumericType::Known(crate::exec::UnitType::Count)
3334 ) {
3335 return Err(KclError::new_semantic(KclErrorDetails::new(
3336 format!(
3337 "{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"
3338 ),
3339 property_sr,
3340 )));
3341 }
3342 if let Some(x) = crate::try_f64_to_usize(value) {
3343 Ok(Property::UInt(x))
3344 } else {
3345 Err(KclError::new_semantic(KclErrorDetails::new(
3346 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
3347 property_sr,
3348 )))
3349 }
3350 }
3351 _ => Err(KclError::new_semantic(KclErrorDetails::new(
3352 "Only numbers (>= 0) can be indexes".to_owned(),
3353 vec![sr],
3354 ))),
3355 }
3356 }
3357}
3358
3359impl Property {
3360 fn type_name(&self) -> &'static str {
3361 match self {
3362 Property::UInt(_) => "number",
3363 Property::String(_) => "string",
3364 }
3365 }
3366}
3367
3368impl Node<PipeExpression> {
3369 #[async_recursion]
3370 pub(super) async fn get_result(
3371 &self,
3372 exec_state: &mut ExecState,
3373 ctx: &ExecutorContext,
3374 ) -> Result<KclValueControlFlow, KclError> {
3375 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
3376 }
3377}
3378
3379#[cfg(test)]
3380mod test {
3381 use std::sync::Arc;
3382
3383 use tokio::io::AsyncWriteExt;
3384
3385 use super::*;
3386 use crate::{
3387 ExecutorSettings,
3388 errors::Severity,
3389 exec::UnitType,
3390 execution::{ContextType, parse_execute},
3391 };
3392
3393 #[tokio::test(flavor = "multi_thread")]
3394 async fn ascription() {
3395 let program = r#"
3396a = 42: number
3397b = a: number
3398p = {
3399 origin = { x = 0, y = 0, z = 0 },
3400 xAxis = { x = 1, y = 0, z = 0 },
3401 yAxis = { x = 0, y = 1, z = 0 },
3402 zAxis = { x = 0, y = 0, z = 1 }
3403}: Plane
3404arr1 = [42]: [number(cm)]
3405"#;
3406
3407 let result = parse_execute(program).await.unwrap();
3408 let mem = result.exec_state.stack();
3409 assert!(matches!(
3410 mem.memory
3411 .get_from("p", result.mem_env, SourceRange::default(), 0)
3412 .unwrap(),
3413 KclValue::Plane { .. }
3414 ));
3415 let arr1 = mem
3416 .memory
3417 .get_from("arr1", result.mem_env, SourceRange::default(), 0)
3418 .unwrap();
3419 if let KclValue::HomArray { value, ty } = arr1 {
3420 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
3421 assert_eq!(
3422 *ty,
3423 RuntimeType::known_length(kittycad_modeling_cmds::units::UnitLength::Centimeters)
3424 );
3425 if let KclValue::Number { value, ty, .. } = &value[0] {
3427 assert_eq!(*value, 42.0);
3429 assert_eq!(
3430 *ty,
3431 NumericType::Known(UnitType::Length(kittycad_modeling_cmds::units::UnitLength::Centimeters))
3432 );
3433 } else {
3434 panic!("Expected a number; found {:?}", value[0]);
3435 }
3436 } else {
3437 panic!("Expected HomArray; found {arr1:?}");
3438 }
3439
3440 let program = r#"
3441a = 42: string
3442"#;
3443 let result = parse_execute(program).await;
3444 let err = result.unwrap_err();
3445 assert!(
3446 err.to_string()
3447 .contains("could not coerce a number (with type `number`) to type `string`"),
3448 "Expected error but found {err:?}"
3449 );
3450
3451 let program = r#"
3452a = 42: Plane
3453"#;
3454 let result = parse_execute(program).await;
3455 let err = result.unwrap_err();
3456 assert!(
3457 err.to_string()
3458 .contains("could not coerce a number (with type `number`) to type `Plane`"),
3459 "Expected error but found {err:?}"
3460 );
3461
3462 let program = r#"
3463arr = [0]: [string]
3464"#;
3465 let result = parse_execute(program).await;
3466 let err = result.unwrap_err();
3467 assert!(
3468 err.to_string().contains(
3469 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
3470 ),
3471 "Expected error but found {err:?}"
3472 );
3473
3474 let program = r#"
3475mixedArr = [0, "a"]: [number(mm)]
3476"#;
3477 let result = parse_execute(program).await;
3478 let err = result.unwrap_err();
3479 assert!(
3480 err.to_string().contains(
3481 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3482 ),
3483 "Expected error but found {err:?}"
3484 );
3485
3486 let program = r#"
3487mixedArr = [0, "a"]: [mm]
3488"#;
3489 let result = parse_execute(program).await;
3490 let err = result.unwrap_err();
3491 assert!(
3492 err.to_string().contains(
3493 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
3494 ),
3495 "Expected error but found {err:?}"
3496 );
3497 }
3498
3499 #[tokio::test(flavor = "multi_thread")]
3500 async fn neg_plane() {
3501 let program = r#"
3502p = {
3503 origin = { x = 0, y = 0, z = 0 },
3504 xAxis = { x = 1, y = 0, z = 0 },
3505 yAxis = { x = 0, y = 1, z = 0 },
3506}: Plane
3507p2 = -p
3508"#;
3509
3510 let result = parse_execute(program).await.unwrap();
3511 let mem = result.exec_state.stack();
3512 match mem
3513 .memory
3514 .get_from("p2", result.mem_env, SourceRange::default(), 0)
3515 .unwrap()
3516 {
3517 KclValue::Plane { value } => {
3518 assert_eq!(value.info.x_axis.x, -1.0);
3519 assert_eq!(value.info.x_axis.y, 0.0);
3520 assert_eq!(value.info.x_axis.z, 0.0);
3521 }
3522 _ => unreachable!(),
3523 }
3524 }
3525
3526 #[tokio::test(flavor = "multi_thread")]
3527 async fn multiple_returns() {
3528 let program = r#"fn foo() {
3529 return 0
3530 return 42
3531}
3532
3533a = foo()
3534"#;
3535
3536 let result = parse_execute(program).await;
3537 assert!(result.unwrap_err().to_string().contains("return"));
3538 }
3539
3540 #[tokio::test(flavor = "multi_thread")]
3541 async fn load_all_modules() {
3542 let program_a_kcl = r#"
3544export a = 1
3545"#;
3546 let program_b_kcl = r#"
3548import a from 'a.kcl'
3549
3550export b = a + 1
3551"#;
3552 let program_c_kcl = r#"
3554import a from 'a.kcl'
3555
3556export c = a + 2
3557"#;
3558
3559 let main_kcl = r#"
3561import b from 'b.kcl'
3562import c from 'c.kcl'
3563
3564d = b + c
3565"#;
3566
3567 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
3568 .parse_errs_as_err()
3569 .unwrap();
3570
3571 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
3572
3573 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
3574 .await
3575 .unwrap()
3576 .write_all(main_kcl.as_bytes())
3577 .await
3578 .unwrap();
3579
3580 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
3581 .await
3582 .unwrap()
3583 .write_all(program_a_kcl.as_bytes())
3584 .await
3585 .unwrap();
3586
3587 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
3588 .await
3589 .unwrap()
3590 .write_all(program_b_kcl.as_bytes())
3591 .await
3592 .unwrap();
3593
3594 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
3595 .await
3596 .unwrap()
3597 .write_all(program_c_kcl.as_bytes())
3598 .await
3599 .unwrap();
3600
3601 let exec_ctxt = ExecutorContext {
3602 engine: Arc::new(Box::new(
3603 crate::engine::conn_mock::EngineConnection::new()
3604 .map_err(|err| {
3605 internal_err(
3606 format!("Failed to create mock engine connection: {err}"),
3607 SourceRange::default(),
3608 )
3609 })
3610 .unwrap(),
3611 )),
3612 fs: Arc::new(crate::fs::FileManager::new()),
3613 settings: ExecutorSettings {
3614 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
3615 ..Default::default()
3616 },
3617 context_type: ContextType::Mock,
3618 };
3619 let mut exec_state = ExecState::new(&exec_ctxt);
3620
3621 exec_ctxt
3622 .run(
3623 &crate::Program {
3624 ast: main.clone(),
3625 original_file_contents: "".to_owned(),
3626 },
3627 &mut exec_state,
3628 )
3629 .await
3630 .unwrap();
3631 }
3632
3633 #[tokio::test(flavor = "multi_thread")]
3634 async fn user_coercion() {
3635 let program = r#"fn foo(x: Axis2d) {
3636 return 0
3637}
3638
3639foo(x = { direction = [0, 0], origin = [0, 0]})
3640"#;
3641
3642 parse_execute(program).await.unwrap();
3643
3644 let program = r#"fn foo(x: Axis3d) {
3645 return 0
3646}
3647
3648foo(x = { direction = [0, 0], origin = [0, 0]})
3649"#;
3650
3651 parse_execute(program).await.unwrap_err();
3652 }
3653
3654 #[tokio::test(flavor = "multi_thread")]
3655 async fn coerce_return() {
3656 let program = r#"fn foo(): number(mm) {
3657 return 42
3658}
3659
3660a = foo()
3661"#;
3662
3663 parse_execute(program).await.unwrap();
3664
3665 let program = r#"fn foo(): mm {
3666 return 42
3667}
3668
3669a = foo()
3670"#;
3671
3672 parse_execute(program).await.unwrap();
3673
3674 let program = r#"fn foo(): number(mm) {
3675 return { bar: 42 }
3676}
3677
3678a = foo()
3679"#;
3680
3681 parse_execute(program).await.unwrap_err();
3682
3683 let program = r#"fn foo(): mm {
3684 return { bar: 42 }
3685}
3686
3687a = foo()
3688"#;
3689
3690 parse_execute(program).await.unwrap_err();
3691 }
3692
3693 #[tokio::test(flavor = "multi_thread")]
3694 async fn test_sensible_error_when_missing_equals_in_kwarg() {
3695 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)"]
3696 .into_iter()
3697 .enumerate()
3698 {
3699 let program = format!(
3700 "fn foo() {{ return 0 }}
3701z = 0
3702fn f(x, y, z) {{ return 0 }}
3703{call}"
3704 );
3705 let err = parse_execute(&program).await.unwrap_err();
3706 let msg = err.message();
3707 assert!(
3708 msg.contains("This argument needs a label, but it doesn't have one"),
3709 "failed test {i}: {msg}"
3710 );
3711 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
3712 if i == 0 {
3713 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
3714 }
3715 }
3716 }
3717
3718 #[tokio::test(flavor = "multi_thread")]
3719 async fn default_param_for_unlabeled() {
3720 let ast = r#"fn myExtrude(@sk, length) {
3723 return extrude(sk, length)
3724}
3725sketch001 = startSketchOn(XY)
3726 |> circle(center = [0, 0], radius = 93.75)
3727 |> myExtrude(length = 40)
3728"#;
3729
3730 parse_execute(ast).await.unwrap();
3731 }
3732
3733 #[tokio::test(flavor = "multi_thread")]
3734 async fn dont_use_unlabelled_as_input() {
3735 let ast = r#"length = 10
3737startSketchOn(XY)
3738 |> circle(center = [0, 0], radius = 93.75)
3739 |> extrude(length)
3740"#;
3741
3742 parse_execute(ast).await.unwrap();
3743 }
3744
3745 #[tokio::test(flavor = "multi_thread")]
3746 async fn ascription_in_binop() {
3747 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
3748 parse_execute(ast).await.unwrap();
3749
3750 let ast = r#"foo = tan(0): rad - 4deg"#;
3751 parse_execute(ast).await.unwrap();
3752 }
3753
3754 #[tokio::test(flavor = "multi_thread")]
3755 async fn neg_sqrt() {
3756 let ast = r#"bad = sqrt(-2)"#;
3757
3758 let e = parse_execute(ast).await.unwrap_err();
3759 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
3761 }
3762
3763 #[tokio::test(flavor = "multi_thread")]
3764 async fn non_array_fns() {
3765 let ast = r#"push(1, item = 2)
3766pop(1)
3767map(1, f = fn(@x) { return x + 1 })
3768reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
3769
3770 parse_execute(ast).await.unwrap();
3771 }
3772
3773 #[tokio::test(flavor = "multi_thread")]
3774 async fn non_array_indexing() {
3775 let good = r#"a = 42
3776good = a[0]
3777"#;
3778 let result = parse_execute(good).await.unwrap();
3779 let mem = result.exec_state.stack();
3780 let num = mem
3781 .memory
3782 .get_from("good", result.mem_env, SourceRange::default(), 0)
3783 .unwrap()
3784 .as_ty_f64()
3785 .unwrap();
3786 assert_eq!(num.n, 42.0);
3787
3788 let bad = r#"a = 42
3789bad = a[1]
3790"#;
3791
3792 parse_execute(bad).await.unwrap_err();
3793 }
3794
3795 #[tokio::test(flavor = "multi_thread")]
3796 async fn coerce_unknown_to_length() {
3797 let ast = r#"x = 2mm * 2mm
3798y = x: number(Length)"#;
3799 let e = parse_execute(ast).await.unwrap_err();
3800 assert!(
3801 e.message().contains("could not coerce"),
3802 "Error message: '{}'",
3803 e.message()
3804 );
3805
3806 let ast = r#"x = 2mm
3807y = x: number(Length)"#;
3808 let result = parse_execute(ast).await.unwrap();
3809 let mem = result.exec_state.stack();
3810 let num = mem
3811 .memory
3812 .get_from("y", result.mem_env, SourceRange::default(), 0)
3813 .unwrap()
3814 .as_ty_f64()
3815 .unwrap();
3816 assert_eq!(num.n, 2.0);
3817 assert_eq!(num.ty, NumericType::mm());
3818 }
3819
3820 #[tokio::test(flavor = "multi_thread")]
3821 async fn one_warning_unknown() {
3822 let ast = r#"
3823// Should warn once
3824a = PI * 2
3825// Should warn once
3826b = (PI * 2) / 3
3827// Should not warn
3828c = ((PI * 2) / 3): number(deg)
3829"#;
3830
3831 let result = parse_execute(ast).await.unwrap();
3832 assert_eq!(result.exec_state.errors().len(), 2);
3833 }
3834
3835 #[tokio::test(flavor = "multi_thread")]
3836 async fn non_count_indexing() {
3837 let ast = r#"x = [0, 0]
3838y = x[1mm]
3839"#;
3840 parse_execute(ast).await.unwrap_err();
3841
3842 let ast = r#"x = [0, 0]
3843y = 1deg
3844z = x[y]
3845"#;
3846 parse_execute(ast).await.unwrap_err();
3847
3848 let ast = r#"x = [0, 0]
3849y = x[0mm + 1]
3850"#;
3851 parse_execute(ast).await.unwrap_err();
3852 }
3853
3854 #[tokio::test(flavor = "multi_thread")]
3855 async fn getting_property_of_plane() {
3856 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
3857 parse_execute(&ast).await.unwrap();
3858 }
3859
3860 #[cfg(feature = "artifact-graph")]
3861 #[tokio::test(flavor = "multi_thread")]
3862 async fn no_artifacts_from_within_hole_call() {
3863 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
3868 let out = parse_execute(&ast).await.unwrap();
3869
3870 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3872
3873 let expected = 5;
3877 assert_eq!(
3878 actual_operations.len(),
3879 expected,
3880 "expected {expected} operations, received {}:\n{actual_operations:#?}",
3881 actual_operations.len(),
3882 );
3883 }
3884
3885 #[cfg(feature = "artifact-graph")]
3886 #[tokio::test(flavor = "multi_thread")]
3887 async fn feature_tree_annotation_on_user_defined_kcl() {
3888 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
3891 let out = parse_execute(&ast).await.unwrap();
3892
3893 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3895
3896 let expected = 0;
3897 assert_eq!(
3898 actual_operations.len(),
3899 expected,
3900 "expected {expected} operations, received {}:\n{actual_operations:#?}",
3901 actual_operations.len(),
3902 );
3903 }
3904
3905 #[cfg(feature = "artifact-graph")]
3906 #[tokio::test(flavor = "multi_thread")]
3907 async fn no_feature_tree_annotation_on_user_defined_kcl() {
3908 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
3911 let out = parse_execute(&ast).await.unwrap();
3912
3913 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
3915
3916 let expected = 2;
3917 assert_eq!(
3918 actual_operations.len(),
3919 expected,
3920 "expected {expected} operations, received {}:\n{actual_operations:#?}",
3921 actual_operations.len(),
3922 );
3923 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
3924 assert!(matches!(actual_operations[1], Operation::GroupEnd));
3925 }
3926
3927 #[tokio::test(flavor = "multi_thread")]
3928 async fn custom_warning() {
3929 let warn = r#"
3930a = PI * 2
3931"#;
3932 let result = parse_execute(warn).await.unwrap();
3933 assert_eq!(result.exec_state.errors().len(), 1);
3934 assert_eq!(result.exec_state.errors()[0].severity, Severity::Warning);
3935
3936 let allow = r#"
3937@warnings(allow = unknownUnits)
3938a = PI * 2
3939"#;
3940 let result = parse_execute(allow).await.unwrap();
3941 assert_eq!(result.exec_state.errors().len(), 0);
3942
3943 let deny = r#"
3944@warnings(deny = [unknownUnits])
3945a = PI * 2
3946"#;
3947 let result = parse_execute(deny).await.unwrap();
3948 assert_eq!(result.exec_state.errors().len(), 1);
3949 assert_eq!(result.exec_state.errors()[0].severity, Severity::Error);
3950 }
3951
3952 #[tokio::test(flavor = "multi_thread")]
3953 async fn cannot_solid_extrude_an_open_profile() {
3954 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
3957 let program = crate::Program::parse_no_errs(&code).expect("should parse");
3958 let exec_ctxt = ExecutorContext::new_mock(None).await;
3959 let mut exec_state = ExecState::new(&exec_ctxt);
3960
3961 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
3962 assert!(matches!(err, KclError::Semantic { .. }));
3963 exec_ctxt.close().await;
3964 }
3965}