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 never_type_ranges: IndexMap<ModuleId, Vec<SourceRange>>,
96 pub mod_loader: ModuleLoader,
98 pub issues: Vec<CompilationIssue>,
100 pub deprecation_version_override: Option<String>,
104 pub entry_point_kcl_version: Option<KclVersion>,
114 pub artifacts: ArtifactState,
116 pub root_module_artifacts: ModuleArtifactState,
118 pub segment_ids_edited: AhashIndexSet<ObjectId>,
120 pub drag_anchors: Vec<SegmentDragAnchor>,
122 pub sketch_mode: bool,
127 pub geometry_only: bool,
129 pub std_not_yet_added: IndexMap<String, NotYetAdded>,
133 #[cfg(test)]
136 pub(crate) hint_all_not_yet_added: bool,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
143pub struct NotYetAdded {
144 pub added_in: annotations::VersionConstraint,
146 pub is_std: bool,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
153pub(crate) struct ScopedNotYetAdded {
154 pub item: NotYetAdded,
155 pub exported: bool,
156}
157
158impl GlobalState {
159 pub(crate) fn operations_by_module(&self) -> OperationsByModule {
160 let mut operations = OperationsByModule::default();
161 operations.insert(ModuleId::default(), self.root_module_artifacts.operations.clone());
162
163 for (module_id, module_info) in &self.module_infos {
164 match &module_info.repr {
165 ModuleRepr::Root => {}
166 ModuleRepr::Kcl(_, Some(outcome)) => {
167 operations.insert(*module_id, outcome.artifacts.operations.clone());
168 }
169 ModuleRepr::Foreign(_, Some((_, artifacts))) => {
170 operations.insert(*module_id, artifacts.operations.clone());
171 }
172 ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
173 }
174 }
175
176 operations
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
181pub(crate) enum ConstraintKey {
182 LineCircle([usize; 10]),
183 CircleCircle([usize; 12]),
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub(crate) enum TangencyMode {
188 LineCircle(ezpz::LineSide),
189 CircleCircle(ezpz::CircleSide),
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub(crate) enum ConstraintState {
194 Tangency(TangencyMode),
195}
196
197#[derive(Debug, Clone, Default)]
198pub(super) struct ArtifactState {
199 pub artifacts: IndexMap<ArtifactId, Artifact>,
202 pub graph: ArtifactGraph,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
208#[ts(export)]
209#[serde(rename_all = "camelCase")]
210pub enum EdgeRefactorStdlibFn {
211 GetOppositeEdge,
212 GetNextAdjacentEdge,
213 GetPreviousAdjacentEdge,
214 GetCommonEdge,
215 EdgeId,
216 DirectEdgeTag,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
221#[ts(export)]
222#[serde(rename_all = "camelCase")]
223pub struct EdgeRefactorMeta {
224 pub edge_id: Uuid,
225 pub face_ids: [Uuid; 2],
226 #[serde(default, skip_serializing_if = "Vec::is_empty")]
227 pub end_face_ids: Vec<Uuid>,
228 pub source_range: SourceRange,
229 pub stdlib_fn: EdgeRefactorStdlibFn,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
235pub(crate) struct PendingEdgeRefactorMeta {
236 pub edge_id: Uuid,
237 pub source_range: SourceRange,
238 pub stdlib_fn: EdgeRefactorStdlibFn,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
243#[ts(export)]
244#[serde(rename_all = "camelCase")]
245pub struct DirectTagFilletTagEntry {
246 pub tag_identifier: String,
247 pub edge_id: Uuid,
248 pub face_ids: [Uuid; 2],
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
253#[ts(export)]
254#[serde(rename_all = "camelCase")]
255pub struct DirectTagFilletMeta {
256 pub call_source_range: SourceRange,
257 pub tags: Vec<DirectTagFilletTagEntry>,
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
263#[ts(export)]
264#[serde(rename_all = "camelCase")]
265pub struct LegacyAngleRefactorMeta {
266 pub source_range: SourceRange,
267 pub sector: u8,
268 pub inverse: bool,
269}
270
271#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
273#[ts(export)]
274#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
275pub enum RefactorMetadata {
276 EdgeRefactor(Box<EdgeRefactorMeta>),
277 DirectTagFillet(DirectTagFilletMeta),
278 LegacyAngle(LegacyAngleRefactorMeta),
279}
280
281#[derive(Debug, Clone)]
282pub(crate) struct PendingLegacyAngleRefactorMeta {
283 pub source_range: SourceRange,
284 pub lines: [ConstrainableLine2d; 2],
285 pub desired_angle_radians: f64,
286}
287
288#[derive(Debug, Clone, Default, PartialEq, Serialize)]
290pub struct ModuleArtifactState {
291 pub artifacts: IndexMap<ArtifactId, Artifact>,
293 #[serde(skip)]
296 pub unprocessed_commands: Vec<ArtifactCommand>,
297 pub commands: Vec<ArtifactCommand>,
299 #[cfg(feature = "snapshot-engine-responses")]
301 pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
302 pub operations: Vec<Operation>,
305 pub object_id_generator: IncIdGenerator<usize>,
307 pub scene_objects: Vec<Object>,
309 pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
312 pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
314 pub var_solutions: Vec<(SourceRange, Option<NodePath>, Number)>,
316 pub refactor_metadata: Vec<RefactorMetadata>,
318 #[serde(skip)]
321 pub(crate) pending_edge_refactor_metadata: Vec<PendingEdgeRefactorMeta>,
322}
323
324#[derive(Debug, Clone)]
325pub(super) struct ModuleState {
326 pub module_id: ModuleId,
328 pub id_generator: IdGenerator,
330 pub stack: Stack,
331 pub(super) call_stack_size: usize,
335 pub(crate) machine_call_depth: usize,
338 pub pipe_value: Option<KclValue>,
341 pub being_declared: Option<String>,
345 pub sketch_block: Option<SketchBlockState>,
347 pub inside_stdlib: bool,
350 pub stdlib_entry_source_range: Option<SourceRange>,
352 pub module_exports: Vec<String>,
354 pub not_yet_added: IndexMap<String, ScopedNotYetAdded>,
357 pub settings: MetaSettings,
359 pub sketch_mode: bool,
362 pub freedom_analysis: bool,
366 pub(super) explicit_length_units: bool,
367 pub(super) path: ModulePath,
368 pub artifacts: ModuleArtifactState,
370 pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
374
375 pub(super) allowed_warnings: Vec<&'static str>,
376 pub(super) denied_warnings: Vec<&'static str>,
377
378 pub(super) consumed_solids: AHashMap<ConsumedSolidKey, ConsumedSolidInfo>,
383 pub(super) consumed_solid_ids: AHashMap<Uuid, ConsumedSolidInfo>,
389 pub(super) consumed_regions: AHashMap<Uuid, ConsumedRegionInfo>,
393}
394
395#[derive(Debug, Clone, Copy)]
397pub(crate) struct ConsumedRegionInfo {
398 operation: ConsumedRegionOperation,
399}
400
401impl ConsumedRegionInfo {
402 pub(crate) fn new(operation: ConsumedRegionOperation) -> Self {
403 Self { operation }
404 }
405
406 pub(crate) fn operation(self) -> ConsumedRegionOperation {
407 self.operation
408 }
409}
410
411#[derive(Debug, Clone, Copy, PartialEq, Eq)]
412pub(crate) enum ConsumedRegionOperation {
413 Extrude,
414 Revolve,
415 Sweep,
416 Delete,
417}
418
419impl std::fmt::Display for ConsumedRegionOperation {
420 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421 match self {
422 Self::Extrude => f.write_str("extrude"),
423 Self::Revolve => f.write_str("revolve"),
424 Self::Sweep => f.write_str("sweep"),
425 Self::Delete => f.write_str("delete"),
426 }
427 }
428}
429
430#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
432pub(crate) struct ConsumedSolidKey {
433 engine_id: Uuid,
435 instance_id: Uuid,
438}
439
440impl ConsumedSolidKey {
441 pub(crate) fn new(engine_id: Uuid, instance_id: Uuid) -> Self {
442 Self { engine_id, instance_id }
443 }
444
445 pub(crate) fn engine_id(&self) -> Uuid {
446 self.engine_id
447 }
448
449 pub(crate) fn instance_id(&self) -> Uuid {
450 self.instance_id
451 }
452}
453
454#[derive(Debug, Clone)]
458pub(crate) struct ConsumedSolidInfo {
459 operation: ConsumedSolidOperation,
461 suggested_replacement_key: Option<ConsumedSolidKey>,
465 returned_solid_keys: Vec<ConsumedSolidKey>,
468}
469
470impl ConsumedSolidInfo {
471 pub(crate) fn new(operation: ConsumedSolidOperation, returned_solid_keys: Vec<ConsumedSolidKey>) -> Self {
472 Self {
473 operation,
474 suggested_replacement_key: returned_solid_keys.first().copied(),
475 returned_solid_keys,
476 }
477 }
478
479 pub(crate) fn operation(&self) -> ConsumedSolidOperation {
480 self.operation
481 }
482
483 pub(crate) fn suggested_replacement_key(&self) -> Option<ConsumedSolidKey> {
484 self.suggested_replacement_key
485 }
486
487 pub(crate) fn should_report_reused_engine_id_as_consumed(&self, key: ConsumedSolidKey) -> bool {
488 !self.returned_solid_keys.contains(&key)
489 }
490}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq)]
493pub(crate) enum ConsumedSolidOperation {
494 Union,
495 Intersect,
496 Subtract,
497 Split,
498 JoinSurfaces,
499}
500
501impl ConsumedSolidOperation {
502 pub(crate) fn indefinite_article(self) -> &'static str {
503 match self {
504 Self::Intersect => "an",
505 Self::Union | Self::Subtract | Self::Split | Self::JoinSurfaces => "a",
506 }
507 }
508}
509
510impl std::fmt::Display for ConsumedSolidOperation {
511 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
512 match self {
513 Self::Union => f.write_str("union"),
514 Self::Intersect => f.write_str("intersect"),
515 Self::Subtract => f.write_str("subtract"),
516 Self::Split => f.write_str("split"),
517 Self::JoinSurfaces => f.write_str("joinSurfaces"),
518 }
519 }
520}
521
522#[derive(Debug, Clone, Default)]
523pub(crate) struct SketchBlockState {
524 pub sketch_vars: Vec<KclValue>,
525 pub sketch_id: Option<ObjectId>,
526 pub sketch_constraints: Vec<ObjectId>,
527 pub solver_constraints: Vec<ezpz::Constraint>,
528 pub solver_optional_constraints: Vec<ezpz::Constraint>,
529 pub needed_by_engine: Vec<UnsolvedSegment>,
530 pub segment_tags: IndexMap<ObjectId, TagNode>,
531 pub pending_legacy_angle_refactor_metadata: Vec<PendingLegacyAngleRefactorMeta>,
532}
533
534impl ExecState {
535 pub fn new(exec_context: &super::ExecutorContext) -> Self {
536 ExecState {
537 execution_callbacks: exec_context.execution_callbacks.clone(),
538 global: GlobalState::new(&exec_context.settings, Default::default()),
539 mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
540 }
541 }
542
543 #[cfg(test)]
544 pub(crate) fn new_with_memory_backend(exec_context: &super::ExecutorContext, backend: MemoryBackendKind) -> Self {
545 ExecState {
546 execution_callbacks: exec_context.execution_callbacks.clone(),
547 global: GlobalState::new(&exec_context.settings, Default::default()),
548 mod_local: ModuleState::new(
549 ModulePath::Main,
550 ProgramMemory::new_with_backend(backend),
551 Default::default(),
552 false,
553 true,
554 ),
555 }
556 }
557
558 pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
559 let segment_ids_edited = mock_config.segment_ids_edited.clone();
560 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
561 global.drag_anchors = mock_config.drag_anchors.clone();
562 global.sketch_mode = mock_config.sketch_block_id.is_some();
563 ExecState {
564 execution_callbacks: exec_context.execution_callbacks.clone(),
565 global,
566 mod_local: ModuleState::new(
567 ModulePath::Main,
568 ProgramMemory::new(),
569 Default::default(),
570 mock_config.sketch_block_id.is_some(),
571 mock_config.freedom_analysis,
572 ),
573 }
574 }
575
576 #[cfg(test)]
577 pub(crate) fn new_mock_with_memory_backend(
578 exec_context: &super::ExecutorContext,
579 mock_config: &MockConfig,
580 backend: MemoryBackendKind,
581 ) -> Self {
582 let segment_ids_edited = mock_config.segment_ids_edited.clone();
583 let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
584 global.drag_anchors = mock_config.drag_anchors.clone();
585 global.sketch_mode = mock_config.sketch_block_id.is_some();
586 ExecState {
587 execution_callbacks: exec_context.execution_callbacks.clone(),
588 global,
589 mod_local: ModuleState::new(
590 ModulePath::Main,
591 ProgramMemory::new_with_backend(backend),
592 Default::default(),
593 mock_config.sketch_block_id.is_some(),
594 mock_config.freedom_analysis,
595 ),
596 }
597 }
598
599 pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
600 let global = GlobalState::new(&exec_context.settings, Default::default());
601
602 *self = ExecState {
603 execution_callbacks: exec_context.execution_callbacks.clone(),
604 global,
605 mod_local: ModuleState::new(
606 self.mod_local.path.clone(),
607 ProgramMemory::new(),
608 Default::default(),
609 false,
610 true,
611 ),
612 };
613 }
614
615 pub fn err(&mut self, e: CompilationIssue) {
617 self.global.issues.push(e);
618 }
619
620 pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
622 debug_assert!(annotations::WARN_VALUES.contains(&name));
623
624 if self.mod_local.allowed_warnings.contains(&name) {
625 return;
626 }
627
628 if self.mod_local.denied_warnings.contains(&name) {
629 e.severity = Severity::Error;
630 } else {
631 e.severity = Severity::Warning;
632 }
633
634 self.global.issues.push(e);
635 }
636
637 pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
638 let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
639 return;
640 };
641 let error = CompilationIssue {
642 source_range,
643 message: format!("Use of {feature_name} is experimental and may change or be removed."),
644 suggestion: None,
645 severity,
646 tag: crate::errors::Tag::None,
647 };
648
649 self.global.issues.push(error);
650 }
651
652 pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
653 self.global.issues = std::mem::take(&mut self.global.issues)
654 .into_iter()
655 .filter(|e| {
656 e.severity != Severity::Warning
657 || !source_range.contains_range(&e.source_range)
658 || e.tag != crate::errors::Tag::UnknownNumericUnits
659 })
660 .collect();
661 }
662
663 pub fn issues(&self) -> &[CompilationIssue] {
664 &self.global.issues
665 }
666
667 pub(crate) fn deprecation_version(&self) -> &str {
668 self.global
669 .deprecation_version_override
670 .as_deref()
671 .unwrap_or(self.mod_local.settings.kcl_version.as_str())
672 }
673
674 pub(crate) fn entry_point_kcl_version(&self) -> KclVersion {
678 self.global.entry_point_kcl_version.unwrap_or_default()
679 }
680
681 pub(crate) fn record_not_yet_added(
684 &mut self,
685 key: String,
686 added_in: annotations::VersionConstraint,
687 exported: bool,
688 ) {
689 let item = NotYetAdded {
690 added_in,
691 is_std: matches!(self.mod_local.path, ModulePath::Std { .. }),
692 };
693 self.mod_local
694 .not_yet_added
695 .insert(key, ScopedNotYetAdded { item, exported });
696 }
697
698 pub(crate) fn import_not_yet_added(&mut self, records: &IndexMap<String, NotYetAdded>, exported: bool) {
701 for (key, item) in records {
702 self.mod_local
703 .not_yet_added
704 .entry(key.clone())
705 .or_insert_with(|| ScopedNotYetAdded {
706 item: item.clone(),
707 exported,
708 });
709 }
710 }
711
712 pub(crate) fn not_yet_added_in_scope(&self, key: &str) -> Option<&NotYetAdded> {
714 self.mod_local
715 .not_yet_added
716 .get(key)
717 .map(|scoped| &scoped.item)
718 .or_else(|| self.global.std_not_yet_added.get(key))
719 }
720
721 pub(crate) fn with_not_yet_added_hint(&self, keys: &[&str], err: KclError) -> KclError {
725 match keys.iter().find_map(|key| self.not_yet_added_in_scope(key)) {
726 Some(item) if self.hints_for(item) => self.not_yet_added_hint(item, err),
727 _ => err,
728 }
729 }
730
731 pub(crate) fn with_not_yet_added_hint_from(
734 &self,
735 records: &IndexMap<String, NotYetAdded>,
736 keys: &[&str],
737 err: KclError,
738 ) -> KclError {
739 match keys.iter().find_map(|key| records.get(*key)) {
740 Some(item) if self.hints_for(item) => self.not_yet_added_hint(item, err),
741 _ => err,
742 }
743 }
744
745 fn hints_for(&self, item: &NotYetAdded) -> bool {
750 #[cfg(test)]
751 if self.global.hint_all_not_yet_added {
752 return true;
753 }
754 item.is_std
755 }
756
757 fn not_yet_added_hint(&self, item: &NotYetAdded, mut err: KclError) -> KclError {
758 let details = err.details_mut();
759 details.message = format!(
760 "{}; it was added in KCL {}, but this program uses KCL {}",
761 details.message,
762 item.added_in,
763 self.entry_point_kcl_version().as_str()
764 );
765 err
766 }
767
768 #[cfg(test)]
769 pub(crate) fn set_deprecation_version_override(&mut self, version: Option<&str>) {
770 self.global.deprecation_version_override = version.map(str::to_owned);
771 }
772
773 #[cfg(test)]
774 pub(crate) fn program_memory_for_tests(
775 &self,
776 main_ref: EnvironmentRef,
777 ) -> Result<IndexMap<String, KclValue>, KclError> {
778 self.mod_local.variables(main_ref)
779 }
780
781 pub async fn into_exec_outcome(
785 self,
786 main_ref: EnvironmentRef,
787 ctx: &ExecutorContext,
788 ) -> Result<ExecOutcome, KclError> {
789 let variables = self.mod_local.variables(main_ref)?;
792 #[cfg(test)]
793 let test_program_memory = variables.clone();
794 let variables = variables
795 .into_iter()
796 .map(|(key, value)| (key, KclValueView::from(value)))
797 .collect();
798 Ok(ExecOutcome {
799 variables,
800 filenames: self.global.filenames(),
801 operations: self.global.operations_by_module(),
802 artifact_graph: self.global.artifacts.graph,
803 scene_objects: self.global.root_module_artifacts.scene_objects,
804 source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
805 var_solutions: self.global.root_module_artifacts.var_solutions,
806 refactor_metadata: self.global.root_module_artifacts.refactor_metadata.clone(),
807 issues: self.global.issues,
808 source_files: self.global.id_to_source,
809 default_planes: ctx.engine.get_default_planes().read().await.clone(),
810 #[cfg(test)]
811 test_program_memory,
812 })
813 }
814
815 #[cfg(feature = "snapshot-engine-responses")]
816 pub(crate) fn take_root_module_responses(
817 &mut self,
818 ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
819 std::mem::take(&mut self.global.root_module_artifacts.responses)
820 }
821
822 pub(crate) fn geometry_only(&self) -> bool {
823 self.global.geometry_only
824 }
825
826 pub(crate) fn stack(&self) -> &Stack {
827 &self.mod_local.stack
828 }
829
830 pub(crate) fn mut_stack(&mut self) -> &mut Stack {
831 &mut self.mod_local.stack
832 }
833
834 pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
837 const LIMIT: usize = 50;
840 if self.mod_local.call_stack_size >= LIMIT {
841 return Err(KclError::new_max_call_stack(KclErrorDetails::new(
842 format!(
843 "Call depth limit ({LIMIT}) exceeded. This usually means a function is recursing without a base case."
844 ),
845 vec![range],
846 )));
847 }
848 self.mod_local.call_stack_size += 1;
849 Ok(())
850 }
851
852 pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
855 if self.mod_local.call_stack_size == 0 {
857 let message = "call stack size below zero".to_owned();
858 debug_assert!(false, "{message}");
859 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
860 }
861 self.mod_local.call_stack_size -= 1;
862 Ok(())
863 }
864
865 #[allow(dead_code)]
871 pub(crate) fn machine_depth_high_water(&self) -> usize {
872 self.global.machine_depth_high_water
873 }
874
875 pub(crate) fn sketch_mode(&self) -> bool {
880 self.mod_local.sketch_mode
881 && match &self.mod_local.path {
882 ModulePath::Main => true,
883 ModulePath::Local { .. } => true,
884 ModulePath::Std { .. } => false,
885 }
886 }
887
888 pub(crate) fn is_sketch_mode_execution(&self) -> bool {
892 self.global.sketch_mode
893 }
894
895 pub fn next_object_id(&mut self) -> ObjectId {
896 ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
897 }
898
899 pub fn peek_object_id(&self) -> ObjectId {
900 ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
901 }
902
903 pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
904 let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
905 map.get(key).copied()
906 }
907
908 pub(crate) fn set_constraint_state(
909 &mut self,
910 sketch_block_id: ObjectId,
911 key: ConstraintKey,
912 state: ConstraintState,
913 ) {
914 let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
915 map.insert(key, state);
916 }
917
918 pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
919 let id = obj.id;
920 debug_assert!(
921 id.0 == self.mod_local.artifacts.scene_objects.len(),
922 "Adding scene object with ID {} but next ID is {}",
923 id.0,
924 self.mod_local.artifacts.scene_objects.len()
925 );
926 let artifact_id = obj.artifact_id;
927 self.mod_local.artifacts.scene_objects.push(obj);
928 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
929 self.mod_local
930 .artifacts
931 .artifact_id_to_scene_object
932 .insert(artifact_id, id);
933 id
934 }
935
936 pub fn add_placeholder_scene_object(
939 &mut self,
940 id: ObjectId,
941 source_range: SourceRange,
942 node_path: Option<NodePath>,
943 ) -> ObjectId {
944 debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
945 self.mod_local
946 .artifacts
947 .scene_objects
948 .push(Object::placeholder(id, source_range, node_path));
949 self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
950 id
951 }
952
953 pub fn set_scene_object(&mut self, object: Object) {
955 let id = object.id;
956 let artifact_id = object.artifact_id;
957 self.mod_local.artifacts.scene_objects[id.0] = object;
958 self.mod_local
959 .artifacts
960 .artifact_id_to_scene_object
961 .insert(artifact_id, id);
962 }
963
964 pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
965 self.mod_local
966 .artifacts
967 .artifact_id_to_scene_object
968 .get(&artifact_id)
969 .cloned()
970 }
971
972 pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
973 self.global.segment_ids_edited.contains(object_id)
974 }
975
976 pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
977 self.global
978 .drag_anchors
979 .iter()
980 .find(|anchor| &anchor.segment_id == object_id)
981 .map(|anchor| &anchor.target)
982 }
983
984 pub(super) fn is_in_sketch_block(&self) -> bool {
985 self.mod_local.sketch_block.is_some()
986 }
987
988 pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
989 self.mod_local.sketch_block.as_mut()
990 }
991
992 pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
993 self.mod_local.sketch_block.as_ref()
994 }
995
996 pub fn next_uuid(&mut self) -> Uuid {
997 self.mod_local.id_generator.next_uuid()
998 }
999
1000 pub fn next_artifact_id(&mut self) -> ArtifactId {
1001 self.mod_local.id_generator.next_artifact_id()
1002 }
1003
1004 pub fn id_generator(&mut self) -> &mut IdGenerator {
1005 &mut self.mod_local.id_generator
1006 }
1007
1008 pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
1010 self.mod_local.consumed_solids.insert(consumed_key, info);
1011 }
1012
1013 pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
1016 self.mod_local.consumed_solid_ids.insert(consumed_id, info);
1017 }
1018
1019 pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
1022 self.mod_local.consumed_solids.get(key)
1023 }
1024
1025 pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
1028 self.mod_local.consumed_solid_ids.get(id)
1029 }
1030
1031 pub(crate) fn mark_region_consumed(&mut self, id: Uuid, info: ConsumedRegionInfo) {
1032 self.mod_local.consumed_regions.insert(id, info);
1033 }
1034
1035 pub(crate) fn check_region_consumed(&self, id: &Uuid) -> Option<ConsumedRegionInfo> {
1036 self.mod_local.consumed_regions.get(id).copied()
1037 }
1038
1039 pub(crate) fn find_var_name_for_region_id(&self, target_id: Uuid) -> Result<Option<String>, KclError> {
1043 fn contains_region_id(value: &KclValue, target_id: Uuid) -> bool {
1044 match value {
1045 KclValue::Sketch { value } => value.origin_sketch_id.is_some() && value.id == target_id,
1046 KclValue::HomArray { value, .. } | KclValue::Tuple { value, .. } => {
1047 value.iter().any(|value| contains_region_id(value, target_id))
1048 }
1049 KclValue::Object { value, .. } => value.values().any(|value| contains_region_id(value, target_id)),
1050 _ => false,
1051 }
1052 }
1053
1054 self.mod_local
1055 .stack
1056 .find_var_name_in_all_envs(|value| contains_region_id(value, target_id))
1057 }
1058
1059 pub(crate) fn latest_consumed_output(
1062 &self,
1063 suggested_replacement_key: Option<ConsumedSolidKey>,
1064 ) -> Option<ConsumedSolidKey> {
1065 let mut latest = suggested_replacement_key?;
1066 let mut seen = AhashIndexSet::default();
1067
1068 while seen.insert(latest) {
1069 let Some(next) = self
1070 .mod_local
1071 .consumed_solids
1072 .get(&latest)
1073 .and_then(|info| info.suggested_replacement_key())
1074 else {
1075 break;
1076 };
1077 latest = next;
1078 }
1079
1080 Some(latest)
1081 }
1082
1083 pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
1087 fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
1088 match value {
1089 KclValue::Solid { value } => {
1090 value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
1091 }
1092 KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
1093 _ => false,
1094 }
1095 }
1096 self.mod_local
1097 .stack
1098 .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
1099 }
1100
1101 pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
1102 let id = artifact.id();
1103 self.mod_local.artifacts.artifacts.insert(id, artifact);
1104 }
1105
1106 pub(crate) fn registered_named_views(&self) -> impl Iterator<Item = (ModuleId, &str)> {
1127 self.mod_local
1128 .artifacts
1129 .artifacts
1130 .values()
1131 .chain(self.global.artifacts.artifacts.values())
1132 .filter_map(|artifact| match artifact {
1133 Artifact::NamedView(view) => Some((view.code_ref.range.module_id(), view.name.as_str())),
1134 _ => None,
1135 })
1136 }
1137
1138 pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
1139 self.mod_local.artifacts.artifacts.get_mut(&id)
1140 }
1141
1142 pub(crate) fn is_sketch_block_path(&self, path_id: ArtifactId) -> bool {
1143 self.mod_local
1144 .artifacts
1145 .artifacts
1146 .values()
1147 .chain(self.global.artifacts.artifacts.values())
1148 .any(|artifact| {
1149 matches!(artifact, Artifact::SketchBlock(sketch_block) if sketch_block.path_id == Some(path_id))
1150 })
1151 }
1152
1153 pub(crate) fn push_op(&mut self, op: Operation) {
1154 let index = self.mod_local.artifacts.operations.len();
1155 self.mod_local.artifacts.operations.push(op);
1156 if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
1157 && let Some(callbacks) = &self.execution_callbacks
1158 {
1159 callbacks.on_operation(OperationCallbackArgs {
1160 module_id: self.mod_local.module_id,
1161 operation,
1162 index,
1163 });
1164 }
1165 }
1166
1167 pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
1168 self.mod_local.artifacts.unprocessed_commands.push(command);
1169 }
1170
1171 pub(super) fn next_module_id(&self) -> ModuleId {
1172 ModuleId::from_usize(self.global.path_to_source_id.len())
1173 }
1174
1175 pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
1176 self.global.path_to_source_id.get(path).cloned()
1177 }
1178
1179 pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
1180 debug_assert!(!self.global.path_to_source_id.contains_key(&path));
1181 self.global.path_to_source_id.insert(path, id);
1182 }
1183
1184 pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
1185 let root_id = ModuleId::default();
1186 let path = self
1188 .global
1189 .path_to_source_id
1190 .iter()
1191 .find(|(_, v)| **v == root_id)
1192 .unwrap()
1193 .0
1194 .clone();
1195 self.add_id_to_source(
1196 root_id,
1197 ModuleSource {
1198 path,
1199 source: program.original_file_contents.to_string(),
1200 },
1201 );
1202 }
1203
1204 pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
1205 self.global.id_to_source.insert(id, source);
1206 }
1207
1208 pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
1209 debug_assert!(self.global.path_to_source_id.contains_key(&path));
1210 let module_info = ModuleInfo { id, repr, path };
1211 self.global.module_infos.insert(id, module_info);
1212 }
1213
1214 pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
1215 self.global.module_infos.get(&id)
1216 }
1217
1218 #[cfg(test)]
1219 pub(crate) fn modules(&self) -> &ModuleInfoMap {
1220 &self.global.module_infos
1221 }
1222
1223 #[cfg(test)]
1224 pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
1225 &self.global.root_module_artifacts
1226 }
1227
1228 pub(crate) fn record_edge_refactor_meta(&mut self, meta: EdgeRefactorMeta) {
1230 self.mod_local
1231 .artifacts
1232 .refactor_metadata
1233 .push(RefactorMetadata::EdgeRefactor(Box::new(meta)));
1234 }
1235
1236 pub(crate) fn record_pending_edge_refactor_meta(&mut self, meta: PendingEdgeRefactorMeta) {
1237 self.mod_local.artifacts.pending_edge_refactor_metadata.push(meta);
1238 }
1239
1240 pub(crate) fn pending_edge_refactor_meta(
1241 &self,
1242 edge_id: Uuid,
1243 argument_source_range: SourceRange,
1244 ) -> Option<PendingEdgeRefactorMeta> {
1245 if let Some(pending) = self
1246 .mod_local
1247 .artifacts
1248 .pending_edge_refactor_metadata
1249 .iter()
1250 .find(|meta| meta.edge_id == edge_id && argument_source_range.contains_range(&meta.source_range))
1251 {
1252 return Some(pending.clone());
1253 }
1254
1255 let mut matches = self
1258 .mod_local
1259 .artifacts
1260 .pending_edge_refactor_metadata
1261 .iter()
1262 .filter(|meta| meta.edge_id == edge_id);
1263 let pending = matches.next()?.clone();
1264 matches.next().is_none().then_some(pending)
1265 }
1266
1267 pub(crate) fn record_edge_refactor_meta_from_pending(
1268 &mut self,
1269 edge_id: Uuid,
1270 source_range: SourceRange,
1271 face_ids: [Uuid; 2],
1272 ) -> bool {
1273 if self.mod_local.artifacts.refactor_metadata.iter().any(|meta| {
1274 matches!(
1275 meta,
1276 RefactorMetadata::EdgeRefactor(meta)
1277 if meta.edge_id == edge_id && meta.source_range == source_range
1278 )
1279 }) {
1280 return true;
1281 }
1282
1283 let exact_pending_meta = self
1284 .mod_local
1285 .artifacts
1286 .pending_edge_refactor_metadata
1287 .iter()
1288 .find(|meta| meta.edge_id == edge_id && meta.source_range == source_range)
1289 .cloned();
1290
1291 let edge_pending_meta = || {
1292 let mut matches = self
1293 .mod_local
1294 .artifacts
1295 .pending_edge_refactor_metadata
1296 .iter()
1297 .filter(|meta| meta.edge_id == edge_id);
1298 let pending_meta = matches.next()?.clone();
1299 matches.next().is_none().then_some(pending_meta)
1300 };
1301
1302 let Some(pending_meta) = exact_pending_meta.or_else(edge_pending_meta) else {
1303 return false;
1304 };
1305
1306 self.record_edge_refactor_meta(EdgeRefactorMeta {
1307 edge_id,
1308 face_ids,
1309 end_face_ids: Vec::new(),
1310 source_range: pending_meta.source_range,
1311 stdlib_fn: pending_meta.stdlib_fn,
1312 });
1313
1314 true
1315 }
1316
1317 pub(crate) fn record_direct_tag_fillet_meta(&mut self, meta: DirectTagFilletMeta) {
1319 self.mod_local
1320 .artifacts
1321 .refactor_metadata
1322 .push(RefactorMetadata::DirectTagFillet(meta));
1323 }
1324
1325 pub fn edge_refactor_metadata(&self) -> Vec<EdgeRefactorMeta> {
1327 self.global
1328 .root_module_artifacts
1329 .refactor_metadata
1330 .iter()
1331 .filter_map(|m| match m {
1332 RefactorMetadata::EdgeRefactor(meta) => Some(meta.as_ref().clone()),
1333 RefactorMetadata::DirectTagFillet(_) | RefactorMetadata::LegacyAngle(_) => None,
1334 })
1335 .collect()
1336 }
1337
1338 pub fn direct_tag_fillet_metadata(&self) -> Vec<DirectTagFilletMeta> {
1340 self.global
1341 .root_module_artifacts
1342 .refactor_metadata
1343 .iter()
1344 .filter_map(|m| match m {
1345 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::LegacyAngle(_) => None,
1346 RefactorMetadata::DirectTagFillet(meta) => Some(meta.clone()),
1347 })
1348 .collect()
1349 }
1350
1351 pub fn current_default_units(&self) -> NumericType {
1352 NumericType::Default {
1353 len: self.length_unit(),
1354 angle: self.angle_unit(),
1355 }
1356 }
1357
1358 pub fn length_unit(&self) -> UnitLength {
1359 self.mod_local.settings.default_length_units
1360 }
1361
1362 pub fn angle_unit(&self) -> UnitAngle {
1363 self.mod_local.settings.default_angle_units
1364 }
1365
1366 pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
1367 KclError::new_import_cycle(KclErrorDetails::new(
1368 format!(
1369 "circular import of modules is not allowed: {} -> {}",
1370 self.global
1371 .mod_loader
1372 .import_stack
1373 .iter()
1374 .map(|p| p.to_string_lossy())
1375 .collect::<Vec<_>>()
1376 .join(" -> "),
1377 path,
1378 ),
1379 vec![source_range],
1380 ))
1381 }
1382
1383 pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
1384 self.mod_local.pipe_value.as_ref()
1385 }
1386
1387 pub(crate) fn error_with_outputs(
1388 &self,
1389 error: KclError,
1390 main_ref: Option<EnvironmentRef>,
1391 default_planes: Option<DefaultPlanes>,
1392 ) -> KclErrorWithOutputs {
1393 let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
1394 .global
1395 .path_to_source_id
1396 .iter()
1397 .map(|(k, v)| ((*v), k.clone()))
1398 .collect();
1399
1400 KclErrorWithOutputs::new(
1401 error,
1402 self.issues().to_vec(),
1403 main_ref
1404 .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
1405 .unwrap_or_default(),
1406 self.global.operations_by_module(),
1407 Default::default(),
1408 self.global.artifacts.graph.clone(),
1409 self.global.root_module_artifacts.scene_objects.clone(),
1410 self.global.root_module_artifacts.source_range_to_object.clone(),
1411 self.global.root_module_artifacts.var_solutions.clone(),
1412 self.global.root_module_artifacts.refactor_metadata.clone(),
1413 module_id_to_module_path,
1414 self.global.id_to_source.clone(),
1415 default_planes,
1416 )
1417 }
1418
1419 pub(crate) fn build_program_lookup(
1420 &self,
1421 current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1422 ) -> ProgramLookup {
1423 ProgramLookup::new(current, self.global.module_infos.clone())
1424 }
1425
1426 pub(crate) async fn build_artifact_graph(
1427 &mut self,
1428 engine: &Arc<EngineManager>,
1429 program: NodeRef<'_, crate::parsing::ast::types::Program>,
1430 ) -> Result<(), KclError> {
1431 let mut new_commands = Vec::new();
1432 let mut new_exec_artifacts = IndexMap::new();
1433 for module in self.global.module_infos.values_mut() {
1434 match &mut module.repr {
1435 ModuleRepr::Kcl(_, Some(outcome)) => {
1436 new_commands.extend(outcome.artifacts.process_commands());
1437 new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
1438 }
1439 ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
1440 new_commands.extend(module_artifacts.process_commands());
1441 new_exec_artifacts.extend(module_artifacts.artifacts.clone());
1442 }
1443 ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
1444 }
1445 }
1446 new_commands.extend(self.global.root_module_artifacts.process_commands());
1449 new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
1452 let new_responses = engine.take_responses().await;
1453
1454 for (id, exec_artifact) in new_exec_artifacts {
1457 self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
1461 }
1462
1463 let initial_graph = self.global.artifacts.graph.clone();
1464
1465 let programs = self.build_program_lookup(program.clone());
1467 let graph_result = crate::execution::artifact::build_artifact_graph(
1468 &new_commands,
1469 &new_responses,
1470 program,
1471 &mut self.global.artifacts.artifacts,
1472 initial_graph,
1473 &programs,
1474 &self.global.module_infos,
1475 );
1476
1477 #[cfg(feature = "snapshot-engine-responses")]
1478 {
1479 self.global.root_module_artifacts.responses.extend(new_responses);
1481 }
1482
1483 let artifact_graph = graph_result?;
1484 self.global.artifacts.graph = artifact_graph;
1485
1486 Ok(())
1487 }
1488
1489 pub(crate) fn kcl_version(&self) -> KclVersion {
1496 match self.global.entry_point_kcl_version {
1497 Some(version) if version >= KclVersion::V3Preview => version,
1498 _ => self.legacy_caller_kcl_version(),
1499 }
1500 }
1501
1502 pub(crate) fn legacy_caller_kcl_version(&self) -> KclVersion {
1511 self.mod_local.settings.kcl_version
1512 }
1513
1514 pub(crate) fn entry_point_version_is_v3_or_higher(&self) -> bool {
1519 self.global
1520 .entry_point_kcl_version
1521 .is_some_and(|v| v >= KclVersion::V3Preview)
1522 }
1523
1524 pub(crate) fn set_entry_point_kcl_version(&mut self, program: &crate::Program) -> Result<(), KclError> {
1529 let version = program.language_version()?;
1530 self.global.entry_point_kcl_version = declared_kcl_version(&program.ast)?.map(|_| version);
1532 Ok(())
1533 }
1534
1535 pub(crate) fn check_imported_module_kcl_version(
1557 &self,
1558 path: &ModulePath,
1559 program: &Node<Program>,
1560 import_range: Option<SourceRange>,
1561 ) -> Result<(), KclError> {
1562 if !path.is_local() {
1563 return Ok(());
1566 }
1567 let Some((declared, declared_range)) = declared_kcl_version(program)? else {
1568 return Ok(());
1569 };
1570 let entry_point_version = self.global.entry_point_kcl_version;
1571 let allowed = if self.entry_point_version_is_v3_or_higher() {
1572 Some(declared) == entry_point_version
1573 } else {
1574 declared < KclVersion::V3Preview
1575 };
1576 if allowed {
1577 return Ok(());
1578 }
1579
1580 let entry_point = match self
1583 .global
1584 .module_infos
1585 .get(&ModuleId::default())
1586 .map(|info| &info.path)
1587 {
1588 Some(root @ ModulePath::Local { .. }) if !root.to_string().is_empty() => {
1589 format!("The entry point `{root}`")
1590 }
1591 _ => "The entry point".to_owned(),
1592 };
1593 let (entry_point_declares, fix) = match entry_point_version {
1594 Some(version) => (
1595 format!("declares kclVersion {}", version.as_str()),
1596 "Update the kclVersion setting in one of these files to match the other.",
1597 ),
1598 None => (
1599 "does not declare a kclVersion".to_owned(),
1600 "Declare the same kclVersion in the entry point, or update the setting in the imported file.",
1601 ),
1602 };
1603 let mut source_ranges = vec![declared_range];
1604 source_ranges.extend(import_range);
1605 Err(KclError::new_semantic(KclErrorDetails::new(
1606 format!(
1607 "Mixing KCL versions in a single program is not allowed. {entry_point} {entry_point_declares}, but the imported file `{path}` declares kclVersion {}. {fix}",
1608 declared.as_str(),
1609 ),
1610 source_ranges,
1611 )))
1612 }
1613
1614 pub(crate) fn validate_imported_module(
1619 &self,
1620 path: &ModulePath,
1621 module_id: ModuleId,
1622 program: &Node<Program>,
1623 import_range: Option<SourceRange>,
1624 ) -> Result<(), KclError> {
1625 self.check_imported_module_kcl_version(path, program, import_range)?;
1626 if !path.is_local() {
1627 return Ok(());
1628 }
1629
1630 let ranges = self.global.never_type_ranges.get(&module_id).ok_or_else(|| {
1631 KclError::new_internal(KclErrorDetails::new(
1632 format!("Missing `never` type ranges for imported KCL module `{path}`"),
1633 import_range.into_iter().collect(),
1634 ))
1635 })?;
1636 crate::parsing::validate_never_type_ranges(
1637 ranges,
1638 crate::parsing::SyntaxSource::UserCode(self.entry_point_kcl_version()),
1639 )
1640 .map_err(|error| match import_range {
1641 Some(range) => error.add_import_location(&path.import_name(), range),
1642 None => error,
1643 })?;
1644 if !self.entry_point_version_is_v3_or_higher() {
1645 return Ok(());
1646 }
1647
1648 let source = self.global.id_to_source.get(&module_id).ok_or_else(|| {
1649 KclError::new_internal(KclErrorDetails::new(
1650 format!("Missing source for imported KCL module `{path}`"),
1651 import_range.into_iter().collect(),
1652 ))
1653 })?;
1654 let validation = crate::parsing::validate_use_keyword_source(&source.source, module_id)
1655 .and_then(|_| crate::parsing::validate_enum_keyword_source(&source.source, module_id))
1656 .and_then(|_| crate::parsing::validate_import_modifier_source(&source.source, module_id));
1657 validation.map_err(|error| match import_range {
1658 Some(range) => error.add_import_location(&path.import_name(), range),
1659 None => error,
1660 })
1661 }
1662}
1663
1664pub(crate) fn declared_kcl_version(program: &Node<Program>) -> Result<Option<(KclVersion, SourceRange)>, KclError> {
1670 let Some(property) = program
1671 .inner_attrs
1672 .iter()
1673 .rev()
1674 .filter(|annotation| annotation.name() == Some(annotations::SETTINGS))
1675 .find_map(|annotation| {
1676 annotation
1677 .properties
1678 .as_deref()
1679 .unwrap_or_default()
1680 .iter()
1681 .rev()
1682 .find(|property| &*property.inner.key.name == annotations::SETTINGS_VERSION)
1683 })
1684 else {
1685 return Ok(None);
1686 };
1687 let value = annotations::expect_kcl_version(&property.inner.value)?;
1688 let version = value.parse::<KclVersion>().map_err(|err| {
1689 KclError::new_semantic(KclErrorDetails::new(err.to_string(), vec![property.as_source_range()]))
1690 })?;
1691 Ok(Some((version, property.as_source_range())))
1692}
1693
1694pub(crate) fn computed_kcl_version(program: &Node<Program>) -> KclVersion {
1697 match declared_kcl_version(program) {
1698 Ok(Some((version, _))) => version,
1699 Ok(None) => KclVersion::default(),
1700 Err(_) => KclVersion::default(),
1701 }
1702}
1703
1704impl GlobalState {
1705 fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
1706 let mut global = GlobalState {
1707 machine_depth_high_water: 0,
1708 path_to_source_id: Default::default(),
1709 module_infos: Default::default(),
1710 never_type_ranges: Default::default(),
1711 artifacts: Default::default(),
1712 root_module_artifacts: Default::default(),
1713 mod_loader: Default::default(),
1714 issues: Default::default(),
1715 deprecation_version_override: None,
1716 entry_point_kcl_version: None,
1717 id_to_source: Default::default(),
1718 segment_ids_edited,
1719 drag_anchors: Vec::new(),
1720 sketch_mode: false,
1721 geometry_only: settings.geometry_only,
1722 std_not_yet_added: Default::default(),
1723 #[cfg(test)]
1724 hint_all_not_yet_added: false,
1725 };
1726
1727 let root_id = ModuleId::default();
1728 let root_path = settings.current_file.clone().unwrap_or_default();
1729 global.module_infos.insert(
1730 root_id,
1731 ModuleInfo {
1732 id: root_id,
1733 path: ModulePath::Local {
1734 value: root_path.clone(),
1735 original_import_path: None,
1736 },
1737 repr: ModuleRepr::Root,
1738 },
1739 );
1740 global.path_to_source_id.insert(
1741 ModulePath::Local {
1742 value: root_path,
1743 original_import_path: None,
1744 },
1745 root_id,
1746 );
1747 global
1748 }
1749
1750 pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1751 self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1752 }
1753
1754 pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1755 self.id_to_source.get(&id)
1756 }
1757}
1758
1759impl ArtifactState {
1760 pub fn cached_body_items(&self) -> usize {
1761 self.graph.item_count()
1762 }
1763
1764 pub(crate) fn clear(&mut self) {
1765 self.artifacts.clear();
1766 self.graph.clear();
1767 }
1768}
1769
1770impl ModuleArtifactState {
1771 pub fn legacy_angle_refactor_metadata(&self) -> Vec<LegacyAngleRefactorMeta> {
1772 self.refactor_metadata
1773 .iter()
1774 .filter_map(|metadata| match metadata {
1775 RefactorMetadata::LegacyAngle(metadata) => Some(*metadata),
1776 RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::DirectTagFillet(_) => None,
1777 })
1778 .collect()
1779 }
1780
1781 pub(crate) fn clear(&mut self) {
1782 self.artifacts.clear();
1783 self.unprocessed_commands.clear();
1784 self.commands.clear();
1785 self.operations.clear();
1786 self.refactor_metadata.clear();
1787 }
1788
1789 pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1790 self.scene_objects = scene_objects.to_vec();
1791 self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1792 self.source_range_to_object.clear();
1793 self.artifact_id_to_scene_object.clear();
1794
1795 for (expected_id, object) in self.scene_objects.iter().enumerate() {
1796 debug_assert_eq!(
1797 object.id.0, expected_id,
1798 "Restored cached scene object ID {} does not match its position {}",
1799 object.id.0, expected_id
1800 );
1801
1802 match &object.kind {
1803 ObjectKind::Wall(wall) => {
1804 self.source_range_to_object.insert(wall.source.solid.range, object.id);
1805 }
1806 ObjectKind::Cap(cap) => {
1807 self.source_range_to_object.insert(cap.source.solid.range, object.id);
1808 }
1809 _ => match &object.source {
1810 crate::front::SourceRef::Simple { range, node_path: _ } => {
1811 self.source_range_to_object.insert(*range, object.id);
1812 }
1813 crate::front::SourceRef::BackTrace { ranges } => {
1814 if let Some((range, _)) = ranges.first() {
1817 self.source_range_to_object.insert(*range, object.id);
1818 }
1819 }
1820 },
1821 }
1822
1823 if object.artifact_id != ArtifactId::placeholder() {
1825 self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1826 }
1827 }
1828 }
1829
1830 pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1832 self.artifacts.extend(other.artifacts);
1833 self.unprocessed_commands.extend(other.unprocessed_commands);
1834 self.commands.extend(other.commands);
1835 self.operations.extend(other.operations);
1836 if other.scene_objects.len() > self.scene_objects.len() {
1837 self.scene_objects
1838 .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1839 }
1840 self.source_range_to_object.extend(other.source_range_to_object);
1841 self.artifact_id_to_scene_object
1842 .extend(other.artifact_id_to_scene_object);
1843 self.var_solutions.extend(other.var_solutions);
1844 self.refactor_metadata.extend(other.refactor_metadata);
1845 }
1846
1847 pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1851 let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1852 let new_module_commands = unprocessed.clone();
1853 self.commands.extend(unprocessed);
1854 new_module_commands
1855 }
1856
1857 pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1858 debug_assert!(
1859 id.0 < self.scene_objects.len(),
1860 "Requested object ID {} but only have {} objects",
1861 id.0,
1862 self.scene_objects.len()
1863 );
1864 self.scene_objects.get(id.0)
1865 }
1866
1867 pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1868 debug_assert!(
1869 id.0 < self.scene_objects.len(),
1870 "Requested object ID {} but only have {} objects",
1871 id.0,
1872 self.scene_objects.len()
1873 );
1874 self.scene_objects.get_mut(id.0)
1875 }
1876}
1877
1878impl ModuleState {
1879 pub(super) fn new(
1880 path: ModulePath,
1881 memory: Arc<ProgramMemory>,
1882 module_id: Option<ModuleId>,
1883 sketch_mode: bool,
1884 freedom_analysis: bool,
1885 ) -> Self {
1886 let state_module_id = module_id.unwrap_or_default();
1887 ModuleState {
1888 module_id: state_module_id,
1889 id_generator: IdGenerator::new(module_id),
1890 stack: memory.new_stack(),
1891 call_stack_size: 0,
1892 machine_call_depth: 0,
1893 pipe_value: Default::default(),
1894 being_declared: Default::default(),
1895 sketch_block: Default::default(),
1896 stdlib_entry_source_range: Default::default(),
1897 module_exports: Default::default(),
1898 not_yet_added: Default::default(),
1899 explicit_length_units: false,
1900 path,
1901 settings: Default::default(),
1902 sketch_mode,
1903 freedom_analysis,
1904 artifacts: Default::default(),
1905 constraint_state: Default::default(),
1906 allowed_warnings: Vec::new(),
1907 denied_warnings: Vec::new(),
1908 consumed_solids: AHashMap::default(),
1909 consumed_solid_ids: AHashMap::default(),
1910 consumed_regions: AHashMap::default(),
1911 inside_stdlib: false,
1912 }
1913 }
1914
1915 pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1916 self.stack.find_all_in_env_owned(main_ref)
1917 }
1918}
1919
1920impl SketchBlockState {
1921 pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1922 SketchVarId(self.sketch_vars.len())
1923 }
1924
1925 pub(crate) fn var_solutions(
1928 &self,
1929 solve_outcome: &Solved,
1930 solution_ty: NumericType,
1931 sketch_block_range: SourceRange,
1932 ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1933 self.sketch_vars
1934 .iter()
1935 .map(|v| {
1936 let Some(sketch_var) = v.as_sketch_var() else {
1937 return Err(KclError::new_internal(KclErrorDetails::new(
1938 "Expected sketch variable".to_owned(),
1939 vec![sketch_block_range],
1940 )));
1941 };
1942 let var_index = sketch_var.id.0;
1943 let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1944 let message = format!("No solution for sketch variable with id {}", var_index);
1945 debug_assert!(false, "{}", &message);
1946 KclError::new_internal(KclErrorDetails::new(
1947 message,
1948 sketch_var.meta.iter().map(|m| m.source_range).collect(),
1949 ))
1950 })?;
1951 let solved_value = Number {
1952 value: *solved_n,
1953 units: solution_ty.try_into().map_err(|_| {
1954 KclError::new_internal(KclErrorDetails::new(
1955 "Failed to convert numeric type to units".to_owned(),
1956 vec![sketch_block_range],
1957 ))
1958 })?,
1959 };
1960 let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1961 return Ok(None);
1962 };
1963 Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1964 })
1965 .filter_map(Result::transpose)
1966 .collect::<Result<Vec<_>, KclError>>()
1967 }
1968}
1969
1970#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1971#[ts(export)]
1972#[serde(rename_all = "camelCase")]
1973pub struct MetaSettings {
1974 pub default_length_units: UnitLength,
1975 pub default_angle_units: UnitAngle,
1976 pub experimental_features: annotations::WarningLevel,
1977 pub kcl_version: KclVersion,
1978}
1979
1980impl Default for MetaSettings {
1981 fn default() -> Self {
1982 MetaSettings {
1983 default_length_units: UnitLength::Millimeters,
1984 default_angle_units: UnitAngle::Degrees,
1985 experimental_features: annotations::WarningLevel::Deny,
1986 kcl_version: KclVersion::default(),
1987 }
1988 }
1989}
1990
1991impl MetaSettings {
1992 pub(crate) fn update_from_annotation(
1993 &mut self,
1994 annotation: &crate::parsing::ast::types::Node<Annotation>,
1995 ) -> Result<(bool, bool), KclError> {
1996 let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1997
1998 let mut updated_len = false;
1999 let mut updated_angle = false;
2000 for p in properties {
2001 match &*p.inner.key.name {
2002 annotations::SETTINGS_UNIT_LENGTH => {
2003 let value = annotations::expect_ident(&p.inner.value)?;
2004 let value = super::types::length_from_str(value, annotation.as_source_range())?;
2005 self.default_length_units = value;
2006 updated_len = true;
2007 }
2008 annotations::SETTINGS_UNIT_ANGLE => {
2009 let value = annotations::expect_ident(&p.inner.value)?;
2010 let value = super::types::angle_from_str(value, annotation.as_source_range())?;
2011 self.default_angle_units = value;
2012 updated_angle = true;
2013 }
2014 annotations::SETTINGS_VERSION => {
2015 let value = annotations::expect_kcl_version(&p.inner.value)?;
2016 self.kcl_version = value.parse()?;
2017 }
2018 annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
2019 let value = annotations::expect_ident(&p.inner.value)?;
2020 let value = annotations::WarningLevel::from_str(value).map_err(|_| {
2021 KclError::new_semantic(KclErrorDetails::new(
2022 format!(
2023 "Invalid value for {} settings property, expected one of: {}",
2024 annotations::SETTINGS_EXPERIMENTAL_FEATURES,
2025 annotations::WARN_LEVELS.join(", ")
2026 ),
2027 annotation.as_source_ranges(),
2028 ))
2029 })?;
2030 self.experimental_features = value;
2031 }
2032 name => {
2033 return Err(KclError::new_semantic(KclErrorDetails::new(
2034 format!(
2035 "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
2036 annotations::SETTINGS_UNIT_LENGTH,
2037 annotations::SETTINGS_UNIT_ANGLE
2038 ),
2039 vec![annotation.as_source_range()],
2040 )));
2041 }
2042 }
2043 }
2044
2045 Ok((updated_len, updated_angle))
2046 }
2047}
2048
2049#[cfg(test)]
2050mod tests {
2051
2052 use uuid::Uuid;
2053
2054 use super::KclVersion;
2055 use super::ModuleArtifactState;
2056 use crate::NodePath;
2057 use crate::NodePathExt;
2058 use crate::SourceRange;
2059 use crate::execution::ArtifactId;
2060 use crate::front::Object;
2061 use crate::front::ObjectId;
2062 use crate::front::ObjectKind;
2063 use crate::front::Plane;
2064 use crate::front::SourceRef;
2065
2066 #[test]
2067 fn declared_kcl_version_finds_the_setting_and_its_range() {
2068 let parse = |code: &str| crate::parsing::top_level_parse(code).unwrap();
2069
2070 assert_eq!(super::declared_kcl_version(&parse("x = 1\n")).unwrap(), None);
2071 assert_eq!(
2072 super::declared_kcl_version(&parse("@settings(defaultLengthUnit = in)\nx = 1\n")).unwrap(),
2073 None
2074 );
2075
2076 let code = "@settings(defaultLengthUnit = in, kclVersion = 2.0)\nx = 1\n";
2077 let (version, range) = super::declared_kcl_version(&parse(code)).unwrap().unwrap();
2078 assert_eq!(version, KclVersion::V2);
2079 let start = code.find("kclVersion").unwrap();
2080 assert_eq!((range.start(), range.end()), (start, start + "kclVersion = 2.0".len()));
2081
2082 let (version, _) = super::declared_kcl_version(&parse("@settings(kclVersion = \"3.0-preview\")\n"))
2083 .unwrap()
2084 .unwrap();
2085 assert_eq!(version, KclVersion::V3Preview);
2086
2087 let code = "@settings(kclVersion = 1.0)\n@settings(defaultLengthUnit = in)\n@settings(kclVersion = 2.0, kclVersion = \"3.0-preview\")\n";
2090 let (version, range) = super::declared_kcl_version(&parse(code)).unwrap().unwrap();
2091 assert_eq!(version, KclVersion::V3Preview);
2092 let start = code.rfind("kclVersion").unwrap();
2093 assert_eq!(
2094 (range.start(), range.end()),
2095 (start, start + "kclVersion = \"3.0-preview\"".len())
2096 );
2097
2098 let code = "@settings(kclVersion = 9.0)\n";
2100 let error = super::declared_kcl_version(&parse(code)).unwrap_err();
2101 let start = code.find("kclVersion").unwrap();
2102 assert_eq!(
2103 error.source_ranges().first().map(|range| (range.start(), range.end())),
2104 Some((start, start + "kclVersion = 9.0".len()))
2105 );
2106 }
2107
2108 #[test]
2109 fn kcl_version_serializes_as_canonical_setting_value() {
2110 assert_eq!(serde_json::to_string(&KclVersion::V1).unwrap(), r#""1.0""#);
2111 assert_eq!(serde_json::to_string(&KclVersion::V2).unwrap(), r#""2.0""#);
2112 assert_eq!(serde_json::to_string(&KclVersion::V3).unwrap(), r#""3.0""#);
2113 assert_eq!(
2114 serde_json::to_string(&KclVersion::V3Preview).unwrap(),
2115 r#""3.0-preview""#
2116 );
2117 }
2118
2119 #[test]
2120 fn restore_scene_objects_rebuilds_lookup_maps() {
2121 let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
2122 let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
2123 let plane_range = SourceRange::from([1, 4, 0]);
2124 let plane_node_path = Some(NodePath::placeholder());
2125 let sketch_ranges = vec![
2126 (SourceRange::from([5, 9, 0]), None),
2127 (SourceRange::from([10, 12, 0]), None),
2128 ];
2129 let cached_objects = vec![
2130 Object {
2131 id: ObjectId(0),
2132 kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
2133 label: Default::default(),
2134 comments: Default::default(),
2135 artifact_id: plane_artifact_id,
2136 source: SourceRef::new(plane_range, plane_node_path),
2137 },
2138 Object {
2139 id: ObjectId(1),
2140 kind: ObjectKind::Nil,
2141 label: Default::default(),
2142 comments: Default::default(),
2143 artifact_id: sketch_artifact_id,
2144 source: SourceRef::BackTrace {
2145 ranges: sketch_ranges.clone(),
2146 },
2147 },
2148 Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
2149 ];
2150
2151 let mut artifacts = ModuleArtifactState::default();
2152 artifacts.restore_scene_objects(&cached_objects);
2153
2154 assert_eq!(artifacts.scene_objects, cached_objects);
2155 assert_eq!(
2156 artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
2157 Some(&ObjectId(0))
2158 );
2159 assert_eq!(
2160 artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
2161 Some(&ObjectId(1))
2162 );
2163 assert_eq!(
2164 artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
2165 None
2166 );
2167 assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
2168 assert_eq!(
2169 artifacts.source_range_to_object.get(&sketch_ranges[0].0),
2170 Some(&ObjectId(1))
2171 );
2172 assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
2174 }
2175}