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#[cfg(feature = "artifact-graph")]
97#[derive(Debug, Clone, Default)]
98pub(super) struct ArtifactState {
99    /// Internal map of UUIDs to exec artifacts.  This needs to persist across
100    /// executions to allow the graph building to refer to cached artifacts.
101    pub artifacts: IndexMap<ArtifactId, Artifact>,
102    /// Output artifact graph.
103    pub graph: ArtifactGraph,
104}
105
106#[cfg(not(feature = "artifact-graph"))]
107#[derive(Debug, Clone, Default)]
108pub(super) struct ArtifactState {}
109
110/// Artifact state for a single module.
111#[cfg(feature = "artifact-graph")]
112#[derive(Debug, Clone, Default, Serialize)]
113#[cfg_attr(not(feature = "snapshot-engine-responses"), derive(PartialEq))]
114pub struct ModuleArtifactState {
115    /// Internal map of UUIDs to exec artifacts.
116    pub artifacts: IndexMap<ArtifactId, Artifact>,
117    /// Outgoing engine commands that have not yet been processed and integrated
118    /// into the artifact graph.
119    #[serde(skip)]
120    pub unprocessed_commands: Vec<ArtifactCommand>,
121    /// Outgoing engine commands.
122    pub commands: Vec<ArtifactCommand>,
123    /// Incoming engine commands.
124    #[cfg(feature = "snapshot-engine-responses")]
125    pub responses: IndexMap<Uuid, kittycad_modeling_cmds::websocket::WebSocketResponse>,
126    /// Operations that have been performed in execution order, for display in
127    /// the Feature Tree.
128    pub operations: Vec<Operation>,
129    /// [`ObjectId`] generator.
130    pub object_id_generator: IncIdGenerator<usize>,
131    /// Objects in the scene, created from execution.
132    pub scene_objects: Vec<Object>,
133    /// Map from source range to object ID for lookup of objects by their source
134    /// range.
135    pub source_range_to_object: BTreeMap<SourceRange, ObjectId>,
136    /// Map from artifact ID to object ID in the scene.
137    pub artifact_id_to_scene_object: IndexMap<ArtifactId, ObjectId>,
138    /// Solutions for sketch variables.
139    pub var_solutions: Vec<(SourceRange, Number)>,
140}
141
142// It's error-prone to implement this manually. New fields are likely to be
143// forgotten. So we only use this implementation when the feature is enabled.
144#[cfg(feature = "snapshot-engine-responses")]
145impl PartialEq for ModuleArtifactState {
146    fn eq(&self, other: &Self) -> bool {
147        self.artifacts == other.artifacts
148            && self.unprocessed_commands == other.unprocessed_commands
149            && self.commands == other.commands
150            // WebSocketResponse type doesn't implement `PartialEq`.
151            // && self.responses == other.responses
152            && self.operations == other.operations
153            && self.object_id_generator == other.object_id_generator
154            && self.scene_objects == other.scene_objects
155            && self.source_range_to_object == other.source_range_to_object
156            && self.artifact_id_to_scene_object == other.artifact_id_to_scene_object
157            && self.var_solutions == other.var_solutions
158    }
159}
160
161#[cfg(not(feature = "artifact-graph"))]
162#[derive(Debug, Clone, Default, PartialEq, Serialize)]
163pub struct ModuleArtifactState {
164    /// [`ObjectId`] generator.
165    pub object_id_generator: IncIdGenerator<usize>,
166}
167
168#[derive(Debug, Clone)]
169pub(super) struct ModuleState {
170    /// The id generator for this module.
171    pub id_generator: IdGenerator,
172    pub stack: Stack,
173    /// The size of the call stack. This is used to prevent stack overflows with
174    /// recursive function calls. In general, this doesn't match `stack`'s size
175    /// since it's conservative in reclaiming frames between executions.
176    pub(super) call_stack_size: usize,
177    /// The current value of the pipe operator returned from the previous
178    /// expression.  If we're not currently in a pipeline, this will be None.
179    pub pipe_value: Option<KclValue>,
180    /// The closest variable declaration being executed in any parent node in the AST.
181    /// This is used to provide better error messages, e.g. noticing when the user is trying
182    /// to use the variable `length` inside the RHS of its own definition, like `length = tan(length)`.
183    pub being_declared: Option<String>,
184    /// Present if we're currently executing inside a sketch block.
185    pub sketch_block: Option<SketchBlockState>,
186    /// Tracks if KCL being executed is currently inside a stdlib function or not.
187    /// This matters because e.g. we shouldn't emit artifacts from declarations declared inside a stdlib function.
188    pub inside_stdlib: bool,
189    /// The source range where we entered the standard library.
190    pub stdlib_entry_source_range: Option<SourceRange>,
191    /// Identifiers that have been exported from the current module.
192    pub module_exports: Vec<String>,
193    /// Settings specified from annotations.
194    pub settings: MetaSettings,
195    /// True if executing in sketch mode. Only a single sketch block will be
196    /// executed. All other code is ignored.
197    pub sketch_mode: bool,
198    /// True to do more costly analysis of whether the sketch block segments are
199    /// under-constrained. The only time we disable this is when a user is
200    /// dragging segments.
201    pub freedom_analysis: bool,
202    pub(super) explicit_length_units: bool,
203    pub(super) path: ModulePath,
204    /// Artifacts for only this module.
205    pub artifacts: ModuleArtifactState,
206
207    pub(super) allowed_warnings: Vec<&'static str>,
208    pub(super) denied_warnings: Vec<&'static str>,
209}
210
211#[derive(Debug, Clone, Default)]
212pub(crate) struct SketchBlockState {
213    pub sketch_vars: Vec<KclValue>,
214    #[cfg(feature = "artifact-graph")]
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    #[cfg(feature = "artifact-graph")]
404    pub fn add_scene_object(&mut self, obj: Object, source_range: SourceRange) -> ObjectId {
405        let id = obj.id;
406        debug_assert!(
407            id.0 == self.mod_local.artifacts.scene_objects.len(),
408            "Adding scene object with ID {} but next ID is {}",
409            id.0,
410            self.mod_local.artifacts.scene_objects.len()
411        );
412        let artifact_id = obj.artifact_id;
413        self.mod_local.artifacts.scene_objects.push(obj);
414        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
415        self.mod_local
416            .artifacts
417            .artifact_id_to_scene_object
418            .insert(artifact_id, id);
419        id
420    }
421
422    /// Add a placeholder scene object. This is useful when we need to reserve
423    /// an ID before we have all the information to create the full object.
424    #[cfg(feature = "artifact-graph")]
425    pub fn add_placeholder_scene_object(
426        &mut self,
427        id: ObjectId,
428        source_range: SourceRange,
429        node_path: Option<NodePath>,
430    ) -> ObjectId {
431        debug_assert!(id.0 == self.mod_local.artifacts.scene_objects.len());
432        self.mod_local
433            .artifacts
434            .scene_objects
435            .push(Object::placeholder(id, source_range, node_path));
436        self.mod_local.artifacts.source_range_to_object.insert(source_range, id);
437        id
438    }
439
440    /// Update a scene object. This is useful to replace a placeholder.
441    #[cfg(feature = "artifact-graph")]
442    pub fn set_scene_object(&mut self, object: Object) {
443        let id = object.id;
444        let artifact_id = object.artifact_id;
445        self.mod_local.artifacts.scene_objects[id.0] = object;
446        self.mod_local
447            .artifacts
448            .artifact_id_to_scene_object
449            .insert(artifact_id, id);
450    }
451
452    #[cfg(feature = "artifact-graph")]
453    pub fn scene_object_id_by_artifact_id(&self, artifact_id: ArtifactId) -> Option<ObjectId> {
454        self.mod_local
455            .artifacts
456            .artifact_id_to_scene_object
457            .get(&artifact_id)
458            .cloned()
459    }
460
461    #[cfg(feature = "artifact-graph")]
462    pub fn segment_ids_edited_contains(&self, object_id: &ObjectId) -> bool {
463        self.global.segment_ids_edited.contains(object_id)
464    }
465
466    pub(super) fn is_in_sketch_block(&self) -> bool {
467        self.mod_local.sketch_block.is_some()
468    }
469
470    pub(crate) fn sketch_block_mut(&mut self) -> Option<&mut SketchBlockState> {
471        self.mod_local.sketch_block.as_mut()
472    }
473
474    pub fn next_uuid(&mut self) -> Uuid {
475        self.mod_local.id_generator.next_uuid()
476    }
477
478    #[cfg(feature = "artifact-graph")]
479    pub fn next_artifact_id(&mut self) -> ArtifactId {
480        self.mod_local.id_generator.next_artifact_id()
481    }
482
483    pub fn id_generator(&mut self) -> &mut IdGenerator {
484        &mut self.mod_local.id_generator
485    }
486
487    #[cfg(feature = "artifact-graph")]
488    pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
489        let id = artifact.id();
490        self.mod_local.artifacts.artifacts.insert(id, artifact);
491    }
492
493    #[cfg(feature = "artifact-graph")]
494    pub(crate) fn artifact_mut(&mut self, id: ArtifactId) -> Option<&mut Artifact> {
495        self.mod_local.artifacts.artifacts.get_mut(&id)
496    }
497
498    pub(crate) fn push_op(&mut self, op: Operation) {
499        #[cfg(feature = "artifact-graph")]
500        self.mod_local.artifacts.operations.push(op);
501        #[cfg(not(feature = "artifact-graph"))]
502        drop(op);
503    }
504
505    #[cfg(feature = "artifact-graph")]
506    pub(crate) fn push_command(&mut self, command: ArtifactCommand) {
507        self.mod_local.artifacts.unprocessed_commands.push(command);
508        #[cfg(not(feature = "artifact-graph"))]
509        drop(command);
510    }
511
512    pub(super) fn next_module_id(&self) -> ModuleId {
513        ModuleId::from_usize(self.global.path_to_source_id.len())
514    }
515
516    pub(super) fn id_for_module(&self, path: &ModulePath) -> Option<ModuleId> {
517        self.global.path_to_source_id.get(path).cloned()
518    }
519
520    pub(super) fn add_path_to_source_id(&mut self, path: ModulePath, id: ModuleId) {
521        debug_assert!(!self.global.path_to_source_id.contains_key(&path));
522        self.global.path_to_source_id.insert(path, id);
523    }
524
525    pub(crate) fn add_root_module_contents(&mut self, program: &crate::Program) {
526        let root_id = ModuleId::default();
527        // Get the path for the root module.
528        let path = self
529            .global
530            .path_to_source_id
531            .iter()
532            .find(|(_, v)| **v == root_id)
533            .unwrap()
534            .0
535            .clone();
536        self.add_id_to_source(
537            root_id,
538            ModuleSource {
539                path,
540                source: program.original_file_contents.to_string(),
541            },
542        );
543    }
544
545    pub(super) fn add_id_to_source(&mut self, id: ModuleId, source: ModuleSource) {
546        self.global.id_to_source.insert(id, source);
547    }
548
549    pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
550        debug_assert!(self.global.path_to_source_id.contains_key(&path));
551        let module_info = ModuleInfo { id, repr, path };
552        self.global.module_infos.insert(id, module_info);
553    }
554
555    pub fn get_module(&mut self, id: ModuleId) -> Option<&ModuleInfo> {
556        self.global.module_infos.get(&id)
557    }
558
559    #[cfg(all(test, feature = "artifact-graph"))]
560    pub(crate) fn modules(&self) -> &ModuleInfoMap {
561        &self.global.module_infos
562    }
563
564    #[cfg(all(test, feature = "artifact-graph"))]
565    pub(crate) fn root_module_artifact_state(&self) -> &ModuleArtifactState {
566        &self.global.root_module_artifacts
567    }
568
569    pub fn current_default_units(&self) -> NumericType {
570        NumericType::Default {
571            len: self.length_unit(),
572            angle: self.angle_unit(),
573        }
574    }
575
576    pub fn length_unit(&self) -> UnitLength {
577        self.mod_local.settings.default_length_units
578    }
579
580    pub fn angle_unit(&self) -> UnitAngle {
581        self.mod_local.settings.default_angle_units
582    }
583
584    pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
585        KclError::new_import_cycle(KclErrorDetails::new(
586            format!(
587                "circular import of modules is not allowed: {} -> {}",
588                self.global
589                    .mod_loader
590                    .import_stack
591                    .iter()
592                    .map(|p| p.to_string_lossy())
593                    .collect::<Vec<_>>()
594                    .join(" -> "),
595                path,
596            ),
597            vec![source_range],
598        ))
599    }
600
601    pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
602        self.mod_local.pipe_value.as_ref()
603    }
604
605    pub(crate) fn error_with_outputs(
606        &self,
607        error: KclError,
608        main_ref: Option<EnvironmentRef>,
609        default_planes: Option<DefaultPlanes>,
610    ) -> KclErrorWithOutputs {
611        let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
612            .global
613            .path_to_source_id
614            .iter()
615            .map(|(k, v)| ((*v), k.clone()))
616            .collect();
617
618        KclErrorWithOutputs::new(
619            error,
620            self.issues().to_vec(),
621            main_ref
622                .map(|main_ref| self.mod_local.variables(main_ref))
623                .unwrap_or_default(),
624            #[cfg(feature = "artifact-graph")]
625            self.global.root_module_artifacts.operations.clone(),
626            #[cfg(feature = "artifact-graph")]
627            Default::default(),
628            #[cfg(feature = "artifact-graph")]
629            self.global.artifacts.graph.clone(),
630            #[cfg(feature = "artifact-graph")]
631            self.global.root_module_artifacts.scene_objects.clone(),
632            #[cfg(feature = "artifact-graph")]
633            self.global.root_module_artifacts.source_range_to_object.clone(),
634            #[cfg(feature = "artifact-graph")]
635            self.global.root_module_artifacts.var_solutions.clone(),
636            module_id_to_module_path,
637            self.global.id_to_source.clone(),
638            default_planes,
639        )
640    }
641
642    #[cfg(feature = "artifact-graph")]
643    pub(crate) fn build_program_lookup(
644        &self,
645        current: crate::parsing::ast::types::Node<crate::parsing::ast::types::Program>,
646    ) -> ProgramLookup {
647        ProgramLookup::new(current, self.global.module_infos.clone())
648    }
649
650    #[cfg(feature = "artifact-graph")]
651    pub(crate) async fn build_artifact_graph(
652        &mut self,
653        engine: &Arc<Box<dyn EngineManager>>,
654        program: NodeRef<'_, crate::parsing::ast::types::Program>,
655    ) -> Result<(), KclError> {
656        let mut new_commands = Vec::new();
657        let mut new_exec_artifacts = IndexMap::new();
658        for module in self.global.module_infos.values_mut() {
659            match &mut module.repr {
660                ModuleRepr::Kcl(_, Some(outcome)) => {
661                    new_commands.extend(outcome.artifacts.process_commands());
662                    new_exec_artifacts.extend(outcome.artifacts.artifacts.clone());
663                }
664                ModuleRepr::Foreign(_, Some((_, module_artifacts))) => {
665                    new_commands.extend(module_artifacts.process_commands());
666                    new_exec_artifacts.extend(module_artifacts.artifacts.clone());
667                }
668                ModuleRepr::Root | ModuleRepr::Kcl(_, None) | ModuleRepr::Foreign(_, None) | ModuleRepr::Dummy => {}
669            }
670        }
671        // Take from the module artifacts so that we don't try to process them
672        // again next time due to execution caching.
673        new_commands.extend(self.global.root_module_artifacts.process_commands());
674        // Note: These will get re-processed, but since we're just adding them
675        // to a map, it's fine.
676        new_exec_artifacts.extend(self.global.root_module_artifacts.artifacts.clone());
677        let new_responses = engine.take_responses().await;
678
679        // Move the artifacts into ExecState global to simplify cache
680        // management.
681        for (id, exec_artifact) in new_exec_artifacts {
682            // Only insert if it wasn't already present. We don't want to
683            // overwrite what was previously there. We haven't filled in node
684            // paths yet.
685            self.global.artifacts.artifacts.entry(id).or_insert(exec_artifact);
686        }
687
688        let initial_graph = self.global.artifacts.graph.clone();
689
690        // Build the artifact graph.
691        let programs = self.build_program_lookup(program.clone());
692        let graph_result = crate::execution::artifact::build_artifact_graph(
693            &new_commands,
694            &new_responses,
695            program,
696            &mut self.global.artifacts.artifacts,
697            initial_graph,
698            &programs,
699            &self.global.module_infos,
700        );
701
702        #[cfg(feature = "snapshot-engine-responses")]
703        {
704            // Store engine responses for debugging.
705            self.global.root_module_artifacts.responses.extend(new_responses);
706        }
707
708        let artifact_graph = graph_result?;
709        self.global.artifacts.graph = artifact_graph;
710
711        Ok(())
712    }
713
714    #[cfg(not(feature = "artifact-graph"))]
715    pub(crate) async fn build_artifact_graph(
716        &mut self,
717        _engine: &Arc<Box<dyn EngineManager>>,
718        _program: NodeRef<'_, crate::parsing::ast::types::Program>,
719    ) -> Result<(), KclError> {
720        Ok(())
721    }
722}
723
724impl GlobalState {
725    fn new(settings: &ExecutorSettings, segment_ids_edited: AhashIndexSet<ObjectId>) -> Self {
726        #[cfg(not(feature = "artifact-graph"))]
727        drop(segment_ids_edited);
728        let mut global = GlobalState {
729            path_to_source_id: Default::default(),
730            module_infos: Default::default(),
731            artifacts: Default::default(),
732            root_module_artifacts: Default::default(),
733            mod_loader: Default::default(),
734            issues: Default::default(),
735            id_to_source: Default::default(),
736            #[cfg(feature = "artifact-graph")]
737            segment_ids_edited,
738        };
739
740        let root_id = ModuleId::default();
741        let root_path = settings.current_file.clone().unwrap_or_default();
742        global.module_infos.insert(
743            root_id,
744            ModuleInfo {
745                id: root_id,
746                path: ModulePath::Local {
747                    value: root_path.clone(),
748                    original_import_path: None,
749                },
750                repr: ModuleRepr::Root,
751            },
752        );
753        global.path_to_source_id.insert(
754            ModulePath::Local {
755                value: root_path,
756                original_import_path: None,
757            },
758            root_id,
759        );
760        global
761    }
762
763    pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
764        self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
765    }
766
767    pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
768        self.id_to_source.get(&id)
769    }
770}
771
772impl ArtifactState {
773    #[cfg(feature = "artifact-graph")]
774    pub fn cached_body_items(&self) -> usize {
775        self.graph.item_count
776    }
777
778    pub(crate) fn clear(&mut self) {
779        #[cfg(feature = "artifact-graph")]
780        {
781            self.artifacts.clear();
782            self.graph.clear();
783        }
784    }
785}
786
787impl ModuleArtifactState {
788    pub(crate) fn clear(&mut self) {
789        #[cfg(feature = "artifact-graph")]
790        {
791            self.artifacts.clear();
792            self.unprocessed_commands.clear();
793            self.commands.clear();
794            self.operations.clear();
795        }
796    }
797
798    #[cfg(feature = "artifact-graph")]
799    pub(crate) fn restore_scene_objects(&mut self, scene_objects: &[Object]) {
800        self.scene_objects = scene_objects.to_vec();
801        self.object_id_generator = IncIdGenerator::new(self.scene_objects.len());
802        self.source_range_to_object.clear();
803        self.artifact_id_to_scene_object.clear();
804
805        for (expected_id, object) in self.scene_objects.iter().enumerate() {
806            debug_assert_eq!(
807                object.id.0, expected_id,
808                "Restored cached scene object ID {} does not match its position {}",
809                object.id.0, expected_id
810            );
811
812            match &object.source {
813                crate::front::SourceRef::Simple { range, node_path: _ } => {
814                    self.source_range_to_object.insert(*range, object.id);
815                }
816                crate::front::SourceRef::BackTrace { ranges } => {
817                    // Don't map the entire backtrace, only the most specific
818                    // range.
819                    if let Some((range, _)) = ranges.first() {
820                        self.source_range_to_object.insert(*range, object.id);
821                    }
822                }
823            }
824
825            // Ignore placeholder artifacts.
826            if object.artifact_id != ArtifactId::placeholder() {
827                self.artifact_id_to_scene_object.insert(object.artifact_id, object.id);
828            }
829        }
830    }
831
832    #[cfg(not(feature = "artifact-graph"))]
833    pub(crate) fn extend(&mut self, _other: ModuleArtifactState) {}
834
835    /// When self is a cached state, extend it with new state.
836    #[cfg(feature = "artifact-graph")]
837    pub(crate) fn extend(&mut self, other: ModuleArtifactState) {
838        self.artifacts.extend(other.artifacts);
839        self.unprocessed_commands.extend(other.unprocessed_commands);
840        self.commands.extend(other.commands);
841        self.operations.extend(other.operations);
842        if other.scene_objects.len() > self.scene_objects.len() {
843            self.scene_objects
844                .extend(other.scene_objects[self.scene_objects.len()..].iter().cloned());
845        }
846        self.source_range_to_object.extend(other.source_range_to_object);
847        self.artifact_id_to_scene_object
848            .extend(other.artifact_id_to_scene_object);
849        self.var_solutions.extend(other.var_solutions);
850    }
851
852    // Move unprocessed artifact commands so that we don't try to process them
853    // again next time due to execution caching.  Returns a clone of the
854    // commands that were moved.
855    #[cfg(feature = "artifact-graph")]
856    pub(crate) fn process_commands(&mut self) -> Vec<ArtifactCommand> {
857        let unprocessed = std::mem::take(&mut self.unprocessed_commands);
858        let new_module_commands = unprocessed.clone();
859        self.commands.extend(unprocessed);
860        new_module_commands
861    }
862
863    #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
864    pub(crate) fn scene_object_by_id(&self, id: ObjectId) -> Option<&Object> {
865        #[cfg(feature = "artifact-graph")]
866        {
867            debug_assert!(
868                id.0 < self.scene_objects.len(),
869                "Requested object ID {} but only have {} objects",
870                id.0,
871                self.scene_objects.len()
872            );
873            self.scene_objects.get(id.0)
874        }
875        #[cfg(not(feature = "artifact-graph"))]
876        {
877            let _ = id;
878            None
879        }
880    }
881
882    #[cfg_attr(not(feature = "artifact-graph"), expect(dead_code))]
883    pub(crate) fn scene_object_by_id_mut(&mut self, id: ObjectId) -> Option<&mut 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_mut(id.0)
893        }
894        #[cfg(not(feature = "artifact-graph"))]
895        {
896            let _ = id;
897            None
898        }
899    }
900}
901
902impl ModuleState {
903    pub(super) fn new(
904        path: ModulePath,
905        memory: Arc<ProgramMemory>,
906        module_id: Option<ModuleId>,
907        sketch_mode: bool,
908        freedom_analysis: bool,
909    ) -> Self {
910        ModuleState {
911            id_generator: IdGenerator::new(module_id),
912            stack: memory.new_stack(),
913            call_stack_size: 0,
914            pipe_value: Default::default(),
915            being_declared: Default::default(),
916            sketch_block: Default::default(),
917            stdlib_entry_source_range: Default::default(),
918            module_exports: Default::default(),
919            explicit_length_units: false,
920            path,
921            settings: Default::default(),
922            sketch_mode,
923            freedom_analysis,
924            artifacts: Default::default(),
925            allowed_warnings: Vec::new(),
926            denied_warnings: Vec::new(),
927            inside_stdlib: false,
928        }
929    }
930
931    pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
932        self.stack
933            .find_all_in_env(main_ref)
934            .map(|(k, v)| (k.clone(), v.clone()))
935            .collect()
936    }
937}
938
939impl SketchBlockState {
940    pub(crate) fn next_sketch_var_id(&self) -> SketchVarId {
941        SketchVarId(self.sketch_vars.len())
942    }
943
944    /// Given a solve outcome, return the solutions for the sketch variables and
945    /// enough information to update them in the source.
946    #[cfg(feature = "artifact-graph")]
947    pub(crate) fn var_solutions(
948        &self,
949        solve_outcome: &Solved,
950        solution_ty: NumericType,
951        range: SourceRange,
952    ) -> Result<Vec<(SourceRange, Number)>, KclError> {
953        self.sketch_vars
954            .iter()
955            .map(|v| {
956                let Some(sketch_var) = v.as_sketch_var() else {
957                    return Err(KclError::new_internal(KclErrorDetails::new(
958                        "Expected sketch variable".to_owned(),
959                        vec![range],
960                    )));
961                };
962                let var_index = sketch_var.id.0;
963                let solved_n = solve_outcome.final_values.get(var_index).ok_or_else(|| {
964                    let message = format!("No solution for sketch variable with id {}", var_index);
965                    debug_assert!(false, "{}", &message);
966                    KclError::new_internal(KclErrorDetails::new(
967                        message,
968                        sketch_var.meta.iter().map(|m| m.source_range).collect(),
969                    ))
970                })?;
971                let solved_value = Number {
972                    value: *solved_n,
973                    units: solution_ty.try_into().map_err(|_| {
974                        KclError::new_internal(KclErrorDetails::new(
975                            "Failed to convert numeric type to units".to_owned(),
976                            vec![range],
977                        ))
978                    })?,
979                };
980                let Some(source_range) = sketch_var.meta.first().map(|m| m.source_range) else {
981                    return Ok(None);
982                };
983                Ok(Some((source_range, solved_value)))
984            })
985            .filter_map(Result::transpose)
986            .collect::<Result<Vec<_>, KclError>>()
987    }
988}
989
990#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
991#[ts(export)]
992#[serde(rename_all = "camelCase")]
993pub struct MetaSettings {
994    pub default_length_units: UnitLength,
995    pub default_angle_units: UnitAngle,
996    pub experimental_features: annotations::WarningLevel,
997    pub kcl_version: String,
998}
999
1000impl Default for MetaSettings {
1001    fn default() -> Self {
1002        MetaSettings {
1003            default_length_units: UnitLength::Millimeters,
1004            default_angle_units: UnitAngle::Degrees,
1005            experimental_features: annotations::WarningLevel::Deny,
1006            kcl_version: "1.0".to_owned(),
1007        }
1008    }
1009}
1010
1011impl MetaSettings {
1012    pub(crate) fn update_from_annotation(
1013        &mut self,
1014        annotation: &crate::parsing::ast::types::Node<Annotation>,
1015    ) -> Result<(bool, bool), KclError> {
1016        let properties = annotations::expect_properties(annotations::SETTINGS, annotation)?;
1017
1018        let mut updated_len = false;
1019        let mut updated_angle = false;
1020        for p in properties {
1021            match &*p.inner.key.name {
1022                annotations::SETTINGS_UNIT_LENGTH => {
1023                    let value = annotations::expect_ident(&p.inner.value)?;
1024                    let value = super::types::length_from_str(value, annotation.as_source_range())?;
1025                    self.default_length_units = value;
1026                    updated_len = true;
1027                }
1028                annotations::SETTINGS_UNIT_ANGLE => {
1029                    let value = annotations::expect_ident(&p.inner.value)?;
1030                    let value = super::types::angle_from_str(value, annotation.as_source_range())?;
1031                    self.default_angle_units = value;
1032                    updated_angle = true;
1033                }
1034                annotations::SETTINGS_VERSION => {
1035                    let value = annotations::expect_number(&p.inner.value)?;
1036                    self.kcl_version = value;
1037                }
1038                annotations::SETTINGS_EXPERIMENTAL_FEATURES => {
1039                    let value = annotations::expect_ident(&p.inner.value)?;
1040                    let value = annotations::WarningLevel::from_str(value).map_err(|_| {
1041                        KclError::new_semantic(KclErrorDetails::new(
1042                            format!(
1043                                "Invalid value for {} settings property, expected one of: {}",
1044                                annotations::SETTINGS_EXPERIMENTAL_FEATURES,
1045                                annotations::WARN_LEVELS.join(", ")
1046                            ),
1047                            annotation.as_source_ranges(),
1048                        ))
1049                    })?;
1050                    self.experimental_features = value;
1051                }
1052                name => {
1053                    return Err(KclError::new_semantic(KclErrorDetails::new(
1054                        format!(
1055                            "Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
1056                            annotations::SETTINGS_UNIT_LENGTH,
1057                            annotations::SETTINGS_UNIT_ANGLE
1058                        ),
1059                        vec![annotation.as_source_range()],
1060                    )));
1061                }
1062            }
1063        }
1064
1065        Ok((updated_len, updated_angle))
1066    }
1067}
1068
1069#[cfg(all(feature = "artifact-graph", test))]
1070mod tests {
1071    use uuid::Uuid;
1072
1073    use super::ModuleArtifactState;
1074    use crate::NodePath;
1075    use crate::SourceRange;
1076    use crate::execution::ArtifactId;
1077    use crate::front::Object;
1078    use crate::front::ObjectId;
1079    use crate::front::ObjectKind;
1080    use crate::front::Plane;
1081    use crate::front::SourceRef;
1082
1083    #[test]
1084    fn restore_scene_objects_rebuilds_lookup_maps() {
1085        let plane_artifact_id = ArtifactId::new(Uuid::from_u128(1));
1086        let sketch_artifact_id = ArtifactId::new(Uuid::from_u128(2));
1087        let plane_range = SourceRange::from([1, 4, 0]);
1088        let plane_node_path = Some(NodePath::placeholder());
1089        let sketch_ranges = vec![
1090            (SourceRange::from([5, 9, 0]), None),
1091            (SourceRange::from([10, 12, 0]), None),
1092        ];
1093        let cached_objects = vec![
1094            Object {
1095                id: ObjectId(0),
1096                kind: ObjectKind::Plane(Plane::Object(ObjectId(0))),
1097                label: Default::default(),
1098                comments: Default::default(),
1099                artifact_id: plane_artifact_id,
1100                source: SourceRef::new(plane_range, plane_node_path),
1101            },
1102            Object {
1103                id: ObjectId(1),
1104                kind: ObjectKind::Nil,
1105                label: Default::default(),
1106                comments: Default::default(),
1107                artifact_id: sketch_artifact_id,
1108                source: SourceRef::BackTrace {
1109                    ranges: sketch_ranges.clone(),
1110                },
1111            },
1112            Object::placeholder(ObjectId(2), SourceRange::from([13, 14, 0]), None),
1113        ];
1114
1115        let mut artifacts = ModuleArtifactState::default();
1116        artifacts.restore_scene_objects(&cached_objects);
1117
1118        assert_eq!(artifacts.scene_objects, cached_objects);
1119        assert_eq!(
1120            artifacts.artifact_id_to_scene_object.get(&plane_artifact_id),
1121            Some(&ObjectId(0))
1122        );
1123        assert_eq!(
1124            artifacts.artifact_id_to_scene_object.get(&sketch_artifact_id),
1125            Some(&ObjectId(1))
1126        );
1127        assert_eq!(
1128            artifacts.artifact_id_to_scene_object.get(&ArtifactId::placeholder()),
1129            None
1130        );
1131        assert_eq!(artifacts.source_range_to_object.get(&plane_range), Some(&ObjectId(0)));
1132        assert_eq!(
1133            artifacts.source_range_to_object.get(&sketch_ranges[0].0),
1134            Some(&ObjectId(1))
1135        );
1136        // We don't map all the ranges in a backtrace.
1137        assert_eq!(artifacts.source_range_to_object.get(&sketch_ranges[1].0), None);
1138    }
1139}