Skip to main content

kcl_lib/execution/
state.rs

1use std::collections::BTreeMap;
2use std::str::FromStr;
3use std::sync::Arc;
4
5use ahash::AHashMap;
6use anyhow::Result;
7use indexmap::IndexMap;
8use kcl_api::UnitAngle;
9use kcl_api::UnitLength;
10use serde::Deserialize;
11use serde::Serialize;
12use uuid::Uuid;
13
14use crate::CompilationIssue;
15use crate::ExecutorContext;
16use crate::KclErrorWithOutputs;
17use crate::MockConfig;
18use crate::NodePath;
19use crate::SegmentDragAnchor;
20use crate::SourceRange;
21use crate::collections::AhashIndexSet;
22use crate::engine::engine_manager::EngineManager;
23use crate::errors::KclError;
24use crate::errors::KclErrorDetails;
25use crate::errors::Severity;
26use crate::exec::DefaultPlanes;
27use crate::execution::Artifact;
28use crate::execution::ArtifactCommand;
29use crate::execution::ArtifactGraph;
30use crate::execution::ArtifactId;
31use crate::execution::ConstrainableLine2d;
32use crate::execution::EnvironmentRef;
33use crate::execution::ExecOutcome;
34use crate::execution::ExecutorSettings;
35use crate::execution::KclValue;
36use crate::execution::KclValueView;
37use crate::execution::OperationCallbackArgs;
38use crate::execution::OperationsByModule;
39use crate::execution::ProgramLookup;
40use crate::execution::SketchVarId;
41use crate::execution::UnsolvedSegment;
42use crate::execution::annotations;
43use crate::execution::cad_op::Operation;
44use crate::execution::id_generator::IdGenerator;
45#[cfg(test)]
46use crate::execution::memory::MemoryBackendKind;
47use crate::execution::memory::ProgramMemory;
48use crate::execution::memory::Stack;
49use crate::execution::sketch_solve::Solved;
50use crate::execution::types::NumericType;
51use crate::front::Number;
52use crate::front::Object;
53use crate::front::ObjectId;
54use crate::front::ObjectKind;
55use crate::id::IncIdGenerator;
56use crate::modules::ModuleId;
57use crate::modules::ModuleInfo;
58use crate::modules::ModuleLoader;
59use crate::modules::ModulePath;
60use crate::modules::ModuleRepr;
61use crate::modules::ModuleSource;
62use crate::parsing::ast::types::Annotation;
63use crate::parsing::ast::types::NodeRef;
64use crate::parsing::ast::types::TagNode;
65
66/// State for executing a program.
67#[derive(Debug, Clone)]
68pub struct ExecState {
69    pub(super) execution_callbacks: Option<std::sync::Arc<dyn crate::execution::ExecutionCallbacks>>,
70    pub(super) global: GlobalState,
71    pub(super) mod_local: ModuleState,
72}
73
74pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
75
76#[derive(Debug, Clone)]
77pub(super) struct GlobalState {
78    /// Map from source file absolute path to module ID.
79    pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
80    /// Map from module ID to source file.
81    pub id_to_source: IndexMap<ModuleId, ModuleSource>,
82    /// Map from module ID to module info.
83    pub module_infos: ModuleInfoMap,
84    /// Module loader.
85    pub mod_loader: ModuleLoader,
86    /// Errors and warnings.
87    pub issues: Vec<CompilationIssue>,
88    /// If set, use this version only when deciding whether to emit
89    /// `deprecated_since` warnings. Runtime behavior still uses the version
90    /// declared by the KCL program.
91    pub deprecation_version_override: Option<String>,
92    /// Global artifacts that represent the entire program.
93    pub artifacts: ArtifactState,
94    /// Artifacts for only the root module.
95    pub root_module_artifacts: ModuleArtifactState,
96    /// The segments that were edited that triggered this execution.
97    pub segment_ids_edited: AhashIndexSet<ObjectId>,
98    /// Segment-body drag anchors that temporarily pull a point on a segment toward the cursor.
99    pub drag_anchors: Vec<SegmentDragAnchor>,
100    /// True if this execution is sketch mode execution, executing a single
101    /// sketch block. Unlike [`ModuleState::sketch_mode`], this is constant for
102    /// the entire execution, including while executing the body of the sketch
103    /// block being edited.
104    pub sketch_mode: bool,
105}
106
107impl GlobalState {
108    pub(crate) fn operations_by_module(&self) -> OperationsByModule {
109        let mut operations = OperationsByModule::default();
110        operations.insert(ModuleId::default(), self.root_module_artifacts.operations.clone());
111
112        for (module_id, module_info) in &self.module_infos {
113            match &module_info.repr {
114                ModuleRepr::Root => {}
115                ModuleRepr::Kcl(_, Some(outcome)) => {
116                    operations.insert(*module_id, outcome.artifacts.operations.clone());
117                }
118                ModuleRepr::Foreign(_, Some((_, artifacts))) => {
119                    operations.insert(*module_id, artifacts.operations.clone());
120                }
121                ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
122            }
123        }
124
125        operations
126    }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
130pub(crate) enum ConstraintKey {
131    LineCircle([usize; 10]),
132    CircleCircle([usize; 12]),
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub(crate) enum TangencyMode {
137    LineCircle(ezpz::LineSide),
138    CircleCircle(ezpz::CircleSide),
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub(crate) enum ConstraintState {
143    Tangency(TangencyMode),
144}
145
146#[derive(Debug, Clone, Default)]
147pub(super) struct ArtifactState {
148    /// Internal map of UUIDs to exec artifacts.  This needs to persist across
149    /// executions to allow the graph building to refer to cached artifacts.
150    pub artifacts: IndexMap<ArtifactId, Artifact>,
151    /// Output artifact graph.
152    pub graph: ArtifactGraph,
153}
154
155/// Which stdlib edge function produced this refactor metadata (for lint/code mod).
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
157#[ts(export)]
158#[serde(rename_all = "camelCase")]
159pub enum EdgeRefactorStdlibFn {
160    GetOppositeEdge,
161    GetNextAdjacentEdge,
162    GetPreviousAdjacentEdge,
163    GetCommonEdge,
164    EdgeId,
165}
166
167/// Metadata collected when a deprecated edge stdlib function runs, for refactor-to-edgeRefs lint/code mod.
168#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
169#[ts(export)]
170#[serde(rename_all = "camelCase")]
171pub struct EdgeRefactorMeta {
172    pub edge_id: Uuid,
173    pub face_ids: [Uuid; 2],
174    #[serde(default, skip_serializing_if = "Vec::is_empty")]
175    pub end_face_ids: Vec<Uuid>,
176    pub source_range: SourceRange,
177    pub stdlib_fn: EdgeRefactorStdlibFn,
178}
179
180/// Metadata for a deprecated edge stdlib function whose edge ID was resolved,
181/// but whose adjacent face IDs could not be recorded at the helper callsite.
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub(crate) struct PendingEdgeRefactorMeta {
184    pub edge_id: Uuid,
185    pub source_range: SourceRange,
186    pub stdlib_fn: EdgeRefactorStdlibFn,
187}
188
189/// One tag entry in a fillet/chamfer call that used `tags` directly (for refactor to edgeRefs).
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
191#[ts(export)]
192#[serde(rename_all = "camelCase")]
193pub struct DirectTagFilletTagEntry {
194    pub tag_identifier: String,
195    pub edge_id: Uuid,
196    pub face_ids: [Uuid; 2],
197}
198
199/// Metadata for one fillet/chamfer call that used `tags` directly (no stdlib call).
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
201#[ts(export)]
202#[serde(rename_all = "camelCase")]
203pub struct DirectTagFilletMeta {
204    pub call_source_range: SourceRange,
205    pub tags: Vec<DirectTagFilletTagEntry>,
206}
207
208/// Information needed to rewrite one legacy `angle` call while preserving its
209/// currently solved directed-angle branch.
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
211#[ts(export)]
212#[serde(rename_all = "camelCase")]
213pub struct LegacyAngleRefactorMeta {
214    pub source_range: SourceRange,
215    pub sector: u8,
216    pub inverse: bool,
217}
218
219/// Unified metadata stream for Z0006 and future execution-backed refactors.
220#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS)]
221#[ts(export)]
222#[serde(tag = "kind", content = "data", rename_all = "camelCase")]
223pub enum RefactorMetadata {
224    EdgeRefactor(Box<EdgeRefactorMeta>),
225    DirectTagFillet(DirectTagFilletMeta),
226    LegacyAngle(LegacyAngleRefactorMeta),
227}
228
229#[derive(Debug, Clone)]
230pub(crate) struct PendingLegacyAngleRefactorMeta {
231    pub source_range: SourceRange,
232    pub lines: [ConstrainableLine2d; 2],
233    pub desired_angle_radians: f64,
234}
235
236/// Artifact state for a single module.
237#[derive(Debug, Clone, Default, PartialEq, Serialize)]
238pub struct ModuleArtifactState {
239    /// Internal map of UUIDs to exec artifacts.
240    pub artifacts: IndexMap<ArtifactId, Artifact>,
241    /// Outgoing engine commands that have not yet been processed and integrated
242    /// into the artifact graph.
243    #[serde(skip)]
244    pub unprocessed_commands: Vec<ArtifactCommand>,
245    /// Outgoing engine commands.
246    pub commands: Vec<ArtifactCommand>,
247    /// Incoming engine commands.
248    #[cfg(feature = "snapshot-engine-responses")]
249    pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
250    /// Operations that have been performed in execution order, for display in
251    /// the Feature Tree.
252    pub operations: Vec<Operation>,
253    /// [`ObjectId`] generator.
254    pub object_id_generator: IncIdGenerator<usize>,
255    /// Objects in the scene, created from execution.
256    pub scene_objects: Vec<Object>,
257    /// Map from source range to object ID for lookup of objects by their source
258    /// range.
259    pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
260    /// Map from artifact ID to object ID in the scene.
261    pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
262    /// Solutions for sketch variables.
263    pub var_solutions: Vec<(SourceRange, Option<NodePath>, Number)>,
264    /// Metadata collected during execution for refactor lint/code-mod paths (Z0006 and future).
265    pub refactor_metadata: Vec<RefactorMetadata>,
266    /// Deprecated edge helper callsites that may be completed by a downstream
267    /// operation that knows the target solid.
268    #[serde(skip)]
269    pub(crate) pending_edge_refactor_metadata: Vec<PendingEdgeRefactorMeta>,
270}
271
272#[derive(Debug, Clone)]
273pub(super) struct ModuleState {
274    /// The id of this module.
275    pub module_id: ModuleId,
276    /// The id generator for this module.
277    pub id_generator: IdGenerator,
278    pub stack: Stack,
279    /// The size of the call stack. This is used to prevent stack overflows with
280    /// recursive function calls. In general, this doesn't match `stack`'s size
281    /// since it's conservative in reclaiming frames between executions.
282    pub(super) call_stack_size: usize,
283    /// The current value of the pipe operator returned from the previous
284    /// expression.  If we're not currently in a pipeline, this will be None.
285    pub pipe_value: Option<KclValue>,
286    /// The closest variable declaration being executed in any parent node in the AST.
287    /// This is used to provide better error messages, e.g. noticing when the user is trying
288    /// to use the variable `length` inside the RHS of its own definition, like `length = tan(length)`.
289    pub being_declared: Option<String>,
290    /// Present if we're currently executing inside a sketch block.
291    pub sketch_block: Option<SketchBlockState>,
292    /// Tracks if KCL being executed is currently inside a stdlib function or not.
293    /// This matters because e.g. we shouldn't emit artifacts from declarations declared inside a stdlib function.
294    pub inside_stdlib: bool,
295    /// The source range where we entered the standard library.
296    pub stdlib_entry_source_range: Option<SourceRange>,
297    /// Identifiers that have been exported from the current module.
298    pub module_exports: Vec<String>,
299    /// Settings specified from annotations.
300    pub settings: MetaSettings,
301    /// True if executing in sketch mode. Only a single sketch block will be
302    /// executed. All other code is ignored.
303    pub sketch_mode: bool,
304    /// True to do more costly analysis of whether the sketch block segments are
305    /// under-constrained. The only time we disable this is when a user is
306    /// dragging segments.
307    pub freedom_analysis: bool,
308    pub(super) explicit_length_units: bool,
309    pub(super) path: ModulePath,
310    /// Artifacts for only this module.
311    pub artifacts: ModuleArtifactState,
312    /// Sticky per-constraint state persisted across sketch-mode mock solves.
313    /// Maps from sketch block ID to a map for that sketch.
314    /// Then the inner map is per constraint (in that sketch block) to its state.
315    pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
316
317    pub(super) allowed_warnings: Vec<&'static str>,
318    pub(super) denied_warnings: Vec<&'static str>,
319
320    /// Map from consumed solid values to information about the operation that
321    /// consumed them. Populated by operations that destroy their inputs so that
322    /// subsequent attempts to use a consumed solid produce a clear KCL-level
323    /// error rather than a cryptic engine error.
324    pub(super) consumed_solids: AHashMap<ConsumedSolidKey, ConsumedSolidInfo>,
325    /// Defensive map from consumed engine UUID to consumption info.
326    /// Rust code may create a `Solid` with a consumed `engine_id` and a
327    /// different `instance_id` that was not recorded in `consumed_solids`. When
328    /// the exact key lookup misses, this map lets us reject that solid by
329    /// `engine_id`, unless the key is a recorded operation output.
330    pub(super) consumed_solid_ids: AHashMap<Uuid, ConsumedSolidInfo>,
331    /// Region engine UUIDs consumed by successful modeling operations. Regions
332    /// use the KCL `Sketch` representation, so this state keeps stale Region
333    /// values from reaching an engine object that has become something else.
334    pub(super) consumed_regions: AHashMap<Uuid, ConsumedRegionInfo>,
335}
336
337/// Information about the operation that consumed a Region.
338#[derive(Debug, Clone, Copy)]
339pub(crate) struct ConsumedRegionInfo {
340    operation: ConsumedRegionOperation,
341}
342
343impl ConsumedRegionInfo {
344    pub(crate) fn new(operation: ConsumedRegionOperation) -> Self {
345        Self { operation }
346    }
347
348    pub(crate) fn operation(self) -> ConsumedRegionOperation {
349        self.operation
350    }
351}
352
353#[derive(Debug, Clone, Copy, PartialEq, Eq)]
354pub(crate) enum ConsumedRegionOperation {
355    Extrude,
356    Revolve,
357    Sweep,
358    Delete,
359}
360
361impl std::fmt::Display for ConsumedRegionOperation {
362    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363        match self {
364            Self::Extrude => f.write_str("extrude"),
365            Self::Revolve => f.write_str("revolve"),
366            Self::Sweep => f.write_str("sweep"),
367            Self::Delete => f.write_str("delete"),
368        }
369    }
370}
371
372/// Internal identity for one runtime KCL solid value.
373#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
374pub(crate) struct ConsumedSolidKey {
375    /// The engine body UUID.
376    engine_id: Uuid,
377    /// Distinguishes this KCL runtime instance from other values that may reuse
378    /// the same engine body UUID.
379    instance_id: Uuid,
380}
381
382impl ConsumedSolidKey {
383    pub(crate) fn new(engine_id: Uuid, instance_id: Uuid) -> Self {
384        Self { engine_id, instance_id }
385    }
386
387    pub(crate) fn engine_id(&self) -> Uuid {
388        self.engine_id
389    }
390
391    pub(crate) fn instance_id(&self) -> Uuid {
392        self.instance_id
393    }
394}
395
396/// Information about a solid value that was consumed by an operation.
397/// Stored in `ModuleState.consumed_solids` so subsequent attempts to use the
398/// solid produce a clear error pointing at the operation that consumed it.
399#[derive(Debug, Clone)]
400pub(crate) struct ConsumedSolidInfo {
401    /// The operation that consumed the solid.
402    operation: ConsumedSolidOperation,
403    /// First returned solid value, used only for replacement suggestions in
404    /// error messages. When present, this key is also included in
405    /// `returned_solid_keys`.
406    suggested_replacement_key: Option<ConsumedSolidKey>,
407    /// All solid values returned by that operation. This is used as the
408    /// allow-list for returned solids that reuse a consumed engine UUID.
409    returned_solid_keys: Vec<ConsumedSolidKey>,
410}
411
412impl ConsumedSolidInfo {
413    pub(crate) fn new(operation: ConsumedSolidOperation, returned_solid_keys: Vec<ConsumedSolidKey>) -> Self {
414        Self {
415            operation,
416            suggested_replacement_key: returned_solid_keys.first().copied(),
417            returned_solid_keys,
418        }
419    }
420
421    pub(crate) fn operation(&self) -> ConsumedSolidOperation {
422        self.operation
423    }
424
425    pub(crate) fn suggested_replacement_key(&self) -> Option<ConsumedSolidKey> {
426        self.suggested_replacement_key
427    }
428
429    pub(crate) fn should_report_reused_engine_id_as_consumed(&self, key: ConsumedSolidKey) -> bool {
430        !self.returned_solid_keys.contains(&key)
431    }
432}
433
434#[derive(Debug, Clone, Copy, PartialEq, Eq)]
435pub(crate) enum ConsumedSolidOperation {
436    Union,
437    Intersect,
438    Subtract,
439    Split,
440    JoinSurfaces,
441}
442
443impl ConsumedSolidOperation {
444    pub(crate) fn indefinite_article(self) -> &'static str {
445        match self {
446            Self::Intersect => "an",
447            Self::Union | Self::Subtract | Self::Split | Self::JoinSurfaces => "a",
448        }
449    }
450}
451
452impl std::fmt::Display for ConsumedSolidOperation {
453    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454        match self {
455            Self::Union => f.write_str("union"),
456            Self::Intersect => f.write_str("intersect"),
457            Self::Subtract => f.write_str("subtract"),
458            Self::Split => f.write_str("split"),
459            Self::JoinSurfaces => f.write_str("joinSurfaces"),
460        }
461    }
462}
463
464#[derive(Debug, Clone, Default)]
465pub(crate) struct SketchBlockState {
466    pub sketch_vars: Vec<KclValue>,
467    pub sketch_id: Option<ObjectId>,
468    pub sketch_constraints: Vec<ObjectId>,
469    pub solver_constraints: Vec<ezpz::Constraint>,
470    pub solver_optional_constraints: Vec<ezpz::Constraint>,
471    pub needed_by_engine: Vec<UnsolvedSegment>,
472    pub segment_tags: IndexMap<ObjectId, TagNode>,
473    pub pending_legacy_angle_refactor_metadata: Vec<PendingLegacyAngleRefactorMeta>,
474}
475
476impl ExecState {
477    pub fn new(exec_context: &super::ExecutorContext) -> Self {
478        ExecState {
479            execution_callbacks: exec_context.execution_callbacks.clone(),
480            global: GlobalState::new(&exec_context.settings, Default::default()),
481            mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
482        }
483    }
484
485    #[cfg(test)]
486    pub(crate) fn new_with_memory_backend(exec_context: &super::ExecutorContext, backend: MemoryBackendKind) -> Self {
487        ExecState {
488            execution_callbacks: exec_context.execution_callbacks.clone(),
489            global: GlobalState::new(&exec_context.settings, Default::default()),
490            mod_local: ModuleState::new(
491                ModulePath::Main,
492                ProgramMemory::new_with_backend(backend),
493                Default::default(),
494                false,
495                true,
496            ),
497        }
498    }
499
500    pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
501        let segment_ids_edited = mock_config.segment_ids_edited.clone();
502        let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
503        global.drag_anchors = mock_config.drag_anchors.clone();
504        global.sketch_mode = mock_config.sketch_block_id.is_some();
505        ExecState {
506            execution_callbacks: exec_context.execution_callbacks.clone(),
507            global,
508            mod_local: ModuleState::new(
509                ModulePath::Main,
510                ProgramMemory::new(),
511                Default::default(),
512                mock_config.sketch_block_id.is_some(),
513                mock_config.freedom_analysis,
514            ),
515        }
516    }
517
518    #[cfg(test)]
519    pub(crate) fn new_mock_with_memory_backend(
520        exec_context: &super::ExecutorContext,
521        mock_config: &MockConfig,
522        backend: MemoryBackendKind,
523    ) -> Self {
524        let segment_ids_edited = mock_config.segment_ids_edited.clone();
525        let mut global = GlobalState::new(&exec_context.settings, segment_ids_edited);
526        global.drag_anchors = mock_config.drag_anchors.clone();
527        global.sketch_mode = mock_config.sketch_block_id.is_some();
528        ExecState {
529            execution_callbacks: exec_context.execution_callbacks.clone(),
530            global,
531            mod_local: ModuleState::new(
532                ModulePath::Main,
533                ProgramMemory::new_with_backend(backend),
534                Default::default(),
535                mock_config.sketch_block_id.is_some(),
536                mock_config.freedom_analysis,
537            ),
538        }
539    }
540
541    pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
542        let global = GlobalState::new(&exec_context.settings, Default::default());
543
544        *self = ExecState {
545            execution_callbacks: exec_context.execution_callbacks.clone(),
546            global,
547            mod_local: ModuleState::new(
548                self.mod_local.path.clone(),
549                ProgramMemory::new(),
550                Default::default(),
551                false,
552                true,
553            ),
554        };
555    }
556
557    /// Log a non-fatal error.
558    pub fn err(&mut self, e: CompilationIssue) {
559        self.global.issues.push(e);
560    }
561
562    /// Log a warning.
563    pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
564        debug_assert!(annotations::WARN_VALUES.contains(&name));
565
566        if self.mod_local.allowed_warnings.contains(&name) {
567            return;
568        }
569
570        if self.mod_local.denied_warnings.contains(&name) {
571            e.severity = Severity::Error;
572        } else {
573            e.severity = Severity::Warning;
574        }
575
576        self.global.issues.push(e);
577    }
578
579    pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
580        let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
581            return;
582        };
583        let error = CompilationIssue {
584            source_range,
585            message: format!("Use of {feature_name} is experimental and may change or be removed."),
586            suggestion: None,
587            severity,
588            tag: crate::errors::Tag::None,
589        };
590
591        self.global.issues.push(error);
592    }
593
594    pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
595        self.global.issues = std::mem::take(&mut self.global.issues)
596            .into_iter()
597            .filter(|e| {
598                e.severity != Severity::Warning
599                    || !source_range.contains_range(&e.source_range)
600                    || e.tag != crate::errors::Tag::UnknownNumericUnits
601            })
602            .collect();
603    }
604
605    pub fn issues(&self) -> &[CompilationIssue] {
606        &self.global.issues
607    }
608
609    pub(crate) fn deprecation_version(&self) -> &str {
610        self.global
611            .deprecation_version_override
612            .as_deref()
613            .unwrap_or(self.mod_local.settings.kcl_version.as_str())
614    }
615
616    #[cfg(test)]
617    pub(crate) fn set_deprecation_version_override(&mut self, version: Option<&str>) {
618        self.global.deprecation_version_override = version.map(str::to_owned);
619    }
620
621    /// Convert to execution outcome when running in WebAssembly.  We want to
622    /// reduce the amount of data that crosses the WASM boundary as much as
623    /// possible.
624    pub async fn into_exec_outcome(
625        self,
626        main_ref: EnvironmentRef,
627        ctx: &ExecutorContext,
628    ) -> Result<ExecOutcome, KclError> {
629        // Fields are opt-in so that we don't accidentally leak private internal
630        // state when we add more to ExecState.
631        let variables = self
632            .mod_local
633            .variables(main_ref)?
634            .into_iter()
635            .map(|(key, value)| (key, KclValueView::from(value)))
636            .collect();
637        Ok(ExecOutcome {
638            variables,
639            filenames: self.global.filenames(),
640            operations: self.global.operations_by_module(),
641            artifact_graph: self.global.artifacts.graph,
642            scene_objects: self.global.root_module_artifacts.scene_objects,
643            source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
644            var_solutions: self.global.root_module_artifacts.var_solutions,
645            refactor_metadata: self.global.root_module_artifacts.refactor_metadata.clone(),
646            issues: self.global.issues,
647            default_planes: ctx.engine.get_default_planes().read().await.clone(),
648        })
649    }
650
651    #[cfg(feature = "snapshot-engine-responses")]
652    pub(crate) fn take_root_module_responses(
653        &mut self,
654    ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
655        std::mem::take(&mut self.global.root_module_artifacts.responses)
656    }
657
658    pub(crate) fn stack(&self) -> &Stack {
659        &self.mod_local.stack
660    }
661
662    pub(crate) fn mut_stack(&mut self) -> &mut Stack {
663        &mut self.mod_local.stack
664    }
665
666    /// Increment the user-level call stack size, returning an error if it
667    /// exceeds the maximum.
668    pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
669        // If you change this, make sure to test in WebAssembly in the app since
670        // that's the limiting factor.
671        if self.mod_local.call_stack_size >= 50 {
672            return Err(KclError::MaxCallStack {
673                details: KclErrorDetails::new("maximum call stack size exceeded".to_owned(), vec![range]),
674            });
675        }
676        self.mod_local.call_stack_size += 1;
677        Ok(())
678    }
679
680    /// Decrement the user-level call stack size, returning an error if it would
681    /// go below zero.
682    pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
683        // Prevent underflow.
684        if self.mod_local.call_stack_size == 0 {
685            let message = "call stack size below zero".to_owned();
686            debug_assert!(false, "{message}");
687            return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
688        }
689        self.mod_local.call_stack_size -= 1;
690        Ok(())
691    }
692
693    /// Returns true if we're executing in sketch mode for the current module.
694    /// In sketch mode, we still want to execute the prelude and other stdlib
695    /// modules as normal, so it can vary per module within a single overall
696    /// execution.
697    pub(crate) fn sketch_mode(&self) -> bool {
698        self.mod_local.sketch_mode
699            && match &self.mod_local.path {
700                ModulePath::Main => true,
701                ModulePath::Local { .. } => true,
702                ModulePath::Std { .. } => false,
703            }
704    }
705
706    /// Returns true if this execution is sketch mode execution, executing a
707    /// single sketch block. Unlike [`Self::sketch_mode`], this doesn't vary
708    /// during the execution.
709    pub(crate) fn is_sketch_mode_execution(&self) -> bool {
710        self.global.sketch_mode
711    }
712
713    pub fn next_object_id(&mut self) -> ObjectId {
714        ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
715    }
716
717    pub fn peek_object_id(&self) -> ObjectId {
718        ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
719    }
720
721    pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
722        let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
723        map.get(key).copied()
724    }
725
726    pub(crate) fn set_constraint_state(
727        &mut self,
728        sketch_block_id: ObjectId,
729        key: ConstraintKey,
730        state: ConstraintState,
731    ) {
732        let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
733        map.insert(key, state);
734    }
735
736    pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
737        let id = obj.id;
738        debug_assert!(
739            id.0 == self.mod_local.artifacts.scene_objects.len(),
740            "Adding scene object with ID {} but next ID is {}",
741            id.0,
742            self.mod_local.artifacts.scene_objects.len()
743        );
744        let artifact_id = obj.artifact_id;
745        self.mod_local.artifacts.scene_objects.push(obj);
746        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
747        self.mod_local
748            .artifacts
749            .artifact_id_to_scene_object
750            .insert(artifact_id, id);
751        id
752    }
753
754    /// Add a placeholder scene object. This is useful when we need to reserve
755    /// an ID before we have all the information to create the full object.
756    pub fn add_placeholder_scene_object(
757        &mut self,
758        id: ObjectId,
759        source_range: SourceRange,
760        node_path: Option<NodePath>,
761    ) -> ObjectId {
762        debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
763        self.mod_local
764            .artifacts
765            .scene_objects
766            .push(Object::placeholder(id, source_range, node_path));
767        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
768        id
769    }
770
771    /// Update a scene object. This is useful to replace a placeholder.
772    pub fn set_scene_object(&mut self, object: Object) {
773        let id = object.id;
774        let artifact_id = object.artifact_id;
775        self.mod_local.artifacts.scene_objects[id.0] = object;
776        self.mod_local
777            .artifacts
778            .artifact_id_to_scene_object
779            .insert(artifact_id, id);
780    }
781
782    pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
783        self.mod_local
784            .artifacts
785            .artifact_id_to_scene_object
786            .get(&artifact_id)
787            .cloned()
788    }
789
790    pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
791        self.global.segment_ids_edited.contains(object_id)
792    }
793
794    pub fn drag_anchor_target(&self, object_id: &ObjectId) -> Option<&crate::front::Point2d<crate::front::Number>> {
795        self.global
796            .drag_anchors
797            .iter()
798            .find(|anchor| &anchor.segment_id == object_id)
799            .map(|anchor| &anchor.target)
800    }
801
802    pub(super) fn is_in_sketch_block(&self) -> bool {
803        self.mod_local.sketch_block.is_some()
804    }
805
806    pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
807        self.mod_local.sketch_block.as_mut()
808    }
809
810    pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
811        self.mod_local.sketch_block.as_ref()
812    }
813
814    pub fn next_uuid(&mut self) -> Uuid {
815        self.mod_local.id_generator.next_uuid()
816    }
817
818    pub fn next_artifact_id(&mut self) -> ArtifactId {
819        self.mod_local.id_generator.next_artifact_id()
820    }
821
822    pub fn id_generator(&mut self) -> &mut IdGenerator {
823        &mut self.mod_local.id_generator
824    }
825
826    /// Record that a solid value has been consumed by a CSG boolean operation.
827    pub(crate) fn mark_solid_consumed(&mut self, consumed_key: ConsumedSolidKey, info: ConsumedSolidInfo) {
828        self.mod_local.consumed_solids.insert(consumed_key, info);
829    }
830
831    /// Record that an engine body UUID has been consumed by a CSG boolean
832    /// operation.
833    pub(crate) fn mark_solid_id_consumed(&mut self, consumed_id: Uuid, info: ConsumedSolidInfo) {
834        self.mod_local.consumed_solid_ids.insert(consumed_id, info);
835    }
836
837    /// Look up whether a solid value was consumed by a previous CSG boolean
838    /// operation.
839    pub(crate) fn check_solid_consumed(&self, key: &ConsumedSolidKey) -> Option<&ConsumedSolidInfo> {
840        self.mod_local.consumed_solids.get(key)
841    }
842
843    /// Look up whether an engine body UUID was consumed by a previous CSG
844    /// boolean operation.
845    pub(crate) fn check_solid_id_consumed(&self, id: &Uuid) -> Option<&ConsumedSolidInfo> {
846        self.mod_local.consumed_solid_ids.get(id)
847    }
848
849    pub(crate) fn mark_region_consumed(&mut self, id: Uuid, info: ConsumedRegionInfo) {
850        self.mod_local.consumed_regions.insert(id, info);
851    }
852
853    pub(crate) fn check_region_consumed(&self, id: &Uuid) -> Option<ConsumedRegionInfo> {
854        self.mod_local.consumed_regions.get(id).copied()
855    }
856
857    /// Find the current variable containing a Region engine UUID. This runs
858    /// only while constructing a diagnostic, so recursively searching arrays
859    /// and objects is preferable to storing variable names in liveness state.
860    pub(crate) fn find_var_name_for_region_id(&self, target_id: Uuid) -> Result<Option<String>, KclError> {
861        fn contains_region_id(value: &KclValue, target_id: Uuid) -> bool {
862            match value {
863                KclValue::Sketch { value } => value.origin_sketch_id.is_some() && value.id == target_id,
864                KclValue::HomArray { value, .. } | KclValue::Tuple { value, .. } => {
865                    value.iter().any(|value| contains_region_id(value, target_id))
866                }
867                KclValue::Object { value, .. } => value.values().any(|value| contains_region_id(value, target_id)),
868                _ => false,
869            }
870        }
871
872        self.mod_local
873            .stack
874            .find_var_name_in_all_envs(|value| contains_region_id(value, target_id))
875    }
876
877    /// Follow direct replacement links until we find the latest known output.
878    /// Used only on error paths so diagnostics can suggest the current solid.
879    pub(crate) fn latest_consumed_output(
880        &self,
881        suggested_replacement_key: Option<ConsumedSolidKey>,
882    ) -> Option<ConsumedSolidKey> {
883        let mut latest = suggested_replacement_key?;
884        let mut seen = AhashIndexSet::default();
885
886        while seen.insert(latest) {
887            let Some(next) = self
888                .mod_local
889                .consumed_solids
890                .get(&latest)
891                .and_then(|info| info.suggested_replacement_key())
892            else {
893                break;
894            };
895            latest = next;
896        }
897
898        Some(latest)
899    }
900
901    /// Search the live environment for the name of a variable holding a Solid
902    /// (or an array of Solids) whose value identity matches `target_key`. Used only on
903    /// error paths to recover variable names for diagnostics.
904    pub(crate) fn find_var_name_for_solid_key(&self, target_key: ConsumedSolidKey) -> Result<Option<String>, KclError> {
905        fn contains_solid_key(value: &KclValue, target_key: ConsumedSolidKey) -> bool {
906            match value {
907                KclValue::Solid { value } => {
908                    value.id == target_key.engine_id() && value.value_id == target_key.instance_id()
909                }
910                KclValue::HomArray { value, .. } => value.iter().any(|v| contains_solid_key(v, target_key)),
911                _ => false,
912            }
913        }
914        self.mod_local
915            .stack
916            .find_var_name_in_all_envs(|value| contains_solid_key(value, target_key))
917    }
918
919    pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
920        let id = artifact.id();
921        self.mod_local.artifacts.artifacts.insert(id, artifact);
922    }
923
924    pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
925        self.mod_local.artifacts.artifacts.get_mut(&id)
926    }
927
928    pub(crate) fn push_op(&mut self, op: Operation) {
929        let index = self.mod_local.artifacts.operations.len();
930        self.mod_local.artifacts.operations.push(op);
931        if let Some(operation) = self.mod_local.artifacts.operations.last().cloned()
932            && let Some(callbacks) = &self.execution_callbacks
933        {
934            callbacks.on_operation(OperationCallbackArgs {
935                module_id: self.mod_local.module_id,
936                operation,
937                index,
938            });
939        }
940    }
941
942    pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
943        self.mod_local.artifacts.unprocessed_commands.push(command);
944    }
945
946    pub(super) fn next_module_id(&self) -> ModuleId {
947        ModuleId::from_usize(self.global.path_to_source_id.len())
948    }
949
950    pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
951        self.global.path_to_source_id.get(path).cloned()
952    }
953
954    pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
955        debug_assert!(!self.global.path_to_source_id.contains_key(&path));
956        self.global.path_to_source_id.insert(path, id);
957    }
958
959    pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
960        let root_id = ModuleId::default();
961        // Get the path for the root module.
962        let path = self
963            .global
964            .path_to_source_id
965            .iter()
966            .find(|(_, v)| **v == root_id)
967            .unwrap()
968            .0
969            .clone();
970        self.add_id_to_source(
971            root_id,
972            ModuleSource {
973                path,
974                source: program.original_file_contents.to_string(),
975            },
976        );
977    }
978
979    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
980        self.global.id_to_source.insert(id, source);
981    }
982
983    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
984        debug_assert!(self.global.path_to_source_id.contains_key(&path));
985        let module_info = ModuleInfo { id, repr, path };
986        self.global.module_infos.insert(id, module_info);
987    }
988
989    pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
990        self.global.module_infos.get(&id)
991    }
992
993    #[cfg(test)]
994    pub(crate) fn modules(&self) -> &ModuleInfoMap {
995        &self.global.module_infos
996    }
997
998    #[cfg(test)]
999    pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
1000        &self.global.root_module_artifacts
1001    }
1002
1003    /// Record metadata from a deprecated edge stdlib call for the Z0006 refactor.
1004    ///
1005    /// This is intentionally collected unconditionally when artifact graph support is enabled.
1006    /// The temporary feature flag only controls whether the lint/action is shown in the app.
1007    pub(crate) fn record_edge_refactor_meta(&mut self, meta: EdgeRefactorMeta) {
1008        self.mod_local
1009            .artifacts
1010            .refactor_metadata
1011            .push(RefactorMetadata::EdgeRefactor(Box::new(meta)));
1012    }
1013
1014    pub(crate) fn record_pending_edge_refactor_meta(&mut self, meta: PendingEdgeRefactorMeta) {
1015        self.mod_local.artifacts.pending_edge_refactor_metadata.push(meta);
1016    }
1017
1018    pub(crate) fn pending_edge_refactor_meta(
1019        &self,
1020        edge_id: Uuid,
1021        argument_source_range: SourceRange,
1022    ) -> Option<PendingEdgeRefactorMeta> {
1023        if let Some(pending) = self
1024            .mod_local
1025            .artifacts
1026            .pending_edge_refactor_metadata
1027            .iter()
1028            .find(|meta| meta.edge_id == edge_id && argument_source_range.contains_range(&meta.source_range))
1029        {
1030            return Some(pending.clone());
1031        }
1032
1033        // A helper assigned to a variable is outside the argument's source
1034        // range. Fall back to the edge ID only when it identifies one helper.
1035        let mut matches = self
1036            .mod_local
1037            .artifacts
1038            .pending_edge_refactor_metadata
1039            .iter()
1040            .filter(|meta| meta.edge_id == edge_id);
1041        let pending = matches.next()?.clone();
1042        matches.next().is_none().then_some(pending)
1043    }
1044
1045    pub(crate) fn record_edge_refactor_meta_from_pending(
1046        &mut self,
1047        edge_id: Uuid,
1048        source_range: SourceRange,
1049        face_ids: [Uuid; 2],
1050    ) -> bool {
1051        if self.mod_local.artifacts.refactor_metadata.iter().any(|meta| {
1052            matches!(
1053                meta,
1054                RefactorMetadata::EdgeRefactor(meta)
1055                    if meta.edge_id == edge_id && meta.source_range == source_range
1056            )
1057        }) {
1058            return true;
1059        }
1060
1061        let exact_pending_meta = self
1062            .mod_local
1063            .artifacts
1064            .pending_edge_refactor_metadata
1065            .iter()
1066            .find(|meta| meta.edge_id == edge_id && meta.source_range == source_range)
1067            .cloned();
1068
1069        let edge_pending_meta = || {
1070            let mut matches = self
1071                .mod_local
1072                .artifacts
1073                .pending_edge_refactor_metadata
1074                .iter()
1075                .filter(|meta| meta.edge_id == edge_id);
1076            let pending_meta = matches.next()?.clone();
1077            matches.next().is_none().then_some(pending_meta)
1078        };
1079
1080        let Some(pending_meta) = exact_pending_meta.or_else(edge_pending_meta) else {
1081            return false;
1082        };
1083
1084        self.record_edge_refactor_meta(EdgeRefactorMeta {
1085            edge_id,
1086            face_ids,
1087            end_face_ids: Vec::new(),
1088            source_range: pending_meta.source_range,
1089            stdlib_fn: pending_meta.stdlib_fn,
1090        });
1091
1092        true
1093    }
1094
1095    /// Record metadata from a fillet/chamfer call that used `tags` directly.
1096    ///
1097    /// This is intentionally collected unconditionally when artifact graph support is enabled.
1098    /// The temporary feature flag only controls whether the lint/action is shown in the app.
1099    pub(crate) fn record_direct_tag_fillet_meta(&mut self, meta: DirectTagFilletMeta) {
1100        self.mod_local
1101            .artifacts
1102            .refactor_metadata
1103            .push(RefactorMetadata::DirectTagFillet(meta));
1104    }
1105
1106    /// Refactor metadata collected when deprecated edge stdlib functions run (for tests and lint).
1107    pub fn edge_refactor_metadata(&self) -> Vec<EdgeRefactorMeta> {
1108        self.global
1109            .root_module_artifacts
1110            .refactor_metadata
1111            .iter()
1112            .filter_map(|m| match m {
1113                RefactorMetadata::EdgeRefactor(meta) => Some(meta.as_ref().clone()),
1114                RefactorMetadata::DirectTagFillet(_) | RefactorMetadata::LegacyAngle(_) => None,
1115            })
1116            .collect()
1117    }
1118
1119    /// Direct-tag fillet/chamfer metadata (for Z0006 code mod).
1120    pub fn direct_tag_fillet_metadata(&self) -> Vec<DirectTagFilletMeta> {
1121        self.global
1122            .root_module_artifacts
1123            .refactor_metadata
1124            .iter()
1125            .filter_map(|m| match m {
1126                RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::LegacyAngle(_) => None,
1127                RefactorMetadata::DirectTagFillet(meta) => Some(meta.clone()),
1128            })
1129            .collect()
1130    }
1131
1132    pub fn current_default_units(&self) -> NumericType {
1133        NumericType::Default {
1134            len: self.length_unit(),
1135            angle: self.angle_unit(),
1136        }
1137    }
1138
1139    pub fn length_unit(&self) -> UnitLength {
1140        self.mod_local.settings.default_length_units
1141    }
1142
1143    pub fn angle_unit(&self) -> UnitAngle {
1144        self.mod_local.settings.default_angle_units
1145    }
1146
1147    pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
1148        KclError::new_import_cycle(KclErrorDetails::new(
1149            format!(
1150                "circular import of modules is not allowed: {} -> {}",
1151                self.global
1152                    .mod_loader
1153                    .import_stack
1154                    .iter()
1155                    .map(|p| p.to_string_lossy())
1156                    .collect::<Vec<_>>()
1157                    .join(" -> "),
1158                path,
1159            ),
1160            vec![source_range],
1161        ))
1162    }
1163
1164    pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
1165        self.mod_local.pipe_value.as_ref()
1166    }
1167
1168    pub(crate) fn error_with_outputs(
1169        &self,
1170        error: KclError,
1171        main_ref: Option<EnvironmentRef>,
1172        default_planes: Option<DefaultPlanes>,
1173    ) -> KclErrorWithOutputs {
1174        let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
1175            .global
1176            .path_to_source_id
1177            .iter()
1178            .map(|(k, v)| ((*v), k.clone()))
1179            .collect();
1180
1181        KclErrorWithOutputs::new(
1182            error,
1183            self.issues().to_vec(),
1184            main_ref
1185                .and_then(|main_ref| self.mod_local.variables(main_ref).ok())
1186                .unwrap_or_default(),
1187            self.global.operations_by_module(),
1188            Default::default(),
1189            self.global.artifacts.graph.clone(),
1190            self.global.root_module_artifacts.scene_objects.clone(),
1191            self.global.root_module_artifacts.source_range_to_object.clone(),
1192            self.global.root_module_artifacts.var_solutions.clone(),
1193            self.global.root_module_artifacts.refactor_metadata.clone(),
1194            module_id_to_module_path,
1195            self.global.id_to_source.clone(),
1196            default_planes,
1197        )
1198    }
1199
1200    pub(crate) fn build_program_lookup(
1201        &self,
1202        current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
1203    ) -> ProgramLookup {
1204        ProgramLookup::new(current, self.global.module_infos.clone())
1205    }
1206
1207    pub(crate) async fn build_artifact_graph(
1208        &mut self,
1209        engine: &Arc<EngineManager>,
1210        program: NodeRef<'_, crate::parsing::ast::types::Program>,
1211    ) -> Result<(), KclError> {
1212        let mut new_commands = Vec::new();
1213        let mut new_exec_artifacts = IndexMap::new();
1214        for module in self.global.module_infos.values_mut() {
1215            match &mut module.repr {
1216                ModuleRepr::Kcl(_, Some(outcome)) => {
1217                    new_commands.extend(outcome.artifacts.process_commands());
1218                    new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
1219                }
1220                ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
1221                    new_commands.extend(module_artifacts.process_commands());
1222                    new_exec_artifacts.extend(module_artifacts.artifacts.clone());
1223                }
1224                ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
1225            }
1226        }
1227        // Take from the module artifacts so that we don't try to process them
1228        // again next time due to execution caching.
1229        new_commands.extend(self.global.root_module_artifacts.process_commands());
1230        // Note: These will get re-processed, but since we're just adding them
1231        // to a map, it's fine.
1232        new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
1233        let new_responses = engine.take_responses().await;
1234
1235        // Move the artifacts into ExecState global to simplify cache
1236        // management.
1237        for (id, exec_artifact) in new_exec_artifacts {
1238            // Only insert if it wasn't already present. We don't want to
1239            // overwrite what was previously there. We haven't filled in node
1240            // paths yet.
1241            self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
1242        }
1243
1244        let initial_graph = self.global.artifacts.graph.clone();
1245
1246        // Build the artifact graph.
1247        let programs = self.build_program_lookup(program.clone());
1248        let graph_result = crate::execution::artifact::build_artifact_graph(
1249            &new_commands,
1250            &new_responses,
1251            program,
1252            &mut self.global.artifacts.artifacts,
1253            initial_graph,
1254            &programs,
1255            &self.global.module_infos,
1256        );
1257
1258        #[cfg(feature = "snapshot-engine-responses")]
1259        {
1260            // Store engine responses for debugging.
1261            self.global.root_module_artifacts.responses.extend(new_responses);
1262        }
1263
1264        let artifact_graph = graph_result?;
1265        self.global.artifacts.graph = artifact_graph;
1266
1267        Ok(())
1268    }
1269
1270    pub(crate) fn kcl_version(&self) -> KclVersion {
1271        self.mod_local.settings.kcl_version
1272    }
1273}
1274
1275#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, Ord, PartialOrd)]
1276#[ts(export)]
1277pub enum KclVersion {
1278    #[default]
1279    #[serde(rename = "1.0")]
1280    V1,
1281    #[serde(rename = "2.0")]
1282    V2,
1283    #[serde(rename = "3.0-preview")]
1284    V3Preview,
1285}
1286
1287impl KclVersion {
1288    pub fn as_str(self) -> &'static str {
1289        match self {
1290            Self::V1 => "1.0",
1291            Self::V2 => "2.0",
1292            Self::V3Preview => "3.0-preview",
1293        }
1294    }
1295}
1296
1297impl FromStr for KclVersion {
1298    type Err = KclError;
1299
1300    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1301        match s {
1302            "1" | "1.0" | "1.0.0" => Ok(Self::V1),
1303            "2" | "2.0" | "2.0.0" => Ok(Self::V2),
1304            "3-preview" | "3.0-preview" | "3.0.0-preview" => Ok(Self::V3Preview),
1305            other => Err(KclError::new_semantic(KclErrorDetails {
1306                source_ranges: Default::default(),
1307                backtrace: Default::default(),
1308                message: format!(
1309                    "Unrecognized version {other}. Valid versions are 1.0, 2.0 and (experimentally) 3.0-preview"
1310                ),
1311            })),
1312        }
1313    }
1314}
1315
1316impl GlobalState {
1317    fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
1318        let mut global = GlobalState {
1319            path_to_source_id: Default::default(),
1320            module_infos: Default::default(),
1321            artifacts: Default::default(),
1322            root_module_artifacts: Default::default(),
1323            mod_loader: Default::default(),
1324            issues: Default::default(),
1325            deprecation_version_override: None,
1326            id_to_source: Default::default(),
1327            segment_ids_edited,
1328            drag_anchors: Vec::new(),
1329            sketch_mode: false,
1330        };
1331
1332        let root_id = ModuleId::default();
1333        let root_path = settings.current_file.clone().unwrap_or_default();
1334        global.module_infos.insert(
1335            root_id,
1336            ModuleInfo {
1337                id: root_id,
1338                path: ModulePath::Local {
1339                    value: root_path.clone(),
1340                    original_import_path: None,
1341                },
1342                repr: ModuleRepr::Root,
1343            },
1344        );
1345        global.path_to_source_id.insert(
1346            ModulePath::Local {
1347                value: root_path,
1348                original_import_path: None,
1349            },
1350            root_id,
1351        );
1352        global
1353    }
1354
1355    pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
1356        self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
1357    }
1358
1359    pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
1360        self.id_to_source.get(&id)
1361    }
1362}
1363
1364impl ArtifactState {
1365    pub fn cached_body_items(&self) -> usize {
1366        self.graph.item_count()
1367    }
1368
1369    pub(crate) fn clear(&mut self) {
1370        self.artifacts.clear();
1371        self.graph.clear();
1372    }
1373}
1374
1375impl ModuleArtifactState {
1376    pub fn legacy_angle_refactor_metadata(&self) -> Vec<LegacyAngleRefactorMeta> {
1377        self.refactor_metadata
1378            .iter()
1379            .filter_map(|metadata| match metadata {
1380                RefactorMetadata::LegacyAngle(metadata) => Some(*metadata),
1381                RefactorMetadata::EdgeRefactor(_) | RefactorMetadata::DirectTagFillet(_) => None,
1382            })
1383            .collect()
1384    }
1385
1386    pub(crate) fn clear(&mut self) {
1387        self.artifacts.clear();
1388        self.unprocessed_commands.clear();
1389        self.commands.clear();
1390        self.operations.clear();
1391        self.refactor_metadata.clear();
1392    }
1393
1394    pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
1395        self.scene_objects = scene_objects.to_vec();
1396        self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
1397        self.source_range_to_object.clear();
1398        self.artifact_id_to_scene_object.clear();
1399
1400        for (expected_id, object) in self.scene_objects.iter().enumerate() {
1401            debug_assert_eq!(
1402                object.id.0, expected_id,
1403                "Restored cached scene object ID {} does not match its position {}",
1404                object.id.0, expected_id
1405            );
1406
1407            match &object.kind {
1408                ObjectKind::Wall(wall) => {
1409                    self.source_range_to_object.insert(wall.source.solid.range, object.id);
1410                }
1411                ObjectKind::Cap(cap) => {
1412                    self.source_range_to_object.insert(cap.source.solid.range, object.id);
1413                }
1414                _ => match &object.source {
1415                    crate::front::SourceRef::Simple { range, node_path: _ } => {
1416                        self.source_range_to_object.insert(*range, object.id);
1417                    }
1418                    crate::front::SourceRef::BackTrace { ranges } => {
1419                        // Don't map the entire backtrace, only the most specific
1420                        // range.
1421                        if let Some((range, _)) = ranges.first() {
1422                            self.source_range_to_object.insert(*range, object.id);
1423                        }
1424                    }
1425                },
1426            }
1427
1428            // Ignore placeholder artifacts.
1429            if object.artifact_id != ArtifactId::placeholder() {
1430                self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
1431            }
1432        }
1433    }
1434
1435    /// When self is a cached state, extend it with new state.
1436    pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
1437        self.artifacts.extend(other.artifacts);
1438        self.unprocessed_commands.extend(other.unprocessed_commands);
1439        self.commands.extend(other.commands);
1440        self.operations.extend(other.operations);
1441        if other.scene_objects.len() > self.scene_objects.len() {
1442            self.scene_objects
1443                .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
1444        }
1445        self.source_range_to_object.extend(other.source_range_to_object);
1446        self.artifact_id_to_scene_object
1447            .extend(other.artifact_id_to_scene_object);
1448        self.var_solutions.extend(other.var_solutions);
1449        self.refactor_metadata.extend(other.refactor_metadata);
1450    }
1451
1452    // Move unprocessed artifact commands so that we don't try to process them
1453    // again next time due to execution caching.  Returns a clone of the
1454    // commands that were moved.
1455    pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
1456        let unprocessed = std::mem::take(&mut self.unprocessed_commands);
1457        let new_module_commands = unprocessed.clone();
1458        self.commands.extend(unprocessed);
1459        new_module_commands
1460    }
1461
1462    pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
1463        debug_assert!(
1464            id.0 < self.scene_objects.len(),
1465            "Requested object ID {} but only have {} objects",
1466            id.0,
1467            self.scene_objects.len()
1468        );
1469        self.scene_objects.get(id.0)
1470    }
1471
1472    pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
1473        debug_assert!(
1474            id.0 < self.scene_objects.len(),
1475            "Requested object ID {} but only have {} objects",
1476            id.0,
1477            self.scene_objects.len()
1478        );
1479        self.scene_objects.get_mut(id.0)
1480    }
1481}
1482
1483impl ModuleState {
1484    pub(super) fn new(
1485        path: ModulePath,
1486        memory: Arc<ProgramMemory>,
1487        module_id: Option<ModuleId>,
1488        sketch_mode: bool,
1489        freedom_analysis: bool,
1490    ) -> Self {
1491        let state_module_id = module_id.unwrap_or_default();
1492        ModuleState {
1493            module_id: state_module_id,
1494            id_generator: IdGenerator::new(module_id),
1495            stack: memory.new_stack(),
1496            call_stack_size: 0,
1497            pipe_value: Default::default(),
1498            being_declared: Default::default(),
1499            sketch_block: Default::default(),
1500            stdlib_entry_source_range: Default::default(),
1501            module_exports: Default::default(),
1502            explicit_length_units: false,
1503            path,
1504            settings: Default::default(),
1505            sketch_mode,
1506            freedom_analysis,
1507            artifacts: Default::default(),
1508            constraint_state: Default::default(),
1509            allowed_warnings: Vec::new(),
1510            denied_warnings: Vec::new(),
1511            consumed_solids: AHashMap::default(),
1512            consumed_solid_ids: AHashMap::default(),
1513            consumed_regions: AHashMap::default(),
1514            inside_stdlib: false,
1515        }
1516    }
1517
1518    pub(super) fn variables(&self, main_ref: EnvironmentRef) -> Result<IndexMap<String, KclValue>, KclError> {
1519        self.stack.find_all_in_env_owned(main_ref)
1520    }
1521}
1522
1523impl SketchBlockState {
1524    pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
1525        SketchVarId(self.sketch_vars.len())
1526    }
1527
1528    /// Given a solve outcome, return the solutions for the sketch variables and
1529    /// enough information to update them in the source.
1530    pub(crate) fn var_solutions(
1531        &self,
1532        solve_outcome: &Solved,
1533        solution_ty: NumericType,
1534        sketch_block_range: SourceRange,
1535    ) -> Result<Vec<(SourceRange, Option<NodePath>, Number)>, KclError> {
1536        self.sketch_vars
1537            .iter()
1538            .map(|v| {
1539                let Some(sketch_var) = v.as_sketch_var() else {
1540                    return Err(KclError::new_internal(KclErrorDetails::new(
1541                        "Expected sketch variable".to_owned(),
1542                        vec![sketch_block_range],
1543                    )));
1544                };
1545                let var_index = sketch_var.id.0;
1546                let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
1547                    let message = format!("No solution for sketch variable with id {}", var_index);
1548                    debug_assert!(false, "{}", &message);
1549                    KclError::new_internal(KclErrorDetails::new(
1550                        message,
1551                        sketch_var.meta.iter().map(|m| m.source_range).collect(),
1552                    ))
1553                })?;
1554                let solved_value = Number {
1555                    value: *solved_n,
1556                    units: solution_ty.try_into().map_err(|_| {
1557                        KclError::new_internal(KclErrorDetails::new(
1558                            "Failed to convert numeric type to units".to_owned(),
1559                            vec![sketch_block_range],
1560                        ))
1561                    })?,
1562                };
1563                let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1564                    return Ok(None);
1565                };
1566                Ok(Some((source_range, sketch_var.node_path.clone(), solved_value)))
1567            })
1568            .filter_map(Result::transpose)
1569            .collect::<Result<Vec<_>, KclError>>()
1570    }
1571}
1572
1573#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1574#[ts(export)]
1575#[serde(rename_all = "camelCase")]
1576pub struct MetaSettings {
1577    pub default_length_units: UnitLength,
1578    pub default_angle_units: UnitAngle,
1579    pub experimental_features: annotations::WarningLevel,
1580    pub kcl_version: KclVersion,
1581}
1582
1583impl Default for MetaSettings {
1584    fn default() -> Self {
1585        MetaSettings {
1586            default_length_units: UnitLength::Millimeters,
1587            default_angle_units: UnitAngle::Degrees,
1588            experimental_features: annotations::WarningLevel::Deny,
1589            kcl_version: KclVersion::default(),
1590        }
1591    }
1592}
1593
1594impl MetaSettings {
1595    pub(crate) fn update_from_annotation(
1596        &mut self,
1597        annotation: &crate::parsing::ast::types::Node<Annotation>,
1598    ) -> Result<(bool, bool), KclError> {
1599        let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1600
1601        let mut updated_len = false;
1602        let mut updated_angle = false;
1603        for p in properties {
1604            match &*p.inner.key.name {
1605                annotations::SETTINGS_UNIT_LENGTH => {
1606                    let value = annotations::expect_ident(&p.inner.value)?;
1607                    let value = super::types::length_from_str(value, annotation.as_source_range())?;
1608                    self.default_length_units = value;
1609                    updated_len = true;
1610                }
1611                annotations::SETTINGS_UNIT_ANGLE => {
1612                    let value = annotations::expect_ident(&p.inner.value)?;
1613                    let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1614                    self.default_angle_units = value;
1615                    updated_angle = true;
1616                }
1617                annotations::SETTINGS_VERSION => {
1618                    let value = annotations::expect_kcl_version(&p.inner.value)?;
1619                    self.kcl_version = value.parse()?;
1620                }
1621                annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1622                    let value = annotations::expect_ident(&p.inner.value)?;
1623                    let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1624                        KclError::new_semantic(KclErrorDetails::new(
1625                            format!(
1626                                "Invalid value for {} settings property, expected one of: {}",
1627                                annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1628                                annotations::WARN_LEVELS.join(", ")
1629                            ),
1630                            annotation.as_source_ranges(),
1631                        ))
1632                    })?;
1633                    self.experimental_features = value;
1634                }
1635                name => {
1636                    return Err(KclError::new_semantic(KclErrorDetails::new(
1637                        format!(
1638                            "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1639                            annotations::SETTINGS_UNIT_LENGTH,
1640                            annotations::SETTINGS_UNIT_ANGLE
1641                        ),
1642                        vec![annotation.as_source_range()],
1643                    )));
1644                }
1645            }
1646        }
1647
1648        Ok((updated_len, updated_angle))
1649    }
1650}
1651
1652#[cfg(test)]
1653mod tests {
1654    use std::str::FromStr;
1655
1656    use uuid::Uuid;
1657
1658    use super::KclVersion;
1659    use super::ModuleArtifactState;
1660    use crate::NodePath;
1661    use crate::NodePathExt;
1662    use crate::SourceRange;
1663    use crate::execution::ArtifactId;
1664    use crate::front::Object;
1665    use crate::front::ObjectId;
1666    use crate::front::ObjectKind;
1667    use crate::front::Plane;
1668    use crate::front::SourceRef;
1669
1670    #[test]
1671    fn kcl_version_parses_supported_spellings() {
1672        assert_eq!(KclVersion::from_str("1"), Ok(KclVersion::V1));
1673        assert_eq!(KclVersion::from_str("1.0.0"), Ok(KclVersion::V1));
1674        assert_eq!(KclVersion::from_str("2"), Ok(KclVersion::V2));
1675        assert_eq!(KclVersion::from_str("2.0.0"), Ok(KclVersion::V2));
1676        assert_eq!(KclVersion::from_str("3.0-preview"), Ok(KclVersion::V3Preview));
1677        // No such version.
1678        KclVersion::from_str("99.123").unwrap_err();
1679    }
1680
1681    #[test]
1682    fn kcl_version_serializes_as_canonical_setting_value() {
1683        assert_eq!(serde_json::to_string(&KclVersion::V1).unwrap(), r#""1.0""#);
1684        assert_eq!(serde_json::to_string(&KclVersion::V2).unwrap(), r#""2.0""#);
1685        assert_eq!(
1686            serde_json::to_string(&KclVersion::V3Preview).unwrap(),
1687            r#""3.0-preview""#
1688        );
1689    }
1690
1691    #[test]
1692    fn restore_scene_objects_rebuilds_lookup_maps() {
1693        let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1694        let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1695        let plane_range = SourceRange::from([1, 4, 0]);
1696        let plane_node_path = Some(NodePath::placeholder());
1697        let sketch_ranges = vec![
1698            (SourceRange::from([5, 9, 0]), None),
1699            (SourceRange::from([10, 12, 0]), None),
1700        ];
1701        let cached_objects = vec![
1702            Object {
1703                id: ObjectId(0),
1704                kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1705                label: Default::default(),
1706                comments: Default::default(),
1707                artifact_id: plane_artifact_id,
1708                source: SourceRef::new(plane_range, plane_node_path),
1709            },
1710            Object {
1711                id: ObjectId(1),
1712                kind: ObjectKind::Nil,
1713                label: Default::default(),
1714                comments: Default::default(),
1715                artifact_id: sketch_artifact_id,
1716                source: SourceRef::BackTrace {
1717                    ranges: sketch_ranges.clone(),
1718                },
1719            },
1720            Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1721        ];
1722
1723        let mut artifacts = ModuleArtifactState::default();
1724        artifacts.restore_scene_objects(&cached_objects);
1725
1726        assert_eq!(artifacts.scene_objects, cached_objects);
1727        assert_eq!(
1728            artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1729            Some(&ObjectId(0))
1730        );
1731        assert_eq!(
1732            artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1733            Some(&ObjectId(1))
1734        );
1735        assert_eq!(
1736            artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1737            None
1738        );
1739        assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1740        assert_eq!(
1741            artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1742            Some(&ObjectId(1))
1743        );
1744        // We don't map all the ranges in a backtrace.
1745        assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1746    }
1747}