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