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