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