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