Skip to main content

kcl_lib/execution/
state.rs

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