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
4use smol_str::{SmolStr, ToSmolStr};
5use std::cell::RefCell;
6use std::collections::{HashMap, HashSet};
7use std::path::{Path, PathBuf};
8use std::rc::{Rc, Weak};
9
10use crate::diagnostics::{BuildDiagnostics, Spanned};
11use crate::expression_tree::Callable;
12use crate::object_tree::{self, Document, ExportedName, Exports};
13use crate::parser::{syntax_nodes, NodeOrToken, SyntaxKind, SyntaxToken};
14use crate::typeregister::TypeRegister;
15use crate::{expression_tree, CompilerConfiguration};
16use crate::{fileaccess, langtype, layout, parser};
17use core::future::Future;
18use itertools::Itertools;
19
20enum LoadedDocument {
21    Document(Document),
22    Invalidated(syntax_nodes::Document),
23}
24
25/// Storage for a cache of all loaded documents
26#[derive(Default)]
27struct LoadedDocuments {
28    /// maps from the canonical file name to the object_tree::Document.
29    /// Also contains the error that occurred when parsing the document (and only the parse error, not further semantic errors)
30    docs: HashMap<PathBuf, (LoadedDocument, Vec<crate::diagnostics::Diagnostic>)>,
31    /// The .slint files that are currently being loaded, potentially asynchronously.
32    /// When a task start loading a file, it will add an empty vector to this map, and
33    /// the same task will remove the entry from the map when finished, and awake all
34    /// wakers.
35    currently_loading: HashMap<PathBuf, Vec<std::task::Waker>>,
36
37    /// The dependencies of the currently loaded files.
38    /// Maps all the files that depends directly on the key
39    dependencies: HashMap<PathBuf, HashSet<PathBuf>>,
40}
41
42#[derive(Debug, Clone)]
43pub enum ImportKind {
44    /// `import {Foo, Bar} from "foo"`
45    ImportList(syntax_nodes::ImportIdentifierList),
46    /// `import "foo"` without an import list
47    FileImport,
48    /// re-export types, as per `export ... from "foo"``.
49    ModuleReexport(syntax_nodes::ExportsList),
50}
51
52#[derive(Debug, Clone)]
53pub struct ImportedTypes {
54    pub import_uri_token: SyntaxToken,
55    pub import_kind: ImportKind,
56    pub file: String,
57}
58
59#[derive(Debug)]
60pub struct ImportedName {
61    // name of export to match in the other file
62    pub external_name: SmolStr,
63    // name to be used locally
64    pub internal_name: SmolStr,
65}
66
67impl ImportedName {
68    pub fn extract_imported_names(
69        import_identifiers: &syntax_nodes::ImportIdentifierList,
70    ) -> impl Iterator<Item = ImportedName> + '_ {
71        import_identifiers.ImportIdentifier().map(Self::from_node)
72    }
73
74    pub fn from_node(importident: syntax_nodes::ImportIdentifier) -> Self {
75        let external_name =
76            parser::normalize_identifier(importident.ExternalName().text().to_smolstr().trim());
77
78        let internal_name = match importident.InternalName() {
79            Some(name_ident) => parser::normalize_identifier(name_ident.text().to_smolstr().trim()),
80            None => external_name.clone(),
81        };
82
83        ImportedName { internal_name, external_name }
84    }
85}
86
87/// This function makes a snapshot of the current state of the type loader.
88/// This snapshot includes everything: Elements, Components, known types, ...
89/// and can be used to roll back to earlier states in the compilation process.
90///
91/// One way this is used is to create a raw `TypeLoader` for analysis purposes
92/// or to load a set of changes, see if those compile and then role back
93///
94/// The result may be `None` if the `TypeLoader` is actually in the process
95/// of loading more documents and is `Some` `TypeLoader` with a copy off all
96/// state connected with the original `TypeLoader`.
97pub fn snapshot(type_loader: &TypeLoader) -> Option<TypeLoader> {
98    let mut snapshotter = Snapshotter {
99        component_map: HashMap::new(),
100        element_map: HashMap::new(),
101        type_register_map: HashMap::new(),
102        keep_alive: Vec::new(),
103        keep_alive_elements: Vec::new(),
104    };
105    snapshotter.snapshot_type_loader(type_loader)
106}
107
108/// This function makes a snapshot of the current state of the type loader.
109/// This snapshot includes everything: Elements, Components, known types, ...
110/// and can be used to roll back to earlier states in the compilation process.
111///
112/// One way this is used is to create a raw `TypeLoader` for analysis purposes
113/// or to load a set of changes, see if those compile and then role back
114///
115/// The result may be `None` if the `TypeLoader` is actually in the process
116/// of loading more documents and is `Some` `TypeLoader` with a copy off all
117/// state connected with the original `TypeLoader`.
118///
119/// The Document will be added to the type_loader after it was snapshotted as well.
120pub(crate) fn snapshot_with_extra_doc(
121    type_loader: &TypeLoader,
122    doc: &object_tree::Document,
123) -> Option<TypeLoader> {
124    let mut snapshotter = Snapshotter {
125        component_map: HashMap::new(),
126        element_map: HashMap::new(),
127        type_register_map: HashMap::new(),
128        keep_alive: Vec::new(),
129        keep_alive_elements: Vec::new(),
130    };
131    let mut result = snapshotter.snapshot_type_loader(type_loader);
132
133    snapshotter.create_document(doc);
134    let new_doc = snapshotter.snapshot_document(doc);
135
136    snapshotter.finalize();
137
138    if let Some(doc_node) = &new_doc.node {
139        let path = doc_node.source_file.path().to_path_buf();
140        if let Some(r) = &mut result {
141            r.all_documents.docs.insert(path, (LoadedDocument::Document(new_doc), vec![]));
142        }
143    }
144
145    result
146}
147
148pub(crate) struct Snapshotter {
149    component_map:
150        HashMap<by_address::ByAddress<Rc<object_tree::Component>>, Weak<object_tree::Component>>,
151    element_map:
152        HashMap<by_address::ByAddress<object_tree::ElementRc>, Weak<RefCell<object_tree::Element>>>,
153    type_register_map:
154        HashMap<by_address::ByAddress<Rc<RefCell<TypeRegister>>>, Rc<RefCell<TypeRegister>>>,
155
156    keep_alive: Vec<(Rc<object_tree::Component>, Rc<object_tree::Component>)>,
157    keep_alive_elements: Vec<(object_tree::ElementRc, object_tree::ElementRc)>,
158}
159
160impl Snapshotter {
161    fn snapshot_globals(&mut self, type_loader: &TypeLoader) {
162        let registry = type_loader.global_type_registry.clone();
163        registry
164            .borrow()
165            .all_elements()
166            .iter()
167            .filter_map(|(_, ty)| match ty {
168                langtype::ElementType::Component(c) if c.is_global() => Some(c),
169                _ => None,
170            })
171            .for_each(|c| {
172                self.create_component(c);
173            });
174    }
175
176    fn finalize(&mut self) {
177        let mut elements = std::mem::take(&mut self.keep_alive_elements);
178
179        while !elements.is_empty() {
180            for (s, t) in elements.iter_mut() {
181                self.snapshot_element(s, &mut t.borrow_mut());
182            }
183            elements = std::mem::take(&mut self.keep_alive_elements);
184        }
185    }
186
187    fn snapshot_type_loader(&mut self, type_loader: &TypeLoader) -> Option<TypeLoader> {
188        self.snapshot_globals(type_loader);
189
190        let all_documents = self.snapshot_loaded_documents(&type_loader.all_documents)?;
191
192        self.finalize();
193
194        Some(TypeLoader {
195            all_documents,
196            global_type_registry: self.snapshot_type_register(&type_loader.global_type_registry),
197            compiler_config: type_loader.compiler_config.clone(),
198            resolved_style: type_loader.resolved_style.clone(),
199        })
200    }
201
202    pub(crate) fn snapshot_type_register(
203        &mut self,
204        type_register: &Rc<RefCell<TypeRegister>>,
205    ) -> Rc<RefCell<TypeRegister>> {
206        if let Some(r) = self.type_register_map.get(&by_address::ByAddress(type_register.clone())) {
207            return r.clone();
208        }
209
210        let tr = Rc::new(RefCell::new(TypeRegister::default()));
211        self.type_register_map.insert(by_address::ByAddress(type_register.clone()), tr.clone());
212
213        *tr.borrow_mut() = self.snapshot_type_register_impl(type_register);
214
215        tr
216    }
217
218    fn snapshot_type_register_impl(
219        &mut self,
220        type_register: &Rc<RefCell<TypeRegister>>,
221    ) -> TypeRegister {
222        type_register.borrow().snapshot(self)
223    }
224
225    fn snapshot_loaded_documents(
226        &mut self,
227        loaded_documents: &LoadedDocuments,
228    ) -> Option<LoadedDocuments> {
229        if !loaded_documents.currently_loading.is_empty() {
230            return None;
231        }
232
233        loaded_documents.docs.values().for_each(|(d, _)| {
234            if let LoadedDocument::Document(d) = d {
235                self.create_document(d)
236            }
237        });
238
239        Some(LoadedDocuments {
240            docs: loaded_documents
241                .docs
242                .iter()
243                .map(|(p, (d, err))| {
244                    (
245                        p.clone(),
246                        (
247                            match d {
248                                LoadedDocument::Document(d) => {
249                                    LoadedDocument::Document(self.snapshot_document(d))
250                                }
251                                LoadedDocument::Invalidated(d) => {
252                                    LoadedDocument::Invalidated(d.clone())
253                                }
254                            },
255                            err.clone(),
256                        ),
257                    )
258                })
259                .collect(),
260            currently_loading: Default::default(),
261            dependencies: Default::default(),
262        })
263    }
264
265    fn create_document(&mut self, document: &object_tree::Document) {
266        document.inner_components.iter().for_each(|ic| {
267            let _ = self.create_component(ic);
268        });
269        if let Some(popup_menu_impl) = &document.popup_menu_impl {
270            let _ = self.create_component(popup_menu_impl);
271        }
272    }
273
274    fn snapshot_document(&mut self, document: &object_tree::Document) -> object_tree::Document {
275        let inner_components = document
276            .inner_components
277            .iter()
278            .map(|ic| {
279                Weak::upgrade(&self.use_component(ic))
280                    .expect("Components can get upgraded at this point")
281            })
282            .collect();
283        let exports = document.exports.snapshot(self);
284
285        object_tree::Document {
286            node: document.node.clone(),
287            inner_components,
288            inner_types: document.inner_types.clone(),
289            local_registry: document.local_registry.snapshot(self),
290            custom_fonts: document.custom_fonts.clone(),
291            imports: document.imports.clone(),
292            exports,
293            embedded_file_resources: document.embedded_file_resources.clone(),
294            #[cfg(feature = "bundle-translations")]
295            translation_builder: document.translation_builder.clone(),
296            used_types: RefCell::new(self.snapshot_used_sub_types(&document.used_types.borrow())),
297            popup_menu_impl: document.popup_menu_impl.as_ref().map(|p| {
298                Weak::upgrade(&self.use_component(p))
299                    .expect("Components can get upgraded at this point")
300            }),
301        }
302    }
303
304    pub(crate) fn create_component(
305        &mut self,
306        component: &Rc<object_tree::Component>,
307    ) -> Rc<object_tree::Component> {
308        let input_address = by_address::ByAddress(component.clone());
309
310        let parent_element = if let Some(pe) = component.parent_element.upgrade() {
311            Rc::downgrade(&self.use_element(&pe))
312        } else {
313            Weak::default()
314        };
315
316        let result = Rc::new_cyclic(|weak| {
317            self.component_map.insert(input_address, weak.clone());
318
319            let root_element = self.create_element(&component.root_element);
320
321            let optimized_elements = RefCell::new(
322                component
323                    .optimized_elements
324                    .borrow()
325                    .iter()
326                    .map(|e| self.create_element(e))
327                    .collect(),
328            );
329
330            let child_insertion_point =
331                RefCell::new(component.child_insertion_point.borrow().clone());
332
333            let popup_windows = RefCell::new(
334                component
335                    .popup_windows
336                    .borrow()
337                    .iter()
338                    .map(|p| self.snapshot_popup_window(p))
339                    .collect(),
340            );
341            let timers = RefCell::new(
342                component.timers.borrow().iter().map(|p| self.snapshot_timer(p)).collect(),
343            );
344            let root_constraints = RefCell::new(
345                self.snapshot_layout_constraints(&component.root_constraints.borrow()),
346            );
347            let menu_item_tree = component
348                .menu_item_tree
349                .borrow()
350                .iter()
351                .map(|it| self.create_component(it))
352                .collect::<Vec<_>>()
353                .into();
354            object_tree::Component {
355                node: component.node.clone(),
356                id: component.id.clone(),
357                child_insertion_point,
358                exported_global_names: RefCell::new(
359                    component.exported_global_names.borrow().clone(),
360                ),
361                used: component.used.clone(),
362                init_code: RefCell::new(component.init_code.borrow().clone()),
363                inherits_popup_window: std::cell::Cell::new(component.inherits_popup_window.get()),
364                optimized_elements,
365                parent_element,
366                popup_windows,
367                timers,
368                menu_item_tree,
369                private_properties: RefCell::new(component.private_properties.borrow().clone()),
370                root_constraints,
371                root_element,
372            }
373        });
374        self.keep_alive.push((component.clone(), result.clone()));
375        result
376    }
377
378    pub(crate) fn use_component(
379        &self,
380        component: &Rc<object_tree::Component>,
381    ) -> Weak<object_tree::Component> {
382        self.component_map
383            .get(&by_address::ByAddress(component.clone()))
384            .expect("Component (Weak!) must exist at this point.")
385            .clone()
386    }
387
388    pub(crate) fn create_element(
389        &mut self,
390        element: &object_tree::ElementRc,
391    ) -> object_tree::ElementRc {
392        let enclosing_component = if let Some(ec) = element.borrow().enclosing_component.upgrade() {
393            self.use_component(&ec)
394        } else {
395            Weak::default()
396        };
397
398        let elem = element.borrow();
399
400        let r = Rc::new_cyclic(|weak| {
401            self.element_map.insert(by_address::ByAddress(element.clone()), weak.clone());
402
403            let children = elem.children.iter().map(|c| self.create_element(c)).collect();
404
405            RefCell::new(object_tree::Element {
406                id: elem.id.clone(),
407                enclosing_component,
408                children,
409                debug: elem.debug.clone(),
410                ..Default::default()
411            })
412        });
413
414        self.keep_alive_elements.push((element.clone(), r.clone()));
415        r
416    }
417
418    fn create_and_snapshot_element(
419        &mut self,
420        element: &object_tree::ElementRc,
421    ) -> object_tree::ElementRc {
422        let target = self.create_element(element);
423        self.snapshot_element(element, &mut target.borrow_mut());
424        target
425    }
426
427    pub(crate) fn use_element(&self, element: &object_tree::ElementRc) -> object_tree::ElementRc {
428        Weak::upgrade(
429            &self
430                .element_map
431                .get(&by_address::ByAddress(element.clone()))
432                .expect("Elements should have been known at this point")
433                .clone(),
434        )
435        .expect("Must be able to upgrade here")
436    }
437
438    fn snapshot_element(
439        &mut self,
440        element: &object_tree::ElementRc,
441        target_element: &mut object_tree::Element,
442    ) {
443        let elem = element.borrow();
444
445        target_element.base_type = self.snapshot_element_type(&elem.base_type);
446
447        target_element.transitions = elem
448            .transitions
449            .iter()
450            .map(|t| object_tree::Transition {
451                direction: t.direction,
452                state_id: t.state_id.clone(),
453                property_animations: t
454                    .property_animations
455                    .iter()
456                    .map(|(nr, sl, el)| {
457                        (nr.snapshot(self), sl.clone(), self.create_and_snapshot_element(el))
458                    })
459                    .collect(),
460                node: t.node.clone(),
461            })
462            .collect();
463
464        target_element.bindings = elem
465            .bindings
466            .iter()
467            .map(|(k, v)| {
468                let bm = v.borrow();
469                let binding = self.snapshot_binding_expression(&bm);
470                (k.clone(), RefCell::new(binding))
471            })
472            .collect();
473        target_element.states = elem
474            .states
475            .iter()
476            .map(|s| object_tree::State {
477                id: s.id.clone(),
478                condition: s.condition.clone(),
479                property_changes: s
480                    .property_changes
481                    .iter()
482                    .map(|(nr, expr, spc)| {
483                        let nr = nr.snapshot(self);
484                        let expr = self.snapshot_expression(expr);
485                        (nr, expr, spc.clone())
486                    })
487                    .collect(),
488            })
489            .collect();
490        target_element.repeated =
491            elem.repeated.as_ref().map(|r| object_tree::RepeatedElementInfo {
492                model: self.snapshot_expression(&r.model),
493                model_data_id: r.model_data_id.clone(),
494                index_id: r.index_id.clone(),
495                is_conditional_element: r.is_conditional_element,
496                is_listview: r.is_listview.as_ref().map(|lv| object_tree::ListViewInfo {
497                    viewport_y: lv.viewport_y.snapshot(self),
498                    viewport_height: lv.viewport_height.snapshot(self),
499                    viewport_width: lv.viewport_width.snapshot(self),
500                    listview_height: lv.listview_height.snapshot(self),
501                    listview_width: lv.listview_width.snapshot(self),
502                }),
503            });
504
505        target_element.accessibility_props = object_tree::AccessibilityProps(
506            elem.accessibility_props.0.iter().map(|(k, v)| (k.clone(), v.snapshot(self))).collect(),
507        );
508        target_element.geometry_props =
509            elem.geometry_props.as_ref().map(|gp| object_tree::GeometryProps {
510                x: gp.x.snapshot(self),
511                y: gp.y.snapshot(self),
512                width: gp.width.snapshot(self),
513                height: gp.height.snapshot(self),
514            });
515        target_element.property_declarations = elem
516            .property_declarations
517            .iter()
518            .map(|(k, v)| {
519                let decl = object_tree::PropertyDeclaration {
520                    property_type: v.property_type.clone(),
521                    node: v.node.clone(),
522                    expose_in_public_api: v.expose_in_public_api,
523                    is_alias: v.is_alias.as_ref().map(|a| a.snapshot(self)),
524                    visibility: v.visibility,
525                    pure: v.pure,
526                };
527                (k.clone(), decl)
528            })
529            .collect();
530        target_element.layout_info_prop =
531            elem.layout_info_prop.as_ref().map(|(n1, n2)| (n1.snapshot(self), n2.snapshot(self)));
532        target_element.property_analysis = RefCell::new(elem.property_analysis.borrow().clone());
533
534        target_element.change_callbacks = elem.change_callbacks.clone();
535        target_element.child_of_layout = elem.child_of_layout;
536        target_element.default_fill_parent = elem.default_fill_parent;
537        target_element.has_popup_child = elem.has_popup_child;
538        target_element.inline_depth = elem.inline_depth;
539        target_element.is_component_placeholder = elem.is_component_placeholder;
540        target_element.is_flickable_viewport = elem.is_flickable_viewport;
541        target_element.is_legacy_syntax = elem.is_legacy_syntax;
542        target_element.item_index = elem.item_index.clone();
543        target_element.item_index_of_first_children = elem.item_index_of_first_children.clone();
544        target_element.named_references = elem.named_references.snapshot(self);
545    }
546
547    fn snapshot_binding_expression(
548        &mut self,
549        binding_expression: &expression_tree::BindingExpression,
550    ) -> expression_tree::BindingExpression {
551        expression_tree::BindingExpression {
552            expression: self.snapshot_expression(&binding_expression.expression),
553            span: binding_expression.span.clone(),
554            priority: binding_expression.priority,
555            animation: binding_expression.animation.as_ref().map(|pa| match pa {
556                object_tree::PropertyAnimation::Static(element) => {
557                    object_tree::PropertyAnimation::Static(
558                        self.create_and_snapshot_element(element),
559                    )
560                }
561                object_tree::PropertyAnimation::Transition { state_ref, animations } => {
562                    object_tree::PropertyAnimation::Transition {
563                        state_ref: self.snapshot_expression(state_ref),
564                        animations: animations
565                            .iter()
566                            .map(|tpa| object_tree::TransitionPropertyAnimation {
567                                state_id: tpa.state_id,
568                                direction: tpa.direction,
569                                animation: self.create_and_snapshot_element(&tpa.animation),
570                            })
571                            .collect(),
572                    }
573                }
574            }),
575            analysis: binding_expression.analysis.as_ref().map(|a| {
576                expression_tree::BindingAnalysis {
577                    is_in_binding_loop: a.is_in_binding_loop.clone(),
578                    is_const: a.is_const,
579                    no_external_dependencies: a.no_external_dependencies,
580                }
581            }),
582            two_way_bindings: binding_expression
583                .two_way_bindings
584                .iter()
585                .map(|twb| twb.snapshot(self))
586                .collect(),
587        }
588    }
589
590    pub(crate) fn snapshot_element_type(
591        &mut self,
592        element_type: &langtype::ElementType,
593    ) -> langtype::ElementType {
594        // Components need to get adapted, the rest is fine I think...
595        match element_type {
596            langtype::ElementType::Component(component) => {
597                // Some components that will get compiled out later...
598                langtype::ElementType::Component(
599                    Weak::upgrade(&self.use_component(component))
600                        .expect("I can unwrap at this point"),
601                )
602            }
603            _ => element_type.clone(),
604        }
605    }
606
607    fn snapshot_used_sub_types(
608        &mut self,
609        used_types: &object_tree::UsedSubTypes,
610    ) -> object_tree::UsedSubTypes {
611        let globals = used_types
612            .globals
613            .iter()
614            .map(|component| {
615                Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
616            })
617            .collect();
618        let structs_and_enums = used_types.structs_and_enums.clone();
619        let sub_components = used_types
620            .sub_components
621            .iter()
622            .map(|component| {
623                Weak::upgrade(&self.use_component(component)).expect("Looking at a known component")
624            })
625            .collect();
626        object_tree::UsedSubTypes { globals, structs_and_enums, sub_components }
627    }
628
629    fn snapshot_popup_window(
630        &mut self,
631        popup_window: &object_tree::PopupWindow,
632    ) -> object_tree::PopupWindow {
633        object_tree::PopupWindow {
634            component: Weak::upgrade(&self.use_component(&popup_window.component))
635                .expect("Looking at a known component"),
636            x: popup_window.x.snapshot(self),
637            y: popup_window.y.snapshot(self),
638            close_policy: popup_window.close_policy.clone(),
639            parent_element: self.use_element(&popup_window.parent_element),
640        }
641    }
642
643    fn snapshot_timer(&mut self, popup_window: &object_tree::Timer) -> object_tree::Timer {
644        object_tree::Timer {
645            interval: popup_window.interval.snapshot(self),
646            running: popup_window.running.snapshot(self),
647            triggered: popup_window.triggered.snapshot(self),
648        }
649    }
650
651    fn snapshot_layout_constraints(
652        &mut self,
653        layout_constraints: &layout::LayoutConstraints,
654    ) -> layout::LayoutConstraints {
655        layout::LayoutConstraints {
656            min_width: layout_constraints.min_width.as_ref().map(|lc| lc.snapshot(self)),
657            max_width: layout_constraints.max_width.as_ref().map(|lc| lc.snapshot(self)),
658            min_height: layout_constraints.min_height.as_ref().map(|lc| lc.snapshot(self)),
659            max_height: layout_constraints.max_height.as_ref().map(|lc| lc.snapshot(self)),
660            preferred_width: layout_constraints
661                .preferred_width
662                .as_ref()
663                .map(|lc| lc.snapshot(self)),
664            preferred_height: layout_constraints
665                .preferred_height
666                .as_ref()
667                .map(|lc| lc.snapshot(self)),
668            horizontal_stretch: layout_constraints
669                .horizontal_stretch
670                .as_ref()
671                .map(|lc| lc.snapshot(self)),
672            vertical_stretch: layout_constraints
673                .vertical_stretch
674                .as_ref()
675                .map(|lc| lc.snapshot(self)),
676            fixed_width: layout_constraints.fixed_width,
677            fixed_height: layout_constraints.fixed_height,
678        }
679    }
680
681    fn snapshot_expression(
682        &mut self,
683        expr: &expression_tree::Expression,
684    ) -> expression_tree::Expression {
685        use expression_tree::Expression;
686        match expr {
687            Expression::PropertyReference(nr) => Expression::PropertyReference(nr.snapshot(self)),
688            Expression::ElementReference(el) => {
689                Expression::ElementReference(if let Some(el) = el.upgrade() {
690                    Rc::downgrade(&el)
691                } else {
692                    Weak::default()
693                })
694            }
695            Expression::RepeaterIndexReference { element } => Expression::RepeaterIndexReference {
696                element: if let Some(el) = element.upgrade() {
697                    Rc::downgrade(&el)
698                } else {
699                    Weak::default()
700                },
701            },
702            Expression::RepeaterModelReference { element } => Expression::RepeaterModelReference {
703                element: if let Some(el) = element.upgrade() {
704                    Rc::downgrade(&el)
705                } else {
706                    Weak::default()
707                },
708            },
709            Expression::StoreLocalVariable { name, value } => Expression::StoreLocalVariable {
710                name: name.clone(),
711                value: Box::new(self.snapshot_expression(value)),
712            },
713            Expression::StructFieldAccess { base, name } => Expression::StructFieldAccess {
714                base: Box::new(self.snapshot_expression(base)),
715                name: name.clone(),
716            },
717            Expression::ArrayIndex { array, index } => Expression::ArrayIndex {
718                array: Box::new(self.snapshot_expression(array)),
719                index: Box::new(self.snapshot_expression(index)),
720            },
721            Expression::Cast { from, to } => {
722                Expression::Cast { from: Box::new(self.snapshot_expression(from)), to: to.clone() }
723            }
724            Expression::CodeBlock(exprs) => {
725                Expression::CodeBlock(exprs.iter().map(|e| self.snapshot_expression(e)).collect())
726            }
727            Expression::FunctionCall { function, arguments, source_location } => {
728                Expression::FunctionCall {
729                    function: match function {
730                        Callable::Callback(nr) => Callable::Callback(nr.snapshot(self)),
731                        Callable::Function(nr) => Callable::Function(nr.snapshot(self)),
732                        Callable::Builtin(b) => Callable::Builtin(b.clone()),
733                    },
734                    arguments: arguments.iter().map(|e| self.snapshot_expression(e)).collect(),
735                    source_location: source_location.clone(),
736                }
737            }
738            Expression::SelfAssignment { lhs, rhs, op, node } => Expression::SelfAssignment {
739                lhs: Box::new(self.snapshot_expression(lhs)),
740                rhs: Box::new(self.snapshot_expression(rhs)),
741                op: *op,
742                node: node.clone(),
743            },
744            Expression::BinaryExpression { lhs, rhs, op } => Expression::BinaryExpression {
745                lhs: Box::new(self.snapshot_expression(lhs)),
746                rhs: Box::new(self.snapshot_expression(rhs)),
747                op: *op,
748            },
749            Expression::UnaryOp { sub, op } => {
750                Expression::UnaryOp { sub: Box::new(self.snapshot_expression(sub)), op: *op }
751            }
752            Expression::Condition { condition, true_expr, false_expr } => Expression::Condition {
753                condition: Box::new(self.snapshot_expression(condition)),
754                true_expr: Box::new(self.snapshot_expression(true_expr)),
755                false_expr: Box::new(self.snapshot_expression(false_expr)),
756            },
757            Expression::Array { element_ty, values } => Expression::Array {
758                element_ty: element_ty.clone(),
759                values: values.iter().map(|e| self.snapshot_expression(e)).collect(),
760            },
761            Expression::Struct { ty, values } => Expression::Struct {
762                ty: ty.clone(),
763                values: values
764                    .iter()
765                    .map(|(k, v)| (k.clone(), self.snapshot_expression(v)))
766                    .collect(),
767            },
768            Expression::PathData(path) => Expression::PathData(match path {
769                expression_tree::Path::Elements(path_elements) => expression_tree::Path::Elements(
770                    path_elements
771                        .iter()
772                        .map(|p| {
773                            expression_tree::PathElement {
774                                element_type: p.element_type.clone(), // builtin should be OK to clone
775                                bindings: p
776                                    .bindings
777                                    .iter()
778                                    .map(|(k, v)| {
779                                        (
780                                            k.clone(),
781                                            RefCell::new(
782                                                self.snapshot_binding_expression(&v.borrow()),
783                                            ),
784                                        )
785                                    })
786                                    .collect(),
787                            }
788                        })
789                        .collect(),
790                ),
791                expression_tree::Path::Events(ex1, ex2) => expression_tree::Path::Events(
792                    ex1.iter().map(|e| self.snapshot_expression(e)).collect(),
793                    ex2.iter().map(|e| self.snapshot_expression(e)).collect(),
794                ),
795                expression_tree::Path::Commands(ex) => {
796                    expression_tree::Path::Commands(Box::new(self.snapshot_expression(ex)))
797                }
798            }),
799            Expression::LinearGradient { angle, stops } => Expression::LinearGradient {
800                angle: Box::new(self.snapshot_expression(angle)),
801                stops: stops
802                    .iter()
803                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
804                    .collect(),
805            },
806            Expression::RadialGradient { stops } => Expression::RadialGradient {
807                stops: stops
808                    .iter()
809                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
810                    .collect(),
811            },
812            Expression::ReturnStatement(expr) => Expression::ReturnStatement(
813                expr.as_ref().map(|e| Box::new(self.snapshot_expression(e))),
814            ),
815            Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
816                Expression::LayoutCacheAccess {
817                    layout_cache_prop: layout_cache_prop.snapshot(self),
818                    index: *index,
819                    repeater_index: repeater_index
820                        .as_ref()
821                        .map(|e| Box::new(self.snapshot_expression(e))),
822                }
823            }
824            Expression::MinMax { ty, op, lhs, rhs } => Expression::MinMax {
825                ty: ty.clone(),
826                lhs: Box::new(self.snapshot_expression(lhs)),
827                rhs: Box::new(self.snapshot_expression(rhs)),
828                op: *op,
829            },
830            _ => expr.clone(),
831        }
832    }
833}
834
835pub struct TypeLoader {
836    pub global_type_registry: Rc<RefCell<TypeRegister>>,
837    pub compiler_config: CompilerConfiguration,
838    /// The style that was specified in the compiler configuration, but resolved. So "native" for example is resolved to the concrete
839    /// style.
840    pub resolved_style: String,
841    all_documents: LoadedDocuments,
842}
843
844struct BorrowedTypeLoader<'a> {
845    tl: &'a mut TypeLoader,
846    diag: &'a mut BuildDiagnostics,
847}
848
849impl TypeLoader {
850    pub fn new(
851        global_type_registry: Rc<RefCell<TypeRegister>>,
852        compiler_config: CompilerConfiguration,
853        diag: &mut BuildDiagnostics,
854    ) -> Self {
855        let mut style = compiler_config
856            .style
857            .clone()
858            .or_else(|| std::env::var("SLINT_STYLE").ok())
859            .unwrap_or_else(|| "native".into());
860
861        if style == "native" {
862            style = get_native_style(&mut diag.all_loaded_files);
863        }
864
865        let myself = Self {
866            global_type_registry,
867            compiler_config,
868            resolved_style: style.clone(),
869            all_documents: Default::default(),
870        };
871
872        let mut known_styles = fileaccess::styles();
873        known_styles.push("native");
874        if !known_styles.contains(&style.as_ref())
875            && myself
876                .find_file_in_include_path(None, &format!("{style}/std-widgets.slint"))
877                .is_none()
878        {
879            diag.push_diagnostic_with_span(
880                format!(
881                    "Style {} is not known. Use one of the builtin styles [{}] or make sure your custom style is found in the include directories",
882                    &style,
883                    known_styles.join(", ")
884                ),
885                Default::default(),
886                crate::diagnostics::DiagnosticLevel::Error,
887            );
888        }
889
890        myself
891    }
892
893    pub fn drop_document(&mut self, path: &Path) -> Result<(), std::io::Error> {
894        if let Some((LoadedDocument::Document(doc), _)) = self.all_documents.docs.remove(path) {
895            for dep in &doc.imports {
896                self.all_documents
897                    .dependencies
898                    .entry(Path::new(&dep.file).into())
899                    .or_default()
900                    .remove(path);
901            }
902        }
903        self.all_documents.dependencies.remove(path);
904        if self.all_documents.currently_loading.contains_key(path) {
905            Err(std::io::Error::new(
906                std::io::ErrorKind::InvalidInput,
907                format!("{path:?} is still loading"),
908            ))
909        } else {
910            Ok(())
911        }
912    }
913
914    /// Invalidate a document and all its dependencies.
915    pub fn invalidate_document(&mut self, path: &Path) -> HashSet<PathBuf> {
916        if let Some((d, _)) = self.all_documents.docs.get_mut(path) {
917            if let LoadedDocument::Document(doc) = d {
918                for dep in &doc.imports {
919                    self.all_documents
920                        .dependencies
921                        .entry(Path::new(&dep.file).into())
922                        .or_default()
923                        .remove(path);
924                }
925                match doc.node.take() {
926                    None => {
927                        self.all_documents.docs.remove(path);
928                    }
929                    Some(n) => {
930                        *d = LoadedDocument::Invalidated(n);
931                    }
932                };
933            } else {
934                return HashSet::new();
935            }
936        } else {
937            return HashSet::new();
938        }
939        let deps = self.all_documents.dependencies.remove(path).unwrap_or_default();
940        let mut extra_deps = HashSet::new();
941        for dep in &deps {
942            extra_deps.extend(self.invalidate_document(dep));
943        }
944        extra_deps.extend(deps);
945        extra_deps
946    }
947
948    /// Imports of files that don't have the .slint extension are returned.
949    pub async fn load_dependencies_recursively<'a>(
950        &'a mut self,
951        doc: &'a syntax_nodes::Document,
952        diag: &'a mut BuildDiagnostics,
953        registry_to_populate: &'a Rc<RefCell<TypeRegister>>,
954    ) -> (Vec<ImportedTypes>, Exports) {
955        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
956        Self::load_dependencies_recursively_impl(
957            &state,
958            doc,
959            registry_to_populate,
960            &Default::default(),
961        )
962        .await
963    }
964
965    async fn load_dependencies_recursively_impl<'a: 'b, 'b>(
966        state: &'a RefCell<BorrowedTypeLoader<'a>>,
967        doc: &'b syntax_nodes::Document,
968        registry_to_populate: &'b Rc<RefCell<TypeRegister>>,
969        import_stack: &'b HashSet<PathBuf>,
970    ) -> (Vec<ImportedTypes>, Exports) {
971        let mut imports = vec![];
972        let mut dependencies_futures = vec![];
973        for mut import in Self::collect_dependencies(state, doc) {
974            if matches!(import.import_kind, ImportKind::FileImport) {
975                if let Some((path, _)) = state.borrow().tl.resolve_import_path(
976                    Some(&import.import_uri_token.clone().into()),
977                    &import.file,
978                ) {
979                    import.file = path.to_string_lossy().into_owned();
980                };
981                imports.push(import);
982                continue;
983            }
984            dependencies_futures.push(Box::pin(async move {
985                let file = import.file.as_str();
986                let doc_path = Self::ensure_document_loaded(
987                    state,
988                    file,
989                    Some(import.import_uri_token.clone().into()),
990                    import_stack.clone(),
991                )
992                .await;
993                (import, doc_path)
994            }));
995        }
996
997        let mut reexports = None;
998        let mut has_star_reexport = false;
999        std::future::poll_fn(|cx| {
1000            dependencies_futures.retain_mut(|fut| {
1001                let core::task::Poll::Ready((mut import, doc_path)) = fut.as_mut().poll(cx) else { return true; };
1002                let Some(doc_path) = doc_path else { return false };
1003                let mut state = state.borrow_mut();
1004                let state = &mut *state;
1005                let Some(doc) = state.tl.get_document(&doc_path) else {
1006                    panic!("Just loaded document not available")
1007                };
1008                match &import.import_kind {
1009                    ImportKind::ImportList(imported_types) => {
1010                        let mut imported_types = ImportedName::extract_imported_names(imported_types).peekable();
1011                        if imported_types.peek().is_some() {
1012                            Self::register_imported_types(doc, &import, imported_types, registry_to_populate, state.diag);
1013                        } else {
1014                            state.diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
1015                        }
1016                    }
1017                    ImportKind::ModuleReexport(export_module_syntax_node) => {
1018                        let exports = reexports.get_or_insert_with(Exports::default);
1019                        if let Some(star_reexport) = export_module_syntax_node.ExportModule().and_then(|x| x.child_token(SyntaxKind::Star))
1020                        {
1021                            if has_star_reexport {
1022                                state.diag.push_error("re-exporting modules is only allowed once per file".into(), &star_reexport);
1023                                return false;
1024                            }
1025                            has_star_reexport = true;
1026                            exports.add_reexports(
1027                                doc.exports.iter().map(|(exported_name, compo_or_type)| {
1028                                    let exported_name = ExportedName {
1029                                        name: exported_name.name.clone(),
1030                                        name_ident: (**export_module_syntax_node).clone(),
1031                                    };
1032                                    (exported_name, compo_or_type.clone())
1033                                }),
1034                                state.diag,
1035                            );
1036                        } else if export_module_syntax_node.ExportSpecifier().next().is_none() {
1037                            state.diag.push_error("Import names are missing. Please specify which types you would like to re-export".into(), export_module_syntax_node);
1038                        } else {
1039                            let e = export_module_syntax_node
1040                                .ExportSpecifier()
1041                                .filter_map(|e| {
1042                                    let (imported_name, exported_name) = ExportedName::from_export_specifier(&e);
1043                                    let Some(r) = doc.exports.find(&imported_name) else {
1044                                        state.diag.push_error(format!("No exported type called '{imported_name}' found in \"{}\"", doc_path.display()), &e);
1045                                        return None;
1046                                    };
1047                                    Some((exported_name, r))
1048                                })
1049                                .collect::<Vec<_>>();
1050                            exports.add_reexports(e, state.diag);
1051                        }
1052                    }
1053                    ImportKind::FileImport => {
1054                        unreachable!("FileImport should have been handled above")
1055                    }
1056                }
1057                import.file = doc_path.to_string_lossy().into_owned();
1058                imports.push(import);
1059                false
1060            });
1061            if dependencies_futures.is_empty() {
1062                core::task::Poll::Ready(())
1063            } else {
1064                core::task::Poll::Pending
1065            }
1066        }).await;
1067        (imports, reexports.unwrap_or_default())
1068    }
1069
1070    pub async fn import_component(
1071        &mut self,
1072        file_to_import: &str,
1073        type_name: &str,
1074        diag: &mut BuildDiagnostics,
1075    ) -> Option<Rc<object_tree::Component>> {
1076        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1077        let doc_path =
1078            match Self::ensure_document_loaded(&state, file_to_import, None, Default::default())
1079                .await
1080            {
1081                Some(doc_path) => doc_path,
1082                None => return None,
1083            };
1084
1085        let Some(doc) = self.get_document(&doc_path) else {
1086            panic!("Just loaded document not available")
1087        };
1088
1089        doc.exports.find(type_name).and_then(|compo_or_type| compo_or_type.left())
1090    }
1091
1092    /// Append a possibly relative path to a base path. Returns the data if it resolves to a built-in (compiled-in)
1093    /// file.
1094    pub fn resolve_import_path(
1095        &self,
1096        import_token: Option<&NodeOrToken>,
1097        maybe_relative_path_or_url: &str,
1098    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1099        if let Some(maybe_library_import) = maybe_relative_path_or_url.strip_prefix('@') {
1100            self.find_file_in_library_path(maybe_library_import)
1101        } else {
1102            let referencing_file_or_url =
1103                import_token.and_then(|tok| tok.source_file().map(|s| s.path()));
1104            self.find_file_in_include_path(referencing_file_or_url, maybe_relative_path_or_url)
1105                .or_else(|| {
1106                    referencing_file_or_url
1107                        .and_then(|base_path_or_url| {
1108                            crate::pathutils::join(
1109                                &crate::pathutils::dirname(base_path_or_url),
1110                                &PathBuf::from(maybe_relative_path_or_url),
1111                            )
1112                        })
1113                        .filter(|p| p.exists())
1114                        .map(|p| (p, None))
1115                })
1116        }
1117    }
1118
1119    async fn ensure_document_loaded<'a: 'b, 'b>(
1120        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1121        file_to_import: &'b str,
1122        import_token: Option<NodeOrToken>,
1123        mut import_stack: HashSet<PathBuf>,
1124    ) -> Option<PathBuf> {
1125        let mut borrowed_state = state.borrow_mut();
1126
1127        let (path_canon, builtin) = match borrowed_state
1128            .tl
1129            .resolve_import_path(import_token.as_ref(), file_to_import)
1130        {
1131            Some(x) => {
1132                if let Some(file_name) = x.0.file_name().and_then(|f| f.to_str()) {
1133                    let len = file_to_import.len();
1134                    if !file_to_import.ends_with(file_name)
1135                        && len >= file_name.len()
1136                        && file_name.eq_ignore_ascii_case(
1137                            file_to_import.get(len - file_name.len()..).unwrap_or(""),
1138                        )
1139                    {
1140                        if import_token.as_ref().and_then(|x| x.source_file()).is_some() {
1141                            borrowed_state.diag.push_warning(
1142                                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"),
1143                                &import_token,
1144                            );
1145                        }
1146                    }
1147                }
1148                x
1149            }
1150            None => {
1151                let import_path = crate::pathutils::clean_path(Path::new(file_to_import));
1152                if import_path.exists() {
1153                    if import_token.as_ref().and_then(|x| x.source_file()).is_some() {
1154                        borrowed_state.diag.push_warning(
1155                        format!(
1156                            "Loading \"{file_to_import}\" relative to the work directory is deprecated. Files should be imported relative to their import location",
1157                        ),
1158                        &import_token,
1159                    );
1160                    }
1161                    (import_path, None)
1162                } else {
1163                    // We will load using the `open_import_fallback`
1164                    // Simplify the path to remove the ".."
1165                    let base_path = import_token
1166                        .as_ref()
1167                        .and_then(|tok| tok.source_file().map(|s| s.path()))
1168                        .map_or(PathBuf::new(), |p| p.into());
1169                    let path = crate::pathutils::join(
1170                        &crate::pathutils::dirname(&base_path),
1171                        Path::new(file_to_import),
1172                    )?;
1173                    (path, None)
1174                }
1175            }
1176        };
1177
1178        if !import_stack.insert(path_canon.clone()) {
1179            borrowed_state.diag.push_error(
1180                format!("Recursive import of \"{}\"", path_canon.display()),
1181                &import_token,
1182            );
1183            return None;
1184        }
1185
1186        drop(borrowed_state);
1187
1188        let (is_loaded, doc_node) = core::future::poll_fn(|cx| {
1189            let mut state = state.borrow_mut();
1190            let all_documents = &mut state.tl.all_documents;
1191            match all_documents.currently_loading.entry(path_canon.clone()) {
1192                std::collections::hash_map::Entry::Occupied(mut e) => {
1193                    let waker = cx.waker();
1194                    if !e.get().iter().any(|w| w.will_wake(waker)) {
1195                        e.get_mut().push(cx.waker().clone());
1196                    }
1197                    core::task::Poll::Pending
1198                }
1199                std::collections::hash_map::Entry::Vacant(v) => {
1200                    match all_documents.docs.get(path_canon.as_path()) {
1201                        Some((LoadedDocument::Document(_), _)) => {
1202                            core::task::Poll::Ready((true, None))
1203                        }
1204                        Some((LoadedDocument::Invalidated(doc), errors)) => {
1205                            v.insert(Default::default());
1206                            core::task::Poll::Ready((false, Some((doc.clone(), errors.clone()))))
1207                        }
1208                        None => {
1209                            v.insert(Default::default());
1210                            core::task::Poll::Ready((false, None))
1211                        }
1212                    }
1213                }
1214            }
1215        })
1216        .await;
1217        if is_loaded {
1218            return Some(path_canon);
1219        }
1220
1221        let doc_node = if let Some((doc_node, errors)) = doc_node {
1222            for e in errors {
1223                state.borrow_mut().diag.push_internal_error(e);
1224            }
1225            Some(doc_node)
1226        } else {
1227            let source_code_result = if let Some(builtin) = builtin {
1228                Ok(String::from(
1229                    core::str::from_utf8(builtin)
1230                        .expect("internal error: embedded file is not UTF-8 source code"),
1231                ))
1232            } else {
1233                let fallback = state.borrow().tl.compiler_config.open_import_fallback.clone();
1234                if let Some(fallback) = fallback {
1235                    let result = fallback(path_canon.to_string_lossy().into()).await;
1236                    result.unwrap_or_else(|| std::fs::read_to_string(&path_canon))
1237                } else {
1238                    std::fs::read_to_string(&path_canon)
1239                }
1240            };
1241            match source_code_result {
1242                Ok(source) => syntax_nodes::Document::new(crate::parser::parse(
1243                    source,
1244                    Some(&path_canon),
1245                    state.borrow_mut().diag,
1246                )),
1247                Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
1248                    state.borrow_mut().diag.push_error(
1249                            if file_to_import.starts_with('@') {
1250                                format!(
1251                                    "Cannot find requested import \"{file_to_import}\" in the library search path",
1252                                )
1253                            } else {
1254                                format!(
1255                                    "Cannot find requested import \"{file_to_import}\" in the include search path",
1256                                )
1257                            },
1258                            &import_token,
1259                        );
1260                    None
1261                }
1262                Err(err) => {
1263                    state.borrow_mut().diag.push_error(
1264                        format!(
1265                            "Error reading requested import \"{}\": {}",
1266                            path_canon.display(),
1267                            err
1268                        ),
1269                        &import_token,
1270                    );
1271                    None
1272                }
1273            }
1274        };
1275
1276        let ok = if let Some(doc_node) = doc_node {
1277            Self::load_file_impl(state, &path_canon, doc_node, builtin.is_some(), &import_stack)
1278                .await;
1279            state.borrow_mut().diag.all_loaded_files.insert(path_canon.clone());
1280            true
1281        } else {
1282            false
1283        };
1284
1285        let wakers = state
1286            .borrow_mut()
1287            .tl
1288            .all_documents
1289            .currently_loading
1290            .remove(path_canon.as_path())
1291            .unwrap();
1292        for x in wakers {
1293            x.wake();
1294        }
1295
1296        ok.then_some(path_canon)
1297    }
1298
1299    /// Load a file, and its dependency, running only the import passes.
1300    ///
1301    /// the path must be the canonical path
1302    pub async fn load_file(
1303        &mut self,
1304        path: &Path,
1305        source_path: &Path,
1306        source_code: String,
1307        is_builtin: bool,
1308        diag: &mut BuildDiagnostics,
1309    ) {
1310        let doc_node: syntax_nodes::Document =
1311            crate::parser::parse(source_code, Some(source_path), diag).into();
1312        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1313        Self::load_file_impl(&state, path, doc_node, is_builtin, &Default::default()).await;
1314    }
1315
1316    /// Reload a cached file
1317    ///
1318    /// The path must be canonical
1319    pub async fn reload_cached_file(&mut self, path: &Path, diag: &mut BuildDiagnostics) {
1320        let Some((LoadedDocument::Invalidated(doc_node), errors)) =
1321            self.all_documents.docs.get(path)
1322        else {
1323            return;
1324        };
1325        let doc_node = doc_node.clone();
1326        for e in errors {
1327            diag.push_internal_error(e.clone());
1328        }
1329        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1330        Self::load_file_impl(&state, path, doc_node, false, &Default::default()).await;
1331    }
1332
1333    /// Load a file, and its dependency, running the full set of passes.
1334    ///
1335    /// the path must be the canonical path
1336    pub async fn load_root_file(
1337        &mut self,
1338        path: &Path,
1339        source_path: &Path,
1340        source_code: String,
1341        keep_raw: bool,
1342        diag: &mut BuildDiagnostics,
1343    ) -> (PathBuf, Option<TypeLoader>) {
1344        let path = crate::pathutils::clean_path(path);
1345        let doc_node: syntax_nodes::Document =
1346            crate::parser::parse(source_code, Some(source_path), diag).into();
1347        let parse_errors = diag.iter().cloned().collect();
1348        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1349        let (path, mut doc) =
1350            Self::load_doc_no_pass(&state, &path, doc_node, false, &Default::default()).await;
1351
1352        let mut state = state.borrow_mut();
1353        let state = &mut *state;
1354        let raw_type_loader = if !state.diag.has_errors() {
1355            crate::passes::run_passes(&mut doc, state.tl, keep_raw, state.diag).await
1356        } else {
1357            None
1358        };
1359        state
1360            .tl
1361            .all_documents
1362            .docs
1363            .insert(path.clone(), (LoadedDocument::Document(doc), parse_errors));
1364        (path, raw_type_loader)
1365    }
1366
1367    async fn load_file_impl<'a>(
1368        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1369        path: &Path,
1370        doc_node: syntax_nodes::Document,
1371        is_builtin: bool,
1372        import_stack: &HashSet<PathBuf>,
1373    ) {
1374        let parse_errors = state
1375            .borrow()
1376            .diag
1377            .iter()
1378            .filter(|e| e.source_file().is_some_and(|f| f == path))
1379            .cloned()
1380            .collect();
1381        let (path, doc) =
1382            Self::load_doc_no_pass(state, path, doc_node, is_builtin, import_stack).await;
1383
1384        let mut state = state.borrow_mut();
1385        let state = &mut *state;
1386        if !state.diag.has_errors() {
1387            crate::passes::run_import_passes(&doc, state.tl, state.diag);
1388        }
1389        for dep in &doc.imports {
1390            state
1391                .tl
1392                .all_documents
1393                .dependencies
1394                .entry(Path::new(&dep.file).into())
1395                .or_default()
1396                .insert(path.clone());
1397        }
1398        state.tl.all_documents.docs.insert(path, (LoadedDocument::Document(doc), parse_errors));
1399    }
1400
1401    async fn load_doc_no_pass<'a>(
1402        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1403        path: &Path,
1404        dependency_doc: syntax_nodes::Document,
1405        is_builtin: bool,
1406        import_stack: &HashSet<PathBuf>,
1407    ) -> (PathBuf, Document) {
1408        let dependency_registry =
1409            Rc::new(RefCell::new(TypeRegister::new(&state.borrow().tl.global_type_registry)));
1410        dependency_registry.borrow_mut().expose_internal_types =
1411            is_builtin || state.borrow().tl.compiler_config.enable_experimental;
1412        let (imports, reexports) = Self::load_dependencies_recursively_impl(
1413            state,
1414            &dependency_doc,
1415            &dependency_registry,
1416            import_stack,
1417        )
1418        .await;
1419
1420        if state.borrow().diag.has_errors() {
1421            // If there was error (esp parse error) we don't want to report further error in this document.
1422            // because they might be nonsense (TODO: we should check that the parse error were really in this document).
1423            // But we still want to create a document to give better error messages in the root document.
1424            let mut ignore_diag = BuildDiagnostics::default();
1425            ignore_diag.push_error_with_span(
1426                "Dummy error because some of the code asserts there was an error".into(),
1427                Default::default(),
1428            );
1429            let doc = crate::object_tree::Document::from_node(
1430                dependency_doc,
1431                imports,
1432                reexports,
1433                &mut ignore_diag,
1434                &dependency_registry,
1435            );
1436            return (path.to_owned(), doc);
1437        }
1438        let mut state = state.borrow_mut();
1439        let state = &mut *state;
1440        let doc = crate::object_tree::Document::from_node(
1441            dependency_doc,
1442            imports,
1443            reexports,
1444            state.diag,
1445            &dependency_registry,
1446        );
1447        (path.to_owned(), doc)
1448    }
1449
1450    fn register_imported_types(
1451        doc: &Document,
1452        import: &ImportedTypes,
1453        imported_types: impl Iterator<Item = ImportedName>,
1454        registry_to_populate: &Rc<RefCell<TypeRegister>>,
1455        build_diagnostics: &mut BuildDiagnostics,
1456    ) {
1457        for import_name in imported_types {
1458            let imported_type = doc.exports.find(&import_name.external_name);
1459
1460            let imported_type = match imported_type {
1461                Some(ty) => ty,
1462                None => {
1463                    build_diagnostics.push_error(
1464                        format!(
1465                            "No exported type called '{}' found in \"{}\"",
1466                            import_name.external_name, import.file
1467                        ),
1468                        &import.import_uri_token,
1469                    );
1470                    continue;
1471                }
1472            };
1473
1474            match imported_type {
1475                itertools::Either::Left(c) => {
1476                    registry_to_populate.borrow_mut().add_with_name(import_name.internal_name, c)
1477                }
1478                itertools::Either::Right(ty) => registry_to_populate
1479                    .borrow_mut()
1480                    .insert_type_with_name(ty, import_name.internal_name),
1481            };
1482        }
1483    }
1484
1485    /// Lookup a library and filename and try to find the absolute filename based on the library path
1486    fn find_file_in_library_path(
1487        &self,
1488        maybe_library_import: &str,
1489    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1490        let (library, file) = maybe_library_import
1491            .splitn(2, '/')
1492            .collect_tuple()
1493            .map(|(library, path)| (library, Some(path)))
1494            .unwrap_or((maybe_library_import, None));
1495        self.compiler_config.library_paths.get(library).and_then(|library_path| {
1496            let path = match file {
1497                // "@library/file.slint" -> "/path/to/library/" + "file.slint"
1498                Some(file) => library_path.join(file),
1499                // "@library" -> "/path/to/library/lib.slint"
1500                None => library_path.clone(),
1501            };
1502            crate::fileaccess::load_file(path.as_path())
1503                .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1504        })
1505    }
1506
1507    /// Lookup a filename and try to find the absolute filename based on the include path or
1508    /// the current file directory
1509    pub fn find_file_in_include_path(
1510        &self,
1511        referencing_file: Option<&Path>,
1512        file_to_import: &str,
1513    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1514        // The directory of the current file is the first in the list of include directories.
1515        referencing_file
1516            .map(base_directory)
1517            .into_iter()
1518            .chain(self.compiler_config.include_paths.iter().map(PathBuf::as_path).map(
1519                |include_path| {
1520                    let base = referencing_file.map(Path::to_path_buf).unwrap_or_default();
1521                    crate::pathutils::join(&crate::pathutils::dirname(&base), include_path)
1522                        .unwrap_or_else(|| include_path.to_path_buf())
1523                },
1524            ))
1525            .chain(
1526                (file_to_import == "std-widgets.slint"
1527                    || referencing_file.is_some_and(|x| x.starts_with("builtin:/")))
1528                .then(|| format!("builtin:/{}", self.resolved_style).into()),
1529            )
1530            .find_map(|include_dir| {
1531                let candidate = crate::pathutils::join(&include_dir, Path::new(file_to_import))?;
1532                crate::fileaccess::load_file(&candidate)
1533                    .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1534            })
1535    }
1536
1537    fn collect_dependencies<'a: 'b, 'b>(
1538        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1539        doc: &'b syntax_nodes::Document,
1540    ) -> impl Iterator<Item = ImportedTypes> + 'a {
1541        doc.ImportSpecifier()
1542            .map(|import| {
1543                let maybe_import_uri = import.child_token(SyntaxKind::StringLiteral);
1544                let kind = import
1545                    .ImportIdentifierList()
1546                    .map(ImportKind::ImportList)
1547                    .unwrap_or(ImportKind::FileImport);
1548                (maybe_import_uri, kind)
1549            })
1550            .chain(
1551                // process `export ... from "foo"`
1552                doc.ExportsList().filter_map(|exports| {
1553                    exports.ExportModule().map(|reexport| {
1554                        let maybe_import_uri = reexport.child_token(SyntaxKind::StringLiteral);
1555                        (maybe_import_uri, ImportKind::ModuleReexport(exports))
1556                    })
1557                }),
1558            )
1559            .filter_map(|(maybe_import_uri, type_specifier)| {
1560                let import_uri = match maybe_import_uri {
1561                    Some(import_uri) => import_uri,
1562                    None => {
1563                        debug_assert!(state.borrow().diag.has_errors());
1564                        return None;
1565                    }
1566                };
1567                let path_to_import = import_uri.text().to_string();
1568                let path_to_import = path_to_import.trim_matches('\"').to_string();
1569
1570                if path_to_import.is_empty() {
1571                    state
1572                        .borrow_mut()
1573                        .diag
1574                        .push_error("Unexpected empty import url".to_owned(), &import_uri);
1575                    return None;
1576                }
1577
1578                Some(ImportedTypes {
1579                    import_uri_token: import_uri,
1580                    import_kind: type_specifier,
1581                    file: path_to_import,
1582                })
1583            })
1584    }
1585
1586    /// Return a document if it was already loaded
1587    pub fn get_document<'b>(&'b self, path: &Path) -> Option<&'b object_tree::Document> {
1588        let path = crate::pathutils::clean_path(path);
1589        if let Some((LoadedDocument::Document(d), _)) = self.all_documents.docs.get(&path) {
1590            Some(d)
1591        } else {
1592            None
1593        }
1594    }
1595
1596    /// Return an iterator over all the loaded file path
1597    pub fn all_files(&self) -> impl Iterator<Item = &PathBuf> {
1598        self.all_documents.docs.keys()
1599    }
1600
1601    /// Returns an iterator over all the loaded documents
1602    pub fn all_documents(&self) -> impl Iterator<Item = &object_tree::Document> + '_ {
1603        self.all_documents.docs.values().filter_map(|(d, _)| match d {
1604            LoadedDocument::Document(d) => Some(d),
1605            LoadedDocument::Invalidated(_) => None,
1606        })
1607    }
1608
1609    /// Returns an iterator over all the loaded documents
1610    pub fn all_file_documents(
1611        &self,
1612    ) -> impl Iterator<Item = (&PathBuf, &syntax_nodes::Document)> + '_ {
1613        self.all_documents.docs.iter().filter_map(|(p, (d, _))| {
1614            Some((
1615                p,
1616                match d {
1617                    LoadedDocument::Document(d) => d.node.as_ref()?,
1618                    LoadedDocument::Invalidated(d) => d,
1619                },
1620            ))
1621        })
1622    }
1623}
1624
1625fn get_native_style(all_loaded_files: &mut std::collections::BTreeSet<PathBuf>) -> String {
1626    // Try to get the value written by the i-slint-backend-selector's build script
1627
1628    // It is in the target/xxx/build directory
1629    let target_path = std::env::var_os("OUT_DIR")
1630        .and_then(|path| {
1631            // Same logic as in i-slint-backend-selector's build script to get the path
1632            crate::pathutils::join(Path::new(&path), Path::new("../../SLINT_DEFAULT_STYLE.txt"))
1633        })
1634        .or_else(|| {
1635            // When we are called from a slint!, OUT_DIR is only defined when the crate having the macro has a build.rs script.
1636            // As a fallback, try to parse the rustc arguments
1637            // https://stackoverflow.com/questions/60264534/getting-the-target-folder-from-inside-a-rust-proc-macro
1638            let mut args = std::env::args();
1639            let mut out_dir = None;
1640            while let Some(arg) = args.next() {
1641                if arg == "--out-dir" {
1642                    out_dir = args.next();
1643                    break;
1644                }
1645            }
1646            out_dir.and_then(|od| {
1647                crate::pathutils::join(
1648                    Path::new(&od),
1649                    Path::new("../build/SLINT_DEFAULT_STYLE.txt"),
1650                )
1651            })
1652        });
1653
1654    if let Some(style) = target_path.and_then(|target_path| {
1655        std::fs::read_to_string(&target_path)
1656            .map(|style| {
1657                all_loaded_files.insert(target_path);
1658                style.trim().into()
1659            })
1660            .ok()
1661    }) {
1662        return style;
1663    }
1664    i_slint_common::get_native_style(false, &std::env::var("TARGET").unwrap_or_default()).into()
1665}
1666
1667/// return the base directory from which imports are loaded
1668///
1669/// For a .slint file, this is the parent directory.
1670/// For a .rs file, this is relative to the CARGO_MANIFEST_DIR
1671///
1672/// Note: this function is only called for .rs path as part of the LSP or viewer.
1673/// Because from a proc_macro, we don't actually know the path of the current file, and this
1674/// is why we must be relative to CARGO_MANIFEST_DIR.
1675pub fn base_directory(referencing_file: &Path) -> PathBuf {
1676    if referencing_file.extension().is_some_and(|e| e == "rs") {
1677        // 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.
1678        let mut candidate = referencing_file;
1679        loop {
1680            candidate =
1681                if let Some(c) = candidate.parent() { c } else { break referencing_file.parent() };
1682
1683            if candidate.join("Cargo.toml").exists() {
1684                break Some(candidate);
1685            }
1686        }
1687    } else {
1688        referencing_file.parent()
1689    }
1690    .map_or_else(Default::default, |p| p.to_path_buf())
1691}
1692
1693#[test]
1694fn test_dependency_loading() {
1695    let test_source_path: PathBuf =
1696        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1697
1698    let mut incdir = test_source_path.clone();
1699    incdir.push("incpath");
1700
1701    let mut compiler_config =
1702        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1703    compiler_config.include_paths = vec![incdir];
1704    compiler_config.library_paths =
1705        HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
1706    compiler_config.style = Some("fluent".into());
1707
1708    let mut main_test_path = test_source_path;
1709    main_test_path.push("dependency_test_main.slint");
1710
1711    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1712    let doc_node = crate::parser::parse_file(main_test_path, &mut test_diags).unwrap();
1713
1714    let doc_node: syntax_nodes::Document = doc_node.into();
1715
1716    let global_registry = TypeRegister::builtin();
1717
1718    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1719
1720    let mut build_diagnostics = BuildDiagnostics::default();
1721
1722    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1723
1724    let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
1725        &doc_node,
1726        &mut build_diagnostics,
1727        &registry,
1728    ));
1729
1730    assert!(!test_diags.has_errors());
1731    assert!(!build_diagnostics.has_errors());
1732    assert_eq!(foreign_imports.len(), 3);
1733    assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
1734}
1735
1736#[test]
1737fn test_dependency_loading_from_rust() {
1738    let test_source_path: PathBuf =
1739        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1740
1741    let mut incdir = test_source_path.clone();
1742    incdir.push("incpath");
1743
1744    let mut compiler_config =
1745        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1746    compiler_config.include_paths = vec![incdir];
1747    compiler_config.library_paths =
1748        HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
1749    compiler_config.style = Some("fluent".into());
1750
1751    let mut main_test_path = test_source_path;
1752    main_test_path.push("some_rust_file.rs");
1753
1754    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1755    let doc_node = crate::parser::parse_file(main_test_path, &mut test_diags).unwrap();
1756
1757    let doc_node: syntax_nodes::Document = doc_node.into();
1758
1759    let global_registry = TypeRegister::builtin();
1760
1761    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1762
1763    let mut build_diagnostics = BuildDiagnostics::default();
1764
1765    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1766
1767    let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
1768        &doc_node,
1769        &mut build_diagnostics,
1770        &registry,
1771    ));
1772
1773    assert!(!test_diags.has_errors());
1774    assert!(test_diags.is_empty()); // also no warnings
1775    assert!(!build_diagnostics.has_errors());
1776    assert!(build_diagnostics.is_empty()); // also no warnings
1777    assert_eq!(foreign_imports.len(), 3);
1778    assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
1779}
1780
1781#[test]
1782fn test_load_from_callback_ok() {
1783    let ok = Rc::new(core::cell::Cell::new(false));
1784    let ok_ = ok.clone();
1785
1786    let mut compiler_config =
1787        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1788    compiler_config.style = Some("fluent".into());
1789    compiler_config.open_import_fallback = Some(Rc::new(move |path| {
1790        let ok_ = ok_.clone();
1791        Box::pin(async move {
1792            assert_eq!(path.replace('\\', "/"), "../FooBar.slint");
1793            assert!(!ok_.get());
1794            ok_.set(true);
1795            Some(Ok("export XX := Rectangle {} ".to_owned()))
1796        })
1797    }));
1798
1799    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1800    let doc_node = crate::parser::parse(
1801        r#"
1802/* ... */
1803import { XX } from "../Ab/.././FooBar.slint";
1804X := XX {}
1805"#
1806        .into(),
1807        Some(std::path::Path::new("HELLO")),
1808        &mut test_diags,
1809    );
1810
1811    let doc_node: syntax_nodes::Document = doc_node.into();
1812    let global_registry = TypeRegister::builtin();
1813    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1814    let mut build_diagnostics = BuildDiagnostics::default();
1815    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1816    spin_on::spin_on(loader.load_dependencies_recursively(
1817        &doc_node,
1818        &mut build_diagnostics,
1819        &registry,
1820    ));
1821    assert!(ok.get());
1822    assert!(!test_diags.has_errors());
1823    assert!(!build_diagnostics.has_errors());
1824}
1825
1826#[test]
1827fn test_load_error_twice() {
1828    let mut compiler_config =
1829        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1830    compiler_config.style = Some("fluent".into());
1831    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1832
1833    let doc_node = crate::parser::parse(
1834        r#"
1835/* ... */
1836import { XX } from "error.slint";
1837component Foo { XX {} }
1838"#
1839        .into(),
1840        Some(std::path::Path::new("HELLO")),
1841        &mut test_diags,
1842    );
1843
1844    let doc_node: syntax_nodes::Document = doc_node.into();
1845    let global_registry = TypeRegister::builtin();
1846    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1847    let mut build_diagnostics = BuildDiagnostics::default();
1848    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1849    spin_on::spin_on(loader.load_dependencies_recursively(
1850        &doc_node,
1851        &mut build_diagnostics,
1852        &registry,
1853    ));
1854    assert!(!test_diags.has_errors());
1855    assert!(build_diagnostics.has_errors());
1856    let diags = build_diagnostics.to_string_vec();
1857    assert_eq!(
1858        diags,
1859        &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
1860    );
1861    // Try loading another time with the same registry
1862    let mut build_diagnostics = BuildDiagnostics::default();
1863    spin_on::spin_on(loader.load_dependencies_recursively(
1864        &doc_node,
1865        &mut build_diagnostics,
1866        &registry,
1867    ));
1868    assert!(build_diagnostics.has_errors());
1869    let diags = build_diagnostics.to_string_vec();
1870    assert_eq!(
1871        diags,
1872        &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
1873    );
1874}
1875
1876#[test]
1877fn test_manual_import() {
1878    let mut compiler_config =
1879        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1880    compiler_config.style = Some("fluent".into());
1881    let global_registry = TypeRegister::builtin();
1882    let mut build_diagnostics = BuildDiagnostics::default();
1883    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1884
1885    let maybe_button_type = spin_on::spin_on(loader.import_component(
1886        "std-widgets.slint",
1887        "Button",
1888        &mut build_diagnostics,
1889    ));
1890
1891    assert!(!build_diagnostics.has_errors());
1892    assert!(maybe_button_type.is_some());
1893}
1894
1895#[test]
1896fn test_builtin_style() {
1897    let test_source_path: PathBuf =
1898        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1899
1900    let incdir = test_source_path.join("custom_style");
1901
1902    let mut compiler_config =
1903        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1904    compiler_config.include_paths = vec![incdir];
1905    compiler_config.style = Some("fluent".into());
1906
1907    let global_registry = TypeRegister::builtin();
1908    let mut build_diagnostics = BuildDiagnostics::default();
1909    let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1910
1911    assert!(!build_diagnostics.has_errors());
1912}
1913
1914#[test]
1915fn test_user_style() {
1916    let test_source_path: PathBuf =
1917        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1918
1919    let incdir = test_source_path.join("custom_style");
1920
1921    let mut compiler_config =
1922        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1923    compiler_config.include_paths = vec![incdir];
1924    compiler_config.style = Some("TestStyle".into());
1925
1926    let global_registry = TypeRegister::builtin();
1927    let mut build_diagnostics = BuildDiagnostics::default();
1928    let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1929
1930    assert!(!build_diagnostics.has_errors());
1931}
1932
1933#[test]
1934fn test_unknown_style() {
1935    let test_source_path: PathBuf =
1936        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1937
1938    let incdir = test_source_path.join("custom_style");
1939
1940    let mut compiler_config =
1941        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1942    compiler_config.include_paths = vec![incdir];
1943    compiler_config.style = Some("FooBar".into());
1944
1945    let global_registry = TypeRegister::builtin();
1946    let mut build_diagnostics = BuildDiagnostics::default();
1947    let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1948
1949    assert!(build_diagnostics.has_errors());
1950    let diags = build_diagnostics.to_string_vec();
1951    assert_eq!(diags.len(), 1);
1952    assert!(diags[0].starts_with("Style FooBar is not known. Use one of the builtin styles ["));
1953}
1954
1955#[test]
1956fn test_library_import() {
1957    let test_source_path: PathBuf =
1958        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
1959
1960    let library_paths = HashMap::from([
1961        ("libdir".into(), test_source_path.clone()),
1962        ("libfile.slint".into(), test_source_path.join("lib.slint")),
1963    ]);
1964
1965    let mut compiler_config =
1966        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1967    compiler_config.library_paths = library_paths;
1968    compiler_config.style = Some("fluent".into());
1969    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1970
1971    let doc_node = crate::parser::parse(
1972        r#"
1973/* ... */
1974import { LibraryType } from "@libfile.slint";
1975import { LibraryHelperType } from "@libdir/library_helper_type.slint";
1976"#
1977        .into(),
1978        Some(std::path::Path::new("HELLO")),
1979        &mut test_diags,
1980    );
1981
1982    let doc_node: syntax_nodes::Document = doc_node.into();
1983    let global_registry = TypeRegister::builtin();
1984    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1985    let mut build_diagnostics = BuildDiagnostics::default();
1986    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1987    spin_on::spin_on(loader.load_dependencies_recursively(
1988        &doc_node,
1989        &mut build_diagnostics,
1990        &registry,
1991    ));
1992    assert!(!test_diags.has_errors());
1993    assert!(!build_diagnostics.has_errors());
1994}
1995
1996#[test]
1997fn test_library_import_errors() {
1998    let test_source_path: PathBuf =
1999        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
2000
2001    let library_paths = HashMap::from([
2002        ("libdir".into(), test_source_path.clone()),
2003        ("libfile.slint".into(), test_source_path.join("lib.slint")),
2004    ]);
2005
2006    let mut compiler_config =
2007        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2008    compiler_config.library_paths = library_paths;
2009    compiler_config.style = Some("fluent".into());
2010    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2011
2012    let doc_node = crate::parser::parse(
2013        r#"
2014/* ... */
2015import { A } from "@libdir";
2016import { B } from "@libdir/unknown.slint";
2017import { C } from "@libfile.slint/unknown.slint";
2018import { D } from "@unknown";
2019import { E } from "@unknown/lib.slint";
2020"#
2021        .into(),
2022        Some(std::path::Path::new("HELLO")),
2023        &mut test_diags,
2024    );
2025
2026    let doc_node: syntax_nodes::Document = doc_node.into();
2027    let global_registry = TypeRegister::builtin();
2028    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
2029    let mut build_diagnostics = BuildDiagnostics::default();
2030    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
2031    spin_on::spin_on(loader.load_dependencies_recursively(
2032        &doc_node,
2033        &mut build_diagnostics,
2034        &registry,
2035    ));
2036    assert!(!test_diags.has_errors());
2037    assert!(build_diagnostics.has_errors());
2038    let diags = build_diagnostics.to_string_vec();
2039    assert_eq!(diags.len(), 5);
2040    assert!(diags[0].starts_with(&format!(
2041        "HELLO:3: Error reading requested import \"{}\": ",
2042        test_source_path.to_string_lossy()
2043    )));
2044    assert_eq!(&diags[1], "HELLO:4: Cannot find requested import \"@libdir/unknown.slint\" in the library search path");
2045    assert_eq!(&diags[2], "HELLO:5: Cannot find requested import \"@libfile.slint/unknown.slint\" in the library search path");
2046    assert_eq!(
2047        &diags[3],
2048        "HELLO:6: Cannot find requested import \"@unknown\" in the library search path"
2049    );
2050    assert_eq!(
2051        &diags[4],
2052        "HELLO:7: Cannot find requested import \"@unknown/lib.slint\" in the library search path"
2053    );
2054}
2055
2056#[test]
2057fn test_snapshotting() {
2058    let mut type_loader = TypeLoader::new(
2059        crate::typeregister::TypeRegister::builtin(),
2060        crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter),
2061        &mut BuildDiagnostics::default(),
2062    );
2063
2064    let path = PathBuf::from("/tmp/test.slint");
2065    let mut diag = BuildDiagnostics::default();
2066    spin_on::spin_on(type_loader.load_file(
2067        &path,
2068        &path,
2069        "export component Foobar inherits Rectangle { }".to_string(),
2070        false,
2071        &mut diag,
2072    ));
2073
2074    assert!(!diag.has_errors());
2075
2076    let doc = type_loader.get_document(&path).unwrap();
2077    let c = doc.inner_components.first().unwrap();
2078    assert_eq!(c.id, "Foobar");
2079    let root_element = c.root_element.clone();
2080    assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2081
2082    let copy = snapshot(&type_loader).unwrap();
2083
2084    let doc = copy.get_document(&path).unwrap();
2085    let c = doc.inner_components.first().unwrap();
2086    assert_eq!(c.id, "Foobar");
2087    let root_element = c.root_element.clone();
2088    assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2089}