Skip to main content

kcl_lib/execution/
state.rs

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