1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use ahash::AHashMap;
6use anyhow::Result;
7use indexmap::IndexMap;
8use kcl_api::UnitAngle;
9use kcl_api::UnitLength;
10use serde::Deserialize;
11use serde::Serialize;
12use uuid::Uuid;
13
14use crate::CompilationIssue;
15use crate::ExecutorContext;
16use crate::KclErrorWithOutputs;
17use crate::MockConfig;
18use crate::NodePath;
19use crate::SegmentDragAnchor;
20use crate::SourceRange;
21use crate::collections::AhashIndexSet;
22use crate::engine::engine_manager::EngineManager;
23use crate::errors::KclError;
24use crate::errors::KclErrorDetails;
25use crate::errors::Severity;
26use crate::exec::DefaultPlanes;
27use crate::execution::Artifact;
28use crate::execution::ArtifactCommand;
29use crate::execution::ArtifactGraph;
30use crate::execution::ArtifactId;
31use crate::execution::EnvironmentRef;
32use crate::execution::ExecOutcome;
33use crate::execution::ExecutorSettings;
34use crate::execution::KclValue;
35use crate::execution::KclValueView;
36use crate::execution::OperationCallbackArgs;
37use crate::execution::OperationsByModule;
38use crate::execution::ProgramLookup;
39use crate::execution::SketchVarId;
40use crate::execution::UnsolvedSegment;
41use crate::execution::annotations;
42use crate::execution::cad_op::Operation;
43use crate::execution::id_generator::IdGenerator;
44#[cfg(test)]
45use crate::execution::memory::MemoryBackendKind;
46use crate::execution::memory::ProgramMemory;
47use crate::execution::memory::Stack;
48use crate::execution::sketch_solve::Solved;
49use crate::execution::types::NumericType;
50use crate::front::Number;
51use crate::front::Object;
52use crate::front::ObjectId;
53use crate::front::ObjectKind;
54use crate::id::IncIdGenerator;
55use crate::modules::ModuleId;
56use crate::modules::ModuleInfo;
57use crate::modules::ModuleLoader;
58use crate::modules::ModulePath;
59use crate::modules::ModuleRepr;
60use crate::modules::ModuleSource;
61use crate::parsing::ast::types::Annotation;
62use crate::parsing::ast::types::NodeRef;
63use crate::parsing::ast::types::TagNode;
64
65#[derive(Debug, Clone)]
67pub struct ExecState {
68 pub(super) execution_callbacks: Option<std::sync::Arc<dyn crate::execution::ExecutionCallbacks>>,
69 pub(super) global: GlobalState,
70 pub(super) mod_local: ModuleState,
71}
72
73pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
74
75#[derive(Debug, Clone)]
76pub(super) struct GlobalState {
77 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
79 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
81 pub module_infos: ModuleInfoMap,
83 pub mod_loader: ModuleLoader,
85 pub issues: Vec<CompilationIssue>,
87 pub artifacts: ArtifactState,
89 pub root_module_artifacts: ModuleArtifactState,
91 pub segment_ids_edited: AhashIndexSet<ObjectId>,
93 pub drag_anchors: Vec<SegmentDragAnchor>,
95 pub sketch_mode: bool,
100}
101
102impl GlobalState {
103 pub(crate) fn operations_by_module(&self) -> OperationsByModule {
104 let mut operations = OperationsByModule::default();
105 operations.insert(ModuleId::default(), self.root_module_artifacts.operations.clone());
106
107 for (module_id, module_info) in &self.module_infos {
108 match &module_info.repr {
109 ModuleRepr::Root => {}
110 ModuleRepr::Kcl(_, Some(outcome)) => {
111 operations.insert(*module_id, outcome.artifacts.operations.clone());
112 }
113 ModuleRepr::Foreign(_, Some((_, artifacts))) => {
114 operations.insert(*module_id, artifacts.operations.clone());
115 }
116 ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
117 }
118 }
119
120 operations
121 }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
125pub(crate) enum ConstraintKey {
126 LineCircle([usize; 10]),
127 CircleCircle([usize; 12]),
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub(crate) enum TangencyMode {
132 LineCircle(ezpz::LineSide),
133 CircleCircle(ezpz::CircleSide),
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub(crate) enum ConstraintState {
138 Tangency(TangencyMode),
139}
140
141#[derive(Debug, Clone, Default)]
142pub(super) struct ArtifactState {
143 pub artifacts: IndexMap<ArtifactId, Artifact>,
146 pub graph: ArtifactGraph,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
152#[ts(export)]
153#[serde(rename_all = "camelCase")]
154pub enum EdgeRefactorStdlibFn {
155 GetOppositeEdge,
156 GetNextAdjacentEdge,
157 GetPreviousAdjacentEdge,
158 GetCommonEdge,
159 EdgeId,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
164#[ts(export)]
165#[serde(rename_all = "camelCase")]
166pub struct EdgeRefactorMeta {
167 pub edge_id: Uuid,
168 pub face_ids: [Uuid; 2],
169 #[serde(default, skip_serializing_if = "Vec::is_empty")]
170 pub end_face_ids: Vec<Uuid>,
171 pub source_range: SourceRange,
172 pub stdlib_fn: EdgeRefactorStdlibFn,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
178pub(crate) struct PendingEdgeRefactorMeta {
179 pub edge_id: Uuid,
180 pub source_range: SourceRange,
181 pub stdlib_fn: EdgeRefactorStdlibFn,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
186#[ts(export)]
187#[serde(rename_all = "camelCase")]
188pub struct DirectTagFilletTagEntry {
189 pub tag_identifier: String,
190 pub edge_id: Uuid,
191 pub face_ids: [Uuid; 2],
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
196#[ts(export)]
197#[serde(rename_all = "camelCase")]
198pub struct DirectTagFilletMeta {
199 pub call_source_range: SourceRange,
200 pub tags: Vec<DirectTagFilletTagEntry>,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
205#[ts(export)]
206#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
207pub enum RefactorMetadata {
208 EdgeRefactor(Box<EdgeRefactorMeta>),
209 DirectTagFillet(DirectTagFilletMeta),
210}
211
212#[derive(Debug, Clone, Default, PartialEq, Serialize)]
214pub struct ModuleArtifactState {
215 pub artifacts: IndexMap<ArtifactId, Artifact>,
217 #[serde(skip)]
220 pub unprocessed_commands: Vec<ArtifactCommand>,
221 pub commands: Vec<ArtifactCommand>,
223 #[cfg(feature = "snapshot-engine-responses")]
225 pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
226 pub operations: Vec<Operation>,
229 pub object_id_generator: IncIdGenerator<usize>,
231 pub scene_objects: Vec<Object>,
233 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
236 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
238 pub var_solutions: Vec<(SourceRange, Option<NodePath>, Number)>,
240 pub refactor_metadata: Vec<RefactorMetadata>,
242 #[serde(skip)]
245 pub(crate) pending_edge_refactor_metadata: Vec<PendingEdgeRefactorMeta>,
246}
247
248#[derive(Debug, Clone)]
249pub(super) struct ModuleState {
250 pub module_id: ModuleId,
252 pub id_generator: IdGenerator,
254 pub stack: Stack,
255 pub(super) call_stack_size: usize,
259 pub pipe_value: Option<KclValue>,
262 pub being_declared: Option<String>,
266 pub sketch_block: Option<SketchBlockState>,
268 pub inside_stdlib: bool,
271 pub stdlib_entry_source_range: Option<SourceRange>,
273 pub module_exports: Vec<String>,
275 pub settings: MetaSettings,
277 pub sketch_mode: bool,
280 pub freedom_analysis: bool,
284 pub(super) explicit_length_units: bool,
285 pub(super) path: ModulePath,
286 pub artifacts: ModuleArtifactState,
288 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
292
293 pub(super) allowed_warnings: Vec<&'static str>,
294 pub(super) denied_warnings: Vec<&'static str>,
295
296 pub(super) consumed_solids: AHashMap<ConsumedSolidKey, ConsumedSolidInfo>,
301 pub(super) consumed_solid_ids: AHashMap<Uuid, ConsumedSolidInfo>,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
311pub(crate) struct ConsumedSolidKey {
312 engine_id: Uuid,
314 instance_id: Uuid,
317}
318
319impl ConsumedSolidKey {
320 pub(crate) fn new(engine_id: Uuid, instance_id: Uuid) -> Self {
321 Self { engine_id, instance_id }
322 }
323
324 pub(crate) fn engine_id(&self) -> Uuid {
325 self.engine_id
326 }
327
328 pub(crate) fn instance_id(&self) -> Uuid {
329 self.instance_id
330 }
331}
332
333#[derive(Debug, Clone)]
337pub(crate) struct ConsumedSolidInfo {
338 operation: ConsumedSolidOperation,
340 suggested_replacement_key: Option<ConsumedSolidKey>,
344 returned_solid_keys: Vec<ConsumedSolidKey>,
347}
348
349impl ConsumedSolidInfo {
350 pub(crate) fn new(operation: ConsumedSolidOperation, returned_solid_keys: Vec<ConsumedSolidKey>) -> Self {
351 Self {
352 operation,
353 suggested_replacement_key: returned_solid_keys.first().copied(),
354 returned_solid_keys,
355 }
356 }
357
358 pub(crate) fn operation(&self) -> ConsumedSolidOperation {
359 self.operation
360 }
361
362 pub(crate) fn suggested_replacement_key(&self) -> Option<ConsumedSolidKey> {
363 self.suggested_replacement_key
364 }
365
366 pub(crate) fn should_report_reused_engine_id_as_consumed(&self, key: ConsumedSolidKey) -> bool {
367 !self.returned_solid_keys.contains(&key)
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub(crate) enum ConsumedSolidOperation {
373 Union,
374 Intersect,
375 Subtract,
376 Split,
377 JoinSurfaces,
378}
379
380impl ConsumedSolidOperation {
381 pub(crate) fn indefinite_article(self) -> &'static str {
382 match self {
383 Self::Intersect => "an",
384 Self::Union | Self::Subtract | Self::Split | Self::JoinSurfaces => "a",
385 }
386 }
387}
388
389impl std::fmt::Display for ConsumedSolidOperation {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 match self {
392 Self::Union => f.write_str("union"),
393 Self::Intersect => f.write_str("intersect"),
394 Self::Subtract => f.write_str("subtract"),
395 Self::Split => f.write_str("split"),
396 Self::JoinSurfaces => f.write_str("joinSurfaces"),
397 }
398 }
399}
400
401#[derive(Debug, Clone, Default)]
402pub(crate) struct SketchBlockState {
403 pub sketch_vars: Vec<KclValue>,
404 pub sketch_id: Option<ObjectId>,
405 pub sketch_constraints: Vec<ObjectId>,
406 pub solver_constraints: Vec<ezpz::Constraint>,
407 pub solver_optional_constraints: Vec<ezpz::Constraint>,
408 pub needed_by_engine: Vec<UnsolvedSegment>,
409 pub segment_tags: IndexMap<ObjectId, TagNode>,
410}
411
412impl ExecState {
413 pub fn new(exec_context: &super::ExecutorContext) -> Self {
414 ExecState {
415 execution_callbacks: exec_context.execution_callbacks.clone(),
416 global: GlobalState::new(&exec_context.settings, Default::default()),
417 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
418 }
419 }
420
421 #[cfg(test)]
422 pub(crate) fn new_with_memory_backend(exec_context: &super::ExecutorContext, backend: MemoryBackendKind) -> Self {
423 ExecState {
424 execution_callbacks: exec_context.execution_callbacks.clone(),
425 global: GlobalState::new(&exec_context.settings, Default::default()),
426 mod_local: ModuleState::new(
427 ModulePath::Main,
428 ProgramMemory::new_with_backend(backend),
429 Default::default(),
430 false,
431 true,
432 ),
433 }
434 }
435
436 pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
437 let segment_ids_edited = mock_config.segment_ids_edited.clone();
438 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
439 global.drag_anchors = mock_config.drag_anchors.clone();
440 global.sketch_mode = mock_config.sketch_block_id.is_some();
441 ExecState {
442 execution_callbacks: exec_context.execution_callbacks.clone(),
443 global,
444 mod_local: ModuleState::new(
445 ModulePath::Main,
446 ProgramMemory::new(),
447 Default::default(),
448 mock_config.sketch_block_id.is_some(),
449 mock_config.freedom_analysis,
450 ),
451 }
452 }
453
454 #[cfg(test)]
455 pub(crate) fn new_mock_with_memory_backend(
456 exec_context: &super::ExecutorContext,
457 mock_config: &MockConfig,
458 backend: MemoryBackendKind,
459 ) -> Self {
460 let segment_ids_edited = mock_config.segment_ids_edited.clone();
461 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
462 global.drag_anchors = mock_config.drag_anchors.clone();
463 global.sketch_mode = mock_config.sketch_block_id.is_some();
464 ExecState {
465 execution_callbacks: exec_context.execution_callbacks.clone(),
466 global,
467 mod_local: ModuleState::new(
468 ModulePath::Main,
469 ProgramMemory::new_with_backend(backend),
470 Default::default(),
471 mock_config.sketch_block_id.is_some(),
472 mock_config.freedom_analysis,
473 ),
474 }
475 }
476
477 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
478 let global = GlobalState::new(&exec_context.settings, Default::default());
479
480 *self = ExecState {
481 execution_callbacks: exec_context.execution_callbacks.clone(),
482 global,
483 mod_local: ModuleState::new(
484 self.mod_local.path.clone(),
485 ProgramMemory::new(),
486 Default::default(),
487 false,
488 true,
489 ),
490 };
491 }
492
493 pub fn err(&mut self, e: CompilationIssue) {
495 self.global.issues.push(e);
496 }
497
498 pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
500 debug_assert!(annotations::WARN_VALUES.contains(&name));
501
502 if self.mod_local.allowed_warnings.contains(&name) {
503 return;
504 }
505
506 if self.mod_local.denied_warnings.contains(&name) {
507 e.severity = Severity::Error;
508 } else {
509 e.severity = Severity::Warning;
510 }
511
512 self.global.issues.push(e);
513 }
514
515 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
516 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
517 return;
518 };
519 let error = CompilationIssue {
520 source_range,
521 message: format!("Use of {feature_name} is experimental and may change or be removed."),
522 suggestion: None,
523 severity,
524 tag: crate::errors::Tag::None,
525 };
526
527 self.global.issues.push(error);
528 }
529
530 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
531 self.global.issues = std::mem::take(&mut self.global.issues)
532 .into_iter()
533 .filter(|e| {
534 e.severity != Severity::Warning
535 || !source_range.contains_range(&e.source_range)
536 || e.tag != crate::errors::Tag::UnknownNumericUnits
537 })
538 .collect();
539 }
540
541 pub fn issues(&self) -> &[CompilationIssue] {
542 &self.global.issues
543 }
544
545 pub async fn into_exec_outcome(
549 self,
550 main_ref: EnvironmentRef,
551 ctx: &ExecutorContext,
552 ) -> Result<ExecOutcome, KclError> {
553 let variables = self
556 .mod_local
557 .variables(main_ref)?
558 .into_iter()
559 .map(|(key, value)| (key, KclValueView::from(value)))
560 .collect();
561 Ok(ExecOutcome {
562 variables,
563 filenames: self.global.filenames(),
564 operations: self.global.operations_by_module(),
565 artifact_graph: self.global.artifacts.graph,
566 scene_objects: self.global.root_module_artifacts.scene_objects,
567 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
568 var_solutions: self.global.root_module_artifacts.var_solutions,
569 refactor_metadata: self.global.root_module_artifacts.refactor_metadata.clone(),
570 issues: self.global.issues,
571 default_planes: ctx.engine.get_default_planes().read().await.clone(),
572 })
573 }
574
575 #[cfg(feature = "snapshot-engine-responses")]
576 pub(crate) fn take_root_module_responses(
577 &mut self,
578 ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
579 std::mem::take(&mut self.global.root_module_artifacts.responses)
580 }
581
582 pub(crate) fn stack(&self) -> &Stack {
583 &self.mod_local.stack
584 }
585
586 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
587 &mut self.mod_local.stack
588 }
589
590 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
593 if self.mod_local.call_stack_size >= 50 {
596 return Err(KclError::MaxCallStack {
597 details: KclErrorDetails::new("maximum call stack size exceeded".to_owned(), vec![range]),
598 });
599 }
600 self.mod_local.call_stack_size += 1;
601 Ok(())
602 }
603
604 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
607 if self.mod_local.call_stack_size == 0 {
609 let message = "call stack size below zero".to_owned();
610 debug_assert!(false, "{message}");
611 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
612 }
613 self.mod_local.call_stack_size -= 1;
614 Ok(())
615 }
616
617 pub(crate) fn sketch_mode(&self) -> bool {
622 self.mod_local.sketch_mode
623 && match &self.mod_local.path {
624 ModulePath::Main => true,
625 ModulePath::Local { .. } => true,
626 ModulePath::Std { .. } => false,
627 }
628 }
629
630 pub(crate) fn is_sketch_mode_execution(&self) -> bool {
634 self.global.sketch_mode
635 }
636
637 pub fn next_object_id(&mut self) -> ObjectId {
638 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
639 }
640
641 pub fn peek_object_id(&self) -> ObjectId {
642 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
643 }
644
645 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
646 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
647 map.get(key).copied()
648 }
649
650 pub(crate) fn set_constraint_state(
651 &mut self,
652 sketch_block_id: ObjectId,
653 key: ConstraintKey,
654 state: ConstraintState,
655 ) {
656 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
657 map.insert(key, state);
658 }
659
660 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
661 let id = obj.id;
662 debug_assert!(
663 id.0 == self.mod_local.artifacts.scene_objects.len(),
664 "Adding scene object with ID {} but next ID is {}",
665 id.0,
666 self.mod_local.artifacts.scene_objects.len()
667 );
668 let artifact_id = obj.artifact_id;
669 self.mod_local.artifacts.scene_objects.push(obj);
670 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
671 self.mod_local
672 .artifacts
673 .artifact_id_to_scene_object
674 .insert(artifact_id, id);
675 id
676 }
677
678 pub fn add_placeholder_scene_object(
681 &mut self,
682 id: ObjectId,
683 source_range: SourceRange,
684 node_path: Option<NodePath>,
685 ) -> ObjectId {
686 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
687 self.mod_local
688 .artifacts
689 .scene_objects
690 .push(Object::placeholder(id, source_range, node_path));
691 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
692 id
693 }
694
695 pub fn set_scene_object(&mut self, object: Object) {
697 let id = object.id;
698 let artifact_id = object.artifact_id;
699 self.mod_local.artifacts.scene_objects[id.0] = object;
700 self.mod_local
701 .artifacts
702 .artifact_id_to_scene_object
703 .insert(artifact_id, id);
704 }
705
706 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
707 self.mod_local
708 .artifacts
709 .artifact_id_to_scene_object
710 .get(&artifact_id)
711 .cloned()
712 }
713
714 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
715 self.global.segment_ids_edited.contains(object_id)
716 }
717
718 pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
719 self.global
720 .drag_anchors
721 .iter()
722 .find(|anchor| &anchor.segment_id == object_id)
723 .map(|anchor| &anchor.target)
724 }
725
726 pub(super) fn is_in_sketch_block(&self) -> bool {
727 self.mod_local.sketch_block.is_some()
728 }
729
730 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
731 self.mod_local.sketch_block.as_mut()
732 }
733
734 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
735 self.mod_local.sketch_block.as_ref()
736 }
737
738 pub fn next_uuid(&mut self) -> Uuid {
739 self.mod_local.id_generator.next_uuid()
740 }
741
742 pub fn next_artifact_id(&mut self) -> ArtifactId {
743 self.mod_local.id_generator.next_artifact_id()
744 }
745
746 pub fn id_generator(&mut self) -> &mut IdGenerator {
747 &mut self.mod_local.id_generator
748 }
749
750 pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
752 self.mod_local.consumed_solids.insert(consumed_key, info);
753 }
754
755 pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
758 self.mod_local.consumed_solid_ids.insert(consumed_id, info);
759 }
760
761 pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
764 self.mod_local.consumed_solids.get(key)
765 }
766
767 pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
770 self.mod_local.consumed_solid_ids.get(id)
771 }
772
773 pub(crate) fn latest_consumed_output(
776 &self,
777 suggested_replacement_key: Option<ConsumedSolidKey>,
778 ) -> Option<ConsumedSolidKey> {
779 let mut latest = suggested_replacement_key?;
780 let mut seen = AhashIndexSet::default();
781
782 while seen.insert(latest) {
783 let Some(next) = self
784 .mod_local
785 .consumed_solids
786 .get(&latest)
787 .and_then(|info| info.suggested_replacement_key())
788 else {
789 break;
790 };
791 latest = next;
792 }
793
794 Some(latest)
795 }
796
797 pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
801 fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
802 match value {
803 KclValue::Solid { value } => {
804 value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
805 }
806 KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
807 _ => false,
808 }
809 }
810 self.mod_local
811 .stack
812 .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
813 }
814
815 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
816 let id = artifact.id();
817 self.mod_local.artifacts.artifacts.insert(id, artifact);
818 }
819
820 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
821 self.mod_local.artifacts.artifacts.get_mut(&id)
822 }
823
824 pub(crate) fn push_op(&mut self, op: Operation) {
825 let index = self.mod_local.artifacts.operations.len();
826 self.mod_local.artifacts.operations.push(op);
827 if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
828 && let Some(callbacks) = &self.execution_callbacks
829 {
830 callbacks.on_operation(OperationCallbackArgs {
831 module_id: self.mod_local.module_id,
832 operation,
833 index,
834 });
835 }
836 }
837
838 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
839 self.mod_local.artifacts.unprocessed_commands.push(command);
840 }
841
842 pub(super) fn next_module_id(&self) -> ModuleId {
843 ModuleId::from_usize(self.global.path_to_source_id.len())
844 }
845
846 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
847 self.global.path_to_source_id.get(path).cloned()
848 }
849
850 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
851 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
852 self.global.path_to_source_id.insert(path, id);
853 }
854
855 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
856 let root_id = ModuleId::default();
857 let path = self
859 .global
860 .path_to_source_id
861 .iter()
862 .find(|(_, v)| **v == root_id)
863 .unwrap()
864 .0
865 .clone();
866 self.add_id_to_source(
867 root_id,
868 ModuleSource {
869 path,
870 source: program.original_file_contents.to_string(),
871 },
872 );
873 }
874
875 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
876 self.global.id_to_source.insert(id, source);
877 }
878
879 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
880 debug_assert!(self.global.path_to_source_id.contains_key(&path));
881 let module_info = ModuleInfo { id, repr, path };
882 self.global.module_infos.insert(id, module_info);
883 }
884
885 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
886 self.global.module_infos.get(&id)
887 }
888
889 #[cfg(test)]
890 pub(crate) fn modules(&self) -> &ModuleInfoMap {
891 &self.global.module_infos
892 }
893
894 #[cfg(test)]
895 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
896 &self.global.root_module_artifacts
897 }
898
899 pub(crate) fn record_edge_refactor_meta(&mut self, meta: EdgeRefactorMeta) {
904 self.mod_local
905 .artifacts
906 .refactor_metadata
907 .push(RefactorMetadata::EdgeRefactor(Box::new(meta)));
908 }
909
910 pub(crate) fn record_pending_edge_refactor_meta(&mut self, meta: PendingEdgeRefactorMeta) {
911 self.mod_local.artifacts.pending_edge_refactor_metadata.push(meta);
912 }
913
914 pub(crate) fn pending_edge_refactor_meta(
915 &self,
916 edge_id: Uuid,
917 argument_source_range: SourceRange,
918 ) -> Option<PendingEdgeRefactorMeta> {
919 if let Some(pending) = self
920 .mod_local
921 .artifacts
922 .pending_edge_refactor_metadata
923 .iter()
924 .find(|meta| meta.edge_id == edge_id && argument_source_range.contains_range(&meta.source_range))
925 {
926 return Some(pending.clone());
927 }
928
929 let mut matches = self
932 .mod_local
933 .artifacts
934 .pending_edge_refactor_metadata
935 .iter()
936 .filter(|meta| meta.edge_id == edge_id);
937 let pending = matches.next()?.clone();
938 matches.next().is_none().then_some(pending)
939 }
940
941 pub(crate) fn record_edge_refactor_meta_from_pending(
942 &mut self,
943 edge_id: Uuid,
944 source_range: SourceRange,
945 face_ids: [Uuid; 2],
946 ) -> bool {
947 if self.mod_local.artifacts.refactor_metadata.iter().any(|meta| {
948 matches!(
949 meta,
950 RefactorMetadata::EdgeRefactor(meta)
951 if meta.edge_id == edge_id && meta.source_range == source_range
952 )
953 }) {
954 return true;
955 }
956
957 let exact_pending_meta = self
958 .mod_local
959 .artifacts
960 .pending_edge_refactor_metadata
961 .iter()
962 .find(|meta| meta.edge_id == edge_id && meta.source_range == source_range)
963 .cloned();
964
965 let edge_pending_meta = || {
966 let mut matches = self
967 .mod_local
968 .artifacts
969 .pending_edge_refactor_metadata
970 .iter()
971 .filter(|meta| meta.edge_id == edge_id);
972 let pending_meta = matches.next()?.clone();
973 matches.next().is_none().then_some(pending_meta)
974 };
975
976 let Some(pending_meta) = exact_pending_meta.or_else(edge_pending_meta) else {
977 return false;
978 };
979
980 self.record_edge_refactor_meta(EdgeRefactorMeta {
981 edge_id,
982 face_ids,
983 end_face_ids: Vec::new(),
984 source_range: pending_meta.source_range,
985 stdlib_fn: pending_meta.stdlib_fn,
986 });
987
988 true
989 }
990
991 pub(crate) fn record_direct_tag_fillet_meta(&mut self, meta: DirectTagFilletMeta) {
996 self.mod_local
997 .artifacts
998 .refactor_metadata
999 .push(RefactorMetadata::DirectTagFillet(meta));
1000 }
1001
1002 pub fn edge_refactor_metadata(&self) -> Vec<EdgeRefactorMeta> {
1004 self.global
1005 .root_module_artifacts
1006 .refactor_metadata
1007 .iter()
1008 .filter_map(|m| match m {
1009 RefactorMetadata::EdgeRefactor(meta) => Some(meta.as_ref().clone()),
1010 RefactorMetadata::DirectTagFillet(_) => None,
1011 })
1012 .collect()
1013 }
1014
1015 pub fn direct_tag_fillet_metadata(&self) -> Vec<DirectTagFilletMeta> {
1017 self.global
1018 .root_module_artifacts
1019 .refactor_metadata
1020 .iter()
1021 .filter_map(|m| match m {
1022 RefactorMetadata::EdgeRefactor(_) => None,
1023 RefactorMetadata::DirectTagFillet(meta) => Some(meta.clone()),
1024 })
1025 .collect()
1026 }
1027
1028 pub fn current_default_units(&self) -> NumericType {
1029 NumericType::Default {
1030 len: self.length_unit(),
1031 angle: self.angle_unit(),
1032 }
1033 }
1034
1035 pub fn length_unit(&self) -> UnitLength {
1036 self.mod_local.settings.default_length_units
1037 }
1038
1039 pub fn angle_unit(&self) -> UnitAngle {
1040 self.mod_local.settings.default_angle_units
1041 }
1042
1043 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
1044 KclError::new_import_cycle(KclErrorDetails::new(
1045 format!(
1046 "circular import of modules is not allowed: {} -> {}",
1047 self.global
1048 .mod_loader
1049 .import_stack
1050 .iter()
1051 .map(|p| p.to_string_lossy())
1052 .collect::<Vec<_>>()
1053 .join(" -> "),
1054 path,
1055 ),
1056 vec![source_range],
1057 ))
1058 }
1059
1060 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
1061 self.mod_local.pipe_value.as_ref()
1062 }
1063
1064 pub(crate) fn error_with_outputs(
1065 &self,
1066 error: KclError,
1067 main_ref: Option<EnvironmentRef>,
1068 default_planes: Option<DefaultPlanes>,
1069 ) -> KclErrorWithOutputs {
1070 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
1071 .global
1072 .path_to_source_id
1073 .iter()
1074 .map(|(k, v)| ((*v), k.clone()))
1075 .collect();
1076
1077 KclErrorWithOutputs::new(
1078 error,
1079 self.issues().to_vec(),
1080 main_ref
1081 .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
1082 .unwrap_or_default(),
1083 self.global.operations_by_module(),
1084 Default::default(),
1085 self.global.artifacts.graph.clone(),
1086 self.global.root_module_artifacts.scene_objects.clone(),
1087 self.global.root_module_artifacts.source_range_to_object.clone(),
1088 self.global.root_module_artifacts.var_solutions.clone(),
1089 self.global.root_module_artifacts.refactor_metadata.clone(),
1090 module_id_to_module_path,
1091 self.global.id_to_source.clone(),
1092 default_planes,
1093 )
1094 }
1095
1096 pub(crate) fn build_program_lookup(
1097 &self,
1098 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1099 ) -> ProgramLookup {
1100 ProgramLookup::new(current, self.global.module_infos.clone())
1101 }
1102
1103 pub(crate) async fn build_artifact_graph(
1104 &mut self,
1105 engine: &Arc<EngineManager>,
1106 program: NodeRef<'_, crate::parsing::ast::types::Program>,
1107 ) -> Result<(), KclError> {
1108 let mut new_commands = Vec::new();
1109 let mut new_exec_artifacts = IndexMap::new();
1110 for module in self.global.module_infos.values_mut() {
1111 match &mut module.repr {
1112 ModuleRepr::Kcl(_, Some(outcome)) => {
1113 new_commands.extend(outcome.artifacts.process_commands());
1114 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
1115 }
1116 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
1117 new_commands.extend(module_artifacts.process_commands());
1118 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
1119 }
1120 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
1121 }
1122 }
1123 new_commands.extend(self.global.root_module_artifacts.process_commands());
1126 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
1129 let new_responses = engine.take_responses().await;
1130
1131 for (id, exec_artifact) in new_exec_artifacts {
1134 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
1138 }
1139
1140 let initial_graph = self.global.artifacts.graph.clone();
1141
1142 let programs = self.build_program_lookup(program.clone());
1144 let graph_result = crate::execution::artifact::build_artifact_graph(
1145 &new_commands,
1146 &new_responses,
1147 program,
1148 &mut self.global.artifacts.artifacts,
1149 initial_graph,
1150 &programs,
1151 &self.global.module_infos,
1152 );
1153
1154 #[cfg(feature = "snapshot-engine-responses")]
1155 {
1156 self.global.root_module_artifacts.responses.extend(new_responses);
1158 }
1159
1160 let artifact_graph = graph_result?;
1161 self.global.artifacts.graph = artifact_graph;
1162
1163 Ok(())
1164 }
1165
1166 pub(crate) fn kcl_version(&self) -> KclVersion {
1167 self.mod_local.settings.kcl_version.parse().unwrap_or_default()
1168 }
1169}
1170
1171#[derive(Default)]
1172pub enum KclVersion {
1173 #[default]
1174 V1,
1175 V2,
1176}
1177
1178impl FromStr for KclVersion {
1179 type Err = KclError;
1180
1181 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1182 match s {
1183 "1" | "1.0" | "1.0.0" => Ok(Self::V1),
1184 "2" | "2.0" | "2.0.0" => Ok(Self::V2),
1185 other => Err(KclError::new_semantic(KclErrorDetails {
1186 source_ranges: Default::default(),
1187 backtrace: Default::default(),
1188 message: format!("Unrecognized version {other}. Valid versions are 1.0 and 2.0"),
1189 })),
1190 }
1191 }
1192}
1193
1194impl GlobalState {
1195 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
1196 let mut global = GlobalState {
1197 path_to_source_id: Default::default(),
1198 module_infos: Default::default(),
1199 artifacts: Default::default(),
1200 root_module_artifacts: Default::default(),
1201 mod_loader: Default::default(),
1202 issues: Default::default(),
1203 id_to_source: Default::default(),
1204 segment_ids_edited,
1205 drag_anchors: Vec::new(),
1206 sketch_mode: false,
1207 };
1208
1209 let root_id = ModuleId::default();
1210 let root_path = settings.current_file.clone().unwrap_or_default();
1211 global.module_infos.insert(
1212 root_id,
1213 ModuleInfo {
1214 id: root_id,
1215 path: ModulePath::Local {
1216 value: root_path.clone(),
1217 original_import_path: None,
1218 },
1219 repr: ModuleRepr::Root,
1220 },
1221 );
1222 global.path_to_source_id.insert(
1223 ModulePath::Local {
1224 value: root_path,
1225 original_import_path: None,
1226 },
1227 root_id,
1228 );
1229 global
1230 }
1231
1232 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1233 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1234 }
1235
1236 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1237 self.id_to_source.get(&id)
1238 }
1239}
1240
1241impl ArtifactState {
1242 pub fn cached_body_items(&self) -> usize {
1243 self.graph.item_count()
1244 }
1245
1246 pub(crate) fn clear(&mut self) {
1247 self.artifacts.clear();
1248 self.graph.clear();
1249 }
1250}
1251
1252impl ModuleArtifactState {
1253 pub(crate) fn clear(&mut self) {
1254 self.artifacts.clear();
1255 self.unprocessed_commands.clear();
1256 self.commands.clear();
1257 self.operations.clear();
1258 self.refactor_metadata.clear();
1259 }
1260
1261 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1262 self.scene_objects = scene_objects.to_vec();
1263 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1264 self.source_range_to_object.clear();
1265 self.artifact_id_to_scene_object.clear();
1266
1267 for (expected_id, object) in self.scene_objects.iter().enumerate() {
1268 debug_assert_eq!(
1269 object.id.0, expected_id,
1270 "Restored cached scene object ID {} does not match its position {}",
1271 object.id.0, expected_id
1272 );
1273
1274 match &object.kind {
1275 ObjectKind::Wall(wall) => {
1276 self.source_range_to_object.insert(wall.source.solid.range, object.id);
1277 }
1278 ObjectKind::Cap(cap) => {
1279 self.source_range_to_object.insert(cap.source.solid.range, object.id);
1280 }
1281 _ => match &object.source {
1282 crate::front::SourceRef::Simple { range, node_path: _ } => {
1283 self.source_range_to_object.insert(*range, object.id);
1284 }
1285 crate::front::SourceRef::BackTrace { ranges } => {
1286 if let Some((range, _)) = ranges.first() {
1289 self.source_range_to_object.insert(*range, object.id);
1290 }
1291 }
1292 },
1293 }
1294
1295 if object.artifact_id != ArtifactId::placeholder() {
1297 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1298 }
1299 }
1300 }
1301
1302 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1304 self.artifacts.extend(other.artifacts);
1305 self.unprocessed_commands.extend(other.unprocessed_commands);
1306 self.commands.extend(other.commands);
1307 self.operations.extend(other.operations);
1308 if other.scene_objects.len() > self.scene_objects.len() {
1309 self.scene_objects
1310 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1311 }
1312 self.source_range_to_object.extend(other.source_range_to_object);
1313 self.artifact_id_to_scene_object
1314 .extend(other.artifact_id_to_scene_object);
1315 self.var_solutions.extend(other.var_solutions);
1316 self.refactor_metadata.extend(other.refactor_metadata);
1317 }
1318
1319 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1323 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1324 let new_module_commands = unprocessed.clone();
1325 self.commands.extend(unprocessed);
1326 new_module_commands
1327 }
1328
1329 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1330 debug_assert!(
1331 id.0 < self.scene_objects.len(),
1332 "Requested object ID {} but only have {} objects",
1333 id.0,
1334 self.scene_objects.len()
1335 );
1336 self.scene_objects.get(id.0)
1337 }
1338
1339 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1340 debug_assert!(
1341 id.0 < self.scene_objects.len(),
1342 "Requested object ID {} but only have {} objects",
1343 id.0,
1344 self.scene_objects.len()
1345 );
1346 self.scene_objects.get_mut(id.0)
1347 }
1348}
1349
1350impl ModuleState {
1351 pub(super) fn new(
1352 path: ModulePath,
1353 memory: Arc<ProgramMemory>,
1354 module_id: Option<ModuleId>,
1355 sketch_mode: bool,
1356 freedom_analysis: bool,
1357 ) -> Self {
1358 let state_module_id = module_id.unwrap_or_default();
1359 ModuleState {
1360 module_id: state_module_id,
1361 id_generator: IdGenerator::new(module_id),
1362 stack: memory.new_stack(),
1363 call_stack_size: 0,
1364 pipe_value: Default::default(),
1365 being_declared: Default::default(),
1366 sketch_block: Default::default(),
1367 stdlib_entry_source_range: Default::default(),
1368 module_exports: Default::default(),
1369 explicit_length_units: false,
1370 path,
1371 settings: Default::default(),
1372 sketch_mode,
1373 freedom_analysis,
1374 artifacts: Default::default(),
1375 constraint_state: Default::default(),
1376 allowed_warnings: Vec::new(),
1377 denied_warnings: Vec::new(),
1378 consumed_solids: AHashMap::default(),
1379 consumed_solid_ids: AHashMap::default(),
1380 inside_stdlib: false,
1381 }
1382 }
1383
1384 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1385 self.stack.find_all_in_env_owned(main_ref)
1386 }
1387}
1388
1389impl SketchBlockState {
1390 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1391 SketchVarId(self.sketch_vars.len())
1392 }
1393
1394 pub(crate) fn var_solutions(
1397 &self,
1398 solve_outcome: &Solved,
1399 solution_ty: NumericType,
1400 sketch_block_range: SourceRange,
1401 ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1402 self.sketch_vars
1403 .iter()
1404 .map(|v| {
1405 let Some(sketch_var) = v.as_sketch_var() else {
1406 return Err(KclError::new_internal(KclErrorDetails::new(
1407 "Expected sketch variable".to_owned(),
1408 vec![sketch_block_range],
1409 )));
1410 };
1411 let var_index = sketch_var.id.0;
1412 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1413 let message = format!("No solution for sketch variable with id {}", var_index);
1414 debug_assert!(false, "{}", &message);
1415 KclError::new_internal(KclErrorDetails::new(
1416 message,
1417 sketch_var.meta.iter().map(|m| m.source_range).collect(),
1418 ))
1419 })?;
1420 let solved_value = Number {
1421 value: *solved_n,
1422 units: solution_ty.try_into().map_err(|_| {
1423 KclError::new_internal(KclErrorDetails::new(
1424 "Failed to convert numeric type to units".to_owned(),
1425 vec![sketch_block_range],
1426 ))
1427 })?,
1428 };
1429 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1430 return Ok(None);
1431 };
1432 Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1433 })
1434 .filter_map(Result::transpose)
1435 .collect::<Result<Vec<_>, KclError>>()
1436 }
1437}
1438
1439#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1440#[ts(export)]
1441#[serde(rename_all = "camelCase")]
1442pub struct MetaSettings {
1443 pub default_length_units: UnitLength,
1444 pub default_angle_units: UnitAngle,
1445 pub experimental_features: annotations::WarningLevel,
1446 pub kcl_version: String,
1447}
1448
1449impl Default for MetaSettings {
1450 fn default() -> Self {
1451 MetaSettings {
1452 default_length_units: UnitLength::Millimeters,
1453 default_angle_units: UnitAngle::Degrees,
1454 experimental_features: annotations::WarningLevel::Deny,
1455 kcl_version: "1.0".to_owned(),
1456 }
1457 }
1458}
1459
1460impl MetaSettings {
1461 pub(crate) fn update_from_annotation(
1462 &mut self,
1463 annotation: &crate::parsing::ast::types::Node<Annotation>,
1464 ) -> Result<(bool, bool), KclError> {
1465 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1466
1467 let mut updated_len = false;
1468 let mut updated_angle = false;
1469 for p in properties {
1470 match &*p.inner.key.name {
1471 annotations::SETTINGS_UNIT_LENGTH => {
1472 let value = annotations::expect_ident(&p.inner.value)?;
1473 let value = super::types::length_from_str(value, annotation.as_source_range())?;
1474 self.default_length_units = value;
1475 updated_len = true;
1476 }
1477 annotations::SETTINGS_UNIT_ANGLE => {
1478 let value = annotations::expect_ident(&p.inner.value)?;
1479 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1480 self.default_angle_units = value;
1481 updated_angle = true;
1482 }
1483 annotations::SETTINGS_VERSION => {
1484 let value = annotations::expect_number(&p.inner.value)?;
1485 self.kcl_version = value;
1486 }
1487 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1488 let value = annotations::expect_ident(&p.inner.value)?;
1489 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1490 KclError::new_semantic(KclErrorDetails::new(
1491 format!(
1492 "Invalid value for {} settings property, expected one of: {}",
1493 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1494 annotations::WARN_LEVELS.join(", ")
1495 ),
1496 annotation.as_source_ranges(),
1497 ))
1498 })?;
1499 self.experimental_features = value;
1500 }
1501 name => {
1502 return Err(KclError::new_semantic(KclErrorDetails::new(
1503 format!(
1504 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1505 annotations::SETTINGS_UNIT_LENGTH,
1506 annotations::SETTINGS_UNIT_ANGLE
1507 ),
1508 vec![annotation.as_source_range()],
1509 )));
1510 }
1511 }
1512 }
1513
1514 Ok((updated_len, updated_angle))
1515 }
1516}
1517
1518#[cfg(test)]
1519mod tests {
1520 use uuid::Uuid;
1521
1522 use super::ModuleArtifactState;
1523 use crate::NodePath;
1524 use crate::NodePathExt;
1525 use crate::SourceRange;
1526 use crate::execution::ArtifactId;
1527 use crate::front::Object;
1528 use crate::front::ObjectId;
1529 use crate::front::ObjectKind;
1530 use crate::front::Plane;
1531 use crate::front::SourceRef;
1532
1533 #[test]
1534 fn restore_scene_objects_rebuilds_lookup_maps() {
1535 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1536 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1537 let plane_range = SourceRange::from([1, 4, 0]);
1538 let plane_node_path = Some(NodePath::placeholder());
1539 let sketch_ranges = vec![
1540 (SourceRange::from([5, 9, 0]), None),
1541 (SourceRange::from([10, 12, 0]), None),
1542 ];
1543 let cached_objects = vec![
1544 Object {
1545 id: ObjectId(0),
1546 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1547 label: Default::default(),
1548 comments: Default::default(),
1549 artifact_id: plane_artifact_id,
1550 source: SourceRef::new(plane_range, plane_node_path),
1551 },
1552 Object {
1553 id: ObjectId(1),
1554 kind: ObjectKind::Nil,
1555 label: Default::default(),
1556 comments: Default::default(),
1557 artifact_id: sketch_artifact_id,
1558 source: SourceRef::BackTrace {
1559 ranges: sketch_ranges.clone(),
1560 },
1561 },
1562 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1563 ];
1564
1565 let mut artifacts = ModuleArtifactState::default();
1566 artifacts.restore_scene_objects(&cached_objects);
1567
1568 assert_eq!(artifacts.scene_objects, cached_objects);
1569 assert_eq!(
1570 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1571 Some(&ObjectId(0))
1572 );
1573 assert_eq!(
1574 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1575 Some(&ObjectId(1))
1576 );
1577 assert_eq!(
1578 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1579 None
1580 );
1581 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1582 assert_eq!(
1583 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1584 Some(&ObjectId(1))
1585 );
1586 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1588 }
1589}