Skip to main content

i_slint_compiler/
typeloader.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore importident incdir splitn
5use smol_str::{SmolStr, ToSmolStr};
6use std::cell::RefCell;
7use std::collections::{HashMap, HashSet};
8use std::io::ErrorKind;
9use std::path::{Path, PathBuf};
10use std::rc::{Rc, Weak};
11
12use crate::diagnostics::{BuildDiagnostics, Diagnostic, Spanned};
13use crate::expression_tree::Callable;
14use crate::object_tree::{self, Document, ExportedName, Exports};
15use crate::parser::{NodeOrToken, SyntaxKind, SyntaxToken, syntax_nodes};
16use crate::typeregister::TypeRegister;
17use crate::{CompilerConfiguration, expression_tree};
18use crate::{fileaccess, langtype, layout, parser};
19use core::future::Future;
20use itertools::Itertools;
21
22#[allow(clippy::large_enum_variant)]
23enum LoadedDocument {
24    Document(Document),
25    /// A dependency of this file has changed, so we need to re-analyze it.
26    /// The file contents have not changed, so we can keep the parsed CST around.
27    Invalidated(syntax_nodes::Document),
28}
29
30/// Storage for a cache of all loaded documents
31#[derive(Default)]
32struct LoadedDocuments {
33    /// maps from the canonical file name to the object_tree::Document.
34    /// Also contains the error that occurred when parsing the document (and only the parse error, not further semantic errors)
35    docs: HashMap<PathBuf, (LoadedDocument, Vec<crate::diagnostics::Diagnostic>)>,
36    /// The .slint files that are currently being loaded, potentially asynchronously.
37    /// When a task start loading a file, it will add an empty vector to this map, and
38    /// the same task will remove the entry from the map when finished, and awake all
39    /// wakers.
40    currently_loading: HashMap<PathBuf, Vec<std::task::Waker>>,
41
42    /// The dependencies of the currently loaded files.
43    /// Maps all the files that depends directly on the key
44    dependencies: HashMap<PathBuf, HashSet<PathBuf>>,
45}
46
47#[derive(Debug, Clone)]
48pub enum ImportKind {
49    /// `import {Foo, Bar} from "foo"`
50    ImportList(syntax_nodes::ImportIdentifierList),
51    /// `import "foo"` without an import list
52    FileImport,
53    /// re-export types, as per `export ... from "foo"``.
54    ModuleReexport(syntax_nodes::ExportsList),
55}
56
57#[derive(Debug, Clone)]
58pub struct LibraryInfo {
59    pub name: String,
60    pub package: String,
61    pub module: Option<String>,
62    pub exports: Vec<ExportedName>,
63}
64
65#[derive(Debug, Clone)]
66pub struct ImportedTypes {
67    pub import_uri_token: SyntaxToken,
68    pub import_kind: ImportKind,
69    pub file: String,
70
71    /// `import {Foo, Bar} from "@Foo"` where Foo is an external
72    /// library located in another crate
73    pub library_info: Option<LibraryInfo>,
74}
75
76#[derive(Debug)]
77pub struct ImportedName {
78    // name of export to match in the other file
79    pub external_name: SmolStr,
80    // name to be used locally
81    pub internal_name: SmolStr,
82}
83
84impl ImportedName {
85    pub fn extract_imported_names(
86        import_identifiers: &syntax_nodes::ImportIdentifierList,
87    ) -> impl Iterator<Item = ImportedName> + '_ {
88        import_identifiers.ImportIdentifier().map(Self::from_node)
89    }
90
91    pub fn from_node(importident: syntax_nodes::ImportIdentifier) -> Self {
92        let external_name =
93            parser::normalize_identifier(importident.ExternalName().text().to_smolstr().trim());
94
95        let internal_name = match importident.InternalName() {
96            Some(name_ident) => parser::normalize_identifier(name_ident.text().to_smolstr().trim()),
97            None => external_name.clone(),
98        };
99
100        ImportedName { internal_name, external_name }
101    }
102}
103
104/// This function makes a snapshot of the current state of the type loader.
105/// This snapshot includes everything: Elements, Components, known types, ...
106/// and can be used to roll back to earlier states in the compilation process.
107///
108/// One way this is used is to create a raw `TypeLoader` for analysis purposes
109/// or to load a set of changes, see if those compile and then role back
110///
111/// The result may be `None` if the `TypeLoader` is actually in the process
112/// of loading more documents and is `Some` `TypeLoader` with a copy off all
113/// state connected with the original `TypeLoader`.
114pub fn snapshot(type_loader: &TypeLoader) -> Option<TypeLoader> {
115    let mut snapshotter = Snapshotter {
116        component_map: HashMap::new(),
117        element_map: HashMap::new(),
118        type_register_map: HashMap::new(),
119        keep_alive: Vec::new(),
120        keep_alive_elements: Vec::new(),
121    };
122    snapshotter.snapshot_type_loader(type_loader)
123}
124
125/// This function makes a snapshot of the current state of the type loader.
126/// This snapshot includes everything: Elements, Components, known types, ...
127/// and can be used to roll back to earlier states in the compilation process.
128///
129/// One way this is used is to create a raw `TypeLoader` for analysis purposes
130/// or to load a set of changes, see if those compile and then role back
131///
132/// The result may be `None` if the `TypeLoader` is actually in the process
133/// of loading more documents and is `Some` `TypeLoader` with a copy off all
134/// state connected with the original `TypeLoader`.
135///
136/// The Document will be added to the type_loader after it was snapshotted as well.
137pub(crate) fn snapshot_with_extra_doc(
138    type_loader: &TypeLoader,
139    doc: &object_tree::Document,
140) -> Option<TypeLoader> {
141    let mut snapshotter = Snapshotter {
142        component_map: HashMap::new(),
143        element_map: HashMap::new(),
144        type_register_map: HashMap::new(),
145        keep_alive: Vec::new(),
146        keep_alive_elements: Vec::new(),
147    };
148    let mut result = snapshotter.snapshot_type_loader(type_loader);
149
150    snapshotter.create_document(doc);
151    let new_doc = snapshotter.snapshot_document(doc);
152
153    snapshotter.finalize();
154
155    if let Some(doc_node) = &new_doc.node {
156        let path = doc_node.source_file.path().to_path_buf();
157        if let Some(r) = &mut result {
158            r.all_documents.docs.insert(path, (LoadedDocument::Document(new_doc), Vec::new()));
159        }
160    }
161
162    result
163}
164
165pub(crate) struct Snapshotter {
166    component_map:
167        HashMap<by_address::ByAddress<Rc<object_tree::Component>>, Weak<object_tree::Component>>,
168    element_map:
169        HashMap<by_address::ByAddress<object_tree::ElementRc>, Weak<RefCell<object_tree::Element>>>,
170    type_register_map:
171        HashMap<by_address::ByAddress<Rc<RefCell<TypeRegister>>>, Rc<RefCell<TypeRegister>>>,
172
173    keep_alive: Vec<(Rc<object_tree::Component>, Rc<object_tree::Component>)>,
174    keep_alive_elements: Vec<(object_tree::ElementRc, object_tree::ElementRc)>,
175}
176
177impl Snapshotter {
178    fn snapshot_globals(&mut self, type_loader: &TypeLoader) {
179        let registry = type_loader.global_type_registry.clone();
180        registry
181            .borrow()
182            .all_elements()
183            .values()
184            .filter_map(|ty| match ty {
185                langtype::ElementType::Component(c) if c.is_global() => Some(c),
186                _ => None,
187            })
188            .for_each(|c| {
189                self.create_component(c);
190            });
191    }
192
193    fn finalize(&mut self) {
194        let mut elements = std::mem::take(&mut self.keep_alive_elements);
195
196        while !elements.is_empty() {
197            for (s, t) in elements.iter_mut() {
198                self.snapshot_element(s, &mut t.borrow_mut());
199            }
200            elements = std::mem::take(&mut self.keep_alive_elements);
201        }
202    }
203
204    fn snapshot_type_loader(&mut self, type_loader: &TypeLoader) -> Option<TypeLoader> {
205        self.snapshot_globals(type_loader);
206
207        let all_documents = self.snapshot_loaded_documents(&type_loader.all_documents)?;
208
209        self.finalize();
210
211        Some(TypeLoader {
212            all_documents,
213            global_type_registry: self.snapshot_type_register(&type_loader.global_type_registry),
214            compiler_config: type_loader.compiler_config.clone(),
215            resolved_style: type_loader.resolved_style.clone(),
216            revision: type_loader.revision,
217            // Share the counters so names generated after the snapshot stay unique.
218            symbol_counters: type_loader.symbol_counters.clone(),
219        })
220    }
221
222    pub(crate) fn snapshot_type_register(
223        &mut self,
224        type_register: &Rc<RefCell<TypeRegister>>,
225    ) -> Rc<RefCell<TypeRegister>> {
226        if let Some(r) = self.type_register_map.get(&by_address::ByAddress(type_register.clone())) {
227            return r.clone();
228        }
229
230        let tr = Rc::new(RefCell::new(TypeRegister::default()));
231        self.type_register_map.insert(by_address::ByAddress(type_register.clone()), tr.clone());
232
233        *tr.borrow_mut() = self.snapshot_type_register_impl(type_register);
234
235        tr
236    }
237
238    fn snapshot_type_register_impl(
239        &mut self,
240        type_register: &Rc<RefCell<TypeRegister>>,
241    ) -> TypeRegister {
242        type_register.borrow().snapshot(self)
243    }
244
245    fn snapshot_loaded_documents(
246        &mut self,
247        loaded_documents: &LoadedDocuments,
248    ) -> Option<LoadedDocuments> {
249        if !loaded_documents.currently_loading.is_empty() {
250            return None;
251        }
252
253        loaded_documents.docs.values().for_each(|(d, _)| {
254            if let LoadedDocument::Document(d) = d {
255                self.create_document(d)
256            }
257        });
258
259        Some(LoadedDocuments {
260            docs: loaded_documents
261                .docs
262                .iter()
263                .map(|(p, (d, err))| {
264                    (
265                        p.clone(),
266                        (
267                            match d {
268                                LoadedDocument::Document(d) => {
269                                    LoadedDocument::Document(self.snapshot_document(d))
270                                }
271                                LoadedDocument::Invalidated(d) => {
272                                    LoadedDocument::Invalidated(d.clone())
273                                }
274                            },
275                            err.clone(),
276                        ),
277                    )
278                })
279                .collect(),
280            currently_loading: Default::default(),
281            dependencies: Default::default(),
282        })
283    }
284
285    fn create_document(&mut self, document: &object_tree::Document) {
286        document.inner_components.iter().for_each(|ic| {
287            let _ = self.create_component(ic);
288        });
289        if let Some(popup_menu_impl) = &document.popup_menu_impl {
290            let _ = self.create_component(popup_menu_impl);
291        }
292    }
293
294    fn snapshot_document(&mut self, document: &object_tree::Document) -> object_tree::Document {
295        let inner_components = document
296            .inner_components
297            .iter()
298            .map(|ic| {
299                Weak::upgrade(&self.use_component(ic))
300                    .expect("Components can get upgraded at this point")
301            })
302            .collect();
303        let exports = document.exports.snapshot(self);
304
305        object_tree::Document {
306            node: document.node.clone(),
307            inner_components,
308            inner_types: document.inner_types.clone(),
309            local_registry: document.local_registry.snapshot(self),
310            custom_fonts: document.custom_fonts.clone(),
311            imports: document.imports.clone(),
312            exports,
313            library_exports: document.library_exports.clone(),
314            embedded_file_resources: document.embedded_file_resources.clone(),
315            #[cfg(feature = "bundle-translations")]
316            translation_builder: document.translation_builder.clone(),
317            used_types: RefCell::new(self.snapshot_used_sub_types(&document.used_types.borrow())),
318            popup_menu_impl: document.popup_menu_impl.as_ref().map(|p| {
319                Weak::upgrade(&self.use_component(p))
320                    .expect("Components can get upgraded at this point")
321            }),
322        }
323    }
324
325    pub(crate) fn create_component(
326        &mut self,
327        component: &Rc<object_tree::Component>,
328    ) -> Rc<object_tree::Component> {
329        let input_address = by_address::ByAddress(component.clone());
330
331        let parent_element = if let Some(pe) = component.parent_element() {
332            Rc::downgrade(&self.use_element(&pe))
333        } else {
334            Weak::default()
335        };
336
337        let result = Rc::new_cyclic(|weak| {
338            self.component_map.insert(input_address, weak.clone());
339
340            let root_element = self.create_element(&component.root_element);
341
342            let optimized_elements = RefCell::new(
343                component
344                    .optimized_elements
345                    .borrow()
346                    .iter()
347                    .map(|e| self.create_element(e))
348                    .collect(),
349            );
350
351            let child_insertion_points =
352                RefCell::new(component.child_insertion_points.borrow().clone());
353            let declared_slots = component.declared_slots.clone();
354
355            let popup_windows = RefCell::new(
356                component
357                    .popup_windows
358                    .borrow()
359                    .iter()
360                    .map(|p| self.snapshot_popup_window(p))
361                    .collect(),
362            );
363            let timers = RefCell::new(
364                component.timers.borrow().iter().map(|p| self.snapshot_timer(p)).collect(),
365            );
366            let root_constraints = RefCell::new(
367                self.snapshot_layout_constraints(&component.root_constraints.borrow()),
368            );
369            let menu_item_tree = component
370                .menu_item_tree
371                .borrow()
372                .iter()
373                .map(|it| self.create_component(it))
374                .collect::<Vec<_>>()
375                .into();
376            object_tree::Component {
377                node: component.node.clone(),
378                id: component.id.clone(),
379                child_insertion_points,
380                declared_slots,
381                exported_global_names: RefCell::new(
382                    component.exported_global_names.borrow().clone(),
383                ),
384                used: component.used.clone(),
385                init_code: RefCell::new(component.init_code.borrow().clone()),
386                inherits_popup_window: std::cell::Cell::new(component.inherits_popup_window.get()),
387                optimized_elements,
388                parent_element: RefCell::new(parent_element),
389                popup_windows,
390                timers,
391                menu_item_tree,
392                private_properties: RefCell::new(component.private_properties.borrow().clone()),
393                root_constraints,
394                root_element,
395                from_library: core::cell::Cell::new(false),
396            }
397        });
398        self.keep_alive.push((component.clone(), result.clone()));
399        result
400    }
401
402    pub(crate) fn use_component(
403        &self,
404        component: &Rc<object_tree::Component>,
405    ) -> Weak<object_tree::Component> {
406        self.component_map
407            .get(&by_address::ByAddress(component.clone()))
408            .expect("Component (Weak!) must exist at this point.")
409            .clone()
410    }
411
412    pub(crate) fn create_element(
413        &mut self,
414        element: &object_tree::ElementRc,
415    ) -> object_tree::ElementRc {
416        let enclosing_component = if let Some(ec) = element.borrow().enclosing_component.upgrade() {
417            self.use_component(&ec)
418        } else {
419            Weak::default()
420        };
421
422        let elem = element.borrow();
423
424        let r = Rc::new_cyclic(|weak| {
425            self.element_map.insert(by_address::ByAddress(element.clone()), weak.clone());
426
427            let children = elem.children.iter().map(|c| self.create_element(c)).collect();
428
429            RefCell::new(object_tree::Element {
430                id: elem.id.clone(),
431                enclosing_component,
432                children,
433                debug: elem.debug.clone(),
434                ..Default::default()
435            })
436        });
437
438        self.keep_alive_elements.push((element.clone(), r.clone()));
439        r
440    }
441
442    fn create_and_snapshot_element(
443        &mut self,
444        element: &object_tree::ElementRc,
445    ) -> object_tree::ElementRc {
446        let target = self.create_element(element);
447        self.snapshot_element(element, &mut target.borrow_mut());
448        target
449    }
450
451    pub(crate) fn use_element(&self, element: &object_tree::ElementRc) -> object_tree::ElementRc {
452        Weak::upgrade(
453            &self
454                .element_map
455                .get(&by_address::ByAddress(element.clone()))
456                .expect("Elements should have been known at this point")
457                .clone(),
458        )
459        .expect("Must be able to upgrade here")
460    }
461
462    fn snapshot_element(
463        &mut self,
464        element: &object_tree::ElementRc,
465        target_element: &mut object_tree::Element,
466    ) {
467        let elem = element.borrow();
468
469        target_element.base_type = self.snapshot_element_type(&elem.base_type);
470
471        target_element.transitions = elem
472            .transitions
473            .iter()
474            .map(|t| object_tree::Transition {
475                direction: t.direction,
476                state_id: t.state_id.clone(),
477                property_animations: t
478                    .property_animations
479                    .iter()
480                    .map(|(nr, sl, el)| {
481                        (nr.snapshot(self), sl.clone(), self.create_and_snapshot_element(el))
482                    })
483                    .collect(),
484                node: t.node.clone(),
485            })
486            .collect();
487
488        target_element.bindings = elem
489            .bindings_including_synthetic()
490            .map(|(k, v)| {
491                let bm = v.borrow();
492                let binding = self.snapshot_binding_expression(&bm);
493                (k.clone(), RefCell::new(binding))
494            })
495            .collect();
496        target_element.states = elem
497            .states
498            .iter()
499            .map(|s| object_tree::State {
500                id: s.id.clone(),
501                condition: s.condition.clone(),
502                property_changes: s
503                    .property_changes
504                    .iter()
505                    .map(|(nr, expr, spc)| {
506                        let nr = nr.snapshot(self);
507                        let expr = self.snapshot_expression(expr);
508                        (nr, expr, spc.clone())
509                    })
510                    .collect(),
511                selection: s.selection.clone(),
512            })
513            .collect();
514        target_element.repeated =
515            elem.repeated.as_ref().map(|r| object_tree::RepeatedElementInfo {
516                model: self.snapshot_expression(&r.model),
517                model_data_id: r.model_data_id.clone(),
518                index_id: r.index_id.clone(),
519                is_conditional_element: r.is_conditional_element,
520                is_listview: r.is_listview.as_ref().map(|lv| object_tree::ListViewInfo {
521                    content_y: lv.content_y.snapshot(self),
522                    content_height: lv.content_height.as_ref().map(|height| height.snapshot(self)),
523                    content_width: lv.content_width.as_ref().map(|width| width.snapshot(self)),
524                    listview_height: lv.listview_height.snapshot(self),
525                    listview_width: lv.listview_width.snapshot(self),
526                }),
527            });
528
529        target_element.accessibility_props = object_tree::AccessibilityProps(
530            elem.accessibility_props.0.iter().map(|(k, v)| (k.clone(), v.snapshot(self))).collect(),
531        );
532        target_element.geometry_props =
533            elem.geometry_props.as_ref().map(|gp| object_tree::GeometryProps {
534                x: gp.x.snapshot(self),
535                y: gp.y.snapshot(self),
536                width: gp.width.snapshot(self),
537                height: gp.height.snapshot(self),
538            });
539        target_element.property_declarations = elem
540            .property_declarations
541            .iter()
542            .map(|(k, v)| {
543                let decl = object_tree::PropertyDeclaration {
544                    property_type: v.property_type.clone(),
545                    node: v.node.clone(),
546                    expose_in_public_api: v.expose_in_public_api,
547                    is_alias: v.is_alias.as_ref().map(|a| a.snapshot(self)),
548                    visibility: v.visibility,
549                    pure: v.pure,
550                    shadowed_name: v.shadowed_name.clone(),
551                    shadowable: v.shadowable,
552                    moved_from: v.moved_from.clone(),
553                    deprecated: v.deprecated.clone(),
554                };
555                (k.clone(), decl)
556            })
557            .collect();
558        target_element.shadowing_members = elem.shadowing_members.clone();
559        target_element.layout_info_prop =
560            elem.layout_info_prop.as_ref().map(|(n1, n2)| (n1.snapshot(self), n2.snapshot(self)));
561        target_element.property_analysis = RefCell::new(elem.property_analysis.borrow().clone());
562
563        target_element.change_callbacks = elem.change_callbacks.clone();
564        target_element.child_of_layout = elem.child_of_layout;
565        target_element.child_of_flexbox = elem.child_of_flexbox;
566        target_element.default_fill_parent = elem.default_fill_parent;
567        target_element.has_popup_child = elem.has_popup_child;
568        target_element.inline_depth = elem.inline_depth;
569        target_element.is_component_placeholder = elem.is_component_placeholder;
570        target_element.is_flickable_content = elem.is_flickable_content;
571        target_element.is_legacy_syntax = elem.is_legacy_syntax;
572        target_element.item_index = elem.item_index.clone();
573        target_element.item_index_of_first_children = elem.item_index_of_first_children.clone();
574        target_element.named_references = elem.named_references.snapshot(self);
575    }
576
577    fn snapshot_binding_expression(
578        &mut self,
579        binding_expression: &expression_tree::BindingExpression,
580    ) -> expression_tree::BindingExpression {
581        expression_tree::BindingExpression {
582            expression: self.snapshot_expression(&binding_expression.expression),
583            span: binding_expression.span.clone(),
584            priority: binding_expression.priority,
585            animation: binding_expression.animation.as_ref().map(|pa| match pa {
586                object_tree::PropertyAnimation::Static(element) => {
587                    object_tree::PropertyAnimation::Static(
588                        self.create_and_snapshot_element(element),
589                    )
590                }
591                object_tree::PropertyAnimation::Transition { state_ref, animations } => {
592                    object_tree::PropertyAnimation::Transition {
593                        state_ref: self.snapshot_expression(state_ref),
594                        animations: animations
595                            .iter()
596                            .map(|tpa| object_tree::TransitionPropertyAnimation {
597                                state_id: tpa.state_id,
598                                direction: tpa.direction,
599                                animation: self.create_and_snapshot_element(&tpa.animation),
600                            })
601                            .collect(),
602                    }
603                }
604            }),
605            analysis: binding_expression.analysis.as_ref().map(|a| {
606                expression_tree::BindingAnalysis {
607                    is_in_binding_loop: a.is_in_binding_loop.clone(),
608                    is_const: a.is_const,
609                    no_external_dependencies: a.no_external_dependencies,
610                }
611            }),
612            two_way_bindings: binding_expression
613                .two_way_bindings
614                .iter()
615                .map(|twb| match twb {
616                    crate::expression_tree::TwoWayBinding::Property { property, field_access } => {
617                        crate::expression_tree::TwoWayBinding::Property {
618                            property: property.snapshot(self),
619                            field_access: field_access.clone(),
620                        }
621                    }
622                    crate::expression_tree::TwoWayBinding::ModelData {
623                        repeated_element,
624                        field_access,
625                    } => crate::expression_tree::TwoWayBinding::ModelData {
626                        repeated_element: repeated_element.clone(),
627                        field_access: field_access.clone(),
628                    },
629                })
630                .collect(),
631        }
632    }
633
634    pub(crate) fn snapshot_element_type(
635        &mut self,
636        element_type: &langtype::ElementType,
637    ) -> langtype::ElementType {
638        // Components need to get adapted, the rest is fine I think...
639        match element_type {
640            langtype::ElementType::Component(component) => {
641                // Some components that will get compiled out later...
642                langtype::ElementType::Component(
643                    Weak::upgrade(&self.use_component(component))
644                        .expect("I can unwrap at this point"),
645                )
646            }
647            _ => element_type.clone(),
648        }
649    }
650
651    fn snapshot_used_sub_types(
652        &mut self,
653        used_types: &object_tree::UsedSubTypes,
654    ) -> object_tree::UsedSubTypes {
655        let globals = used_types
656            .globals
657            .iter()
658            .map(|component| {
659                Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
660            })
661            .collect();
662        let structs_and_enums = used_types.structs_and_enums.clone();
663        let sub_components = used_types
664            .sub_components
665            .iter()
666            .map(|component| {
667                Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
668            })
669            .collect();
670        let library_types_imports = used_types.library_types_imports.clone();
671        let library_global_imports = used_types.library_global_imports.clone();
672        object_tree::UsedSubTypes {
673            globals,
674            structs_and_enums,
675            sub_components,
676            library_types_imports,
677            library_global_imports,
678            deprecated_type_aliases: Vec::new(),
679            collision_renamed_names: Default::default(),
680        }
681    }
682
683    fn snapshot_popup_window(
684        &mut self,
685        popup_window: &object_tree::PopupWindow,
686    ) -> object_tree::PopupWindow {
687        object_tree::PopupWindow {
688            component: Weak::upgrade(&self.use_component(&popup_window.component))
689                .expect("Looking at a known component"),
690            x: popup_window.x.snapshot(self),
691            y: popup_window.y.snapshot(self),
692            close_policy: popup_window.close_policy.clone(),
693            parent_element: self.use_element(&popup_window.parent_element),
694            is_tooltip: popup_window.is_tooltip,
695            is_open: popup_window.is_open.as_ref().map(|is_open| is_open.snapshot(self)),
696        }
697    }
698
699    fn snapshot_timer(&mut self, timer: &object_tree::Timer) -> object_tree::Timer {
700        object_tree::Timer {
701            interval: timer.interval.snapshot(self),
702            running: timer.running.snapshot(self),
703            triggered: timer.triggered.snapshot(self),
704            element: timer.element.clone(),
705        }
706    }
707
708    fn snapshot_layout_constraints(
709        &mut self,
710        layout_constraints: &layout::LayoutConstraints,
711    ) -> layout::LayoutConstraints {
712        layout::LayoutConstraints {
713            min_width: layout_constraints.min_width.as_ref().map(|lc| lc.snapshot(self)),
714            max_width: layout_constraints.max_width.as_ref().map(|lc| lc.snapshot(self)),
715            min_height: layout_constraints.min_height.as_ref().map(|lc| lc.snapshot(self)),
716            max_height: layout_constraints.max_height.as_ref().map(|lc| lc.snapshot(self)),
717            preferred_width: layout_constraints
718                .preferred_width
719                .as_ref()
720                .map(|lc| lc.snapshot(self)),
721            preferred_height: layout_constraints
722                .preferred_height
723                .as_ref()
724                .map(|lc| lc.snapshot(self)),
725            horizontal_stretch: layout_constraints
726                .horizontal_stretch
727                .as_ref()
728                .map(|lc| lc.snapshot(self)),
729            vertical_stretch: layout_constraints
730                .vertical_stretch
731                .as_ref()
732                .map(|lc| lc.snapshot(self)),
733            fixed_width: layout_constraints.fixed_width,
734            fixed_height: layout_constraints.fixed_height,
735            local: layout_constraints.local.clone(),
736        }
737    }
738
739    fn snapshot_expression(
740        &mut self,
741        expr: &expression_tree::Expression,
742    ) -> expression_tree::Expression {
743        use expression_tree::Expression;
744        match expr {
745            Expression::PropertyReference(nr) => Expression::PropertyReference(nr.snapshot(self)),
746            Expression::ElementReference(el) => {
747                Expression::ElementReference(if let Some(el) = el.upgrade() {
748                    Rc::downgrade(&el)
749                } else {
750                    Weak::default()
751                })
752            }
753            Expression::RepeaterIndexReference { element } => Expression::RepeaterIndexReference {
754                element: if let Some(el) = element.upgrade() {
755                    Rc::downgrade(&el)
756                } else {
757                    Weak::default()
758                },
759            },
760            Expression::RepeaterModelReference { element } => Expression::RepeaterModelReference {
761                element: if let Some(el) = element.upgrade() {
762                    Rc::downgrade(&el)
763                } else {
764                    Weak::default()
765                },
766            },
767            Expression::StoreLocalVariable { name, value } => Expression::StoreLocalVariable {
768                name: name.clone(),
769                value: Box::new(self.snapshot_expression(value)),
770            },
771            Expression::StructFieldAccess { base, name } => Expression::StructFieldAccess {
772                base: Box::new(self.snapshot_expression(base)),
773                name: name.clone(),
774            },
775            Expression::ArrayIndex { array, index } => Expression::ArrayIndex {
776                array: Box::new(self.snapshot_expression(array)),
777                index: Box::new(self.snapshot_expression(index)),
778            },
779            Expression::Cast { from, to } => {
780                Expression::Cast { from: Box::new(self.snapshot_expression(from)), to: to.clone() }
781            }
782            Expression::CodeBlock(exprs) => {
783                Expression::CodeBlock(exprs.iter().map(|e| self.snapshot_expression(e)).collect())
784            }
785            Expression::FunctionCall { function, arguments, source_location } => {
786                Expression::FunctionCall {
787                    function: match function {
788                        Callable::Callback(nr) => Callable::Callback(nr.snapshot(self)),
789                        Callable::Function(nr) => Callable::Function(nr.snapshot(self)),
790                        Callable::Builtin(b) => Callable::Builtin(b.clone()),
791                    },
792                    arguments: arguments.iter().map(|e| self.snapshot_expression(e)).collect(),
793                    source_location: source_location.clone(),
794                }
795            }
796            Expression::SelfAssignment { lhs, rhs, op, node } => Expression::SelfAssignment {
797                lhs: Box::new(self.snapshot_expression(lhs)),
798                rhs: Box::new(self.snapshot_expression(rhs)),
799                op: *op,
800                node: node.clone(),
801            },
802            Expression::BinaryExpression { lhs, rhs, op, .. } => Expression::BinaryExpression {
803                lhs: Box::new(self.snapshot_expression(lhs)),
804                rhs: Box::new(self.snapshot_expression(rhs)),
805                op: *op,
806                source_location: None,
807            },
808            Expression::UnaryOp { sub, op } => {
809                Expression::UnaryOp { sub: Box::new(self.snapshot_expression(sub)), op: *op }
810            }
811            Expression::Condition { condition, true_expr, false_expr, .. } => {
812                Expression::Condition {
813                    condition: Box::new(self.snapshot_expression(condition)),
814                    true_expr: Box::new(self.snapshot_expression(true_expr)),
815                    false_expr: Box::new(self.snapshot_expression(false_expr)),
816                    source_location: None,
817                }
818            }
819            Expression::Array { element_ty, values } => Expression::Array {
820                element_ty: element_ty.clone(),
821                values: values.iter().map(|e| self.snapshot_expression(e)).collect(),
822            },
823            Expression::Struct { ty, values } => Expression::Struct {
824                ty: ty.clone(),
825                values: values
826                    .iter()
827                    .map(|(k, v)| (k.clone(), self.snapshot_expression(v)))
828                    .collect(),
829            },
830            Expression::PathData(path) => Expression::PathData(match path {
831                expression_tree::Path::Elements(path_elements) => expression_tree::Path::Elements(
832                    path_elements
833                        .iter()
834                        .map(|p| {
835                            expression_tree::PathElement {
836                                element_type: p.element_type.clone(), // builtin should be OK to clone
837                                bindings: p
838                                    .bindings
839                                    .iter()
840                                    .map(|(k, v)| {
841                                        (
842                                            k.clone(),
843                                            RefCell::new(
844                                                self.snapshot_binding_expression(&v.borrow()),
845                                            ),
846                                        )
847                                    })
848                                    .collect(),
849                            }
850                        })
851                        .collect(),
852                ),
853                expression_tree::Path::Events(ex1, ex2) => expression_tree::Path::Events(
854                    ex1.iter().map(|e| self.snapshot_expression(e)).collect(),
855                    ex2.iter().map(|e| self.snapshot_expression(e)).collect(),
856                ),
857                expression_tree::Path::Commands(ex) => {
858                    expression_tree::Path::Commands(Box::new(self.snapshot_expression(ex)))
859                }
860            }),
861            Expression::LinearGradient { angle, stops } => Expression::LinearGradient {
862                angle: Box::new(self.snapshot_expression(angle)),
863                stops: stops
864                    .iter()
865                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
866                    .collect(),
867            },
868            Expression::RadialGradient { center, radius, stops } => Expression::RadialGradient {
869                center: center.as_ref().map(|(cx, cy)| {
870                    (Box::new(self.snapshot_expression(cx)), Box::new(self.snapshot_expression(cy)))
871                }),
872                radius: radius.as_ref().map(|r| Box::new(self.snapshot_expression(r))),
873                stops: stops
874                    .iter()
875                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
876                    .collect(),
877            },
878            Expression::ConicGradient { from_angle, center, stops } => Expression::ConicGradient {
879                from_angle: Box::new(self.snapshot_expression(from_angle)),
880                center: center.as_ref().map(|(cx, cy)| {
881                    (Box::new(self.snapshot_expression(cx)), Box::new(self.snapshot_expression(cy)))
882                }),
883                stops: stops
884                    .iter()
885                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
886                    .collect(),
887            },
888            Expression::ReturnStatement(expr) => Expression::ReturnStatement(
889                expr.as_ref().map(|e| Box::new(self.snapshot_expression(e))),
890            ),
891            Expression::LayoutCacheAccess {
892                layout_cache_prop,
893                index,
894                repeater_index,
895                entries_per_item,
896            } => Expression::LayoutCacheAccess {
897                layout_cache_prop: layout_cache_prop.snapshot(self),
898                index: *index,
899                repeater_index: repeater_index
900                    .as_ref()
901                    .map(|e| Box::new(self.snapshot_expression(e))),
902                entries_per_item: *entries_per_item,
903            },
904            Expression::GridRepeaterCacheAccess {
905                layout_cache_prop,
906                index,
907                repeater_index,
908                stride,
909                child_offset,
910                inner_repeater_index,
911                entries_per_item,
912            } => Expression::GridRepeaterCacheAccess {
913                layout_cache_prop: layout_cache_prop.snapshot(self),
914                index: *index,
915                repeater_index: Box::new(self.snapshot_expression(repeater_index)),
916                stride: Box::new(self.snapshot_expression(stride)),
917                child_offset: *child_offset,
918                inner_repeater_index: inner_repeater_index
919                    .as_ref()
920                    .map(|e| Box::new(self.snapshot_expression(e))),
921                entries_per_item: *entries_per_item,
922            },
923            Expression::MinMax { ty, op, lhs, rhs } => Expression::MinMax {
924                ty: ty.clone(),
925                lhs: Box::new(self.snapshot_expression(lhs)),
926                rhs: Box::new(self.snapshot_expression(rhs)),
927                op: *op,
928            },
929            _ => expr.clone(),
930        }
931    }
932}
933
934pub struct TypeLoader {
935    pub global_type_registry: Rc<RefCell<TypeRegister>>,
936    pub compiler_config: CompilerConfiguration,
937    /// The style that was specified in the compiler configuration, but resolved. So "native" for example is resolved to the concrete
938    /// style.
939    pub resolved_style: String,
940    /// The revision in the TypeLoader marks changes to the TypeLoader.
941    /// Any changes should increase the revision number via [Self::bump_revision]
942    revision: u64,
943    all_documents: LoadedDocuments,
944    /// Counters for the deterministic unique symbol names generated by the
945    /// passes. Shared across all documents of the compilation so the names stay
946    /// unique even after inlining merges components from different documents.
947    pub symbol_counters: Rc<crate::symbol_counters::SymbolCounters>,
948}
949
950struct BorrowedTypeLoader<'a> {
951    tl: &'a mut TypeLoader,
952    diag: &'a mut BuildDiagnostics,
953}
954
955impl TypeLoader {
956    pub fn new(compiler_config: CompilerConfiguration, diag: &mut BuildDiagnostics) -> Self {
957        let mut style = compiler_config.style.clone().unwrap_or_else(|| "fluent".into());
958
959        if style == "native" {
960            style = get_native_style(&mut diag.all_loaded_files);
961        }
962
963        let symbol_counters = crate::symbol_counters::SymbolCounters::shared();
964        let myself = Self {
965            global_type_registry: if compiler_config.enable_experimental {
966                crate::typeregister::TypeRegister::builtin_experimental()
967            } else {
968                crate::typeregister::TypeRegister::builtin()
969            },
970            compiler_config,
971            resolved_style: style.clone(),
972            revision: 0,
973            all_documents: Default::default(),
974            symbol_counters,
975        };
976
977        let mut known_styles = fileaccess::styles();
978        known_styles.push("native");
979        if !known_styles.contains(&style.as_ref())
980            && myself
981                .find_file_in_include_path(None, &format!("{style}/std-widgets.slint"))
982                .is_none()
983        {
984            diag.push_diagnostic_with_span(
985                format!(
986                    "Style {} is not known. Use one of the builtin styles [{}] or make sure your custom style is found in the include directories",
987                    style,
988                    known_styles.join(", ")
989                ),
990                Default::default(),
991                crate::diagnostics::DiagnosticLevel::Error,
992            );
993        }
994
995        myself
996    }
997
998    fn bump_revision(&mut self) {
999        self.revision = self.revision.wrapping_add(1);
1000    }
1001
1002    pub fn revision(&self) -> u64 {
1003        self.revision
1004    }
1005
1006    /// Drop a document from the TypeLoader and invalidate all of its dependencies.
1007    /// Returns the list of all (transitive) dependencies.
1008    ///
1009    /// This forces the compiler to entirely reload the document from scratch.
1010    /// To only cause a re-analyze, but not a reparse, use [Self::invalidate_document]
1011    pub fn drop_document(&mut self, path: &Path) -> Result<HashSet<PathBuf>, std::io::Error> {
1012        let dependencies = self.invalidate_document(path);
1013        self.all_documents.docs.remove(path);
1014        self.bump_revision();
1015
1016        if self.all_documents.currently_loading.contains_key(path) {
1017            Err(std::io::Error::new(ErrorKind::InvalidInput, format!("{path:?} is still loading")))
1018        } else {
1019            Ok(dependencies)
1020        }
1021    }
1022
1023    /// Invalidate a document and all its dependencies.
1024    ///
1025    /// This will keep the CST of the document in cache, but mark that it needs to be re-analyzed
1026    /// to reconstruct its types.
1027    ///
1028    /// To entirely forget a document and cause a complete re-parse, use [Self::drop_document].
1029    pub fn invalidate_document(&mut self, path: &Path) -> HashSet<PathBuf> {
1030        if let Some((d, _)) = self.all_documents.docs.get_mut(path) {
1031            if let LoadedDocument::Document(doc) = d {
1032                for import in &doc.imports {
1033                    self.all_documents
1034                        .dependencies
1035                        .entry(Path::new(&import.file).into())
1036                        .or_default()
1037                        .remove(path);
1038                }
1039                match doc.node.take() {
1040                    None => {
1041                        self.all_documents.docs.remove(path);
1042                    }
1043                    Some(n) => {
1044                        *d = LoadedDocument::Invalidated(n);
1045                    }
1046                };
1047            } else {
1048                return HashSet::new();
1049            }
1050        } else {
1051            // If a document is not in the TypeLoader, it may still have dependencies,
1052            // as another document may have tried to import it, but it failed (e.g. the file didn't exist).
1053            // So still invalidate all dependencies, even if the file is not in the TypeLoader.
1054            // (Fallthrough)
1055        }
1056        let deps = self.all_documents.dependencies.remove(path).unwrap_or_default();
1057        let mut extra_deps = HashSet::new();
1058        for dep in &deps {
1059            extra_deps.extend(self.invalidate_document(dep));
1060        }
1061        extra_deps.extend(deps);
1062        self.bump_revision();
1063        extra_deps
1064    }
1065
1066    /// Imports of files that don't have the .slint extension are returned.
1067    pub async fn load_dependencies_recursively<'a>(
1068        &'a mut self,
1069        doc: &'a syntax_nodes::Document,
1070        diag: &'a mut BuildDiagnostics,
1071        registry_to_populate: &'a Rc<RefCell<TypeRegister>>,
1072    ) -> (Vec<ImportedTypes>, Exports) {
1073        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1074        Self::load_dependencies_recursively_impl(
1075            &state,
1076            doc,
1077            registry_to_populate,
1078            &Default::default(),
1079        )
1080        .await
1081    }
1082
1083    async fn load_dependencies_recursively_impl<'a: 'b, 'b>(
1084        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1085        doc: &'b syntax_nodes::Document,
1086        registry_to_populate: &'b Rc<RefCell<TypeRegister>>,
1087        import_stack: &'b HashSet<PathBuf>,
1088    ) -> (Vec<ImportedTypes>, Exports) {
1089        let mut imports = Vec::new();
1090        let mut dependencies_futures = Vec::new();
1091        for mut import in Self::collect_dependencies(state, doc) {
1092            // The embedded files import each other by that path, so only a
1093            // document outside them is rejected.
1094            if import.file.starts_with("builtin:")
1095                && !import.import_uri_token.source_file.path().starts_with("builtin:")
1096            {
1097                state.borrow_mut().diag.push_error(
1098                    format!(
1099                        "Cannot import \"{}\": the files built into the compiler are internal. Import the widgets from \"std-widgets.slint\"",
1100                        import.file
1101                    ),
1102                    &import.import_uri_token,
1103                );
1104                continue;
1105            }
1106
1107            // The path shapes that don't resolve relative to the importing
1108            // file. Rejecting them here, before any search path is consulted,
1109            // keeps the Slint SC error the only diagnostic and leaves the
1110            // named file unread. No builtin file imports this way, so skipping
1111            // the load can't leave a builtin document half-loaded.
1112            #[cfg(feature = "slint-sc")]
1113            if state.borrow().diag.slint_sc {
1114                let rejected = if import.file.starts_with('@') {
1115                    Some("Library imports are")
1116                } else if crate::pathutils::is_absolute(Path::new(import.file.as_str())) {
1117                    Some("Absolute import paths are")
1118                } else {
1119                    None
1120                };
1121                if let Some(feature) = rejected {
1122                    state.borrow_mut().diag.slint_sc_error(feature, &import.import_uri_token);
1123                    continue;
1124                }
1125            }
1126
1127            if matches!(import.import_kind, ImportKind::FileImport) {
1128                if let Some((path, _)) = state.borrow().tl.resolve_import_path(
1129                    Some(&import.import_uri_token.clone().into()),
1130                    &import.file,
1131                ) {
1132                    import.file = path.to_string_lossy().into_owned();
1133                };
1134                imports.push(import);
1135                continue;
1136            }
1137
1138            dependencies_futures.push(Box::pin(async move {
1139                #[cfg(feature = "experimental-library-module")]
1140                let import_file = import.file.clone();
1141                #[cfg(feature = "experimental-library-module")]
1142                if let Some(maybe_library_import) = import_file.strip_prefix('@')
1143                    && let Ok(library_name) = std::env::var(format!(
1144                        "DEP_{}_SLINT_LIBRARY_NAME",
1145                        maybe_library_import.to_uppercase()
1146                    ))
1147                    && library_name == maybe_library_import
1148                {
1149                    let library_slint_source = std::env::var(format!(
1150                        "DEP_{}_SLINT_LIBRARY_SOURCE",
1151                        maybe_library_import.to_uppercase()
1152                    ))
1153                    .unwrap_or_default();
1154
1155                    import.file = library_slint_source;
1156
1157                    if let Ok(library_package) = std::env::var(format!(
1158                        "DEP_{}_SLINT_LIBRARY_PACKAGE",
1159                        maybe_library_import.to_uppercase()
1160                    )) {
1161                        import.library_info = Some(LibraryInfo {
1162                            name: library_name,
1163                            package: library_package,
1164                            module: std::env::var(format!(
1165                                "DEP_{}_SLINT_LIBRARY_MODULE",
1166                                maybe_library_import.to_uppercase()
1167                            ))
1168                            .ok(),
1169                            exports: Vec::new(),
1170                        });
1171                    } else {
1172                        // This should never happen
1173                        let mut state = state.borrow_mut();
1174                        state.diag.push_error(
1175                            format!(
1176                                "DEP_{}_SLINT_LIBRARY_PACKAGE is missing for external library import",
1177                                maybe_library_import.to_uppercase()
1178                            ),
1179                            &import.import_uri_token.parent(),
1180                        );
1181                    }
1182                }
1183
1184                let doc_path = Self::ensure_document_loaded(
1185                    state,
1186                    import.file.as_str(),
1187                    Some(import.import_uri_token.clone().into()),
1188                    import_stack.clone(),
1189                )
1190                .await;
1191                (import, doc_path)
1192            }));
1193        }
1194
1195        let mut reexports = None;
1196        let mut has_star_reexport = false;
1197        std::future::poll_fn(|cx| {
1198            dependencies_futures.retain_mut(|fut| {
1199                let core::task::Poll::Ready((mut import, doc_path)) = fut.as_mut().poll(cx) else { return true; };
1200                let doc_path = match doc_path {
1201                    Ok(doc_path) => doc_path,
1202                    Err(Some(doc_path)) => {
1203                        // Even if the import failed (e.g. the file doesn't exist), we need to add it to the document imports so that
1204                        // the dependency graph is correct and we can retry loading the document if the imported file changes or is created.
1205                        import.file = doc_path.to_string_lossy().into_owned();
1206                        imports.push(import);
1207
1208                        return false;
1209                    }
1210                    Err(None) => return false,
1211                };
1212                let mut state = state.borrow_mut();
1213                let state: &mut BorrowedTypeLoader<'a> = &mut state;
1214                let Some(doc) = state.tl.get_document(&doc_path) else {
1215                    panic!("Just loaded document not available")
1216                };
1217
1218                // The widget library and the styles are built into the
1219                // compiler and aren't part of the subset. This catches the
1220                // "std-widgets.slint" spelling, which only becomes a builtin
1221                // path here; naming the embedded path is rejected earlier, for
1222                // every mode. Their own imports reach this too, but the error
1223                // is suppressed for a builtin referencing file.
1224                #[cfg(feature = "slint-sc")]
1225                if doc_path.starts_with("builtin:") {
1226                    state.diag.slint_sc_error(
1227                        &format!("Importing the builtin file '{}' is", import.file),
1228                        &import.import_uri_token,
1229                    );
1230                }
1231
1232                match &import.import_kind {
1233                    ImportKind::ImportList(imported_types) => {
1234                        let mut imported_types = ImportedName::extract_imported_names(imported_types).peekable();
1235                        if imported_types.peek().is_some() {
1236                            Self::register_imported_types(doc, &import, imported_types, registry_to_populate, state.diag);
1237
1238                            #[cfg(feature = "experimental-library-module")]
1239                            if let Some(library_info) = import.library_info.as_mut() {
1240                                library_info.exports =
1241                                    doc.exports.iter().map(|(exported_name, _compo_or_type)| {
1242                                        exported_name.clone()
1243                                    }).collect();
1244                            }
1245                        } else {
1246                            state.diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
1247                        }
1248                    }
1249                    ImportKind::ModuleReexport(export_module_syntax_node) => {
1250                        let exports = reexports.get_or_insert_with(Exports::default);
1251                        if let Some(star_reexport) = export_module_syntax_node.ExportModule().and_then(|x| x.child_token(SyntaxKind::Star))
1252                        {
1253                            if has_star_reexport {
1254                                state.diag.push_error("re-exporting modules is only allowed once per file".into(), &star_reexport);
1255                                return false;
1256                            }
1257                            has_star_reexport = true;
1258                            exports.add_reexports(
1259                                doc.exports.iter().map(|(exported_name, compo_or_type)| {
1260                                    let exported_name = ExportedName {
1261                                        name: exported_name.name.clone(),
1262                                        name_ident: (**export_module_syntax_node).clone(),
1263                                    };
1264                                    (exported_name, compo_or_type.clone())
1265                                }),
1266                                state.diag,
1267                            );
1268                        } else if export_module_syntax_node.ExportSpecifier().next().is_none() {
1269                            state.diag.push_error("Import names are missing. Please specify which types you would like to re-export".into(), export_module_syntax_node);
1270                        } else {
1271                            let e = export_module_syntax_node
1272                                .ExportSpecifier()
1273                                .filter_map(|e| {
1274                                    let (imported_name, exported_name) = ExportedName::from_export_specifier(&e);
1275                                    let Some(r) = doc.exports.find(&imported_name) else {
1276                                        state.diag.push_error(format!("No exported type called '{imported_name}' found in \"{}\"", doc_path.display()), &e);
1277                                        return None;
1278                                    };
1279                                    Some((exported_name, r))
1280                                })
1281                                .collect::<Vec<_>>();
1282                            exports.add_reexports(e, state.diag);
1283                        }
1284                    }
1285                    ImportKind::FileImport => {
1286                        unreachable!("FileImport should have been handled above")
1287                    }
1288                }
1289                import.file = doc_path.to_string_lossy().into_owned();
1290                imports.push(import);
1291                false
1292            });
1293            if dependencies_futures.is_empty() {
1294                core::task::Poll::Ready(())
1295            } else {
1296                core::task::Poll::Pending
1297            }
1298        }).await;
1299        (imports, reexports.unwrap_or_default())
1300    }
1301
1302    pub async fn import_component(
1303        &mut self,
1304        file_to_import: &str,
1305        type_name: &str,
1306        diag: &mut BuildDiagnostics,
1307    ) -> Option<Rc<object_tree::Component>> {
1308        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1309        let doc_path =
1310            match Self::ensure_document_loaded(&state, file_to_import, None, Default::default())
1311                .await
1312            {
1313                Ok(doc_path) => doc_path,
1314                Err(_) => return None,
1315            };
1316
1317        let Some(doc) = self.get_document(&doc_path) else {
1318            panic!("Just loaded document not available")
1319        };
1320
1321        doc.exports.find(type_name).and_then(|compo_or_type| compo_or_type.left())
1322    }
1323
1324    /// Append a possibly relative path to a base path. Returns the data if it resolves to a built-in (compiled-in)
1325    /// file.
1326    pub fn resolve_import_path(
1327        &self,
1328        import_token: Option<&NodeOrToken>,
1329        maybe_relative_path_or_url: &str,
1330    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1331        if let Some(maybe_library_import) = maybe_relative_path_or_url.strip_prefix('@') {
1332            self.find_file_in_library_path(maybe_library_import)
1333        } else {
1334            let referencing_file_or_url =
1335                import_token.and_then(|tok| tok.source_file().map(|s| s.path()));
1336            self.find_file_in_include_path(referencing_file_or_url, maybe_relative_path_or_url)
1337                .or_else(|| {
1338                    referencing_file_or_url
1339                        .and_then(|base_path_or_url| {
1340                            crate::pathutils::join(
1341                                &crate::pathutils::dirname(base_path_or_url),
1342                                &PathBuf::from(maybe_relative_path_or_url),
1343                            )
1344                        })
1345                        .filter(|p| p.exists())
1346                        .map(|p| (p, None))
1347                })
1348        }
1349    }
1350
1351    /// Returns whether the file was successfully loaded.
1352    /// If not, the path that was attempted to be loaded is returned (if any).
1353    #[allow(clippy::await_holding_refcell_ref)] // false positive: explicit drop() before await
1354    async fn ensure_document_loaded<'a: 'b, 'b>(
1355        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1356        file_to_import: &'b str,
1357        import_token: Option<NodeOrToken>,
1358        mut import_stack: HashSet<PathBuf>,
1359    ) -> Result<PathBuf, Option<PathBuf>> {
1360        let mut borrowed_state = state.borrow_mut();
1361
1362        let mut resolved = false;
1363        let (path_canon, builtin) = match borrowed_state
1364            .tl
1365            .resolve_import_path(import_token.as_ref(), file_to_import)
1366        {
1367            Some(x) => {
1368                resolved = true;
1369                if let Some(file_name) = x.0.file_name().and_then(|f| f.to_str()) {
1370                    let len = file_to_import.len();
1371                    if !file_to_import.ends_with(file_name)
1372                        && len >= file_name.len()
1373                        && file_name.eq_ignore_ascii_case(
1374                            file_to_import.get(len - file_name.len()..).unwrap_or(""),
1375                        )
1376                        && import_token.as_ref().and_then(|x| x.source_file()).is_some()
1377                    {
1378                        borrowed_state.diag.push_warning(
1379                                format!("Loading \"{file_to_import}\" resolved to a file named \"{file_name}\" with different casing. This behavior is not cross platform. Rename the file, or edit the import to use the same casing"),
1380                                &import_token,
1381                            );
1382                    }
1383                }
1384                x
1385            }
1386            None => {
1387                let import_path = crate::pathutils::clean_path(Path::new(file_to_import));
1388                if import_path.exists() {
1389                    if import_token.as_ref().and_then(|x| x.source_file()).is_some() {
1390                        borrowed_state.diag.push_warning(
1391                        format!(
1392                            "Loading \"{file_to_import}\" relative to the work directory is deprecated. Files should be imported relative to their import location",
1393                        ),
1394                        &import_token,
1395                    );
1396                    }
1397                    (import_path, None)
1398                } else {
1399                    // We will load using the `open_import_callback`
1400                    // Simplify the path to remove the ".."
1401                    let base_path = import_token
1402                        .as_ref()
1403                        .and_then(|tok| tok.source_file().map(|s| s.path()))
1404                        .map_or(PathBuf::new(), |p| p.into());
1405                    let path = crate::pathutils::join(
1406                        &crate::pathutils::dirname(&base_path),
1407                        Path::new(file_to_import),
1408                    )
1409                    .ok_or(None)?;
1410                    (path, None)
1411                }
1412            }
1413        };
1414
1415        if !import_stack.insert(path_canon.clone()) {
1416            borrowed_state.diag.push_error(
1417                format!("Recursive import of \"{}\"", path_canon.display()),
1418                &import_token,
1419            );
1420            return Err(Some(path_canon));
1421        }
1422
1423        drop(borrowed_state);
1424
1425        let (is_loaded, doc_node) = core::future::poll_fn(|cx| {
1426            let mut state = state.borrow_mut();
1427            let all_documents = &mut state.tl.all_documents;
1428            match all_documents.currently_loading.entry(path_canon.clone()) {
1429                std::collections::hash_map::Entry::Occupied(mut e) => {
1430                    let waker = cx.waker();
1431                    if !e.get().iter().any(|w| w.will_wake(waker)) {
1432                        e.get_mut().push(cx.waker().clone());
1433                    }
1434                    core::task::Poll::Pending
1435                }
1436                std::collections::hash_map::Entry::Vacant(v) => {
1437                    match all_documents.docs.get(path_canon.as_path()) {
1438                        Some((LoadedDocument::Document(_), _)) => {
1439                            core::task::Poll::Ready((true, None))
1440                        }
1441                        Some((LoadedDocument::Invalidated(doc), errors)) => {
1442                            v.insert(Default::default());
1443                            core::task::Poll::Ready((false, Some((doc.clone(), errors.clone()))))
1444                        }
1445                        None => {
1446                            v.insert(Default::default());
1447                            core::task::Poll::Ready((false, None))
1448                        }
1449                    }
1450                }
1451            }
1452        })
1453        .await;
1454        if is_loaded {
1455            return Ok(path_canon);
1456        }
1457
1458        let doc_node = if let Some((doc_node, errors)) = doc_node {
1459            for e in errors {
1460                state.borrow_mut().diag.push_internal_error(e);
1461            }
1462            Some(doc_node)
1463        } else {
1464            let source_code_result = if let Some(builtin) = builtin {
1465                Ok(String::from(
1466                    core::str::from_utf8(builtin)
1467                        .expect("internal error: embedded file is not UTF-8 source code"),
1468                ))
1469            } else {
1470                let callback = state.borrow().tl.compiler_config.open_import_callback.clone();
1471                if let Some(callback) = callback {
1472                    let result = callback(path_canon.to_string_lossy().into()).await;
1473                    result.unwrap_or_else(|| std::fs::read_to_string(&path_canon))
1474                } else {
1475                    std::fs::read_to_string(&path_canon)
1476                }
1477            };
1478            match source_code_result {
1479                Ok(source) => syntax_nodes::Document::new(crate::parser::parse(
1480                    source,
1481                    Some(&path_canon),
1482                    state.borrow_mut().diag,
1483                )),
1484                Err(err)
1485                    if !resolved
1486                        && matches!(
1487                            err.kind(),
1488                            // A path that can't name a file (e.g. one with a character
1489                            // Windows forbids) can't be found either, so report it the
1490                            // same way rather than leaking the raw OS error.
1491                            ErrorKind::NotFound
1492                                | ErrorKind::NotADirectory
1493                                | ErrorKind::InvalidFilename
1494                        ) =>
1495                {
1496                    let import_kind =
1497                        if file_to_import.starts_with('@') { "library" } else { "include" };
1498                    state.borrow_mut().diag.push_error(
1499                        format!(
1500                            "Cannot find requested import \"{file_to_import}\" in the {import_kind} search path",
1501                        ),
1502                        &import_token,
1503                    );
1504                    None
1505                }
1506                Err(err) => {
1507                    state.borrow_mut().diag.push_error(
1508                        format!(
1509                            "Error reading requested import \"{}\": {}",
1510                            path_canon.display(),
1511                            err
1512                        ),
1513                        &import_token,
1514                    );
1515                    None
1516                }
1517            }
1518        };
1519
1520        let ok = if let Some(doc_node) = doc_node {
1521            Self::load_file_impl(state, &path_canon, doc_node, builtin.is_some(), &import_stack)
1522                .await;
1523            state.borrow_mut().diag.all_loaded_files.insert(path_canon.clone());
1524            true
1525        } else {
1526            false
1527        };
1528
1529        let wakers = state
1530            .borrow_mut()
1531            .tl
1532            .all_documents
1533            .currently_loading
1534            .remove(path_canon.as_path())
1535            .unwrap();
1536        for x in wakers {
1537            x.wake();
1538        }
1539
1540        if ok { Ok(path_canon) } else { Err(Some(path_canon)) }
1541    }
1542
1543    /// Load a file, and its dependency, running only the import passes.
1544    ///
1545    /// the path must be the canonical path
1546    pub async fn load_file(
1547        &mut self,
1548        path: &Path,
1549        source_path: &Path,
1550        source_code: String,
1551        is_builtin: bool,
1552        diag: &mut BuildDiagnostics,
1553    ) {
1554        let doc_node: syntax_nodes::Document =
1555            crate::parser::parse(source_code, Some(source_path), diag).into();
1556        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1557        Self::load_file_impl(&state, path, doc_node, is_builtin, &Default::default()).await;
1558    }
1559
1560    /// Reload a cached file
1561    ///
1562    /// The path must be canonical
1563    pub async fn reload_cached_file(&mut self, path: &Path, diag: &mut BuildDiagnostics) {
1564        let Some((LoadedDocument::Invalidated(doc_node), errors)) =
1565            self.all_documents.docs.get(path)
1566        else {
1567            return;
1568        };
1569        let doc_node = doc_node.clone();
1570        for e in errors {
1571            diag.push_internal_error(e.clone());
1572        }
1573        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1574        Self::load_file_impl(&state, path, doc_node, false, &Default::default()).await;
1575    }
1576
1577    /// Load a file, and its dependency, running the full set of passes.
1578    ///
1579    /// the path must be the canonical path
1580    #[allow(clippy::await_holding_refcell_ref)] // requires mutable typeloader+diag through async pass pipeline
1581    pub async fn load_root_file(
1582        &mut self,
1583        path: &Path,
1584        source_path: &Path,
1585        source_code: String,
1586        keep_raw: bool,
1587        diag: &mut BuildDiagnostics,
1588    ) -> (PathBuf, Option<TypeLoader>) {
1589        let path = crate::pathutils::clean_path(path);
1590        let doc_node: syntax_nodes::Document =
1591            crate::parser::parse(source_code, Some(source_path), diag).into();
1592        let parse_errors = diag.iter().cloned().collect();
1593        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1594        let (path, mut doc) =
1595            Self::load_doc_no_pass(&state, &path, doc_node, false, &Default::default()).await;
1596
1597        let mut state = state.borrow_mut();
1598        let state = &mut *state;
1599        let raw_type_loader = if !state.diag.has_errors() {
1600            crate::passes::run_passes(&mut doc, state.tl, keep_raw, state.diag).await
1601        } else {
1602            None
1603        };
1604        Self::register_document(state, doc, path.clone(), parse_errors);
1605        (path, raw_type_loader)
1606    }
1607
1608    fn register_document(
1609        state: &mut BorrowedTypeLoader<'_>,
1610        doc: Document,
1611        path: PathBuf,
1612        parse_errors: Vec<Diagnostic>,
1613    ) {
1614        for dep in &doc.imports {
1615            state
1616                .tl
1617                .all_documents
1618                .dependencies
1619                .entry(Path::new(&dep.file).into())
1620                .or_default()
1621                .insert(path.clone());
1622        }
1623        state.tl.all_documents.docs.insert(path, (LoadedDocument::Document(doc), parse_errors));
1624        state.tl.bump_revision();
1625    }
1626
1627    async fn load_file_impl<'a>(
1628        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1629        path: &Path,
1630        doc_node: syntax_nodes::Document,
1631        is_builtin: bool,
1632        import_stack: &HashSet<PathBuf>,
1633    ) {
1634        let parse_errors = state
1635            .borrow()
1636            .diag
1637            .iter()
1638            .filter(|e| e.source_file().is_some_and(|f| f == path))
1639            .cloned()
1640            .collect();
1641        let (path, doc) =
1642            Self::load_doc_no_pass(state, path, doc_node, is_builtin, import_stack).await;
1643
1644        let mut state = state.borrow_mut();
1645        let state = &mut *state;
1646        if !state.diag.has_errors() {
1647            crate::passes::run_import_passes(&doc, state.tl, state.diag);
1648        }
1649        Self::register_document(state, doc, path, parse_errors);
1650    }
1651
1652    async fn load_doc_no_pass<'a>(
1653        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1654        path: &Path,
1655        dependency_doc: syntax_nodes::Document,
1656        is_builtin: bool,
1657        import_stack: &HashSet<PathBuf>,
1658    ) -> (PathBuf, Document) {
1659        let dependency_registry =
1660            Rc::new(RefCell::new(TypeRegister::new(&state.borrow().tl.global_type_registry)));
1661        dependency_registry.borrow_mut().expose_internal_types =
1662            is_builtin || state.borrow().tl.compiler_config.enable_experimental;
1663        let (imports, reexports) = Self::load_dependencies_recursively_impl(
1664            state,
1665            &dependency_doc,
1666            &dependency_registry,
1667            import_stack,
1668        )
1669        .await;
1670
1671        let ignore_missing_font_files =
1672            state.borrow().tl.compiler_config.resource_url_mapper.is_some();
1673        let symbol_counters = state.borrow().tl.symbol_counters.clone();
1674        if state.borrow().diag.has_errors() {
1675            // If there was error (esp parse error) we don't want to report further error in this document.
1676            // because they might be nonsense (TODO: we should check that the parse error were really in this document).
1677            // But we still want to create a document to give better error messages in the root document.
1678            let mut ignore_diag = BuildDiagnostics::default();
1679            ignore_diag.push_error_with_span(
1680                "Dummy error because some of the code asserts there was an error".into(),
1681                Default::default(),
1682            );
1683            let doc = crate::object_tree::Document::from_node(
1684                dependency_doc,
1685                imports,
1686                reexports,
1687                &mut ignore_diag,
1688                &dependency_registry,
1689                ignore_missing_font_files,
1690                &symbol_counters,
1691            );
1692            return (path.to_owned(), doc);
1693        }
1694        let mut state = state.borrow_mut();
1695        let state = &mut *state;
1696        let doc = crate::object_tree::Document::from_node(
1697            dependency_doc,
1698            imports,
1699            reexports,
1700            state.diag,
1701            &dependency_registry,
1702            ignore_missing_font_files,
1703            &symbol_counters,
1704        );
1705        (path.to_owned(), doc)
1706    }
1707
1708    fn register_imported_types(
1709        doc: &Document,
1710        import: &ImportedTypes,
1711        imported_types: impl Iterator<Item = ImportedName>,
1712        registry_to_populate: &Rc<RefCell<TypeRegister>>,
1713        build_diagnostics: &mut BuildDiagnostics,
1714    ) {
1715        for import_name in imported_types {
1716            let imported_type = doc.exports.find(&import_name.external_name);
1717
1718            let imported_type = match imported_type {
1719                Some(ty) => ty,
1720                None => {
1721                    build_diagnostics.push_error(
1722                        format!(
1723                            "No exported type called '{}' found in \"{}\"",
1724                            import_name.external_name, import.file
1725                        ),
1726                        &import.import_uri_token,
1727                    );
1728                    continue;
1729                }
1730            };
1731
1732            #[cfg(feature = "slint-sc")]
1733            let internal_name = import_name.internal_name.clone();
1734
1735            #[cfg_attr(not(feature = "slint-sc"), allow(unused_variables))]
1736            let inserted = match imported_type {
1737                itertools::Either::Left(c) => {
1738                    registry_to_populate.borrow_mut().add_with_name(import_name.internal_name, c)
1739                }
1740                itertools::Either::Right(ty) => registry_to_populate
1741                    .borrow_mut()
1742                    .insert_type_with_name(ty, import_name.internal_name),
1743            };
1744
1745            // Regular Slint lets a later import replace an earlier one of the
1746            // same name; Slint SC requires each name to be introduced once.
1747            #[cfg(feature = "slint-sc")]
1748            if !inserted {
1749                build_diagnostics.slint_sc_error(
1750                    &format!("Importing the name '{internal_name}' more than once is"),
1751                    &import.import_uri_token,
1752                );
1753            }
1754        }
1755    }
1756
1757    /// Lookup a library and filename and try to find the absolute filename based on the library path
1758    fn find_file_in_library_path(
1759        &self,
1760        maybe_library_import: &str,
1761    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1762        let (library, file) = maybe_library_import
1763            .splitn(2, '/')
1764            .collect_tuple()
1765            .map(|(library, path)| (library, Some(path)))
1766            .unwrap_or((maybe_library_import, None));
1767        self.compiler_config.library_paths.get(library).and_then(|library_path| {
1768            let path = match file {
1769                // "@library/file.slint" -> "/path/to/library/" + "file.slint"
1770                Some(file) => library_path.join(file),
1771                // "@library" -> "/path/to/library/lib.slint"
1772                None => library_path.clone(),
1773            };
1774            crate::fileaccess::load_file(path.as_path())
1775                .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1776                .or(Some((path, None)))
1777        })
1778    }
1779
1780    /// Lookup a filename and try to find the absolute filename based on the include path or
1781    /// the current file directory
1782    pub fn find_file_in_include_path(
1783        &self,
1784        referencing_file: Option<&Path>,
1785        file_to_import: &str,
1786    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1787        // The directory of the current file is the first in the list of include directories.
1788        referencing_file
1789            .and_then(|x| x.parent().map(|x| x.to_path_buf()))
1790            .into_iter()
1791            .chain(referencing_file.and_then(maybe_base_directory))
1792            .chain(self.compiler_config.include_paths.iter().map(PathBuf::as_path).map(
1793                |include_path| {
1794                    let base = referencing_file.map(Path::to_path_buf).unwrap_or_default();
1795                    crate::pathutils::join(&crate::pathutils::dirname(&base), include_path)
1796                        .unwrap_or_else(|| include_path.to_path_buf())
1797                },
1798            ))
1799            .chain(
1800                (file_to_import == "std-widgets.slint"
1801                    || (file_to_import == "style-base.slint" && referencing_file.is_none())
1802                    || (file_to_import == "std-widgets-impl.slint" && referencing_file.is_none())
1803                    || referencing_file.is_some_and(|x| x.starts_with("builtin:/")))
1804                .then(|| format!("builtin:/{}", self.resolved_style).into()),
1805            )
1806            .find_map(|include_dir| {
1807                let candidate = crate::pathutils::join(&include_dir, Path::new(file_to_import))?;
1808                crate::fileaccess::load_file(&candidate)
1809                    .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1810            })
1811    }
1812
1813    fn collect_dependencies<'a: 'b, 'b>(
1814        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1815        doc: &'b syntax_nodes::Document,
1816    ) -> impl Iterator<Item = ImportedTypes> + 'a {
1817        doc.ImportSpecifier()
1818            .map(|import| {
1819                let maybe_import_uri = import.child_token(SyntaxKind::StringLiteral);
1820
1821                let kind = import
1822                    .ImportIdentifierList()
1823                    .map(ImportKind::ImportList)
1824                    .unwrap_or(ImportKind::FileImport);
1825                (maybe_import_uri, kind)
1826            })
1827            .chain(
1828                // process `export ... from "foo"`
1829                doc.ExportsList().filter_map(|exports| {
1830                    exports.ExportModule().map(|reexport| {
1831                        let maybe_import_uri = reexport.child_token(SyntaxKind::StringLiteral);
1832                        (maybe_import_uri, ImportKind::ModuleReexport(exports))
1833                    })
1834                }),
1835            )
1836            .filter_map(|(maybe_import_uri, type_specifier)| {
1837                let import_uri = match maybe_import_uri {
1838                    Some(import_uri) => import_uri,
1839                    None => {
1840                        debug_assert!(state.borrow().diag.has_errors());
1841                        return None;
1842                    }
1843                };
1844                // The path is taken verbatim: escape sequences aren't decoded, so a
1845                // backslash stays a directory separator rather than an escape.
1846                let path_to_import = import_uri.text().to_string();
1847                let path_to_import = path_to_import.trim_matches('\"').to_string();
1848
1849                if path_to_import.is_empty() {
1850                    state
1851                        .borrow_mut()
1852                        .diag
1853                        .push_error("Unexpected empty import url".to_owned(), &import_uri);
1854                    return None;
1855                }
1856
1857                Some(ImportedTypes {
1858                    import_uri_token: import_uri,
1859                    import_kind: type_specifier,
1860                    file: path_to_import,
1861                    library_info: None,
1862                })
1863            })
1864    }
1865
1866    /// Return a document if it was already loaded
1867    pub fn get_document<'b>(&'b self, path: &Path) -> Option<&'b object_tree::Document> {
1868        let path = crate::pathutils::clean_path(path);
1869        if let Some((LoadedDocument::Document(d), _)) = self.all_documents.docs.get(&path) {
1870            Some(d)
1871        } else {
1872            None
1873        }
1874    }
1875
1876    /// Return an iterator over all the loaded file path
1877    pub fn all_files(&self) -> impl Iterator<Item = &PathBuf> {
1878        self.all_documents.docs.keys()
1879    }
1880
1881    /// Returns all file paths whose on-disk changes can affect the current document graph.
1882    ///
1883    /// This includes loaded documents and unresolved import targets that are kept in the
1884    /// dependency graph so newly created files can invalidate their dependents.
1885    pub fn all_files_to_watch(&self) -> HashSet<PathBuf> {
1886        // Note: This only works if the full set of passes have run (e.g. in load_root_file, but not
1887        // in load_file).
1888        //
1889        // TODO: the LSP will only run the import passes, which do not yet
1890        // detect embedded file resources, so we won't know about them until we
1891        // run the full pass pipeline (e.g. in the editor binary).
1892        fn resource_paths(document: &LoadedDocument) -> Vec<PathBuf> {
1893            match document {
1894                LoadedDocument::Document(document) => document
1895                    .embedded_file_resources
1896                    .borrow()
1897                    .iter()
1898                    .flat_map(|resource| resource.path.as_ref().map(|path| PathBuf::from(&**path)))
1899                    .collect(),
1900                LoadedDocument::Invalidated(_document) => vec![],
1901            }
1902        }
1903
1904        self.all_documents
1905            .docs
1906            .iter()
1907            .flat_map(|(path, (document, _diagnostics))| {
1908                std::iter::once(path.clone()).chain(resource_paths(document))
1909            })
1910            .chain(self.all_documents.dependencies.keys().cloned())
1911            .collect()
1912    }
1913
1914    /// Returns an iterator over all the loaded documents
1915    pub fn all_documents(&self) -> impl Iterator<Item = &object_tree::Document> + '_ {
1916        self.all_documents.docs.values().filter_map(|(d, _)| match d {
1917            LoadedDocument::Document(d) => Some(d),
1918            LoadedDocument::Invalidated(_) => None,
1919        })
1920    }
1921
1922    /// Returns an iterator over all the loaded documents
1923    pub fn all_file_documents(
1924        &self,
1925    ) -> impl Iterator<Item = (&PathBuf, &syntax_nodes::Document)> + '_ {
1926        self.all_documents.docs.iter().filter_map(|(p, (d, _))| {
1927            Some((
1928                p,
1929                match d {
1930                    LoadedDocument::Document(d) => d.node.as_ref()?,
1931                    LoadedDocument::Invalidated(d) => d,
1932                },
1933            ))
1934        })
1935    }
1936}
1937
1938fn get_native_style(all_loaded_files: &mut std::collections::BTreeSet<PathBuf>) -> String {
1939    // Try to get the value written by the i-slint-backend-selector's build script
1940
1941    // It is in the target/xxx/build directory
1942    let target_path = std::env::var_os("OUT_DIR")
1943        .and_then(|path| {
1944            // Same logic as in i-slint-backend-selector's build script to get the path
1945            crate::pathutils::join(Path::new(&path), Path::new("../../SLINT_DEFAULT_STYLE.txt"))
1946        })
1947        .or_else(|| {
1948            // When we are called from a slint!, OUT_DIR is only defined when the crate having the macro has a build.rs script.
1949            // As a fallback, try to parse the rustc arguments
1950            // https://stackoverflow.com/questions/60264534/getting-the-target-folder-from-inside-a-rust-proc-macro
1951            let mut args = std::env::args();
1952            let mut out_dir = None;
1953            while let Some(arg) = args.next() {
1954                if arg == "--out-dir" {
1955                    out_dir = args.next();
1956                    break;
1957                }
1958            }
1959            out_dir.and_then(|od| {
1960                crate::pathutils::join(
1961                    Path::new(&od),
1962                    Path::new("../build/SLINT_DEFAULT_STYLE.txt"),
1963                )
1964            })
1965        });
1966
1967    if let Some(style) = target_path.and_then(|target_path| {
1968        std::fs::read_to_string(&target_path)
1969            .map(|style| {
1970                all_loaded_files.insert(target_path);
1971                style.trim().into()
1972            })
1973            .ok()
1974    }) {
1975        return style;
1976    }
1977    i_slint_common::get_native_style(false, &std::env::var("TARGET").unwrap_or_default()).into()
1978}
1979
1980/// For a .rs file, return the manifest directory
1981///
1982/// This is for compatibility with `slint!` macro as before rust 1.88,
1983/// it was not possible for the macro to know the current path and
1984/// the Cargo.toml file was used instead
1985fn maybe_base_directory(referencing_file: &Path) -> Option<PathBuf> {
1986    if referencing_file.extension().is_some_and(|e| e == "rs") {
1987        // For .rs file, this is a rust macro, and rust macro locates the file relative to the CARGO_MANIFEST_DIR which is the directory that has a Cargo.toml file.
1988        let mut candidate = referencing_file;
1989        loop {
1990            candidate =
1991                if let Some(c) = candidate.parent() { c } else { break referencing_file.parent() };
1992
1993            if candidate.join("Cargo.toml").exists() {
1994                break Some(candidate);
1995            }
1996        }
1997        .map(|x| x.to_path_buf())
1998    } else {
1999        None
2000    }
2001}
2002
2003#[test]
2004fn test_dependency_loading() {
2005    let test_source_path: PathBuf =
2006        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
2007
2008    let mut incdir = test_source_path.clone();
2009    incdir.push("incpath");
2010
2011    let mut compiler_config =
2012        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2013    compiler_config.include_paths = vec![incdir];
2014    compiler_config.library_paths =
2015        HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
2016    compiler_config.style = Some("fluent".into());
2017
2018    let mut main_test_path = test_source_path.clone();
2019    main_test_path.push("dependency_test_main.slint");
2020
2021    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2022    let doc_node = crate::parser::parse_file(&main_test_path, &mut test_diags).unwrap();
2023
2024    let doc_node: syntax_nodes::Document = doc_node.into();
2025
2026    let mut build_diagnostics = BuildDiagnostics::default();
2027
2028    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2029    let registry = Rc::new(RefCell::new(TypeRegister::new(&loader.global_type_registry)));
2030
2031    let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
2032        &doc_node,
2033        &mut build_diagnostics,
2034        &registry,
2035    ));
2036
2037    assert!(!test_diags.has_errors());
2038    assert!(!build_diagnostics.has_errors());
2039    assert_eq!(foreign_imports.len(), 3);
2040    assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
2041
2042    let imported_files: Vec<_> = [
2043        "incpath/local_helper_type.slint",
2044        "incpath/dependency_from_incpath.slint",
2045        "dependency_local.slint",
2046        "library/lib.slint",
2047        "library/dependency_from_library.slint",
2048    ]
2049    .into_iter()
2050    .map(|path| test_source_path.join(path))
2051    .collect();
2052    for file in &imported_files {
2053        assert!(loader.get_document(file).is_some());
2054    }
2055
2056    // Test Typeloader invalidation/dropping
2057    // Dropping/invalidating all leaf nodes should invalidate everything.
2058    let to_drop = test_source_path.join("incpath/local_helper_type.slint");
2059    loader.drop_document(&to_drop).unwrap();
2060    let to_invalidate = test_source_path.join("library/dependency_from_library.slint");
2061    loader.invalidate_document(&to_invalidate);
2062
2063    // Check that the dropped file has indeed been fully dropped.
2064    assert!(!loader.all_files().contains(&to_drop));
2065    // But that the invalidated file is still there (even if get_document won't return it anymore)
2066    assert!(loader.all_files().contains(&to_invalidate));
2067
2068    for file in imported_files {
2069        assert!(loader.get_document(&file).is_none(), "{} is still loaded", file.display());
2070    }
2071}
2072
2073#[test]
2074fn test_dependency_loading_from_rust() {
2075    let test_source_path: PathBuf =
2076        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
2077
2078    let mut incdir = test_source_path.clone();
2079    incdir.push("incpath");
2080
2081    let mut compiler_config =
2082        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2083    compiler_config.include_paths = vec![incdir];
2084    compiler_config.library_paths =
2085        HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
2086    compiler_config.style = Some("fluent".into());
2087
2088    let mut main_test_path = test_source_path;
2089    main_test_path.push("some_rust_file.rs");
2090
2091    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2092    let doc_node = crate::parser::parse_file(main_test_path, &mut test_diags).unwrap();
2093
2094    let doc_node: syntax_nodes::Document = doc_node.into();
2095
2096    let mut build_diagnostics = BuildDiagnostics::default();
2097
2098    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2099    let registry = Rc::new(RefCell::new(TypeRegister::new(&loader.global_type_registry)));
2100
2101    let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
2102        &doc_node,
2103        &mut build_diagnostics,
2104        &registry,
2105    ));
2106
2107    assert!(!test_diags.has_errors());
2108    assert!(test_diags.is_empty()); // also no warnings
2109    assert!(!build_diagnostics.has_errors());
2110    assert!(build_diagnostics.is_empty()); // also no warnings
2111    assert_eq!(foreign_imports.len(), 3);
2112    assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
2113}
2114
2115#[test]
2116fn test_import_path_verbatim() {
2117    // The import path is taken verbatim, not unescaped: a literal Unicode or emoji
2118    // file name is used as written, and a backslash is a directory separator rather
2119    // than an escape, so `sub\comp.slint` names `sub/comp.slint`. An absolute path
2120    // with a backslash cleans to a different string, so it must be registered and
2121    // looked up under that cleaned path or the type loader panics (#12798).
2122    let requested = Rc::new(RefCell::new(Vec::<String>::new()));
2123    let requested_ = requested.clone();
2124
2125    let mut compiler_config =
2126        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2127    compiler_config.style = Some("fluent".into());
2128    compiler_config.open_import_callback = Some(Rc::new(move |path| {
2129        let requested_ = requested_.clone();
2130        Box::pin(async move {
2131            requested_.borrow_mut().push(path);
2132            Some(Ok("export XX := Rectangle {} ".to_owned()))
2133        })
2134    }));
2135
2136    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2137    let doc_node = crate::parser::parse(
2138        r#"
2139import { XX as A } from "naïve.slint";
2140import { XX as B } from "party🎉.slint";
2141import { XX as C } from "sub\comp.slint";
2142import { XX as D } from "/ddd\dd.slint";
2143export component X { A {} B {} C {} D {} }
2144"#
2145        .into(),
2146        Some(std::path::Path::new("HELLO")),
2147        &mut test_diags,
2148    );
2149
2150    let doc_node: syntax_nodes::Document = doc_node.into();
2151    let mut build_diagnostics = BuildDiagnostics::default();
2152    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2153    let registry = Rc::new(RefCell::new(TypeRegister::new(&loader.global_type_registry)));
2154    spin_on::spin_on(loader.load_dependencies_recursively(
2155        &doc_node,
2156        &mut build_diagnostics,
2157        &registry,
2158    ));
2159    assert!(!test_diags.has_errors());
2160    assert!(!build_diagnostics.has_errors(), "{:?}", build_diagnostics.to_string_vec());
2161    let mut requested = requested.borrow().clone();
2162    requested.sort();
2163    // Unicode names are kept as written; a backslash is normalized to a slash.
2164    assert_eq!(requested, ["/ddd/dd.slint", "naïve.slint", "party🎉.slint", "sub/comp.slint"]);
2165}
2166
2167#[test]
2168fn test_load_from_callback_ok() {
2169    let ok = Rc::new(core::cell::Cell::new(false));
2170    let ok_ = ok.clone();
2171
2172    let mut compiler_config =
2173        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2174    compiler_config.style = Some("fluent".into());
2175    compiler_config.open_import_callback = Some(Rc::new(move |path| {
2176        let ok_ = ok_.clone();
2177        Box::pin(async move {
2178            assert_eq!(path.replace('\\', "/"), "../FooBar.slint");
2179            assert!(!ok_.get());
2180            ok_.set(true);
2181            Some(Ok("export XX := Rectangle {} ".to_owned()))
2182        })
2183    }));
2184
2185    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2186    let doc_node = crate::parser::parse(
2187        r#"
2188/* ... */
2189import { XX } from "../Ab/.././FooBar.slint";
2190X := XX {}
2191"#
2192        .into(),
2193        Some(std::path::Path::new("HELLO")),
2194        &mut test_diags,
2195    );
2196
2197    let doc_node: syntax_nodes::Document = doc_node.into();
2198    let mut build_diagnostics = BuildDiagnostics::default();
2199    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2200    let registry = Rc::new(RefCell::new(TypeRegister::new(&loader.global_type_registry)));
2201    spin_on::spin_on(loader.load_dependencies_recursively(
2202        &doc_node,
2203        &mut build_diagnostics,
2204        &registry,
2205    ));
2206    assert!(ok.get());
2207    assert!(!test_diags.has_errors());
2208    assert!(!build_diagnostics.has_errors());
2209}
2210
2211#[test]
2212fn test_load_error_twice() {
2213    let mut compiler_config =
2214        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2215    compiler_config.style = Some("fluent".into());
2216    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2217
2218    let doc_node = crate::parser::parse(
2219        r#"
2220/* ... */
2221import { XX } from "error.slint";
2222component Foo { XX {} }
2223"#
2224        .into(),
2225        Some(std::path::Path::new("HELLO")),
2226        &mut test_diags,
2227    );
2228
2229    let doc_node: syntax_nodes::Document = doc_node.into();
2230    let mut build_diagnostics = BuildDiagnostics::default();
2231    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2232    let registry = Rc::new(RefCell::new(TypeRegister::new(&loader.global_type_registry)));
2233    spin_on::spin_on(loader.load_dependencies_recursively(
2234        &doc_node,
2235        &mut build_diagnostics,
2236        &registry,
2237    ));
2238    assert!(!test_diags.has_errors());
2239    assert!(build_diagnostics.has_errors());
2240    let diags = build_diagnostics.to_string_vec();
2241    assert_eq!(
2242        diags,
2243        &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
2244    );
2245    // Try loading another time with the same registry
2246    let mut build_diagnostics = BuildDiagnostics::default();
2247    spin_on::spin_on(loader.load_dependencies_recursively(
2248        &doc_node,
2249        &mut build_diagnostics,
2250        &registry,
2251    ));
2252    assert!(build_diagnostics.has_errors());
2253    let diags = build_diagnostics.to_string_vec();
2254    assert_eq!(
2255        diags,
2256        &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
2257    );
2258}
2259
2260#[test]
2261fn test_load_file_watches_missing_imports() {
2262    let mut compiler_config =
2263        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2264    compiler_config.style = Some("fluent".into());
2265    compiler_config.embed_resources = crate::EmbedResourcesKind::ListAllResources;
2266    let mut build_diagnostics = BuildDiagnostics::default();
2267    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2268    let main_path = Path::new("/tmp/main.slint");
2269
2270    spin_on::spin_on(
2271        loader.load_file(
2272            main_path,
2273            main_path,
2274            r#"
2275/* ... */
2276import { XX } from "missing/dependency.slint";
2277component Foo { XX {} }
2278"#
2279            .into(),
2280            false,
2281            &mut build_diagnostics,
2282        ),
2283    );
2284
2285    assert!(build_diagnostics.has_errors());
2286
2287    let watch_files = loader.all_files_to_watch();
2288    assert!(watch_files.contains(&PathBuf::from("/tmp/main.slint")));
2289    assert!(watch_files.contains(&PathBuf::from("/tmp/missing/dependency.slint")));
2290}
2291
2292#[test]
2293fn test_load_root_file_tracks_missing_imports() {
2294    let mut compiler_config =
2295        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2296    compiler_config.style = Some("fluent".into());
2297    compiler_config.embed_resources = crate::EmbedResourcesKind::ListAllResources;
2298    let mut build_diagnostics = BuildDiagnostics::default();
2299    let main_path = std::env::temp_dir().join("main.slint");
2300    let missing_path = main_path.with_file_name("missing.slint");
2301    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2302    spin_on::spin_on(
2303        loader.load_root_file(
2304            &main_path,
2305            &main_path,
2306            r#"
2307import { Missing } from "missing.slint";
2308export component Main inherits Window {
2309    Missing { }
2310}
2311"#
2312            .into(),
2313            false,
2314            &mut build_diagnostics,
2315        ),
2316    );
2317
2318    assert!(build_diagnostics.has_errors());
2319    assert!(
2320        loader.all_files_to_watch().contains(&main_path),
2321        "watch paths: {:?}",
2322        loader.all_files_to_watch()
2323    );
2324    assert!(
2325        loader.all_files_to_watch().contains(&missing_path),
2326        "watch paths: {:?}",
2327        loader.all_files_to_watch()
2328    );
2329}
2330
2331#[test]
2332fn test_load_root_file_tracks_missing_resources() {
2333    let mut compiler_config =
2334        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2335    compiler_config.style = Some("fluent".into());
2336    compiler_config.embed_resources = crate::EmbedResourcesKind::ListAllResources;
2337    let mut build_diagnostics = BuildDiagnostics::default();
2338    let main_path = std::env::temp_dir().join("main.slint");
2339    let resource_path = main_path.with_file_name("icon.svg");
2340
2341    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2342    spin_on::spin_on(
2343        loader.load_root_file(
2344            &main_path,
2345            &main_path,
2346            r#"
2347export component Main inherits Window {
2348    Image {
2349        source: @image-url("icon.svg");
2350    }
2351}
2352"#
2353            .into(),
2354            false,
2355            &mut build_diagnostics,
2356        ),
2357    );
2358
2359    // The image is not embedded, so it doesn't cause an error
2360    assert!(!build_diagnostics.has_errors());
2361    assert!(
2362        loader.all_files_to_watch().contains(&main_path),
2363        "watch paths: {:?}",
2364        loader.all_files_to_watch()
2365    );
2366    assert!(
2367        loader.all_files_to_watch().contains(&resource_path),
2368        "watch paths: {:?}",
2369        loader.all_files_to_watch()
2370    );
2371}
2372
2373#[test]
2374fn test_manual_import() {
2375    let mut compiler_config =
2376        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2377    compiler_config.style = Some("fluent".into());
2378    let mut build_diagnostics = BuildDiagnostics::default();
2379    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2380
2381    let maybe_button_type = spin_on::spin_on(loader.import_component(
2382        "std-widgets.slint",
2383        "Button",
2384        &mut build_diagnostics,
2385    ));
2386
2387    assert!(!build_diagnostics.has_errors());
2388    assert!(maybe_button_type.is_some());
2389}
2390
2391#[test]
2392fn test_builtin_style() {
2393    let test_source_path: PathBuf =
2394        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
2395
2396    let incdir = test_source_path.join("custom_style");
2397
2398    let mut compiler_config =
2399        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2400    compiler_config.include_paths = vec![incdir];
2401    compiler_config.style = Some("fluent".into());
2402
2403    let mut build_diagnostics = BuildDiagnostics::default();
2404    let _loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2405
2406    assert!(!build_diagnostics.has_errors());
2407}
2408
2409#[test]
2410fn test_user_style() {
2411    let test_source_path: PathBuf =
2412        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
2413
2414    let incdir = test_source_path.join("custom_style");
2415
2416    let mut compiler_config =
2417        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2418    compiler_config.include_paths = vec![incdir];
2419    compiler_config.style = Some("TestStyle".into());
2420
2421    let mut build_diagnostics = BuildDiagnostics::default();
2422    let _loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2423
2424    assert!(!build_diagnostics.has_errors());
2425}
2426
2427#[test]
2428fn test_unknown_style() {
2429    let test_source_path: PathBuf =
2430        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
2431
2432    let incdir = test_source_path.join("custom_style");
2433
2434    let mut compiler_config =
2435        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2436    compiler_config.include_paths = vec![incdir];
2437    compiler_config.style = Some("FooBar".into());
2438
2439    let mut build_diagnostics = BuildDiagnostics::default();
2440    let _loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2441
2442    assert!(build_diagnostics.has_errors());
2443    let diags = build_diagnostics.to_string_vec();
2444    assert_eq!(diags.len(), 1);
2445    assert!(diags[0].starts_with("Style FooBar is not known. Use one of the builtin styles ["));
2446}
2447
2448#[test]
2449fn test_library_import() {
2450    let test_source_path: PathBuf =
2451        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
2452
2453    let library_paths = HashMap::from([
2454        ("libdir".into(), test_source_path.clone()),
2455        ("libfile.slint".into(), test_source_path.join("lib.slint")),
2456    ]);
2457
2458    let mut compiler_config =
2459        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2460    compiler_config.library_paths = library_paths;
2461    compiler_config.style = Some("fluent".into());
2462    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2463
2464    let doc_node = crate::parser::parse(
2465        r#"
2466/* ... */
2467import { LibraryType } from "@libfile.slint";
2468import { LibraryHelperType } from "@libdir/library_helper_type.slint";
2469"#
2470        .into(),
2471        Some(std::path::Path::new("HELLO")),
2472        &mut test_diags,
2473    );
2474
2475    let doc_node: syntax_nodes::Document = doc_node.into();
2476    let mut build_diagnostics = BuildDiagnostics::default();
2477    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2478    let registry = Rc::new(RefCell::new(TypeRegister::new(&loader.global_type_registry)));
2479    spin_on::spin_on(loader.load_dependencies_recursively(
2480        &doc_node,
2481        &mut build_diagnostics,
2482        &registry,
2483    ));
2484    assert!(!test_diags.has_errors());
2485    assert!(!build_diagnostics.has_errors());
2486}
2487
2488#[test]
2489fn test_library_import_errors() {
2490    let test_source_path: PathBuf =
2491        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
2492
2493    let library_paths = HashMap::from([
2494        ("libdir".into(), test_source_path.clone()),
2495        ("libfile.slint".into(), test_source_path.join("lib.slint")),
2496    ]);
2497
2498    let mut compiler_config =
2499        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2500    compiler_config.library_paths = library_paths;
2501    compiler_config.style = Some("fluent".into());
2502    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2503
2504    let doc_node = crate::parser::parse(
2505        r#"
2506/* ... */
2507import { A } from "@libdir";
2508import { B } from "@libdir/unknown.slint";
2509import { C } from "@libfile.slint/unknown.slint";
2510import { D } from "@unknown";
2511import { E } from "@unknown/lib.slint";
2512"#
2513        .into(),
2514        Some(std::path::Path::new("HELLO")),
2515        &mut test_diags,
2516    );
2517
2518    let doc_node: syntax_nodes::Document = doc_node.into();
2519    let mut build_diagnostics = BuildDiagnostics::default();
2520    let mut loader = TypeLoader::new(compiler_config, &mut build_diagnostics);
2521    let registry = Rc::new(RefCell::new(TypeRegister::new(&loader.global_type_registry)));
2522    spin_on::spin_on(loader.load_dependencies_recursively(
2523        &doc_node,
2524        &mut build_diagnostics,
2525        &registry,
2526    ));
2527    assert!(!test_diags.has_errors());
2528    assert!(build_diagnostics.has_errors());
2529    let diags = build_diagnostics.to_string_vec();
2530    assert_eq!(diags.len(), 5);
2531    assert_starts_with(
2532        &diags[0],
2533        &format!(
2534            "HELLO:3: Error reading requested import \"{}\": ",
2535            test_source_path.to_string_lossy()
2536        ),
2537    );
2538    assert_starts_with(
2539        &diags[1],
2540        &format!(
2541            "HELLO:4: Error reading requested import \"{}\": ",
2542            test_source_path.join("unknown.slint").to_string_lossy(),
2543        ),
2544    );
2545    assert_starts_with(
2546        &diags[2],
2547        &format!(
2548            "HELLO:5: Error reading requested import \"{}\": ",
2549            test_source_path.join("lib.slint").join("unknown.slint").to_string_lossy()
2550        ),
2551    );
2552    assert_eq!(
2553        &diags[3],
2554        "HELLO:6: Cannot find requested import \"@unknown\" in the library search path"
2555    );
2556    assert_eq!(
2557        &diags[4],
2558        "HELLO:7: Cannot find requested import \"@unknown/lib.slint\" in the library search path"
2559    );
2560
2561    #[track_caller]
2562    fn assert_starts_with(actual: &str, start: &str) {
2563        assert!(actual.starts_with(start), "{actual:?} does not start with {start:?}");
2564    }
2565}
2566
2567#[test]
2568fn test_snapshotting() {
2569    let mut type_loader = TypeLoader::new(
2570        crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter),
2571        &mut BuildDiagnostics::default(),
2572    );
2573
2574    let path = PathBuf::from("/tmp/test.slint");
2575    let mut diag = BuildDiagnostics::default();
2576    spin_on::spin_on(type_loader.load_file(
2577        &path,
2578        &path,
2579        "export component Foobar inherits Rectangle { }".to_string(),
2580        false,
2581        &mut diag,
2582    ));
2583
2584    assert!(!diag.has_errors());
2585
2586    let doc = type_loader.get_document(&path).unwrap();
2587    let c = doc.inner_components.first().unwrap();
2588    assert_eq!(c.id, "Foobar");
2589    let root_element = c.root_element.clone();
2590    assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2591
2592    let copy = snapshot(&type_loader).unwrap();
2593    assert_eq!(copy.revision(), type_loader.revision());
2594
2595    let doc = copy.get_document(&path).unwrap();
2596    let c = doc.inner_components.first().unwrap();
2597    assert_eq!(c.id, "Foobar");
2598    let root_element = c.root_element.clone();
2599    assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2600}
2601
2602#[test]
2603fn test_watch_paths_revision_bumps_on_mutations() {
2604    let mut type_loader = TypeLoader::new(
2605        crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter),
2606        &mut BuildDiagnostics::default(),
2607    );
2608
2609    assert_eq!(type_loader.revision(), 0);
2610
2611    let path = PathBuf::from("/tmp/test-revision.slint");
2612    let mut diag = BuildDiagnostics::default();
2613    spin_on::spin_on(type_loader.load_file(
2614        &path,
2615        &path,
2616        "export component Foobar inherits Rectangle { }".to_string(),
2617        false,
2618        &mut diag,
2619    ));
2620    assert!(!diag.has_errors());
2621    let after_load = type_loader.revision();
2622    assert_ne!(after_load, 0);
2623
2624    type_loader.invalidate_document(&path);
2625    let after_invalidate = type_loader.revision();
2626    assert_ne!(after_invalidate, after_load);
2627
2628    type_loader.drop_document(&path).unwrap();
2629    let after_drop = type_loader.revision();
2630    assert_ne!(after_drop, after_invalidate);
2631}