Skip to main content

kcl_lib/execution/
state.rs

1#[cfg(feature = "artifact-graph")]
2use std::collections::BTreeMap;
3use std::str::FromStr;
4use std::sync::Arc;
5
6use anyhow::Result;
7use indexmap::IndexMap;
8use kittycad_modeling_cmds::units::UnitAngle;
9use kittycad_modeling_cmds::units::UnitLength;
10use serde::Deserialize;
11use serde::Serialize;
12use uuid::Uuid;
13
14use crate::CompilationIssue;
15use crate::EngineManager;
16use crate::ExecutorContext;
17use crate::KclErrorWithOutputs;
18use crate::MockConfig;
19#[cfg(feature = "artifact-graph")]
20use crate::NodePath;
21use crate::SourceRange;
22use crate::collections::AhashIndexSet;
23use crate::errors::KclError;
24use crate::errors::KclErrorDetails;
25use crate::errors::Severity;
26use crate::exec::DefaultPlanes;
27#[cfg(feature = "artifact-graph")]
28use crate::execution::Artifact;
29#[cfg(feature = "artifact-graph")]
30use crate::execution::ArtifactCommand;
31#[cfg(feature = "artifact-graph")]
32use crate::execution::ArtifactGraph;
33#[cfg(feature = "artifact-graph")]
34use crate::execution::ArtifactId;
35use crate::execution::EnvironmentRef;
36use crate::execution::ExecOutcome;
37use crate::execution::ExecutorSettings;
38use crate::execution::KclValue;
39#[cfg(feature = "artifact-graph")]
40use crate::execution::ProgramLookup;
41use crate::execution::SketchVarId;
42use crate::execution::UnsolvedSegment;
43use crate::execution::annotations;
44use crate::execution::cad_op::Operation;
45use crate::execution::id_generator::IdGenerator;
46use crate::execution::memory::ProgramMemory;
47use crate::execution::memory::Stack;
48#[cfg(feature = "artifact-graph")]
49use crate::execution::sketch_solve::Solved;
50use crate::execution::types::NumericType;
51#[cfg(feature = "artifact-graph")]
52use crate::front::Number;
53use crate::front::Object;
54use crate::front::ObjectId;
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) global: GlobalState,
70    pub(super) mod_local: ModuleState,
71}
72
73pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
74
75#[derive(Debug, Clone)]
76pub(super) struct GlobalState {
77    /// Map from source file absolute path to module ID.
78    pub path_to_source_id: IndexMap<ModulePath, ModuleId>,
79    /// Map from module ID to source file.
80    pub id_to_source: IndexMap<ModuleId, ModuleSource>,
81    /// Map from module ID to module info.
82    pub module_infos: ModuleInfoMap,
83    /// Module loader.
84    pub mod_loader: ModuleLoader,
85    /// Errors and warnings.
86    pub issues: Vec<CompilationIssue>,
87    /// Global artifacts that represent the entire program.
88    pub artifacts: ArtifactState,
89    /// Artifacts for only the root module.
90    pub root_module_artifacts: ModuleArtifactState,
91    /// The segments that were edited that triggered this execution.
92    #[cfg(feature = "artifact-graph")]
93    pub segment_ids_edited: AhashIndexSet<ObjectId>,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
97pub(crate) enum ConstraintKey {
98    LineCircle([usize; 10]),
99    CircleCircle([usize; 12]),
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub(crate) enum TangencyMode {
104    LineCircle(ezpz::LineSide),
105    CircleCircle(ezpz::CircleSide),
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub(crate) enum ConstraintState {
110    Tangency(TangencyMode),
111}
112
113#[cfg(feature = "artifact-graph")]
114#[derive(Debug, Clone, Default)]
115pub(super) struct ArtifactState {
116    /// Internal map of UUIDs to exec artifacts.  This needs to persist across
117    /// executions to allow the graph building to refer to cached artifacts.
118    pub artifacts: IndexMap<ArtifactId, Artifact>,
119    /// Output artifact graph.
120    pub graph: ArtifactGraph,
121}
122
123#[cfg(not(feature = "artifact-graph"))]
124#[derive(Debug, Clone, Default)]
125pub(super) struct ArtifactState {}
126
127/// Artifact state for a single module.
128#[cfg(feature = "artifact-graph")]
129#[derive(Debug, Clone, Default, PartialEq, Serialize)]
130pub struct ModuleArtifactState {
131    /// Internal map of UUIDs to exec artifacts.
132    pub artifacts: IndexMap<ArtifactId, Artifact>,
133    /// Outgoing engine commands that have not yet been processed and integrated
134    /// into the artifact graph.
135    #[serde(skip)]
136    pub unprocessed_commands: Vec<ArtifactCommand>,
137    /// Outgoing engine commands.
138    pub commands: Vec<ArtifactCommand>,
139    /// Incoming engine commands.
140    #[cfg(feature = "snapshot-engine-responses")]
141    pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
142    /// Operations that have been performed in execution order, for display in
143    /// the Feature Tree.
144    pub operations: Vec<Operation>,
145    /// [`ObjectId`] generator.
146    pub object_id_generator: IncIdGenerator<usize>,
147    /// Objects in the scene, created from execution.
148    pub scene_objects: Vec<Object>,
149    /// Map from source range to object ID for lookup of objects by their source
150    /// range.
151    pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
152    /// Map from artifact ID to object ID in the scene.
153    pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
154    /// Solutions for sketch variables.
155    pub var_solutions: Vec<(SourceRange, Number)>,
156}
157
158#[cfg(not(feature = "artifact-graph"))]
159#[derive(Debug, Clone, Default, PartialEq, Serialize)]
160pub struct ModuleArtifactState {
161    /// [`ObjectId`] generator.
162    pub object_id_generator: IncIdGenerator<usize>,
163}
164
165#[derive(Debug, Clone)]
166pub(super) struct ModuleState {
167    /// The id generator for this module.
168    pub id_generator: IdGenerator,
169    pub stack: Stack,
170    /// The size of the call stack. This is used to prevent stack overflows with
171    /// recursive function calls. In general, this doesn't match `stack`'s size
172    /// since it's conservative in reclaiming frames between executions.
173    pub(super) call_stack_size: usize,
174    /// The current value of the pipe operator returned from the previous
175    /// expression.  If we're not currently in a pipeline, this will be None.
176    pub pipe_value: Option<KclValue>,
177    /// The closest variable declaration being executed in any parent node in the AST.
178    /// This is used to provide better error messages, e.g. noticing when the user is trying
179    /// to use the variable `length` inside the RHS of its own definition, like `length = tan(length)`.
180    pub being_declared: Option<String>,
181    /// Present if we're currently executing inside a sketch block.
182    pub sketch_block: Option<SketchBlockState>,
183    /// Tracks if KCL being executed is currently inside a stdlib function or not.
184    /// This matters because e.g. we shouldn't emit artifacts from declarations declared inside a stdlib function.
185    pub inside_stdlib: bool,
186    /// The source range where we entered the standard library.
187    pub stdlib_entry_source_range: Option<SourceRange>,
188    /// Identifiers that have been exported from the current module.
189    pub module_exports: Vec<String>,
190    /// Settings specified from annotations.
191    pub settings: MetaSettings,
192    /// True if executing in sketch mode. Only a single sketch block will be
193    /// executed. All other code is ignored.
194    pub sketch_mode: bool,
195    /// True to do more costly analysis of whether the sketch block segments are
196    /// under-constrained. The only time we disable this is when a user is
197    /// dragging segments.
198    pub freedom_analysis: bool,
199    pub(super) explicit_length_units: bool,
200    pub(super) path: ModulePath,
201    /// Artifacts for only this module.
202    pub artifacts: ModuleArtifactState,
203    /// Sticky per-constraint state persisted across sketch-mode mock solves.
204    /// Maps from sketch block ID to a map for that sketch.
205    /// Then the inner map is per constraint (in that sketch block) to its state.
206    pub constraint_state: IndexMap<ObjectId, IndexMap<ConstraintKey, ConstraintState>>,
207
208    pub(super) allowed_warnings: Vec<&'static str>,
209    pub(super) denied_warnings: Vec<&'static str>,
210}
211
212#[derive(Debug, Clone, Default)]
213pub(crate) struct SketchBlockState {
214    pub sketch_vars: Vec<KclValue>,
215    pub sketch_id: Option<ObjectId>,
216    #[cfg(feature = "artifact-graph")]
217    pub sketch_constraints: Vec<ObjectId>,
218    pub solver_constraints: Vec<ezpz::Constraint>,
219    pub solver_optional_constraints: Vec<ezpz::Constraint>,
220    pub needed_by_engine: Vec<UnsolvedSegment>,
221    pub segment_tags: IndexMap<ObjectId, TagNode>,
222}
223
224impl ExecState {
225    pub fn new(exec_context: &super::ExecutorContext) -> Self {
226        ExecState {
227            global: GlobalState::new(&exec_context.settings, Default::default()),
228            mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default(), false, true),
229        }
230    }
231
232    pub fn new_mock(exec_context: &super::ExecutorContext, mock_config: &MockConfig) -> Self {
233        #[cfg(feature = "artifact-graph")]
234        let segment_ids_edited = mock_config.segment_ids_edited.clone();
235        #[cfg(not(feature = "artifact-graph"))]
236        let segment_ids_edited = Default::default();
237        ExecState {
238            global: GlobalState::new(&exec_context.settings, segment_ids_edited),
239            mod_local: ModuleState::new(
240                ModulePath::Main,
241                ProgramMemory::new(),
242                Default::default(),
243                mock_config.sketch_block_id.is_some(),
244                mock_config.freedom_analysis,
245            ),
246        }
247    }
248
249    pub(super) fn reset(&mut self, exec_context: &super::ExecutorContext) {
250        let global = GlobalState::new(&exec_context.settings, Default::default());
251
252        *self = ExecState {
253            global,
254            mod_local: ModuleState::new(
255                self.mod_local.path.clone(),
256                ProgramMemory::new(),
257                Default::default(),
258                false,
259                true,
260            ),
261        };
262    }
263
264    /// Log a non-fatal error.
265    pub fn err(&mut self, e: CompilationIssue) {
266        self.global.issues.push(e);
267    }
268
269    /// Log a warning.
270    pub fn warn(&mut self, mut e: CompilationIssue, name: &'static str) {
271        debug_assert!(annotations::WARN_VALUES.contains(&name));
272
273        if self.mod_local.allowed_warnings.contains(&name) {
274            return;
275        }
276
277        if self.mod_local.denied_warnings.contains(&name) {
278            e.severity = Severity::Error;
279        } else {
280            e.severity = Severity::Warning;
281        }
282
283        self.global.issues.push(e);
284    }
285
286    pub fn warn_experimental(&mut self, feature_name: &str, source_range: SourceRange) {
287        let Some(severity) = self.mod_local.settings.experimental_features.severity() else {
288            return;
289        };
290        let error = CompilationIssue {
291            source_range,
292            message: format!("Use of {feature_name} is experimental and may change or be removed."),
293            suggestion: None,
294            severity,
295            tag: crate::errors::Tag::None,
296        };
297
298        self.global.issues.push(error);
299    }
300
301    pub fn clear_units_warnings(&mut self, source_range: &SourceRange) {
302        self.global.issues = std::mem::take(&mut self.global.issues)
303            .into_iter()
304            .filter(|e| {
305                e.severity != Severity::Warning
306                    || !source_range.contains_range(&e.source_range)
307                    || e.tag != crate::errors::Tag::UnknownNumericUnits
308            })
309            .collect();
310    }
311
312    pub fn issues(&self) -> &[CompilationIssue] {
313        &self.global.issues
314    }
315
316    /// Convert to execution outcome when running in WebAssembly.  We want to
317    /// reduce the amount of data that crosses the WASM boundary as much as
318    /// possible.
319    pub async fn into_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
320        // Fields are opt-in so that we don't accidentally leak private internal
321        // state when we add more to ExecState.
322        ExecOutcome {
323            variables: self.mod_local.variables(main_ref),
324            filenames: self.global.filenames(),
325            #[cfg(feature = "artifact-graph")]
326            operations: self.global.root_module_artifacts.operations,
327            #[cfg(feature = "artifact-graph")]
328            artifact_graph: self.global.artifacts.graph,
329            #[cfg(feature = "artifact-graph")]
330            scene_objects: self.global.root_module_artifacts.scene_objects,
331            #[cfg(feature = "artifact-graph")]
332            source_range_to_object: self.global.root_module_artifacts.source_range_to_object,
333            #[cfg(feature = "artifact-graph")]
334            var_solutions: self.global.root_module_artifacts.var_solutions,
335            issues: self.global.issues,
336            default_planes: ctx.engine.get_default_planes().read().await.clone(),
337        }
338    }
339
340    #[cfg(all(feature = "artifact-graph", feature = "snapshot-engine-responses"))]
341    pub(crate) fn take_root_module_responses(
342        &mut self,
343    ) -> IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse> {
344        std::mem::take(&mut self.global.root_module_artifacts.responses)
345    }
346
347    pub(crate) fn stack(&self) -> &Stack {
348        &self.mod_local.stack
349    }
350
351    pub(crate) fn mut_stack(&mut self) -> &mut Stack {
352        &mut self.mod_local.stack
353    }
354
355    /// Increment the user-level call stack size, returning an error if it
356    /// exceeds the maximum.
357    pub(super) fn inc_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
358        // If you change this, make sure to test in WebAssembly in the app since
359        // that's the limiting factor.
360        if self.mod_local.call_stack_size >= 50 {
361            return Err(KclError::MaxCallStack {
362                details: KclErrorDetails::new("maximum call stack size exceeded".to_owned(), vec![range]),
363            });
364        }
365        self.mod_local.call_stack_size += 1;
366        Ok(())
367    }
368
369    /// Decrement the user-level call stack size, returning an error if it would
370    /// go below zero.
371    pub(super) fn dec_call_stack_size(&mut self, range: SourceRange) -> Result<(), KclError> {
372        // Prevent underflow.
373        if self.mod_local.call_stack_size == 0 {
374            let message = "call stack size below zero".to_owned();
375            debug_assert!(false, "{message}");
376            return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
377        }
378        self.mod_local.call_stack_size -= 1;
379        Ok(())
380    }
381
382    /// Returns true if we're executing in sketch mode for the current module.
383    /// In sketch mode, we still want to execute the prelude and other stdlib
384    /// modules as normal, so it can vary per module within a single overall
385    /// execution.
386    pub(crate) fn sketch_mode(&self) -> bool {
387        self.mod_local.sketch_mode
388            && match &self.mod_local.path {
389                ModulePath::Main => true,
390                ModulePath::Local { .. } => true,
391                ModulePath::Std { .. } => false,
392            }
393    }
394
395    pub fn next_object_id(&mut self) -> ObjectId {
396        ObjectId(self.mod_local.artifacts.object_id_generator.next_id())
397    }
398
399    pub fn peek_object_id(&self) -> ObjectId {
400        ObjectId(self.mod_local.artifacts.object_id_generator.peek_id())
401    }
402
403    pub(crate) fn constraint_state(&self, sketch_block_id: ObjectId, key: &ConstraintKey) -> Option<ConstraintState> {
404        let map = self.mod_local.constraint_state.get(&sketch_block_id)?;
405        map.get(key).copied()
406    }
407
408    pub(crate) fn set_constraint_state(
409        &mut self,
410        sketch_block_id: ObjectId,
411        key: ConstraintKey,
412        state: ConstraintState,
413    ) {
414        let map = self.mod_local.constraint_state.entry(sketch_block_id).or_default();
415        map.insert(key, state);
416    }
417
418    #[cfg(feature = "artifact-graph")]
419    pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
420        let id = obj.id;
421        debug_assert!(
422            id.0 == self.mod_local.artifacts.scene_objects.len(),
423            "Adding scene object with ID {} but next ID is {}",
424            id.0,
425            self.mod_local.artifacts.scene_objects.len()
426        );
427        let artifact_id = obj.artifact_id;
428        self.mod_local.artifacts.scene_objects.push(obj);
429        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
430        self.mod_local
431            .artifacts
432            .artifact_id_to_scene_object
433            .insert(artifact_id, id);
434        id
435    }
436
437    /// Add a placeholder scene object. This is useful when we need to reserve
438    /// an ID before we have all the information to create the full object.
439    #[cfg(feature = "artifact-graph")]
440    pub fn add_placeholder_scene_object(
441        &mut self,
442        id: ObjectId,
443        source_range: SourceRange,
444        node_path: Option<NodePath>,
445    ) -> ObjectId {
446        debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
447        self.mod_local
448            .artifacts
449            .scene_objects
450            .push(Object::placeholder(id, source_range, node_path));
451        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
452        id
453    }
454
455    /// Update a scene object. This is useful to replace a placeholder.
456    #[cfg(feature = "artifact-graph")]
457    pub fn set_scene_object(&mut self, object: Object) {
458        let id = object.id;
459        let artifact_id = object.artifact_id;
460        self.mod_local.artifacts.scene_objects[id.0] = object;
461        self.mod_local
462            .artifacts
463            .artifact_id_to_scene_object
464            .insert(artifact_id, id);
465    }
466
467    #[cfg(feature = "artifact-graph")]
468    pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
469        self.mod_local
470            .artifacts
471            .artifact_id_to_scene_object
472            .get(&artifact_id)
473            .cloned()
474    }
475
476    #[cfg(feature = "artifact-graph")]
477    pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
478        self.global.segment_ids_edited.contains(object_id)
479    }
480
481    pub(super) fn is_in_sketch_block(&self) -> bool {
482        self.mod_local.sketch_block.is_some()
483    }
484
485    pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
486        self.mod_local.sketch_block.as_mut()
487    }
488
489    pub(crate) fn sketch_block(&mut self) -> Option<&SketchBlockState> {
490        self.mod_local.sketch_block.as_ref()
491    }
492
493    pub fn next_uuid(&mut self) -> Uuid {
494        self.mod_local.id_generator.next_uuid()
495    }
496
497    #[cfg(feature = "artifact-graph")]
498    pub fn next_artifact_id(&mut self) -> ArtifactId {
499        self.mod_local.id_generator.next_artifact_id()
500    }
501
502    pub fn id_generator(&mut self) -> &mut IdGenerator {
503        &mut self.mod_local.id_generator
504    }
505
506    #[cfg(feature = "artifact-graph")]
507    pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
508        let id = artifact.id();
509        self.mod_local.artifacts.artifacts.insert(id, artifact);
510    }
511
512    #[cfg(feature = "artifact-graph")]
513    pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
514        self.mod_local.artifacts.artifacts.get_mut(&id)
515    }
516
517    pub(crate) fn push_op(&mut self, op: Operation) {
518        #[cfg(feature = "artifact-graph")]
519        self.mod_local.artifacts.operations.push(op);
520        #[cfg(not(feature = "artifact-graph"))]
521        drop(op);
522    }
523
524    #[cfg(feature = "artifact-graph")]
525    pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
526        self.mod_local.artifacts.unprocessed_commands.push(command);
527        #[cfg(not(feature = "artifact-graph"))]
528        drop(command);
529    }
530
531    pub(super) fn next_module_id(&self) -> ModuleId {
532        ModuleId::from_usize(self.global.path_to_source_id.len())
533    }
534
535    pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
536        self.global.path_to_source_id.get(path).cloned()
537    }
538
539    pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
540        debug_assert!(!self.global.path_to_source_id.contains_key(&path));
541        self.global.path_to_source_id.insert(path, id);
542    }
543
544    pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
545        let root_id = ModuleId::default();
546        // Get the path for the root module.
547        let path = self
548            .global
549            .path_to_source_id
550            .iter()
551            .find(|(_, v)| **v == root_id)
552            .unwrap()
553            .0
554            .clone();
555        self.add_id_to_source(
556            root_id,
557            ModuleSource {
558                path,
559                source: program.original_file_contents.to_string(),
560            },
561        );
562    }
563
564    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
565        self.global.id_to_source.insert(id, source);
566    }
567
568    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
569        debug_assert!(self.global.path_to_source_id.contains_key(&path));
570        let module_info = ModuleInfo { id, repr, path };
571        self.global.module_infos.insert(id, module_info);
572    }
573
574    pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
575        self.global.module_infos.get(&id)
576    }
577
578    #[cfg(all(test, feature = "artifact-graph"))]
579    pub(crate) fn modules(&self) -> &ModuleInfoMap {
580        &self.global.module_infos
581    }
582
583    #[cfg(all(test, feature = "artifact-graph"))]
584    pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
585        &self.global.root_module_artifacts
586    }
587
588    pub fn current_default_units(&self) -> NumericType {
589        NumericType::Default {
590            len: self.length_unit(),
591            angle: self.angle_unit(),
592        }
593    }
594
595    pub fn length_unit(&self) -> UnitLength {
596        self.mod_local.settings.default_length_units
597    }
598
599    pub fn angle_unit(&self) -> UnitAngle {
600        self.mod_local.settings.default_angle_units
601    }
602
603    pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
604        KclError::new_import_cycle(KclErrorDetails::new(
605            format!(
606                "circular import of modules is not allowed: {} -> {}",
607                self.global
608                    .mod_loader
609                    .import_stack
610                    .iter()
611                    .map(|p| p.to_string_lossy())
612                    .collect::<Vec<_>>()
613                    .join(" -> "),
614                path,
615            ),
616            vec![source_range],
617        ))
618    }
619
620    pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
621        self.mod_local.pipe_value.as_ref()
622    }
623
624    pub(crate) fn error_with_outputs(
625        &self,
626        error: KclError,
627        main_ref: Option<EnvironmentRef>,
628        default_planes: Option<DefaultPlanes>,
629    ) -> KclErrorWithOutputs {
630        let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
631            .global
632            .path_to_source_id
633            .iter()
634            .map(|(k, v)| ((*v), k.clone()))
635            .collect();
636
637        KclErrorWithOutputs::new(
638            error,
639            self.issues().to_vec(),
640            main_ref
641                .map(|main_ref| self.mod_local.variables(main_ref))
642                .unwrap_or_default(),
643            #[cfg(feature = "artifact-graph")]
644            self.global.root_module_artifacts.operations.clone(),
645            #[cfg(feature = "artifact-graph")]
646            Default::default(),
647            #[cfg(feature = "artifact-graph")]
648            self.global.artifacts.graph.clone(),
649            #[cfg(feature = "artifact-graph")]
650            self.global.root_module_artifacts.scene_objects.clone(),
651            #[cfg(feature = "artifact-graph")]
652            self.global.root_module_artifacts.source_range_to_object.clone(),
653            #[cfg(feature = "artifact-graph")]
654            self.global.root_module_artifacts.var_solutions.clone(),
655            module_id_to_module_path,
656            self.global.id_to_source.clone(),
657            default_planes,
658        )
659    }
660
661    #[cfg(feature = "artifact-graph")]
662    pub(crate) fn build_program_lookup(
663        &self,
664        current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
665    ) -> ProgramLookup {
666        ProgramLookup::new(current, self.global.module_infos.clone())
667    }
668
669    #[cfg(feature = "artifact-graph")]
670    pub(crate) async fn build_artifact_graph(
671        &mut self,
672        engine: &Arc<Box<dyn EngineManager>>,
673        program: NodeRef<'_, crate::parsing::ast::types::Program>,
674    ) -> Result<(), KclError> {
675        let mut new_commands = Vec::new();
676        let mut new_exec_artifacts = IndexMap::new();
677        for module in self.global.module_infos.values_mut() {
678            match &mut module.repr {
679                ModuleRepr::Kcl(_, Some(outcome)) => {
680                    new_commands.extend(outcome.artifacts.process_commands());
681                    new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
682                }
683                ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
684                    new_commands.extend(module_artifacts.process_commands());
685                    new_exec_artifacts.extend(module_artifacts.artifacts.clone());
686                }
687                ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
688            }
689        }
690        // Take from the module artifacts so that we don't try to process them
691        // again next time due to execution caching.
692        new_commands.extend(self.global.root_module_artifacts.process_commands());
693        // Note: These will get re-processed, but since we're just adding them
694        // to a map, it's fine.
695        new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
696        let new_responses = engine.take_responses().await;
697
698        // Move the artifacts into ExecState global to simplify cache
699        // management.
700        for (id, exec_artifact) in new_exec_artifacts {
701            // Only insert if it wasn't already present. We don't want to
702            // overwrite what was previously there. We haven't filled in node
703            // paths yet.
704            self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
705        }
706
707        let initial_graph = self.global.artifacts.graph.clone();
708
709        // Build the artifact graph.
710        let programs = self.build_program_lookup(program.clone());
711        let graph_result = crate::execution::artifact::build_artifact_graph(
712            &new_commands,
713            &new_responses,
714            program,
715            &mut self.global.artifacts.artifacts,
716            initial_graph,
717            &programs,
718            &self.global.module_infos,
719        );
720
721        #[cfg(feature = "snapshot-engine-responses")]
722        {
723            // Store engine responses for debugging.
724            self.global.root_module_artifacts.responses.extend(new_responses);
725        }
726
727        let artifact_graph = graph_result?;
728        self.global.artifacts.graph = artifact_graph;
729
730        Ok(())
731    }
732
733    #[cfg(not(feature = "artifact-graph"))]
734    pub(crate) async fn build_artifact_graph(
735        &mut self,
736        _engine: &Arc<Box<dyn EngineManager>>,
737        _program: NodeRef<'_, crate::parsing::ast::types::Program>,
738    ) -> Result<(), KclError> {
739        Ok(())
740    }
741}
742
743impl GlobalState {
744    fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
745        #[cfg(not(feature = "artifact-graph"))]
746        drop(segment_ids_edited);
747        let mut global = GlobalState {
748            path_to_source_id: Default::default(),
749            module_infos: Default::default(),
750            artifacts: Default::default(),
751            root_module_artifacts: Default::default(),
752            mod_loader: Default::default(),
753            issues: Default::default(),
754            id_to_source: Default::default(),
755            #[cfg(feature = "artifact-graph")]
756            segment_ids_edited,
757        };
758
759        let root_id = ModuleId::default();
760        let root_path = settings.current_file.clone().unwrap_or_default();
761        global.module_infos.insert(
762            root_id,
763            ModuleInfo {
764                id: root_id,
765                path: ModulePath::Local {
766                    value: root_path.clone(),
767                    original_import_path: None,
768                },
769                repr: ModuleRepr::Root,
770            },
771        );
772        global.path_to_source_id.insert(
773            ModulePath::Local {
774                value: root_path,
775                original_import_path: None,
776            },
777            root_id,
778        );
779        global
780    }
781
782    pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
783        self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
784    }
785
786    pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
787        self.id_to_source.get(&id)
788    }
789}
790
791impl ArtifactState {
792    #[cfg(feature = "artifact-graph")]
793    pub fn cached_body_items(&self) -> usize {
794        self.graph.item_count
795    }
796
797    pub(crate) fn clear(&mut self) {
798        #[cfg(feature = "artifact-graph")]
799        {
800            self.artifacts.clear();
801            self.graph.clear();
802        }
803    }
804}
805
806impl ModuleArtifactState {
807    pub(crate) fn clear(&mut self) {
808        #[cfg(feature = "artifact-graph")]
809        {
810            self.artifacts.clear();
811            self.unprocessed_commands.clear();
812            self.commands.clear();
813            self.operations.clear();
814        }
815    }
816
817    #[cfg(feature = "artifact-graph")]
818    pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
819        self.scene_objects = scene_objects.to_vec();
820        self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
821        self.source_range_to_object.clear();
822        self.artifact_id_to_scene_object.clear();
823
824        for (expected_id, object) in self.scene_objects.iter().enumerate() {
825            debug_assert_eq!(
826                object.id.0, expected_id,
827                "Restored cached scene object ID {} does not match its position {}",
828                object.id.0, expected_id
829            );
830
831            match &object.source {
832                crate::front::SourceRef::Simple { range, node_path: _ } => {
833                    self.source_range_to_object.insert(*range, object.id);
834                }
835                crate::front::SourceRef::BackTrace { ranges } => {
836                    // Don't map the entire backtrace, only the most specific
837                    // range.
838                    if let Some((range, _)) = ranges.first() {
839                        self.source_range_to_object.insert(*range, object.id);
840                    }
841                }
842            }
843
844            // Ignore placeholder artifacts.
845            if object.artifact_id != ArtifactId::placeholder() {
846                self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
847            }
848        }
849    }
850
851    #[cfg(not(feature = "artifact-graph"))]
852    pub(crate) fn extend(&mut self, _other: ModuleArtifactState) {}
853
854    /// When self is a cached state, extend it with new state.
855    #[cfg(feature = "artifact-graph")]
856    pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
857        self.artifacts.extend(other.artifacts);
858        self.unprocessed_commands.extend(other.unprocessed_commands);
859        self.commands.extend(other.commands);
860        self.operations.extend(other.operations);
861        if other.scene_objects.len() > self.scene_objects.len() {
862            self.scene_objects
863                .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
864        }
865        self.source_range_to_object.extend(other.source_range_to_object);
866        self.artifact_id_to_scene_object
867            .extend(other.artifact_id_to_scene_object);
868        self.var_solutions.extend(other.var_solutions);
869    }
870
871    // Move unprocessed artifact commands so that we don't try to process them
872    // again next time due to execution caching.  Returns a clone of the
873    // commands that were moved.
874    #[cfg(feature = "artifact-graph")]
875    pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
876        let unprocessed = std::mem::take(&mut self.unprocessed_commands);
877        let new_module_commands = unprocessed.clone();
878        self.commands.extend(unprocessed);
879        new_module_commands
880    }
881
882    #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
883    pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
884        #[cfg(feature = "artifact-graph")]
885        {
886            debug_assert!(
887                id.0 < self.scene_objects.len(),
888                "Requested object ID {} but only have {} objects",
889                id.0,
890                self.scene_objects.len()
891            );
892            self.scene_objects.get(id.0)
893        }
894        #[cfg(not(feature = "artifact-graph"))]
895        {
896            let _ = id;
897            None
898        }
899    }
900
901    #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
902    pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut Object> {
903        #[cfg(feature = "artifact-graph")]
904        {
905            debug_assert!(
906                id.0 < self.scene_objects.len(),
907                "Requested object ID {} but only have {} objects",
908                id.0,
909                self.scene_objects.len()
910            );
911            self.scene_objects.get_mut(id.0)
912        }
913        #[cfg(not(feature = "artifact-graph"))]
914        {
915            let _ = id;
916            None
917        }
918    }
919}
920
921impl ModuleState {
922    pub(super) fn new(
923        path: ModulePath,
924        memory: Arc<ProgramMemory>,
925        module_id: Option<ModuleId>,
926        sketch_mode: bool,
927        freedom_analysis: bool,
928    ) -> Self {
929        ModuleState {
930            id_generator: IdGenerator::new(module_id),
931            stack: memory.new_stack(),
932            call_stack_size: 0,
933            pipe_value: Default::default(),
934            being_declared: Default::default(),
935            sketch_block: Default::default(),
936            stdlib_entry_source_range: Default::default(),
937            module_exports: Default::default(),
938            explicit_length_units: false,
939            path,
940            settings: Default::default(),
941            sketch_mode,
942            freedom_analysis,
943            artifacts: Default::default(),
944            constraint_state: Default::default(),
945            allowed_warnings: Vec::new(),
946            denied_warnings: Vec::new(),
947            inside_stdlib: false,
948        }
949    }
950
951    pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
952        self.stack
953            .find_all_in_env(main_ref)
954            .map(|(k, v)| (k.clone(), v.clone()))
955            .collect()
956    }
957}
958
959impl SketchBlockState {
960    pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
961        SketchVarId(self.sketch_vars.len())
962    }
963
964    /// Given a solve outcome, return the solutions for the sketch variables and
965    /// enough information to update them in the source.
966    #[cfg(feature = "artifact-graph")]
967    pub(crate) fn var_solutions(
968        &self,
969        solve_outcome: &Solved,
970        solution_ty: NumericType,
971        range: SourceRange,
972    ) -> Result<Vec<(SourceRange, Number)>, KclError> {
973        self.sketch_vars
974            .iter()
975            .map(|v| {
976                let Some(sketch_var) = v.as_sketch_var() else {
977                    return Err(KclError::new_internal(KclErrorDetails::new(
978                        "Expected sketch variable".to_owned(),
979                        vec![range],
980                    )));
981                };
982                let var_index = sketch_var.id.0;
983                let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
984                    let message = format!("No solution for sketch variable with id {}", var_index);
985                    debug_assert!(false, "{}", &message);
986                    KclError::new_internal(KclErrorDetails::new(
987                        message,
988                        sketch_var.meta.iter().map(|m| m.source_range).collect(),
989                    ))
990                })?;
991                let solved_value = Number {
992                    value: *solved_n,
993                    units: solution_ty.try_into().map_err(|_| {
994                        KclError::new_internal(KclErrorDetails::new(
995                            "Failed to convert numeric type to units".to_owned(),
996                            vec![range],
997                        ))
998                    })?,
999                };
1000                let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
1001                    return Ok(None);
1002                };
1003                Ok(Some((source_range, solved_value)))
1004            })
1005            .filter_map(Result::transpose)
1006            .collect::<Result<Vec<_>, KclError>>()
1007    }
1008}
1009
1010#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1011#[ts(export)]
1012#[serde(rename_all = "camelCase")]
1013pub struct MetaSettings {
1014    pub default_length_units: UnitLength,
1015    pub default_angle_units: UnitAngle,
1016    pub experimental_features: annotations::WarningLevel,
1017    pub kcl_version: String,
1018}
1019
1020impl Default for MetaSettings {
1021    fn default() -> Self {
1022        MetaSettings {
1023            default_length_units: UnitLength::Millimeters,
1024            default_angle_units: UnitAngle::Degrees,
1025            experimental_features: annotations::WarningLevel::Deny,
1026            kcl_version: "1.0".to_owned(),
1027        }
1028    }
1029}
1030
1031impl MetaSettings {
1032    pub(crate) fn update_from_annotation(
1033        &mut self,
1034        annotation: &crate::parsing::ast::types::Node<Annotation>,
1035    ) -> Result<(bool, bool), KclError> {
1036        let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1037
1038        let mut updated_len = false;
1039        let mut updated_angle = false;
1040        for p in properties {
1041            match &*p.inner.key.name {
1042                annotations::SETTINGS_UNIT_LENGTH => {
1043                    let value = annotations::expect_ident(&p.inner.value)?;
1044                    let value = super::types::length_from_str(value, annotation.as_source_range())?;
1045                    self.default_length_units = value;
1046                    updated_len = true;
1047                }
1048                annotations::SETTINGS_UNIT_ANGLE => {
1049                    let value = annotations::expect_ident(&p.inner.value)?;
1050                    let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1051                    self.default_angle_units = value;
1052                    updated_angle = true;
1053                }
1054                annotations::SETTINGS_VERSION => {
1055                    let value = annotations::expect_number(&p.inner.value)?;
1056                    self.kcl_version = value;
1057                }
1058                annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1059                    let value = annotations::expect_ident(&p.inner.value)?;
1060                    let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1061                        KclError::new_semantic(KclErrorDetails::new(
1062                            format!(
1063                                "Invalid value for {} settings property, expected one of: {}",
1064                                annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1065                                annotations::WARN_LEVELS.join(", ")
1066                            ),
1067                            annotation.as_source_ranges(),
1068                        ))
1069                    })?;
1070                    self.experimental_features = value;
1071                }
1072                name => {
1073                    return Err(KclError::new_semantic(KclErrorDetails::new(
1074                        format!(
1075                            "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1076                            annotations::SETTINGS_UNIT_LENGTH,
1077                            annotations::SETTINGS_UNIT_ANGLE
1078                        ),
1079                        vec![annotation.as_source_range()],
1080                    )));
1081                }
1082            }
1083        }
1084
1085        Ok((updated_len, updated_angle))
1086    }
1087}
1088
1089#[cfg(all(feature = "artifact-graph", test))]
1090mod tests {
1091    use uuid::Uuid;
1092
1093    use super::ModuleArtifactState;
1094    use crate::NodePath;
1095    use crate::SourceRange;
1096    use crate::execution::ArtifactId;
1097    use crate::front::Object;
1098    use crate::front::ObjectId;
1099    use crate::front::ObjectKind;
1100    use crate::front::Plane;
1101    use crate::front::SourceRef;
1102
1103    #[test]
1104    fn restore_scene_objects_rebuilds_lookup_maps() {
1105        let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1106        let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1107        let plane_range = SourceRange::from([1, 4, 0]);
1108        let plane_node_path = Some(NodePath::placeholder());
1109        let sketch_ranges = vec![
1110            (SourceRange::from([5, 9, 0]), None),
1111            (SourceRange::from([10, 12, 0]), None),
1112        ];
1113        let cached_objects = vec![
1114            Object {
1115                id: ObjectId(0),
1116                kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1117                label: Default::default(),
1118                comments: Default::default(),
1119                artifact_id: plane_artifact_id,
1120                source: SourceRef::new(plane_range, plane_node_path),
1121            },
1122            Object {
1123                id: ObjectId(1),
1124                kind: ObjectKind::Nil,
1125                label: Default::default(),
1126                comments: Default::default(),
1127                artifact_id: sketch_artifact_id,
1128                source: SourceRef::BackTrace {
1129                    ranges: sketch_ranges.clone(),
1130                },
1131            },
1132            Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1133        ];
1134
1135        let mut artifacts = ModuleArtifactState::default();
1136        artifacts.restore_scene_objects(&cached_objects);
1137
1138        assert_eq!(artifacts.scene_objects, cached_objects);
1139        assert_eq!(
1140            artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1141            Some(&ObjectId(0))
1142        );
1143        assert_eq!(
1144            artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1145            Some(&ObjectId(1))
1146        );
1147        assert_eq!(
1148            artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1149            None
1150        );
1151        assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1152        assert_eq!(
1153            artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1154            Some(&ObjectId(1))
1155        );
1156        // We don't map all the ranges in a backtrace.
1157        assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1158    }
1159}