1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use ahash::AHashMap;
6use anyhow::Result;
7use indexmap::IndexMap;
8pub use kcl_api::KclVersion;
9use kcl_api::UnitAngle;
10use kcl_api::UnitLength;
11use serde::Deserialize;
12use serde::Serialize;
13use uuid::Uuid;
14
15use crate::CompilationIssue;
16use crate::ExecutorContext;
17use crate::KclErrorWithOutputs;
18use crate::MockConfig;
19use crate::NodePath;
20use crate::SegmentDragAnchor;
21use crate::SourceRange;
22use crate::collections::AhashIndexSet;
23use crate::engine::engine_manager::EngineManager;
24use crate::errors::KclError;
25use crate::errors::KclErrorDetails;
26use crate::errors::Severity;
27use crate::exec::DefaultPlanes;
28use crate::execution::Artifact;
29use crate::execution::ArtifactCommand;
30use crate::execution::ArtifactGraph;
31use crate::execution::ArtifactId;
32use crate::execution::ConstrainableLine2d;
33use crate::execution::EnvironmentRef;
34use crate::execution::ExecOutcome;
35use crate::execution::ExecutorSettings;
36use crate::execution::KclValue;
37use crate::execution::KclValueView;
38use crate::execution::OperationCallbackArgs;
39use crate::execution::OperationsByModule;
40use crate::execution::ProgramLookup;
41use crate::execution::SketchVarId;
42use crate::execution::UnsolvedSegment;
43use crate::execution::annotations;
44use crate::execution::cad_op::Operation;
45use crate::execution::id_generator::IdGenerator;
46#[cfg(test)]
47use crate::execution::memory::MemoryBackendKind;
48use crate::execution::memory::ProgramMemory;
49use crate::execution::memory::Stack;
50use crate::execution::sketch_solve::Solved;
51use crate::execution::types::NumericType;
52use crate::front::Number;
53use crate::front::Object;
54use crate::front::ObjectId;
55use crate::front::ObjectKind;
56use crate::id::IncIdGenerator;
57use crate::modules::ModuleId;
58use crate::modules::ModuleInfo;
59use crate::modules::ModuleLoader;
60use crate::modules::ModulePath;
61use crate::modules::ModuleRepr;
62use crate::modules::ModuleSource;
63use crate::parsing::ast::types::Annotation;
64use crate::parsing::ast::types::Node;
65use crate::parsing::ast::types::NodeRef;
66use crate::parsing::ast::types::Program;
67use crate::parsing::ast::types::TagNode;
68
69#[derive(Debug, Clone)]
71pub struct ExecState {
72 pub(super) execution_callbacks: Option<std::sync::Arc<dyn crate::execution::ExecutionCallbacks>>,
73 pub(super) global: GlobalState,
74 pub(super) mod_local: ModuleState,
75}
76
77pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
78
79#[derive(Debug, Clone)]
80pub(super) struct GlobalState {
81 pub(crate) machine_depth_high_water: usize,
88 pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
90 pub id_to_source: IndexMap<ModuleId, ModuleSource>,
92 pub module_infos: ModuleInfoMap,
94 pub mod_loader: ModuleLoader,
96 pub issues: Vec<CompilationIssue>,
98 pub deprecation_version_override: Option<String>,
102 pub entry_point_kcl_version: Option<KclVersion>,
112 pub artifacts: ArtifactState,
114 pub root_module_artifacts: ModuleArtifactState,
116 pub segment_ids_edited: AhashIndexSet<ObjectId>,
118 pub drag_anchors: Vec<SegmentDragAnchor>,
120 pub sketch_mode: bool,
125 pub geometry_only: bool,
127}
128
129impl GlobalState {
130 pub(crate) fn operations_by_module(&self) -> OperationsByModule {
131 let mut operations = OperationsByModule::default();
132 operations.insert(ModuleId::default(), self.root_module_artifacts.operations.clone());
133
134 for (module_id, module_info) in &self.module_infos {
135 match &module_info.repr {
136 ModuleRepr::Root => {}
137 ModuleRepr::Kcl(_, Some(outcome)) => {
138 operations.insert(*module_id, outcome.artifacts.operations.clone());
139 }
140 ModuleRepr::Foreign(_, Some((_, artifacts))) => {
141 operations.insert(*module_id, artifacts.operations.clone());
142 }
143 ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
144 }
145 }
146
147 operations
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152pub(crate) enum ConstraintKey {
153 LineCircle([usize; 10]),
154 CircleCircle([usize; 12]),
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub(crate) enum TangencyMode {
159 LineCircle(ezpz::LineSide),
160 CircleCircle(ezpz::CircleSide),
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub(crate) enum ConstraintState {
165 Tangency(TangencyMode),
166}
167
168#[derive(Debug, Clone, Default)]
169pub(super) struct ArtifactState {
170 pub artifacts: IndexMap<ArtifactId, Artifact>,
173 pub graph: ArtifactGraph,
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
179#[ts(export)]
180#[serde(rename_all = "camelCase")]
181pub enum EdgeRefactorStdlibFn {
182 GetOppositeEdge,
183 GetNextAdjacentEdge,
184 GetPreviousAdjacentEdge,
185 GetCommonEdge,
186 EdgeId,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
191#[ts(export)]
192#[serde(rename_all = "camelCase")]
193pub struct EdgeRefactorMeta {
194 pub edge_id: Uuid,
195 pub face_ids: [Uuid; 2],
196 #[serde(default, skip_serializing_if = "Vec::is_empty")]
197 pub end_face_ids: Vec<Uuid>,
198 pub source_range: SourceRange,
199 pub stdlib_fn: EdgeRefactorStdlibFn,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
205pub(crate) struct PendingEdgeRefactorMeta {
206 pub edge_id: Uuid,
207 pub source_range: SourceRange,
208 pub stdlib_fn: EdgeRefactorStdlibFn,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
213#[ts(export)]
214#[serde(rename_all = "camelCase")]
215pub struct DirectTagFilletTagEntry {
216 pub tag_identifier: String,
217 pub edge_id: Uuid,
218 pub face_ids: [Uuid; 2],
219}
220
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
223#[ts(export)]
224#[serde(rename_all = "camelCase")]
225pub struct DirectTagFilletMeta {
226 pub call_source_range: SourceRange,
227 pub tags: Vec<DirectTagFilletTagEntry>,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
233#[ts(export)]
234#[serde(rename_all = "camelCase")]
235pub struct LegacyAngleRefactorMeta {
236 pub source_range: SourceRange,
237 pub sector: u8,
238 pub inverse: bool,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
243#[ts(export)]
244#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
245pub enum RefactorMetadata {
246 EdgeRefactor(Box<EdgeRefactorMeta>),
247 DirectTagFillet(DirectTagFilletMeta),
248 LegacyAngle(LegacyAngleRefactorMeta),
249}
250
251#[derive(Debug, Clone)]
252pub(crate) struct PendingLegacyAngleRefactorMeta {
253 pub source_range: SourceRange,
254 pub lines: [ConstrainableLine2d; 2],
255 pub desired_angle_radians: f64,
256}
257
258#[derive(Debug, Clone, Default, PartialEq, Serialize)]
260pub struct ModuleArtifactState {
261 pub artifacts: IndexMap<ArtifactId, Artifact>,
263 #[serde(skip)]
266 pub unprocessed_commands: Vec<ArtifactCommand>,
267 pub commands: Vec<ArtifactCommand>,
269 #[cfg(feature = "snapshot-engine-responses")]
271 pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
272 pub operations: Vec<Operation>,
275 pub object_id_generator: IncIdGenerator<usize>,
277 pub scene_objects: Vec<Object>,
279 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
282 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
284 pub var_solutions: Vec<(SourceRange, Option<NodePath>, Number)>,
286 pub refactor_metadata: Vec<RefactorMetadata>,
288 #[serde(skip)]
291 pub(crate) pending_edge_refactor_metadata: Vec<PendingEdgeRefactorMeta>,
292}
293
294#[derive(Debug, Clone)]
295pub(super) struct ModuleState {
296 pub module_id: ModuleId,
298 pub id_generator: IdGenerator,
300 pub stack: Stack,
301 pub(super) call_stack_size: usize,
305 pub(crate) machine_call_depth: usize,
308 pub pipe_value: Option<KclValue>,
311 pub being_declared: Option<String>,
315 pub sketch_block: Option<SketchBlockState>,
317 pub inside_stdlib: bool,
320 pub stdlib_entry_source_range: Option<SourceRange>,
322 pub module_exports: Vec<String>,
324 pub settings: MetaSettings,
326 pub sketch_mode: bool,
329 pub freedom_analysis: bool,
333 pub(super) explicit_length_units: bool,
334 pub(super) path: ModulePath,
335 pub artifacts: ModuleArtifactState,
337 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
341
342 pub(super) allowed_warnings: Vec<&'static str>,
343 pub(super) denied_warnings: Vec<&'static str>,
344
345 pub(super) consumed_solids: AHashMap<ConsumedSolidKey, ConsumedSolidInfo>,
350 pub(super) consumed_solid_ids: AHashMap<Uuid, ConsumedSolidInfo>,
356 pub(super) consumed_regions: AHashMap<Uuid, ConsumedRegionInfo>,
360}
361
362#[derive(Debug, Clone, Copy)]
364pub(crate) struct ConsumedRegionInfo {
365 operation: ConsumedRegionOperation,
366}
367
368impl ConsumedRegionInfo {
369 pub(crate) fn new(operation: ConsumedRegionOperation) -> Self {
370 Self { operation }
371 }
372
373 pub(crate) fn operation(self) -> ConsumedRegionOperation {
374 self.operation
375 }
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq)]
379pub(crate) enum ConsumedRegionOperation {
380 Extrude,
381 Revolve,
382 Sweep,
383 Delete,
384}
385
386impl std::fmt::Display for ConsumedRegionOperation {
387 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388 match self {
389 Self::Extrude => f.write_str("extrude"),
390 Self::Revolve => f.write_str("revolve"),
391 Self::Sweep => f.write_str("sweep"),
392 Self::Delete => f.write_str("delete"),
393 }
394 }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
399pub(crate) struct ConsumedSolidKey {
400 engine_id: Uuid,
402 instance_id: Uuid,
405}
406
407impl ConsumedSolidKey {
408 pub(crate) fn new(engine_id: Uuid, instance_id: Uuid) -> Self {
409 Self { engine_id, instance_id }
410 }
411
412 pub(crate) fn engine_id(&self) -> Uuid {
413 self.engine_id
414 }
415
416 pub(crate) fn instance_id(&self) -> Uuid {
417 self.instance_id
418 }
419}
420
421#[derive(Debug, Clone)]
425pub(crate) struct ConsumedSolidInfo {
426 operation: ConsumedSolidOperation,
428 suggested_replacement_key: Option<ConsumedSolidKey>,
432 returned_solid_keys: Vec<ConsumedSolidKey>,
435}
436
437impl ConsumedSolidInfo {
438 pub(crate) fn new(operation: ConsumedSolidOperation, returned_solid_keys: Vec<ConsumedSolidKey>) -> Self {
439 Self {
440 operation,
441 suggested_replacement_key: returned_solid_keys.first().copied(),
442 returned_solid_keys,
443 }
444 }
445
446 pub(crate) fn operation(&self) -> ConsumedSolidOperation {
447 self.operation
448 }
449
450 pub(crate) fn suggested_replacement_key(&self) -> Option<ConsumedSolidKey> {
451 self.suggested_replacement_key
452 }
453
454 pub(crate) fn should_report_reused_engine_id_as_consumed(&self, key: ConsumedSolidKey) -> bool {
455 !self.returned_solid_keys.contains(&key)
456 }
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
460pub(crate) enum ConsumedSolidOperation {
461 Union,
462 Intersect,
463 Subtract,
464 Split,
465 JoinSurfaces,
466}
467
468impl ConsumedSolidOperation {
469 pub(crate) fn indefinite_article(self) -> &'static str {
470 match self {
471 Self::Intersect => "an",
472 Self::Union | Self::Subtract | Self::Split | Self::JoinSurfaces => "a",
473 }
474 }
475}
476
477impl std::fmt::Display for ConsumedSolidOperation {
478 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
479 match self {
480 Self::Union => f.write_str("union"),
481 Self::Intersect => f.write_str("intersect"),
482 Self::Subtract => f.write_str("subtract"),
483 Self::Split => f.write_str("split"),
484 Self::JoinSurfaces => f.write_str("joinSurfaces"),
485 }
486 }
487}
488
489#[derive(Debug, Clone, Default)]
490pub(crate) struct SketchBlockState {
491 pub sketch_vars: Vec<KclValue>,
492 pub sketch_id: Option<ObjectId>,
493 pub sketch_constraints: Vec<ObjectId>,
494 pub solver_constraints: Vec<ezpz::Constraint>,
495 pub solver_optional_constraints: Vec<ezpz::Constraint>,
496 pub needed_by_engine: Vec<UnsolvedSegment>,
497 pub segment_tags: IndexMap<ObjectId, TagNode>,
498 pub pending_legacy_angle_refactor_metadata: Vec<PendingLegacyAngleRefactorMeta>,
499}
500
501impl ExecState {
502 pub fn new(exec_context: &super::ExecutorContext) -> Self {
503 ExecState {
504 execution_callbacks: exec_context.execution_callbacks.clone(),
505 global: GlobalState::new(&exec_context.settings, Default::default()),
506 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
507 }
508 }
509
510 #[cfg(test)]
511 pub(crate) fn new_with_memory_backend(exec_context: &super::ExecutorContext, backend: MemoryBackendKind) -> Self {
512 ExecState {
513 execution_callbacks: exec_context.execution_callbacks.clone(),
514 global: GlobalState::new(&exec_context.settings, Default::default()),
515 mod_local: ModuleState::new(
516 ModulePath::Main,
517 ProgramMemory::new_with_backend(backend),
518 Default::default(),
519 false,
520 true,
521 ),
522 }
523 }
524
525 pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
526 let segment_ids_edited = mock_config.segment_ids_edited.clone();
527 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
528 global.drag_anchors = mock_config.drag_anchors.clone();
529 global.sketch_mode = mock_config.sketch_block_id.is_some();
530 ExecState {
531 execution_callbacks: exec_context.execution_callbacks.clone(),
532 global,
533 mod_local: ModuleState::new(
534 ModulePath::Main,
535 ProgramMemory::new(),
536 Default::default(),
537 mock_config.sketch_block_id.is_some(),
538 mock_config.freedom_analysis,
539 ),
540 }
541 }
542
543 #[cfg(test)]
544 pub(crate) fn new_mock_with_memory_backend(
545 exec_context: &super::ExecutorContext,
546 mock_config: &MockConfig,
547 backend: MemoryBackendKind,
548 ) -> Self {
549 let segment_ids_edited = mock_config.segment_ids_edited.clone();
550 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
551 global.drag_anchors = mock_config.drag_anchors.clone();
552 global.sketch_mode = mock_config.sketch_block_id.is_some();
553 ExecState {
554 execution_callbacks: exec_context.execution_callbacks.clone(),
555 global,
556 mod_local: ModuleState::new(
557 ModulePath::Main,
558 ProgramMemory::new_with_backend(backend),
559 Default::default(),
560 mock_config.sketch_block_id.is_some(),
561 mock_config.freedom_analysis,
562 ),
563 }
564 }
565
566 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
567 let global = GlobalState::new(&exec_context.settings, Default::default());
568
569 *self = ExecState {
570 execution_callbacks: exec_context.execution_callbacks.clone(),
571 global,
572 mod_local: ModuleState::new(
573 self.mod_local.path.clone(),
574 ProgramMemory::new(),
575 Default::default(),
576 false,
577 true,
578 ),
579 };
580 }
581
582 pub fn err(&mut self, e: CompilationIssue) {
584 self.global.issues.push(e);
585 }
586
587 pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
589 debug_assert!(annotations::WARN_VALUES.contains(&name));
590
591 if self.mod_local.allowed_warnings.contains(&name) {
592 return;
593 }
594
595 if self.mod_local.denied_warnings.contains(&name) {
596 e.severity = Severity::Error;
597 } else {
598 e.severity = Severity::Warning;
599 }
600
601 self.global.issues.push(e);
602 }
603
604 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
605 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
606 return;
607 };
608 let error = CompilationIssue {
609 source_range,
610 message: format!("Use of {feature_name} is experimental and may change or be removed."),
611 suggestion: None,
612 severity,
613 tag: crate::errors::Tag::None,
614 };
615
616 self.global.issues.push(error);
617 }
618
619 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
620 self.global.issues = std::mem::take(&mut self.global.issues)
621 .into_iter()
622 .filter(|e| {
623 e.severity != Severity::Warning
624 || !source_range.contains_range(&e.source_range)
625 || e.tag != crate::errors::Tag::UnknownNumericUnits
626 })
627 .collect();
628 }
629
630 pub fn issues(&self) -> &[CompilationIssue] {
631 &self.global.issues
632 }
633
634 pub(crate) fn deprecation_version(&self) -> &str {
635 self.global
636 .deprecation_version_override
637 .as_deref()
638 .unwrap_or(self.mod_local.settings.kcl_version.as_str())
639 }
640
641 #[cfg(test)]
642 pub(crate) fn set_deprecation_version_override(&mut self, version: Option<&str>) {
643 self.global.deprecation_version_override = version.map(str::to_owned);
644 }
645
646 #[cfg(test)]
647 pub(crate) fn program_memory_for_tests(
648 &self,
649 main_ref: EnvironmentRef,
650 ) -> Result<IndexMap<String, KclValue>, KclError> {
651 self.mod_local.variables(main_ref)
652 }
653
654 pub async fn into_exec_outcome(
658 self,
659 main_ref: EnvironmentRef,
660 ctx: &ExecutorContext,
661 ) -> Result<ExecOutcome, KclError> {
662 let variables = self.mod_local.variables(main_ref)?;
665 #[cfg(test)]
666 let test_program_memory = variables.clone();
667 let variables = variables
668 .into_iter()
669 .map(|(key, value)| (key, KclValueView::from(value)))
670 .collect();
671 Ok(ExecOutcome {
672 variables,
673 filenames: self.global.filenames(),
674 operations: self.global.operations_by_module(),
675 artifact_graph: self.global.artifacts.graph,
676 scene_objects: self.global.root_module_artifacts.scene_objects,
677 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
678 var_solutions: self.global.root_module_artifacts.var_solutions,
679 refactor_metadata: self.global.root_module_artifacts.refactor_metadata.clone(),
680 issues: self.global.issues,
681 source_files: self.global.id_to_source,
682 default_planes: ctx.engine.get_default_planes().read().await.clone(),
683 #[cfg(test)]
684 test_program_memory,
685 })
686 }
687
688 #[cfg(feature = "snapshot-engine-responses")]
689 pub(crate) fn take_root_module_responses(
690 &mut self,
691 ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
692 std::mem::take(&mut self.global.root_module_artifacts.responses)
693 }
694
695 pub(crate) fn geometry_only(&self) -> bool {
696 self.global.geometry_only
697 }
698
699 pub(crate) fn stack(&self) -> &Stack {
700 &self.mod_local.stack
701 }
702
703 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
704 &mut self.mod_local.stack
705 }
706
707 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
710 const LIMIT: usize = 50;
713 if self.mod_local.call_stack_size >= LIMIT {
714 return Err(KclError::new_max_call_stack(KclErrorDetails::new(
715 format!(
716 "Call depth limit ({LIMIT}) exceeded. This usually means a function is recursing without a base case."
717 ),
718 vec![range],
719 )));
720 }
721 self.mod_local.call_stack_size += 1;
722 Ok(())
723 }
724
725 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
728 if self.mod_local.call_stack_size == 0 {
730 let message = "call stack size below zero".to_owned();
731 debug_assert!(false, "{message}");
732 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
733 }
734 self.mod_local.call_stack_size -= 1;
735 Ok(())
736 }
737
738 #[allow(dead_code)]
744 pub(crate) fn machine_depth_high_water(&self) -> usize {
745 self.global.machine_depth_high_water
746 }
747
748 pub(crate) fn sketch_mode(&self) -> bool {
753 self.mod_local.sketch_mode
754 && match &self.mod_local.path {
755 ModulePath::Main => true,
756 ModulePath::Local { .. } => true,
757 ModulePath::Std { .. } => false,
758 }
759 }
760
761 pub(crate) fn is_sketch_mode_execution(&self) -> bool {
765 self.global.sketch_mode
766 }
767
768 pub fn next_object_id(&mut self) -> ObjectId {
769 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
770 }
771
772 pub fn peek_object_id(&self) -> ObjectId {
773 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
774 }
775
776 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
777 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
778 map.get(key).copied()
779 }
780
781 pub(crate) fn set_constraint_state(
782 &mut self,
783 sketch_block_id: ObjectId,
784 key: ConstraintKey,
785 state: ConstraintState,
786 ) {
787 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
788 map.insert(key, state);
789 }
790
791 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
792 let id = obj.id;
793 debug_assert!(
794 id.0 == self.mod_local.artifacts.scene_objects.len(),
795 "Adding scene object with ID {} but next ID is {}",
796 id.0,
797 self.mod_local.artifacts.scene_objects.len()
798 );
799 let artifact_id = obj.artifact_id;
800 self.mod_local.artifacts.scene_objects.push(obj);
801 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
802 self.mod_local
803 .artifacts
804 .artifact_id_to_scene_object
805 .insert(artifact_id, id);
806 id
807 }
808
809 pub fn add_placeholder_scene_object(
812 &mut self,
813 id: ObjectId,
814 source_range: SourceRange,
815 node_path: Option<NodePath>,
816 ) -> ObjectId {
817 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
818 self.mod_local
819 .artifacts
820 .scene_objects
821 .push(Object::placeholder(id, source_range, node_path));
822 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
823 id
824 }
825
826 pub fn set_scene_object(&mut self, object: Object) {
828 let id = object.id;
829 let artifact_id = object.artifact_id;
830 self.mod_local.artifacts.scene_objects[id.0] = object;
831 self.mod_local
832 .artifacts
833 .artifact_id_to_scene_object
834 .insert(artifact_id, id);
835 }
836
837 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
838 self.mod_local
839 .artifacts
840 .artifact_id_to_scene_object
841 .get(&artifact_id)
842 .cloned()
843 }
844
845 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
846 self.global.segment_ids_edited.contains(object_id)
847 }
848
849 pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
850 self.global
851 .drag_anchors
852 .iter()
853 .find(|anchor| &anchor.segment_id == object_id)
854 .map(|anchor| &anchor.target)
855 }
856
857 pub(super) fn is_in_sketch_block(&self) -> bool {
858 self.mod_local.sketch_block.is_some()
859 }
860
861 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
862 self.mod_local.sketch_block.as_mut()
863 }
864
865 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
866 self.mod_local.sketch_block.as_ref()
867 }
868
869 pub fn next_uuid(&mut self) -> Uuid {
870 self.mod_local.id_generator.next_uuid()
871 }
872
873 pub fn next_artifact_id(&mut self) -> ArtifactId {
874 self.mod_local.id_generator.next_artifact_id()
875 }
876
877 pub fn id_generator(&mut self) -> &mut IdGenerator {
878 &mut self.mod_local.id_generator
879 }
880
881 pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
883 self.mod_local.consumed_solids.insert(consumed_key, info);
884 }
885
886 pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
889 self.mod_local.consumed_solid_ids.insert(consumed_id, info);
890 }
891
892 pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
895 self.mod_local.consumed_solids.get(key)
896 }
897
898 pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
901 self.mod_local.consumed_solid_ids.get(id)
902 }
903
904 pub(crate) fn mark_region_consumed(&mut self, id: Uuid, info: ConsumedRegionInfo) {
905 self.mod_local.consumed_regions.insert(id, info);
906 }
907
908 pub(crate) fn check_region_consumed(&self, id: &Uuid) -> Option<ConsumedRegionInfo> {
909 self.mod_local.consumed_regions.get(id).copied()
910 }
911
912 pub(crate) fn find_var_name_for_region_id(&self, target_id: Uuid) -> Result<Option<String>, KclError> {
916 fn contains_region_id(value: &KclValue, target_id: Uuid) -> bool {
917 match value {
918 KclValue::Sketch { value } => value.origin_sketch_id.is_some() && value.id == target_id,
919 KclValue::HomArray { value, .. } | KclValue::Tuple { value, .. } => {
920 value.iter().any(|value| contains_region_id(value, target_id))
921 }
922 KclValue::Object { value, .. } => value.values().any(|value| contains_region_id(value, target_id)),
923 _ => false,
924 }
925 }
926
927 self.mod_local
928 .stack
929 .find_var_name_in_all_envs(|value| contains_region_id(value, target_id))
930 }
931
932 pub(crate) fn latest_consumed_output(
935 &self,
936 suggested_replacement_key: Option<ConsumedSolidKey>,
937 ) -> Option<ConsumedSolidKey> {
938 let mut latest = suggested_replacement_key?;
939 let mut seen = AhashIndexSet::default();
940
941 while seen.insert(latest) {
942 let Some(next) = self
943 .mod_local
944 .consumed_solids
945 .get(&latest)
946 .and_then(|info| info.suggested_replacement_key())
947 else {
948 break;
949 };
950 latest = next;
951 }
952
953 Some(latest)
954 }
955
956 pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
960 fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
961 match value {
962 KclValue::Solid { value } => {
963 value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
964 }
965 KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
966 _ => false,
967 }
968 }
969 self.mod_local
970 .stack
971 .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
972 }
973
974 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
975 let id = artifact.id();
976 self.mod_local.artifacts.artifacts.insert(id, artifact);
977 }
978
979 pub(crate) fn registered_named_views(&self) -> impl Iterator<Item = (ModuleId, &str)> {
1000 self.mod_local
1001 .artifacts
1002 .artifacts
1003 .values()
1004 .chain(self.global.artifacts.artifacts.values())
1005 .filter_map(|artifact| match artifact {
1006 Artifact::NamedView(view) => Some((view.code_ref.range.module_id(), view.name.as_str())),
1007 _ => None,
1008 })
1009 }
1010
1011 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
1012 self.mod_local.artifacts.artifacts.get_mut(&id)
1013 }
1014
1015 pub(crate) fn is_sketch_block_path(&self, path_id: ArtifactId) -> bool {
1016 self.mod_local
1017 .artifacts
1018 .artifacts
1019 .values()
1020 .chain(self.global.artifacts.artifacts.values())
1021 .any(|artifact| {
1022 matches!(artifact, Artifact::SketchBlock(sketch_block) if sketch_block.path_id == Some(path_id))
1023 })
1024 }
1025
1026 pub(crate) fn push_op(&mut self, op: Operation) {
1027 let index = self.mod_local.artifacts.operations.len();
1028 self.mod_local.artifacts.operations.push(op);
1029 if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
1030 && let Some(callbacks) = &self.execution_callbacks
1031 {
1032 callbacks.on_operation(OperationCallbackArgs {
1033 module_id: self.mod_local.module_id,
1034 operation,
1035 index,
1036 });
1037 }
1038 }
1039
1040 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
1041 self.mod_local.artifacts.unprocessed_commands.push(command);
1042 }
1043
1044 pub(super) fn next_module_id(&self) -> ModuleId {
1045 ModuleId::from_usize(self.global.path_to_source_id.len())
1046 }
1047
1048 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
1049 self.global.path_to_source_id.get(path).cloned()
1050 }
1051
1052 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
1053 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
1054 self.global.path_to_source_id.insert(path, id);
1055 }
1056
1057 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
1058 let root_id = ModuleId::default();
1059 let path = self
1061 .global
1062 .path_to_source_id
1063 .iter()
1064 .find(|(_, v)| **v == root_id)
1065 .unwrap()
1066 .0
1067 .clone();
1068 self.add_id_to_source(
1069 root_id,
1070 ModuleSource {
1071 path,
1072 source: program.original_file_contents.to_string(),
1073 },
1074 );
1075 }
1076
1077 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
1078 self.global.id_to_source.insert(id, source);
1079 }
1080
1081 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
1082 debug_assert!(self.global.path_to_source_id.contains_key(&path));
1083 let module_info = ModuleInfo { id, repr, path };
1084 self.global.module_infos.insert(id, module_info);
1085 }
1086
1087 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
1088 self.global.module_infos.get(&id)
1089 }
1090
1091 #[cfg(test)]
1092 pub(crate) fn modules(&self) -> &ModuleInfoMap {
1093 &self.global.module_infos
1094 }
1095
1096 #[cfg(test)]
1097 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
1098 &self.global.root_module_artifacts
1099 }
1100
1101 pub(crate) fn record_edge_refactor_meta(&mut self, meta: EdgeRefactorMeta) {
1103 self.mod_local
1104 .artifacts
1105 .refactor_metadata
1106 .push(RefactorMetadata::EdgeRefactor(Box::new(meta)));
1107 }
1108
1109 pub(crate) fn record_pending_edge_refactor_meta(&mut self, meta: PendingEdgeRefactorMeta) {
1110 self.mod_local.artifacts.pending_edge_refactor_metadata.push(meta);
1111 }
1112
1113 pub(crate) fn pending_edge_refactor_meta(
1114 &self,
1115 edge_id: Uuid,
1116 argument_source_range: SourceRange,
1117 ) -> Option<PendingEdgeRefactorMeta> {
1118 if !crate::runtime_flags::z0006_refactor_metadata_enabled() {
1119 return None;
1120 }
1121 if let Some(pending) = self
1122 .mod_local
1123 .artifacts
1124 .pending_edge_refactor_metadata
1125 .iter()
1126 .find(|meta| meta.edge_id == edge_id && argument_source_range.contains_range(&meta.source_range))
1127 {
1128 return Some(pending.clone());
1129 }
1130
1131 let mut matches = self
1134 .mod_local
1135 .artifacts
1136 .pending_edge_refactor_metadata
1137 .iter()
1138 .filter(|meta| meta.edge_id == edge_id);
1139 let pending = matches.next()?.clone();
1140 matches.next().is_none().then_some(pending)
1141 }
1142
1143 pub(crate) fn record_edge_refactor_meta_from_pending(
1144 &mut self,
1145 edge_id: Uuid,
1146 source_range: SourceRange,
1147 face_ids: [Uuid; 2],
1148 ) -> bool {
1149 if self.mod_local.artifacts.refactor_metadata.iter().any(|meta| {
1150 matches!(
1151 meta,
1152 RefactorMetadata::EdgeRefactor(meta)
1153 if meta.edge_id == edge_id && meta.source_range == source_range
1154 )
1155 }) {
1156 return true;
1157 }
1158
1159 let exact_pending_meta = self
1160 .mod_local
1161 .artifacts
1162 .pending_edge_refactor_metadata
1163 .iter()
1164 .find(|meta| meta.edge_id == edge_id && meta.source_range == source_range)
1165 .cloned();
1166
1167 let edge_pending_meta = || {
1168 let mut matches = self
1169 .mod_local
1170 .artifacts
1171 .pending_edge_refactor_metadata
1172 .iter()
1173 .filter(|meta| meta.edge_id == edge_id);
1174 let pending_meta = matches.next()?.clone();
1175 matches.next().is_none().then_some(pending_meta)
1176 };
1177
1178 let Some(pending_meta) = exact_pending_meta.or_else(edge_pending_meta) else {
1179 return false;
1180 };
1181
1182 self.record_edge_refactor_meta(EdgeRefactorMeta {
1183 edge_id,
1184 face_ids,
1185 end_face_ids: Vec::new(),
1186 source_range: pending_meta.source_range,
1187 stdlib_fn: pending_meta.stdlib_fn,
1188 });
1189
1190 true
1191 }
1192
1193 pub(crate) fn record_direct_tag_fillet_meta(&mut self, meta: DirectTagFilletMeta) {
1195 self.mod_local
1196 .artifacts
1197 .refactor_metadata
1198 .push(RefactorMetadata::DirectTagFillet(meta));
1199 }
1200
1201 pub fn edge_refactor_metadata(&self) -> Vec<EdgeRefactorMeta> {
1203 self.global
1204 .root_module_artifacts
1205 .refactor_metadata
1206 .iter()
1207 .filter_map(|m| match m {
1208 RefactorMetadata::EdgeRefactor(meta) => Some(meta.as_ref().clone()),
1209 RefactorMetadata::DirectTagFillet(_) | RefactorMetadata::LegacyAngle(_) => None,
1210 })
1211 .collect()
1212 }
1213
1214 pub fn direct_tag_fillet_metadata(&self) -> Vec<DirectTagFilletMeta> {
1216 self.global
1217 .root_module_artifacts
1218 .refactor_metadata
1219 .iter()
1220 .filter_map(|m| match m {
1221 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::LegacyAngle(_) => None,
1222 RefactorMetadata::DirectTagFillet(meta) => Some(meta.clone()),
1223 })
1224 .collect()
1225 }
1226
1227 pub fn current_default_units(&self) -> NumericType {
1228 NumericType::Default {
1229 len: self.length_unit(),
1230 angle: self.angle_unit(),
1231 }
1232 }
1233
1234 pub fn length_unit(&self) -> UnitLength {
1235 self.mod_local.settings.default_length_units
1236 }
1237
1238 pub fn angle_unit(&self) -> UnitAngle {
1239 self.mod_local.settings.default_angle_units
1240 }
1241
1242 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
1243 KclError::new_import_cycle(KclErrorDetails::new(
1244 format!(
1245 "circular import of modules is not allowed: {} -> {}",
1246 self.global
1247 .mod_loader
1248 .import_stack
1249 .iter()
1250 .map(|p| p.to_string_lossy())
1251 .collect::<Vec<_>>()
1252 .join(" -> "),
1253 path,
1254 ),
1255 vec![source_range],
1256 ))
1257 }
1258
1259 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
1260 self.mod_local.pipe_value.as_ref()
1261 }
1262
1263 pub(crate) fn error_with_outputs(
1264 &self,
1265 error: KclError,
1266 main_ref: Option<EnvironmentRef>,
1267 default_planes: Option<DefaultPlanes>,
1268 ) -> KclErrorWithOutputs {
1269 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
1270 .global
1271 .path_to_source_id
1272 .iter()
1273 .map(|(k, v)| ((*v), k.clone()))
1274 .collect();
1275
1276 KclErrorWithOutputs::new(
1277 error,
1278 self.issues().to_vec(),
1279 main_ref
1280 .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
1281 .unwrap_or_default(),
1282 self.global.operations_by_module(),
1283 Default::default(),
1284 self.global.artifacts.graph.clone(),
1285 self.global.root_module_artifacts.scene_objects.clone(),
1286 self.global.root_module_artifacts.source_range_to_object.clone(),
1287 self.global.root_module_artifacts.var_solutions.clone(),
1288 self.global.root_module_artifacts.refactor_metadata.clone(),
1289 module_id_to_module_path,
1290 self.global.id_to_source.clone(),
1291 default_planes,
1292 )
1293 }
1294
1295 pub(crate) fn build_program_lookup(
1296 &self,
1297 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1298 ) -> ProgramLookup {
1299 ProgramLookup::new(current, self.global.module_infos.clone())
1300 }
1301
1302 pub(crate) async fn build_artifact_graph(
1303 &mut self,
1304 engine: &Arc<EngineManager>,
1305 program: NodeRef<'_, crate::parsing::ast::types::Program>,
1306 ) -> Result<(), KclError> {
1307 let mut new_commands = Vec::new();
1308 let mut new_exec_artifacts = IndexMap::new();
1309 for module in self.global.module_infos.values_mut() {
1310 match &mut module.repr {
1311 ModuleRepr::Kcl(_, Some(outcome)) => {
1312 new_commands.extend(outcome.artifacts.process_commands());
1313 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
1314 }
1315 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
1316 new_commands.extend(module_artifacts.process_commands());
1317 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
1318 }
1319 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
1320 }
1321 }
1322 new_commands.extend(self.global.root_module_artifacts.process_commands());
1325 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
1328 let new_responses = engine.take_responses().await;
1329
1330 for (id, exec_artifact) in new_exec_artifacts {
1333 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
1337 }
1338
1339 let initial_graph = self.global.artifacts.graph.clone();
1340
1341 let programs = self.build_program_lookup(program.clone());
1343 let graph_result = crate::execution::artifact::build_artifact_graph(
1344 &new_commands,
1345 &new_responses,
1346 program,
1347 &mut self.global.artifacts.artifacts,
1348 initial_graph,
1349 &programs,
1350 &self.global.module_infos,
1351 );
1352
1353 #[cfg(feature = "snapshot-engine-responses")]
1354 {
1355 self.global.root_module_artifacts.responses.extend(new_responses);
1357 }
1358
1359 let artifact_graph = graph_result?;
1360 self.global.artifacts.graph = artifact_graph;
1361
1362 Ok(())
1363 }
1364
1365 pub(crate) fn kcl_version(&self) -> KclVersion {
1372 match self.global.entry_point_kcl_version {
1373 Some(version) if version >= KclVersion::V3Preview => version,
1374 _ => self.legacy_caller_kcl_version(),
1375 }
1376 }
1377
1378 pub(crate) fn legacy_caller_kcl_version(&self) -> KclVersion {
1387 self.mod_local.settings.kcl_version
1388 }
1389
1390 pub(crate) fn entry_point_version_is_v3_or_higher(&self) -> bool {
1395 self.global
1396 .entry_point_kcl_version
1397 .is_some_and(|v| v >= KclVersion::V3Preview)
1398 }
1399
1400 pub(crate) fn set_entry_point_kcl_version(&mut self, program: &crate::Program) {
1405 self.global.entry_point_kcl_version = declared_kcl_version(&program.ast)
1406 .ok()
1407 .flatten()
1408 .map(|(version, _)| version);
1409 }
1410
1411 pub(crate) fn check_imported_module_kcl_version(
1433 &self,
1434 path: &ModulePath,
1435 program: &Node<Program>,
1436 import_range: Option<SourceRange>,
1437 ) -> Result<(), KclError> {
1438 if !path.is_local() {
1439 return Ok(());
1442 }
1443 let Some((declared, declared_range)) = declared_kcl_version(program)? else {
1444 return Ok(());
1445 };
1446 let entry_point_version = self.global.entry_point_kcl_version;
1447 let allowed = if self.entry_point_version_is_v3_or_higher() {
1448 Some(declared) == entry_point_version
1449 } else {
1450 declared < KclVersion::V3Preview
1451 };
1452 if allowed {
1453 return Ok(());
1454 }
1455
1456 let entry_point = match self
1459 .global
1460 .module_infos
1461 .get(&ModuleId::default())
1462 .map(|info| &info.path)
1463 {
1464 Some(root @ ModulePath::Local { .. }) if !root.to_string().is_empty() => {
1465 format!("The entry point `{root}`")
1466 }
1467 _ => "The entry point".to_owned(),
1468 };
1469 let (entry_point_declares, fix) = match entry_point_version {
1470 Some(version) => (
1471 format!("declares kclVersion {}", version.as_str()),
1472 "Update the kclVersion setting in one of these files to match the other.",
1473 ),
1474 None => (
1475 "does not declare a kclVersion".to_owned(),
1476 "Declare the same kclVersion in the entry point, or update the setting in the imported file.",
1477 ),
1478 };
1479 let mut source_ranges = vec![declared_range];
1480 source_ranges.extend(import_range);
1481 Err(KclError::new_semantic(KclErrorDetails::new(
1482 format!(
1483 "Mixing KCL versions in a single program is not allowed. {entry_point} {entry_point_declares}, but the imported file `{path}` declares kclVersion {}. {fix}",
1484 declared.as_str(),
1485 ),
1486 source_ranges,
1487 )))
1488 }
1489}
1490
1491pub(crate) fn declared_kcl_version(program: &Node<Program>) -> Result<Option<(KclVersion, SourceRange)>, KclError> {
1497 let Some(property) = program
1498 .inner_attrs
1499 .iter()
1500 .rev()
1501 .filter(|annotation| annotation.name() == Some(annotations::SETTINGS))
1502 .find_map(|annotation| {
1503 annotation
1504 .properties
1505 .as_deref()
1506 .unwrap_or_default()
1507 .iter()
1508 .rev()
1509 .find(|property| &*property.inner.key.name == annotations::SETTINGS_VERSION)
1510 })
1511 else {
1512 return Ok(None);
1513 };
1514 let value = annotations::expect_kcl_version(&property.inner.value)?;
1515 let version = value.parse::<KclVersion>().map_err(|err| {
1516 KclError::new_semantic(KclErrorDetails::new(err.to_string(), vec![property.as_source_range()]))
1517 })?;
1518 Ok(Some((version, property.as_source_range())))
1519}
1520
1521impl GlobalState {
1522 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
1523 let mut global = GlobalState {
1524 machine_depth_high_water: 0,
1525 path_to_source_id: Default::default(),
1526 module_infos: Default::default(),
1527 artifacts: Default::default(),
1528 root_module_artifacts: Default::default(),
1529 mod_loader: Default::default(),
1530 issues: Default::default(),
1531 deprecation_version_override: None,
1532 entry_point_kcl_version: None,
1533 id_to_source: Default::default(),
1534 segment_ids_edited,
1535 drag_anchors: Vec::new(),
1536 sketch_mode: false,
1537 geometry_only: settings.geometry_only,
1538 };
1539
1540 let root_id = ModuleId::default();
1541 let root_path = settings.current_file.clone().unwrap_or_default();
1542 global.module_infos.insert(
1543 root_id,
1544 ModuleInfo {
1545 id: root_id,
1546 path: ModulePath::Local {
1547 value: root_path.clone(),
1548 original_import_path: None,
1549 },
1550 repr: ModuleRepr::Root,
1551 },
1552 );
1553 global.path_to_source_id.insert(
1554 ModulePath::Local {
1555 value: root_path,
1556 original_import_path: None,
1557 },
1558 root_id,
1559 );
1560 global
1561 }
1562
1563 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1564 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1565 }
1566
1567 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1568 self.id_to_source.get(&id)
1569 }
1570}
1571
1572impl ArtifactState {
1573 pub fn cached_body_items(&self) -> usize {
1574 self.graph.item_count()
1575 }
1576
1577 pub(crate) fn clear(&mut self) {
1578 self.artifacts.clear();
1579 self.graph.clear();
1580 }
1581}
1582
1583impl ModuleArtifactState {
1584 pub fn legacy_angle_refactor_metadata(&self) -> Vec<LegacyAngleRefactorMeta> {
1585 self.refactor_metadata
1586 .iter()
1587 .filter_map(|metadata| match metadata {
1588 RefactorMetadata::LegacyAngle(metadata) => Some(*metadata),
1589 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::DirectTagFillet(_) => None,
1590 })
1591 .collect()
1592 }
1593
1594 pub(crate) fn clear(&mut self) {
1595 self.artifacts.clear();
1596 self.unprocessed_commands.clear();
1597 self.commands.clear();
1598 self.operations.clear();
1599 self.refactor_metadata.clear();
1600 }
1601
1602 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1603 self.scene_objects = scene_objects.to_vec();
1604 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1605 self.source_range_to_object.clear();
1606 self.artifact_id_to_scene_object.clear();
1607
1608 for (expected_id, object) in self.scene_objects.iter().enumerate() {
1609 debug_assert_eq!(
1610 object.id.0, expected_id,
1611 "Restored cached scene object ID {} does not match its position {}",
1612 object.id.0, expected_id
1613 );
1614
1615 match &object.kind {
1616 ObjectKind::Wall(wall) => {
1617 self.source_range_to_object.insert(wall.source.solid.range, object.id);
1618 }
1619 ObjectKind::Cap(cap) => {
1620 self.source_range_to_object.insert(cap.source.solid.range, object.id);
1621 }
1622 _ => match &object.source {
1623 crate::front::SourceRef::Simple { range, node_path: _ } => {
1624 self.source_range_to_object.insert(*range, object.id);
1625 }
1626 crate::front::SourceRef::BackTrace { ranges } => {
1627 if let Some((range, _)) = ranges.first() {
1630 self.source_range_to_object.insert(*range, object.id);
1631 }
1632 }
1633 },
1634 }
1635
1636 if object.artifact_id != ArtifactId::placeholder() {
1638 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1639 }
1640 }
1641 }
1642
1643 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1645 self.artifacts.extend(other.artifacts);
1646 self.unprocessed_commands.extend(other.unprocessed_commands);
1647 self.commands.extend(other.commands);
1648 self.operations.extend(other.operations);
1649 if other.scene_objects.len() > self.scene_objects.len() {
1650 self.scene_objects
1651 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1652 }
1653 self.source_range_to_object.extend(other.source_range_to_object);
1654 self.artifact_id_to_scene_object
1655 .extend(other.artifact_id_to_scene_object);
1656 self.var_solutions.extend(other.var_solutions);
1657 self.refactor_metadata.extend(other.refactor_metadata);
1658 }
1659
1660 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1664 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1665 let new_module_commands = unprocessed.clone();
1666 self.commands.extend(unprocessed);
1667 new_module_commands
1668 }
1669
1670 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1671 debug_assert!(
1672 id.0 < self.scene_objects.len(),
1673 "Requested object ID {} but only have {} objects",
1674 id.0,
1675 self.scene_objects.len()
1676 );
1677 self.scene_objects.get(id.0)
1678 }
1679
1680 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1681 debug_assert!(
1682 id.0 < self.scene_objects.len(),
1683 "Requested object ID {} but only have {} objects",
1684 id.0,
1685 self.scene_objects.len()
1686 );
1687 self.scene_objects.get_mut(id.0)
1688 }
1689}
1690
1691impl ModuleState {
1692 pub(super) fn new(
1693 path: ModulePath,
1694 memory: Arc<ProgramMemory>,
1695 module_id: Option<ModuleId>,
1696 sketch_mode: bool,
1697 freedom_analysis: bool,
1698 ) -> Self {
1699 let state_module_id = module_id.unwrap_or_default();
1700 ModuleState {
1701 module_id: state_module_id,
1702 id_generator: IdGenerator::new(module_id),
1703 stack: memory.new_stack(),
1704 call_stack_size: 0,
1705 machine_call_depth: 0,
1706 pipe_value: Default::default(),
1707 being_declared: Default::default(),
1708 sketch_block: Default::default(),
1709 stdlib_entry_source_range: Default::default(),
1710 module_exports: Default::default(),
1711 explicit_length_units: false,
1712 path,
1713 settings: Default::default(),
1714 sketch_mode,
1715 freedom_analysis,
1716 artifacts: Default::default(),
1717 constraint_state: Default::default(),
1718 allowed_warnings: Vec::new(),
1719 denied_warnings: Vec::new(),
1720 consumed_solids: AHashMap::default(),
1721 consumed_solid_ids: AHashMap::default(),
1722 consumed_regions: AHashMap::default(),
1723 inside_stdlib: false,
1724 }
1725 }
1726
1727 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1728 self.stack.find_all_in_env_owned(main_ref)
1729 }
1730}
1731
1732impl SketchBlockState {
1733 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1734 SketchVarId(self.sketch_vars.len())
1735 }
1736
1737 pub(crate) fn var_solutions(
1740 &self,
1741 solve_outcome: &Solved,
1742 solution_ty: NumericType,
1743 sketch_block_range: SourceRange,
1744 ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1745 self.sketch_vars
1746 .iter()
1747 .map(|v| {
1748 let Some(sketch_var) = v.as_sketch_var() else {
1749 return Err(KclError::new_internal(KclErrorDetails::new(
1750 "Expected sketch variable".to_owned(),
1751 vec![sketch_block_range],
1752 )));
1753 };
1754 let var_index = sketch_var.id.0;
1755 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1756 let message = format!("No solution for sketch variable with id {}", var_index);
1757 debug_assert!(false, "{}", &message);
1758 KclError::new_internal(KclErrorDetails::new(
1759 message,
1760 sketch_var.meta.iter().map(|m| m.source_range).collect(),
1761 ))
1762 })?;
1763 let solved_value = Number {
1764 value: *solved_n,
1765 units: solution_ty.try_into().map_err(|_| {
1766 KclError::new_internal(KclErrorDetails::new(
1767 "Failed to convert numeric type to units".to_owned(),
1768 vec![sketch_block_range],
1769 ))
1770 })?,
1771 };
1772 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1773 return Ok(None);
1774 };
1775 Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1776 })
1777 .filter_map(Result::transpose)
1778 .collect::<Result<Vec<_>, KclError>>()
1779 }
1780}
1781
1782#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1783#[ts(export)]
1784#[serde(rename_all = "camelCase")]
1785pub struct MetaSettings {
1786 pub default_length_units: UnitLength,
1787 pub default_angle_units: UnitAngle,
1788 pub experimental_features: annotations::WarningLevel,
1789 pub kcl_version: KclVersion,
1790}
1791
1792impl Default for MetaSettings {
1793 fn default() -> Self {
1794 MetaSettings {
1795 default_length_units: UnitLength::Millimeters,
1796 default_angle_units: UnitAngle::Degrees,
1797 experimental_features: annotations::WarningLevel::Deny,
1798 kcl_version: KclVersion::default(),
1799 }
1800 }
1801}
1802
1803impl MetaSettings {
1804 pub(crate) fn update_from_annotation(
1805 &mut self,
1806 annotation: &crate::parsing::ast::types::Node<Annotation>,
1807 ) -> Result<(bool, bool), KclError> {
1808 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1809
1810 let mut updated_len = false;
1811 let mut updated_angle = false;
1812 for p in properties {
1813 match &*p.inner.key.name {
1814 annotations::SETTINGS_UNIT_LENGTH => {
1815 let value = annotations::expect_ident(&p.inner.value)?;
1816 let value = super::types::length_from_str(value, annotation.as_source_range())?;
1817 self.default_length_units = value;
1818 updated_len = true;
1819 }
1820 annotations::SETTINGS_UNIT_ANGLE => {
1821 let value = annotations::expect_ident(&p.inner.value)?;
1822 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1823 self.default_angle_units = value;
1824 updated_angle = true;
1825 }
1826 annotations::SETTINGS_VERSION => {
1827 let value = annotations::expect_kcl_version(&p.inner.value)?;
1828 self.kcl_version = value.parse()?;
1829 }
1830 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1831 let value = annotations::expect_ident(&p.inner.value)?;
1832 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1833 KclError::new_semantic(KclErrorDetails::new(
1834 format!(
1835 "Invalid value for {} settings property, expected one of: {}",
1836 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1837 annotations::WARN_LEVELS.join(", ")
1838 ),
1839 annotation.as_source_ranges(),
1840 ))
1841 })?;
1842 self.experimental_features = value;
1843 }
1844 name => {
1845 return Err(KclError::new_semantic(KclErrorDetails::new(
1846 format!(
1847 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1848 annotations::SETTINGS_UNIT_LENGTH,
1849 annotations::SETTINGS_UNIT_ANGLE
1850 ),
1851 vec![annotation.as_source_range()],
1852 )));
1853 }
1854 }
1855 }
1856
1857 Ok((updated_len, updated_angle))
1858 }
1859}
1860
1861#[cfg(test)]
1862mod tests {
1863
1864 use uuid::Uuid;
1865
1866 use super::KclVersion;
1867 use super::ModuleArtifactState;
1868 use crate::NodePath;
1869 use crate::NodePathExt;
1870 use crate::SourceRange;
1871 use crate::execution::ArtifactId;
1872 use crate::front::Object;
1873 use crate::front::ObjectId;
1874 use crate::front::ObjectKind;
1875 use crate::front::Plane;
1876 use crate::front::SourceRef;
1877
1878 #[test]
1879 fn declared_kcl_version_finds_the_setting_and_its_range() {
1880 let parse = |code: &str| crate::parsing::top_level_parse(code).unwrap();
1881
1882 assert_eq!(super::declared_kcl_version(&parse("x = 1\n")).unwrap(), None);
1883 assert_eq!(
1884 super::declared_kcl_version(&parse("@settings(defaultLengthUnit = in)\nx = 1\n")).unwrap(),
1885 None
1886 );
1887
1888 let code = "@settings(defaultLengthUnit = in, kclVersion = 2.0)\nx = 1\n";
1889 let (version, range) = super::declared_kcl_version(&parse(code)).unwrap().unwrap();
1890 assert_eq!(version, KclVersion::V2);
1891 let start = code.find("kclVersion").unwrap();
1892 assert_eq!((range.start(), range.end()), (start, start + "kclVersion = 2.0".len()));
1893
1894 let (version, _) = super::declared_kcl_version(&parse("@settings(kclVersion = \"3.0-preview\")\n"))
1895 .unwrap()
1896 .unwrap();
1897 assert_eq!(version, KclVersion::V3Preview);
1898
1899 let code = "@settings(kclVersion = 1.0)\n@settings(defaultLengthUnit = in)\n@settings(kclVersion = 2.0, kclVersion = \"3.0-preview\")\n";
1902 let (version, range) = super::declared_kcl_version(&parse(code)).unwrap().unwrap();
1903 assert_eq!(version, KclVersion::V3Preview);
1904 let start = code.rfind("kclVersion").unwrap();
1905 assert_eq!(
1906 (range.start(), range.end()),
1907 (start, start + "kclVersion = \"3.0-preview\"".len())
1908 );
1909
1910 let code = "@settings(kclVersion = 9.0)\n";
1912 let error = super::declared_kcl_version(&parse(code)).unwrap_err();
1913 let start = code.find("kclVersion").unwrap();
1914 assert_eq!(
1915 error.source_ranges().first().map(|range| (range.start(), range.end())),
1916 Some((start, start + "kclVersion = 9.0".len()))
1917 );
1918 }
1919
1920 #[test]
1921 fn kcl_version_serializes_as_canonical_setting_value() {
1922 assert_eq!(serde_json::to_string(&KclVersion::V1).unwrap(), r#""1.0""#);
1923 assert_eq!(serde_json::to_string(&KclVersion::V2).unwrap(), r#""2.0""#);
1924 assert_eq!(
1925 serde_json::to_string(&KclVersion::V3Preview).unwrap(),
1926 r#""3.0-preview""#
1927 );
1928 }
1929
1930 #[test]
1931 fn restore_scene_objects_rebuilds_lookup_maps() {
1932 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1933 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1934 let plane_range = SourceRange::from([1, 4, 0]);
1935 let plane_node_path = Some(NodePath::placeholder());
1936 let sketch_ranges = vec![
1937 (SourceRange::from([5, 9, 0]), None),
1938 (SourceRange::from([10, 12, 0]), None),
1939 ];
1940 let cached_objects = vec![
1941 Object {
1942 id: ObjectId(0),
1943 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1944 label: Default::default(),
1945 comments: Default::default(),
1946 artifact_id: plane_artifact_id,
1947 source: SourceRef::new(plane_range, plane_node_path),
1948 },
1949 Object {
1950 id: ObjectId(1),
1951 kind: ObjectKind::Nil,
1952 label: Default::default(),
1953 comments: Default::default(),
1954 artifact_id: sketch_artifact_id,
1955 source: SourceRef::BackTrace {
1956 ranges: sketch_ranges.clone(),
1957 },
1958 },
1959 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1960 ];
1961
1962 let mut artifacts = ModuleArtifactState::default();
1963 artifacts.restore_scene_objects(&cached_objects);
1964
1965 assert_eq!(artifacts.scene_objects, cached_objects);
1966 assert_eq!(
1967 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1968 Some(&ObjectId(0))
1969 );
1970 assert_eq!(
1971 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1972 Some(&ObjectId(1))
1973 );
1974 assert_eq!(
1975 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1976 None
1977 );
1978 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1979 assert_eq!(
1980 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1981 Some(&ObjectId(1))
1982 );
1983 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1985 }
1986}