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, timer: &object_tree::Timer) -> object_tree::Timer {
644        object_tree::Timer {
645            interval: timer.interval.snapshot(self),
646            running: timer.running.snapshot(self),
647            triggered: timer.triggered.snapshot(self),
648            element: timer.element.clone(),
649        }
650    }
651
652    fn snapshot_layout_constraints(
653        &mut self,
654        layout_constraints: &layout::LayoutConstraints,
655    ) -> layout::LayoutConstraints {
656        layout::LayoutConstraints {
657            min_width: layout_constraints.min_width.as_ref().map(|lc| lc.snapshot(self)),
658            max_width: layout_constraints.max_width.as_ref().map(|lc| lc.snapshot(self)),
659            min_height: layout_constraints.min_height.as_ref().map(|lc| lc.snapshot(self)),
660            max_height: layout_constraints.max_height.as_ref().map(|lc| lc.snapshot(self)),
661            preferred_width: layout_constraints
662                .preferred_width
663                .as_ref()
664                .map(|lc| lc.snapshot(self)),
665            preferred_height: layout_constraints
666                .preferred_height
667                .as_ref()
668                .map(|lc| lc.snapshot(self)),
669            horizontal_stretch: layout_constraints
670                .horizontal_stretch
671                .as_ref()
672                .map(|lc| lc.snapshot(self)),
673            vertical_stretch: layout_constraints
674                .vertical_stretch
675                .as_ref()
676                .map(|lc| lc.snapshot(self)),
677            fixed_width: layout_constraints.fixed_width,
678            fixed_height: layout_constraints.fixed_height,
679        }
680    }
681
682    fn snapshot_expression(
683        &mut self,
684        expr: &expression_tree::Expression,
685    ) -> expression_tree::Expression {
686        use expression_tree::Expression;
687        match expr {
688            Expression::PropertyReference(nr) => Expression::PropertyReference(nr.snapshot(self)),
689            Expression::ElementReference(el) => {
690                Expression::ElementReference(if let Some(el) = el.upgrade() {
691                    Rc::downgrade(&el)
692                } else {
693                    Weak::default()
694                })
695            }
696            Expression::RepeaterIndexReference { element } => Expression::RepeaterIndexReference {
697                element: if let Some(el) = element.upgrade() {
698                    Rc::downgrade(&el)
699                } else {
700                    Weak::default()
701                },
702            },
703            Expression::RepeaterModelReference { element } => Expression::RepeaterModelReference {
704                element: if let Some(el) = element.upgrade() {
705                    Rc::downgrade(&el)
706                } else {
707                    Weak::default()
708                },
709            },
710            Expression::StoreLocalVariable { name, value } => Expression::StoreLocalVariable {
711                name: name.clone(),
712                value: Box::new(self.snapshot_expression(value)),
713            },
714            Expression::StructFieldAccess { base, name } => Expression::StructFieldAccess {
715                base: Box::new(self.snapshot_expression(base)),
716                name: name.clone(),
717            },
718            Expression::ArrayIndex { array, index } => Expression::ArrayIndex {
719                array: Box::new(self.snapshot_expression(array)),
720                index: Box::new(self.snapshot_expression(index)),
721            },
722            Expression::Cast { from, to } => {
723                Expression::Cast { from: Box::new(self.snapshot_expression(from)), to: to.clone() }
724            }
725            Expression::CodeBlock(exprs) => {
726                Expression::CodeBlock(exprs.iter().map(|e| self.snapshot_expression(e)).collect())
727            }
728            Expression::FunctionCall { function, arguments, source_location } => {
729                Expression::FunctionCall {
730                    function: match function {
731                        Callable::Callback(nr) => Callable::Callback(nr.snapshot(self)),
732                        Callable::Function(nr) => Callable::Function(nr.snapshot(self)),
733                        Callable::Builtin(b) => Callable::Builtin(b.clone()),
734                    },
735                    arguments: arguments.iter().map(|e| self.snapshot_expression(e)).collect(),
736                    source_location: source_location.clone(),
737                }
738            }
739            Expression::SelfAssignment { lhs, rhs, op, node } => Expression::SelfAssignment {
740                lhs: Box::new(self.snapshot_expression(lhs)),
741                rhs: Box::new(self.snapshot_expression(rhs)),
742                op: *op,
743                node: node.clone(),
744            },
745            Expression::BinaryExpression { lhs, rhs, op } => Expression::BinaryExpression {
746                lhs: Box::new(self.snapshot_expression(lhs)),
747                rhs: Box::new(self.snapshot_expression(rhs)),
748                op: *op,
749            },
750            Expression::UnaryOp { sub, op } => {
751                Expression::UnaryOp { sub: Box::new(self.snapshot_expression(sub)), op: *op }
752            }
753            Expression::Condition { condition, true_expr, false_expr } => Expression::Condition {
754                condition: Box::new(self.snapshot_expression(condition)),
755                true_expr: Box::new(self.snapshot_expression(true_expr)),
756                false_expr: Box::new(self.snapshot_expression(false_expr)),
757            },
758            Expression::Array { element_ty, values } => Expression::Array {
759                element_ty: element_ty.clone(),
760                values: values.iter().map(|e| self.snapshot_expression(e)).collect(),
761            },
762            Expression::Struct { ty, values } => Expression::Struct {
763                ty: ty.clone(),
764                values: values
765                    .iter()
766                    .map(|(k, v)| (k.clone(), self.snapshot_expression(v)))
767                    .collect(),
768            },
769            Expression::PathData(path) => Expression::PathData(match path {
770                expression_tree::Path::Elements(path_elements) => expression_tree::Path::Elements(
771                    path_elements
772                        .iter()
773                        .map(|p| {
774                            expression_tree::PathElement {
775                                element_type: p.element_type.clone(), // builtin should be OK to clone
776                                bindings: p
777                                    .bindings
778                                    .iter()
779                                    .map(|(k, v)| {
780                                        (
781                                            k.clone(),
782                                            RefCell::new(
783                                                self.snapshot_binding_expression(&v.borrow()),
784                                            ),
785                                        )
786                                    })
787                                    .collect(),
788                            }
789                        })
790                        .collect(),
791                ),
792                expression_tree::Path::Events(ex1, ex2) => expression_tree::Path::Events(
793                    ex1.iter().map(|e| self.snapshot_expression(e)).collect(),
794                    ex2.iter().map(|e| self.snapshot_expression(e)).collect(),
795                ),
796                expression_tree::Path::Commands(ex) => {
797                    expression_tree::Path::Commands(Box::new(self.snapshot_expression(ex)))
798                }
799            }),
800            Expression::LinearGradient { angle, stops } => Expression::LinearGradient {
801                angle: Box::new(self.snapshot_expression(angle)),
802                stops: stops
803                    .iter()
804                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
805                    .collect(),
806            },
807            Expression::RadialGradient { stops } => Expression::RadialGradient {
808                stops: stops
809                    .iter()
810                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
811                    .collect(),
812            },
813            Expression::ConicGradient { stops } => Expression::ConicGradient {
814                stops: stops
815                    .iter()
816                    .map(|(e1, e2)| (self.snapshot_expression(e1), self.snapshot_expression(e2)))
817                    .collect(),
818            },
819            Expression::ReturnStatement(expr) => Expression::ReturnStatement(
820                expr.as_ref().map(|e| Box::new(self.snapshot_expression(e))),
821            ),
822            Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
823                Expression::LayoutCacheAccess {
824                    layout_cache_prop: layout_cache_prop.snapshot(self),
825                    index: *index,
826                    repeater_index: repeater_index
827                        .as_ref()
828                        .map(|e| Box::new(self.snapshot_expression(e))),
829                }
830            }
831            Expression::MinMax { ty, op, lhs, rhs } => Expression::MinMax {
832                ty: ty.clone(),
833                lhs: Box::new(self.snapshot_expression(lhs)),
834                rhs: Box::new(self.snapshot_expression(rhs)),
835                op: *op,
836            },
837            _ => expr.clone(),
838        }
839    }
840}
841
842pub struct TypeLoader {
843    pub global_type_registry: Rc<RefCell<TypeRegister>>,
844    pub compiler_config: CompilerConfiguration,
845    /// The style that was specified in the compiler configuration, but resolved. So "native" for example is resolved to the concrete
846    /// style.
847    pub resolved_style: String,
848    all_documents: LoadedDocuments,
849}
850
851struct BorrowedTypeLoader<'a> {
852    tl: &'a mut TypeLoader,
853    diag: &'a mut BuildDiagnostics,
854}
855
856impl TypeLoader {
857    pub fn new(
858        global_type_registry: Rc<RefCell<TypeRegister>>,
859        compiler_config: CompilerConfiguration,
860        diag: &mut BuildDiagnostics,
861    ) -> Self {
862        let mut style = compiler_config
863            .style
864            .clone()
865            .or_else(|| std::env::var("SLINT_STYLE").ok())
866            .unwrap_or_else(|| "native".into());
867
868        if style == "native" {
869            style = get_native_style(&mut diag.all_loaded_files);
870        }
871
872        let myself = Self {
873            global_type_registry,
874            compiler_config,
875            resolved_style: style.clone(),
876            all_documents: Default::default(),
877        };
878
879        let mut known_styles = fileaccess::styles();
880        known_styles.push("native");
881        if !known_styles.contains(&style.as_ref())
882            && myself
883                .find_file_in_include_path(None, &format!("{style}/std-widgets.slint"))
884                .is_none()
885        {
886            diag.push_diagnostic_with_span(
887                format!(
888                    "Style {} is not known. Use one of the builtin styles [{}] or make sure your custom style is found in the include directories",
889                    &style,
890                    known_styles.join(", ")
891                ),
892                Default::default(),
893                crate::diagnostics::DiagnosticLevel::Error,
894            );
895        }
896
897        myself
898    }
899
900    pub fn drop_document(&mut self, path: &Path) -> Result<(), std::io::Error> {
901        if let Some((LoadedDocument::Document(doc), _)) = self.all_documents.docs.remove(path) {
902            for dep in &doc.imports {
903                self.all_documents
904                    .dependencies
905                    .entry(Path::new(&dep.file).into())
906                    .or_default()
907                    .remove(path);
908            }
909        }
910        self.all_documents.dependencies.remove(path);
911        if self.all_documents.currently_loading.contains_key(path) {
912            Err(std::io::Error::new(
913                std::io::ErrorKind::InvalidInput,
914                format!("{path:?} is still loading"),
915            ))
916        } else {
917            Ok(())
918        }
919    }
920
921    /// Invalidate a document and all its dependencies.
922    pub fn invalidate_document(&mut self, path: &Path) -> HashSet<PathBuf> {
923        if let Some((d, _)) = self.all_documents.docs.get_mut(path) {
924            if let LoadedDocument::Document(doc) = d {
925                for dep in &doc.imports {
926                    self.all_documents
927                        .dependencies
928                        .entry(Path::new(&dep.file).into())
929                        .or_default()
930                        .remove(path);
931                }
932                match doc.node.take() {
933                    None => {
934                        self.all_documents.docs.remove(path);
935                    }
936                    Some(n) => {
937                        *d = LoadedDocument::Invalidated(n);
938                    }
939                };
940            } else {
941                return HashSet::new();
942            }
943        } else {
944            return HashSet::new();
945        }
946        let deps = self.all_documents.dependencies.remove(path).unwrap_or_default();
947        let mut extra_deps = HashSet::new();
948        for dep in &deps {
949            extra_deps.extend(self.invalidate_document(dep));
950        }
951        extra_deps.extend(deps);
952        extra_deps
953    }
954
955    /// Imports of files that don't have the .slint extension are returned.
956    pub async fn load_dependencies_recursively<'a>(
957        &'a mut self,
958        doc: &'a syntax_nodes::Document,
959        diag: &'a mut BuildDiagnostics,
960        registry_to_populate: &'a Rc<RefCell<TypeRegister>>,
961    ) -> (Vec<ImportedTypes>, Exports) {
962        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
963        Self::load_dependencies_recursively_impl(
964            &state,
965            doc,
966            registry_to_populate,
967            &Default::default(),
968        )
969        .await
970    }
971
972    async fn load_dependencies_recursively_impl<'a: 'b, 'b>(
973        state: &'a RefCell<BorrowedTypeLoader<'a>>,
974        doc: &'b syntax_nodes::Document,
975        registry_to_populate: &'b Rc<RefCell<TypeRegister>>,
976        import_stack: &'b HashSet<PathBuf>,
977    ) -> (Vec<ImportedTypes>, Exports) {
978        let mut imports = vec![];
979        let mut dependencies_futures = vec![];
980        for mut import in Self::collect_dependencies(state, doc) {
981            if matches!(import.import_kind, ImportKind::FileImport) {
982                if let Some((path, _)) = state.borrow().tl.resolve_import_path(
983                    Some(&import.import_uri_token.clone().into()),
984                    &import.file,
985                ) {
986                    import.file = path.to_string_lossy().into_owned();
987                };
988                imports.push(import);
989                continue;
990            }
991            dependencies_futures.push(Box::pin(async move {
992                let file = import.file.as_str();
993                let doc_path = Self::ensure_document_loaded(
994                    state,
995                    file,
996                    Some(import.import_uri_token.clone().into()),
997                    import_stack.clone(),
998                )
999                .await;
1000                (import, doc_path)
1001            }));
1002        }
1003
1004        let mut reexports = None;
1005        let mut has_star_reexport = false;
1006        std::future::poll_fn(|cx| {
1007            dependencies_futures.retain_mut(|fut| {
1008                let core::task::Poll::Ready((mut import, doc_path)) = fut.as_mut().poll(cx) else { return true; };
1009                let Some(doc_path) = doc_path else { return false };
1010                let mut state = state.borrow_mut();
1011                let state = &mut *state;
1012                let Some(doc) = state.tl.get_document(&doc_path) else {
1013                    panic!("Just loaded document not available")
1014                };
1015                match &import.import_kind {
1016                    ImportKind::ImportList(imported_types) => {
1017                        let mut imported_types = ImportedName::extract_imported_names(imported_types).peekable();
1018                        if imported_types.peek().is_some() {
1019                            Self::register_imported_types(doc, &import, imported_types, registry_to_populate, state.diag);
1020                        } else {
1021                            state.diag.push_error("Import names are missing. Please specify which types you would like to import".into(), &import.import_uri_token.parent());
1022                        }
1023                    }
1024                    ImportKind::ModuleReexport(export_module_syntax_node) => {
1025                        let exports = reexports.get_or_insert_with(Exports::default);
1026                        if let Some(star_reexport) = export_module_syntax_node.ExportModule().and_then(|x| x.child_token(SyntaxKind::Star))
1027                        {
1028                            if has_star_reexport {
1029                                state.diag.push_error("re-exporting modules is only allowed once per file".into(), &star_reexport);
1030                                return false;
1031                            }
1032                            has_star_reexport = true;
1033                            exports.add_reexports(
1034                                doc.exports.iter().map(|(exported_name, compo_or_type)| {
1035                                    let exported_name = ExportedName {
1036                                        name: exported_name.name.clone(),
1037                                        name_ident: (**export_module_syntax_node).clone(),
1038                                    };
1039                                    (exported_name, compo_or_type.clone())
1040                                }),
1041                                state.diag,
1042                            );
1043                        } else if export_module_syntax_node.ExportSpecifier().next().is_none() {
1044                            state.diag.push_error("Import names are missing. Please specify which types you would like to re-export".into(), export_module_syntax_node);
1045                        } else {
1046                            let e = export_module_syntax_node
1047                                .ExportSpecifier()
1048                                .filter_map(|e| {
1049                                    let (imported_name, exported_name) = ExportedName::from_export_specifier(&e);
1050                                    let Some(r) = doc.exports.find(&imported_name) else {
1051                                        state.diag.push_error(format!("No exported type called '{imported_name}' found in \"{}\"", doc_path.display()), &e);
1052                                        return None;
1053                                    };
1054                                    Some((exported_name, r))
1055                                })
1056                                .collect::<Vec<_>>();
1057                            exports.add_reexports(e, state.diag);
1058                        }
1059                    }
1060                    ImportKind::FileImport => {
1061                        unreachable!("FileImport should have been handled above")
1062                    }
1063                }
1064                import.file = doc_path.to_string_lossy().into_owned();
1065                imports.push(import);
1066                false
1067            });
1068            if dependencies_futures.is_empty() {
1069                core::task::Poll::Ready(())
1070            } else {
1071                core::task::Poll::Pending
1072            }
1073        }).await;
1074        (imports, reexports.unwrap_or_default())
1075    }
1076
1077    pub async fn import_component(
1078        &mut self,
1079        file_to_import: &str,
1080        type_name: &str,
1081        diag: &mut BuildDiagnostics,
1082    ) -> Option<Rc<object_tree::Component>> {
1083        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1084        let doc_path =
1085            match Self::ensure_document_loaded(&state, file_to_import, None, Default::default())
1086                .await
1087            {
1088                Some(doc_path) => doc_path,
1089                None => return None,
1090            };
1091
1092        let Some(doc) = self.get_document(&doc_path) else {
1093            panic!("Just loaded document not available")
1094        };
1095
1096        doc.exports.find(type_name).and_then(|compo_or_type| compo_or_type.left())
1097    }
1098
1099    /// Append a possibly relative path to a base path. Returns the data if it resolves to a built-in (compiled-in)
1100    /// file.
1101    pub fn resolve_import_path(
1102        &self,
1103        import_token: Option<&NodeOrToken>,
1104        maybe_relative_path_or_url: &str,
1105    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1106        if let Some(maybe_library_import) = maybe_relative_path_or_url.strip_prefix('@') {
1107            self.find_file_in_library_path(maybe_library_import)
1108        } else {
1109            let referencing_file_or_url =
1110                import_token.and_then(|tok| tok.source_file().map(|s| s.path()));
1111            self.find_file_in_include_path(referencing_file_or_url, maybe_relative_path_or_url)
1112                .or_else(|| {
1113                    referencing_file_or_url
1114                        .and_then(|base_path_or_url| {
1115                            crate::pathutils::join(
1116                                &crate::pathutils::dirname(base_path_or_url),
1117                                &PathBuf::from(maybe_relative_path_or_url),
1118                            )
1119                        })
1120                        .filter(|p| p.exists())
1121                        .map(|p| (p, None))
1122                })
1123        }
1124    }
1125
1126    async fn ensure_document_loaded<'a: 'b, 'b>(
1127        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1128        file_to_import: &'b str,
1129        import_token: Option<NodeOrToken>,
1130        mut import_stack: HashSet<PathBuf>,
1131    ) -> Option<PathBuf> {
1132        let mut borrowed_state = state.borrow_mut();
1133
1134        let (path_canon, builtin) = match borrowed_state
1135            .tl
1136            .resolve_import_path(import_token.as_ref(), file_to_import)
1137        {
1138            Some(x) => {
1139                if let Some(file_name) = x.0.file_name().and_then(|f| f.to_str()) {
1140                    let len = file_to_import.len();
1141                    if !file_to_import.ends_with(file_name)
1142                        && len >= file_name.len()
1143                        && file_name.eq_ignore_ascii_case(
1144                            file_to_import.get(len - file_name.len()..).unwrap_or(""),
1145                        )
1146                    {
1147                        if import_token.as_ref().and_then(|x| x.source_file()).is_some() {
1148                            borrowed_state.diag.push_warning(
1149                                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"),
1150                                &import_token,
1151                            );
1152                        }
1153                    }
1154                }
1155                x
1156            }
1157            None => {
1158                let import_path = crate::pathutils::clean_path(Path::new(file_to_import));
1159                if import_path.exists() {
1160                    if import_token.as_ref().and_then(|x| x.source_file()).is_some() {
1161                        borrowed_state.diag.push_warning(
1162                        format!(
1163                            "Loading \"{file_to_import}\" relative to the work directory is deprecated. Files should be imported relative to their import location",
1164                        ),
1165                        &import_token,
1166                    );
1167                    }
1168                    (import_path, None)
1169                } else {
1170                    // We will load using the `open_import_fallback`
1171                    // Simplify the path to remove the ".."
1172                    let base_path = import_token
1173                        .as_ref()
1174                        .and_then(|tok| tok.source_file().map(|s| s.path()))
1175                        .map_or(PathBuf::new(), |p| p.into());
1176                    let path = crate::pathutils::join(
1177                        &crate::pathutils::dirname(&base_path),
1178                        Path::new(file_to_import),
1179                    )?;
1180                    (path, None)
1181                }
1182            }
1183        };
1184
1185        if !import_stack.insert(path_canon.clone()) {
1186            borrowed_state.diag.push_error(
1187                format!("Recursive import of \"{}\"", path_canon.display()),
1188                &import_token,
1189            );
1190            return None;
1191        }
1192
1193        drop(borrowed_state);
1194
1195        let (is_loaded, doc_node) = core::future::poll_fn(|cx| {
1196            let mut state = state.borrow_mut();
1197            let all_documents = &mut state.tl.all_documents;
1198            match all_documents.currently_loading.entry(path_canon.clone()) {
1199                std::collections::hash_map::Entry::Occupied(mut e) => {
1200                    let waker = cx.waker();
1201                    if !e.get().iter().any(|w| w.will_wake(waker)) {
1202                        e.get_mut().push(cx.waker().clone());
1203                    }
1204                    core::task::Poll::Pending
1205                }
1206                std::collections::hash_map::Entry::Vacant(v) => {
1207                    match all_documents.docs.get(path_canon.as_path()) {
1208                        Some((LoadedDocument::Document(_), _)) => {
1209                            core::task::Poll::Ready((true, None))
1210                        }
1211                        Some((LoadedDocument::Invalidated(doc), errors)) => {
1212                            v.insert(Default::default());
1213                            core::task::Poll::Ready((false, Some((doc.clone(), errors.clone()))))
1214                        }
1215                        None => {
1216                            v.insert(Default::default());
1217                            core::task::Poll::Ready((false, None))
1218                        }
1219                    }
1220                }
1221            }
1222        })
1223        .await;
1224        if is_loaded {
1225            return Some(path_canon);
1226        }
1227
1228        let doc_node = if let Some((doc_node, errors)) = doc_node {
1229            for e in errors {
1230                state.borrow_mut().diag.push_internal_error(e);
1231            }
1232            Some(doc_node)
1233        } else {
1234            let source_code_result = if let Some(builtin) = builtin {
1235                Ok(String::from(
1236                    core::str::from_utf8(builtin)
1237                        .expect("internal error: embedded file is not UTF-8 source code"),
1238                ))
1239            } else {
1240                let fallback = state.borrow().tl.compiler_config.open_import_fallback.clone();
1241                if let Some(fallback) = fallback {
1242                    let result = fallback(path_canon.to_string_lossy().into()).await;
1243                    result.unwrap_or_else(|| std::fs::read_to_string(&path_canon))
1244                } else {
1245                    std::fs::read_to_string(&path_canon)
1246                }
1247            };
1248            match source_code_result {
1249                Ok(source) => syntax_nodes::Document::new(crate::parser::parse(
1250                    source,
1251                    Some(&path_canon),
1252                    state.borrow_mut().diag,
1253                )),
1254                Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
1255                    state.borrow_mut().diag.push_error(
1256                            if file_to_import.starts_with('@') {
1257                                format!(
1258                                    "Cannot find requested import \"{file_to_import}\" in the library search path",
1259                                )
1260                            } else {
1261                                format!(
1262                                    "Cannot find requested import \"{file_to_import}\" in the include search path",
1263                                )
1264                            },
1265                            &import_token,
1266                        );
1267                    None
1268                }
1269                Err(err) => {
1270                    state.borrow_mut().diag.push_error(
1271                        format!(
1272                            "Error reading requested import \"{}\": {}",
1273                            path_canon.display(),
1274                            err
1275                        ),
1276                        &import_token,
1277                    );
1278                    None
1279                }
1280            }
1281        };
1282
1283        let ok = if let Some(doc_node) = doc_node {
1284            Self::load_file_impl(state, &path_canon, doc_node, builtin.is_some(), &import_stack)
1285                .await;
1286            state.borrow_mut().diag.all_loaded_files.insert(path_canon.clone());
1287            true
1288        } else {
1289            false
1290        };
1291
1292        let wakers = state
1293            .borrow_mut()
1294            .tl
1295            .all_documents
1296            .currently_loading
1297            .remove(path_canon.as_path())
1298            .unwrap();
1299        for x in wakers {
1300            x.wake();
1301        }
1302
1303        ok.then_some(path_canon)
1304    }
1305
1306    /// Load a file, and its dependency, running only the import passes.
1307    ///
1308    /// the path must be the canonical path
1309    pub async fn load_file(
1310        &mut self,
1311        path: &Path,
1312        source_path: &Path,
1313        source_code: String,
1314        is_builtin: bool,
1315        diag: &mut BuildDiagnostics,
1316    ) {
1317        let doc_node: syntax_nodes::Document =
1318            crate::parser::parse(source_code, Some(source_path), diag).into();
1319        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1320        Self::load_file_impl(&state, path, doc_node, is_builtin, &Default::default()).await;
1321    }
1322
1323    /// Reload a cached file
1324    ///
1325    /// The path must be canonical
1326    pub async fn reload_cached_file(&mut self, path: &Path, diag: &mut BuildDiagnostics) {
1327        let Some((LoadedDocument::Invalidated(doc_node), errors)) =
1328            self.all_documents.docs.get(path)
1329        else {
1330            return;
1331        };
1332        let doc_node = doc_node.clone();
1333        for e in errors {
1334            diag.push_internal_error(e.clone());
1335        }
1336        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1337        Self::load_file_impl(&state, path, doc_node, false, &Default::default()).await;
1338    }
1339
1340    /// Load a file, and its dependency, running the full set of passes.
1341    ///
1342    /// the path must be the canonical path
1343    pub async fn load_root_file(
1344        &mut self,
1345        path: &Path,
1346        source_path: &Path,
1347        source_code: String,
1348        keep_raw: bool,
1349        diag: &mut BuildDiagnostics,
1350    ) -> (PathBuf, Option<TypeLoader>) {
1351        let path = crate::pathutils::clean_path(path);
1352        let doc_node: syntax_nodes::Document =
1353            crate::parser::parse(source_code, Some(source_path), diag).into();
1354        let parse_errors = diag.iter().cloned().collect();
1355        let state = RefCell::new(BorrowedTypeLoader { tl: self, diag });
1356        let (path, mut doc) =
1357            Self::load_doc_no_pass(&state, &path, doc_node, false, &Default::default()).await;
1358
1359        let mut state = state.borrow_mut();
1360        let state = &mut *state;
1361        let raw_type_loader = if !state.diag.has_errors() {
1362            crate::passes::run_passes(&mut doc, state.tl, keep_raw, state.diag).await
1363        } else {
1364            None
1365        };
1366        state
1367            .tl
1368            .all_documents
1369            .docs
1370            .insert(path.clone(), (LoadedDocument::Document(doc), parse_errors));
1371        (path, raw_type_loader)
1372    }
1373
1374    async fn load_file_impl<'a>(
1375        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1376        path: &Path,
1377        doc_node: syntax_nodes::Document,
1378        is_builtin: bool,
1379        import_stack: &HashSet<PathBuf>,
1380    ) {
1381        let parse_errors = state
1382            .borrow()
1383            .diag
1384            .iter()
1385            .filter(|e| e.source_file().is_some_and(|f| f == path))
1386            .cloned()
1387            .collect();
1388        let (path, doc) =
1389            Self::load_doc_no_pass(state, path, doc_node, is_builtin, import_stack).await;
1390
1391        let mut state = state.borrow_mut();
1392        let state = &mut *state;
1393        if !state.diag.has_errors() {
1394            crate::passes::run_import_passes(&doc, state.tl, state.diag);
1395        }
1396        for dep in &doc.imports {
1397            state
1398                .tl
1399                .all_documents
1400                .dependencies
1401                .entry(Path::new(&dep.file).into())
1402                .or_default()
1403                .insert(path.clone());
1404        }
1405        state.tl.all_documents.docs.insert(path, (LoadedDocument::Document(doc), parse_errors));
1406    }
1407
1408    async fn load_doc_no_pass<'a>(
1409        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1410        path: &Path,
1411        dependency_doc: syntax_nodes::Document,
1412        is_builtin: bool,
1413        import_stack: &HashSet<PathBuf>,
1414    ) -> (PathBuf, Document) {
1415        let dependency_registry =
1416            Rc::new(RefCell::new(TypeRegister::new(&state.borrow().tl.global_type_registry)));
1417        dependency_registry.borrow_mut().expose_internal_types =
1418            is_builtin || state.borrow().tl.compiler_config.enable_experimental;
1419        let (imports, reexports) = Self::load_dependencies_recursively_impl(
1420            state,
1421            &dependency_doc,
1422            &dependency_registry,
1423            import_stack,
1424        )
1425        .await;
1426
1427        if state.borrow().diag.has_errors() {
1428            // If there was error (esp parse error) we don't want to report further error in this document.
1429            // because they might be nonsense (TODO: we should check that the parse error were really in this document).
1430            // But we still want to create a document to give better error messages in the root document.
1431            let mut ignore_diag = BuildDiagnostics::default();
1432            ignore_diag.push_error_with_span(
1433                "Dummy error because some of the code asserts there was an error".into(),
1434                Default::default(),
1435            );
1436            let doc = crate::object_tree::Document::from_node(
1437                dependency_doc,
1438                imports,
1439                reexports,
1440                &mut ignore_diag,
1441                &dependency_registry,
1442            );
1443            return (path.to_owned(), doc);
1444        }
1445        let mut state = state.borrow_mut();
1446        let state = &mut *state;
1447        let doc = crate::object_tree::Document::from_node(
1448            dependency_doc,
1449            imports,
1450            reexports,
1451            state.diag,
1452            &dependency_registry,
1453        );
1454        (path.to_owned(), doc)
1455    }
1456
1457    fn register_imported_types(
1458        doc: &Document,
1459        import: &ImportedTypes,
1460        imported_types: impl Iterator<Item = ImportedName>,
1461        registry_to_populate: &Rc<RefCell<TypeRegister>>,
1462        build_diagnostics: &mut BuildDiagnostics,
1463    ) {
1464        for import_name in imported_types {
1465            let imported_type = doc.exports.find(&import_name.external_name);
1466
1467            let imported_type = match imported_type {
1468                Some(ty) => ty,
1469                None => {
1470                    build_diagnostics.push_error(
1471                        format!(
1472                            "No exported type called '{}' found in \"{}\"",
1473                            import_name.external_name, import.file
1474                        ),
1475                        &import.import_uri_token,
1476                    );
1477                    continue;
1478                }
1479            };
1480
1481            match imported_type {
1482                itertools::Either::Left(c) => {
1483                    registry_to_populate.borrow_mut().add_with_name(import_name.internal_name, c)
1484                }
1485                itertools::Either::Right(ty) => registry_to_populate
1486                    .borrow_mut()
1487                    .insert_type_with_name(ty, import_name.internal_name),
1488            };
1489        }
1490    }
1491
1492    /// Lookup a library and filename and try to find the absolute filename based on the library path
1493    fn find_file_in_library_path(
1494        &self,
1495        maybe_library_import: &str,
1496    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1497        let (library, file) = maybe_library_import
1498            .splitn(2, '/')
1499            .collect_tuple()
1500            .map(|(library, path)| (library, Some(path)))
1501            .unwrap_or((maybe_library_import, None));
1502        self.compiler_config.library_paths.get(library).and_then(|library_path| {
1503            let path = match file {
1504                // "@library/file.slint" -> "/path/to/library/" + "file.slint"
1505                Some(file) => library_path.join(file),
1506                // "@library" -> "/path/to/library/lib.slint"
1507                None => library_path.clone(),
1508            };
1509            crate::fileaccess::load_file(path.as_path())
1510                .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1511        })
1512    }
1513
1514    /// Lookup a filename and try to find the absolute filename based on the include path or
1515    /// the current file directory
1516    pub fn find_file_in_include_path(
1517        &self,
1518        referencing_file: Option<&Path>,
1519        file_to_import: &str,
1520    ) -> Option<(PathBuf, Option<&'static [u8]>)> {
1521        // The directory of the current file is the first in the list of include directories.
1522        referencing_file
1523            .map(base_directory)
1524            .into_iter()
1525            .chain(self.compiler_config.include_paths.iter().map(PathBuf::as_path).map(
1526                |include_path| {
1527                    let base = referencing_file.map(Path::to_path_buf).unwrap_or_default();
1528                    crate::pathutils::join(&crate::pathutils::dirname(&base), include_path)
1529                        .unwrap_or_else(|| include_path.to_path_buf())
1530                },
1531            ))
1532            .chain(
1533                (file_to_import == "std-widgets.slint"
1534                    || referencing_file.is_some_and(|x| x.starts_with("builtin:/")))
1535                .then(|| format!("builtin:/{}", self.resolved_style).into()),
1536            )
1537            .find_map(|include_dir| {
1538                let candidate = crate::pathutils::join(&include_dir, Path::new(file_to_import))?;
1539                crate::fileaccess::load_file(&candidate)
1540                    .map(|virtual_file| (virtual_file.canon_path, virtual_file.builtin_contents))
1541            })
1542    }
1543
1544    fn collect_dependencies<'a: 'b, 'b>(
1545        state: &'a RefCell<BorrowedTypeLoader<'a>>,
1546        doc: &'b syntax_nodes::Document,
1547    ) -> impl Iterator<Item = ImportedTypes> + 'a {
1548        doc.ImportSpecifier()
1549            .map(|import| {
1550                let maybe_import_uri = import.child_token(SyntaxKind::StringLiteral);
1551                let kind = import
1552                    .ImportIdentifierList()
1553                    .map(ImportKind::ImportList)
1554                    .unwrap_or(ImportKind::FileImport);
1555                (maybe_import_uri, kind)
1556            })
1557            .chain(
1558                // process `export ... from "foo"`
1559                doc.ExportsList().filter_map(|exports| {
1560                    exports.ExportModule().map(|reexport| {
1561                        let maybe_import_uri = reexport.child_token(SyntaxKind::StringLiteral);
1562                        (maybe_import_uri, ImportKind::ModuleReexport(exports))
1563                    })
1564                }),
1565            )
1566            .filter_map(|(maybe_import_uri, type_specifier)| {
1567                let import_uri = match maybe_import_uri {
1568                    Some(import_uri) => import_uri,
1569                    None => {
1570                        debug_assert!(state.borrow().diag.has_errors());
1571                        return None;
1572                    }
1573                };
1574                let path_to_import = import_uri.text().to_string();
1575                let path_to_import = path_to_import.trim_matches('\"').to_string();
1576
1577                if path_to_import.is_empty() {
1578                    state
1579                        .borrow_mut()
1580                        .diag
1581                        .push_error("Unexpected empty import url".to_owned(), &import_uri);
1582                    return None;
1583                }
1584
1585                Some(ImportedTypes {
1586                    import_uri_token: import_uri,
1587                    import_kind: type_specifier,
1588                    file: path_to_import,
1589                })
1590            })
1591    }
1592
1593    /// Return a document if it was already loaded
1594    pub fn get_document<'b>(&'b self, path: &Path) -> Option<&'b object_tree::Document> {
1595        let path = crate::pathutils::clean_path(path);
1596        if let Some((LoadedDocument::Document(d), _)) = self.all_documents.docs.get(&path) {
1597            Some(d)
1598        } else {
1599            None
1600        }
1601    }
1602
1603    /// Return an iterator over all the loaded file path
1604    pub fn all_files(&self) -> impl Iterator<Item = &PathBuf> {
1605        self.all_documents.docs.keys()
1606    }
1607
1608    /// Returns an iterator over all the loaded documents
1609    pub fn all_documents(&self) -> impl Iterator<Item = &object_tree::Document> + '_ {
1610        self.all_documents.docs.values().filter_map(|(d, _)| match d {
1611            LoadedDocument::Document(d) => Some(d),
1612            LoadedDocument::Invalidated(_) => None,
1613        })
1614    }
1615
1616    /// Returns an iterator over all the loaded documents
1617    pub fn all_file_documents(
1618        &self,
1619    ) -> impl Iterator<Item = (&PathBuf, &syntax_nodes::Document)> + '_ {
1620        self.all_documents.docs.iter().filter_map(|(p, (d, _))| {
1621            Some((
1622                p,
1623                match d {
1624                    LoadedDocument::Document(d) => d.node.as_ref()?,
1625                    LoadedDocument::Invalidated(d) => d,
1626                },
1627            ))
1628        })
1629    }
1630}
1631
1632fn get_native_style(all_loaded_files: &mut std::collections::BTreeSet<PathBuf>) -> String {
1633    // Try to get the value written by the i-slint-backend-selector's build script
1634
1635    // It is in the target/xxx/build directory
1636    let target_path = std::env::var_os("OUT_DIR")
1637        .and_then(|path| {
1638            // Same logic as in i-slint-backend-selector's build script to get the path
1639            crate::pathutils::join(Path::new(&path), Path::new("../../SLINT_DEFAULT_STYLE.txt"))
1640        })
1641        .or_else(|| {
1642            // When we are called from a slint!, OUT_DIR is only defined when the crate having the macro has a build.rs script.
1643            // As a fallback, try to parse the rustc arguments
1644            // https://stackoverflow.com/questions/60264534/getting-the-target-folder-from-inside-a-rust-proc-macro
1645            let mut args = std::env::args();
1646            let mut out_dir = None;
1647            while let Some(arg) = args.next() {
1648                if arg == "--out-dir" {
1649                    out_dir = args.next();
1650                    break;
1651                }
1652            }
1653            out_dir.and_then(|od| {
1654                crate::pathutils::join(
1655                    Path::new(&od),
1656                    Path::new("../build/SLINT_DEFAULT_STYLE.txt"),
1657                )
1658            })
1659        });
1660
1661    if let Some(style) = target_path.and_then(|target_path| {
1662        std::fs::read_to_string(&target_path)
1663            .map(|style| {
1664                all_loaded_files.insert(target_path);
1665                style.trim().into()
1666            })
1667            .ok()
1668    }) {
1669        return style;
1670    }
1671    i_slint_common::get_native_style(false, &std::env::var("TARGET").unwrap_or_default()).into()
1672}
1673
1674/// return the base directory from which imports are loaded
1675///
1676/// For a .slint file, this is the parent directory.
1677/// For a .rs file, this is relative to the CARGO_MANIFEST_DIR
1678///
1679/// Note: this function is only called for .rs path as part of the LSP or viewer.
1680/// Because from a proc_macro, we don't actually know the path of the current file, and this
1681/// is why we must be relative to CARGO_MANIFEST_DIR.
1682pub fn base_directory(referencing_file: &Path) -> PathBuf {
1683    if referencing_file.extension().is_some_and(|e| e == "rs") {
1684        // 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.
1685        let mut candidate = referencing_file;
1686        loop {
1687            candidate =
1688                if let Some(c) = candidate.parent() { c } else { break referencing_file.parent() };
1689
1690            if candidate.join("Cargo.toml").exists() {
1691                break Some(candidate);
1692            }
1693        }
1694    } else {
1695        referencing_file.parent()
1696    }
1697    .map_or_else(Default::default, |p| p.to_path_buf())
1698}
1699
1700#[test]
1701fn test_dependency_loading() {
1702    let test_source_path: PathBuf =
1703        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1704
1705    let mut incdir = test_source_path.clone();
1706    incdir.push("incpath");
1707
1708    let mut compiler_config =
1709        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1710    compiler_config.include_paths = vec![incdir];
1711    compiler_config.library_paths =
1712        HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
1713    compiler_config.style = Some("fluent".into());
1714
1715    let mut main_test_path = test_source_path;
1716    main_test_path.push("dependency_test_main.slint");
1717
1718    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1719    let doc_node = crate::parser::parse_file(main_test_path, &mut test_diags).unwrap();
1720
1721    let doc_node: syntax_nodes::Document = doc_node.into();
1722
1723    let global_registry = TypeRegister::builtin();
1724
1725    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1726
1727    let mut build_diagnostics = BuildDiagnostics::default();
1728
1729    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1730
1731    let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
1732        &doc_node,
1733        &mut build_diagnostics,
1734        &registry,
1735    ));
1736
1737    assert!(!test_diags.has_errors());
1738    assert!(!build_diagnostics.has_errors());
1739    assert_eq!(foreign_imports.len(), 3);
1740    assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
1741}
1742
1743#[test]
1744fn test_dependency_loading_from_rust() {
1745    let test_source_path: PathBuf =
1746        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1747
1748    let mut incdir = test_source_path.clone();
1749    incdir.push("incpath");
1750
1751    let mut compiler_config =
1752        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1753    compiler_config.include_paths = vec![incdir];
1754    compiler_config.library_paths =
1755        HashMap::from([("library".into(), test_source_path.join("library").join("lib.slint"))]);
1756    compiler_config.style = Some("fluent".into());
1757
1758    let mut main_test_path = test_source_path;
1759    main_test_path.push("some_rust_file.rs");
1760
1761    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1762    let doc_node = crate::parser::parse_file(main_test_path, &mut test_diags).unwrap();
1763
1764    let doc_node: syntax_nodes::Document = doc_node.into();
1765
1766    let global_registry = TypeRegister::builtin();
1767
1768    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1769
1770    let mut build_diagnostics = BuildDiagnostics::default();
1771
1772    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1773
1774    let (foreign_imports, _) = spin_on::spin_on(loader.load_dependencies_recursively(
1775        &doc_node,
1776        &mut build_diagnostics,
1777        &registry,
1778    ));
1779
1780    assert!(!test_diags.has_errors());
1781    assert!(test_diags.is_empty()); // also no warnings
1782    assert!(!build_diagnostics.has_errors());
1783    assert!(build_diagnostics.is_empty()); // also no warnings
1784    assert_eq!(foreign_imports.len(), 3);
1785    assert!(foreign_imports.iter().all(|x| matches!(x.import_kind, ImportKind::ImportList(..))));
1786}
1787
1788#[test]
1789fn test_load_from_callback_ok() {
1790    let ok = Rc::new(core::cell::Cell::new(false));
1791    let ok_ = ok.clone();
1792
1793    let mut compiler_config =
1794        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1795    compiler_config.style = Some("fluent".into());
1796    compiler_config.open_import_fallback = Some(Rc::new(move |path| {
1797        let ok_ = ok_.clone();
1798        Box::pin(async move {
1799            assert_eq!(path.replace('\\', "/"), "../FooBar.slint");
1800            assert!(!ok_.get());
1801            ok_.set(true);
1802            Some(Ok("export XX := Rectangle {} ".to_owned()))
1803        })
1804    }));
1805
1806    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1807    let doc_node = crate::parser::parse(
1808        r#"
1809/* ... */
1810import { XX } from "../Ab/.././FooBar.slint";
1811X := XX {}
1812"#
1813        .into(),
1814        Some(std::path::Path::new("HELLO")),
1815        &mut test_diags,
1816    );
1817
1818    let doc_node: syntax_nodes::Document = doc_node.into();
1819    let global_registry = TypeRegister::builtin();
1820    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1821    let mut build_diagnostics = BuildDiagnostics::default();
1822    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1823    spin_on::spin_on(loader.load_dependencies_recursively(
1824        &doc_node,
1825        &mut build_diagnostics,
1826        &registry,
1827    ));
1828    assert!(ok.get());
1829    assert!(!test_diags.has_errors());
1830    assert!(!build_diagnostics.has_errors());
1831}
1832
1833#[test]
1834fn test_load_error_twice() {
1835    let mut compiler_config =
1836        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1837    compiler_config.style = Some("fluent".into());
1838    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1839
1840    let doc_node = crate::parser::parse(
1841        r#"
1842/* ... */
1843import { XX } from "error.slint";
1844component Foo { XX {} }
1845"#
1846        .into(),
1847        Some(std::path::Path::new("HELLO")),
1848        &mut test_diags,
1849    );
1850
1851    let doc_node: syntax_nodes::Document = doc_node.into();
1852    let global_registry = TypeRegister::builtin();
1853    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1854    let mut build_diagnostics = BuildDiagnostics::default();
1855    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1856    spin_on::spin_on(loader.load_dependencies_recursively(
1857        &doc_node,
1858        &mut build_diagnostics,
1859        &registry,
1860    ));
1861    assert!(!test_diags.has_errors());
1862    assert!(build_diagnostics.has_errors());
1863    let diags = build_diagnostics.to_string_vec();
1864    assert_eq!(
1865        diags,
1866        &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
1867    );
1868    // Try loading another time with the same registry
1869    let mut build_diagnostics = BuildDiagnostics::default();
1870    spin_on::spin_on(loader.load_dependencies_recursively(
1871        &doc_node,
1872        &mut build_diagnostics,
1873        &registry,
1874    ));
1875    assert!(build_diagnostics.has_errors());
1876    let diags = build_diagnostics.to_string_vec();
1877    assert_eq!(
1878        diags,
1879        &["HELLO:3: Cannot find requested import \"error.slint\" in the include search path"]
1880    );
1881}
1882
1883#[test]
1884fn test_manual_import() {
1885    let mut compiler_config =
1886        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1887    compiler_config.style = Some("fluent".into());
1888    let global_registry = TypeRegister::builtin();
1889    let mut build_diagnostics = BuildDiagnostics::default();
1890    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1891
1892    let maybe_button_type = spin_on::spin_on(loader.import_component(
1893        "std-widgets.slint",
1894        "Button",
1895        &mut build_diagnostics,
1896    ));
1897
1898    assert!(!build_diagnostics.has_errors());
1899    assert!(maybe_button_type.is_some());
1900}
1901
1902#[test]
1903fn test_builtin_style() {
1904    let test_source_path: PathBuf =
1905        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1906
1907    let incdir = test_source_path.join("custom_style");
1908
1909    let mut compiler_config =
1910        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1911    compiler_config.include_paths = vec![incdir];
1912    compiler_config.style = Some("fluent".into());
1913
1914    let global_registry = TypeRegister::builtin();
1915    let mut build_diagnostics = BuildDiagnostics::default();
1916    let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1917
1918    assert!(!build_diagnostics.has_errors());
1919}
1920
1921#[test]
1922fn test_user_style() {
1923    let test_source_path: PathBuf =
1924        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1925
1926    let incdir = test_source_path.join("custom_style");
1927
1928    let mut compiler_config =
1929        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1930    compiler_config.include_paths = vec![incdir];
1931    compiler_config.style = Some("TestStyle".into());
1932
1933    let global_registry = TypeRegister::builtin();
1934    let mut build_diagnostics = BuildDiagnostics::default();
1935    let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1936
1937    assert!(!build_diagnostics.has_errors());
1938}
1939
1940#[test]
1941fn test_unknown_style() {
1942    let test_source_path: PathBuf =
1943        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader"].iter().collect();
1944
1945    let incdir = test_source_path.join("custom_style");
1946
1947    let mut compiler_config =
1948        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1949    compiler_config.include_paths = vec![incdir];
1950    compiler_config.style = Some("FooBar".into());
1951
1952    let global_registry = TypeRegister::builtin();
1953    let mut build_diagnostics = BuildDiagnostics::default();
1954    let _loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1955
1956    assert!(build_diagnostics.has_errors());
1957    let diags = build_diagnostics.to_string_vec();
1958    assert_eq!(diags.len(), 1);
1959    assert!(diags[0].starts_with("Style FooBar is not known. Use one of the builtin styles ["));
1960}
1961
1962#[test]
1963fn test_library_import() {
1964    let test_source_path: PathBuf =
1965        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
1966
1967    let library_paths = HashMap::from([
1968        ("libdir".into(), test_source_path.clone()),
1969        ("libfile.slint".into(), test_source_path.join("lib.slint")),
1970    ]);
1971
1972    let mut compiler_config =
1973        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
1974    compiler_config.library_paths = library_paths;
1975    compiler_config.style = Some("fluent".into());
1976    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
1977
1978    let doc_node = crate::parser::parse(
1979        r#"
1980/* ... */
1981import { LibraryType } from "@libfile.slint";
1982import { LibraryHelperType } from "@libdir/library_helper_type.slint";
1983"#
1984        .into(),
1985        Some(std::path::Path::new("HELLO")),
1986        &mut test_diags,
1987    );
1988
1989    let doc_node: syntax_nodes::Document = doc_node.into();
1990    let global_registry = TypeRegister::builtin();
1991    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
1992    let mut build_diagnostics = BuildDiagnostics::default();
1993    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
1994    spin_on::spin_on(loader.load_dependencies_recursively(
1995        &doc_node,
1996        &mut build_diagnostics,
1997        &registry,
1998    ));
1999    assert!(!test_diags.has_errors());
2000    assert!(!build_diagnostics.has_errors());
2001}
2002
2003#[test]
2004fn test_library_import_errors() {
2005    let test_source_path: PathBuf =
2006        [env!("CARGO_MANIFEST_DIR"), "tests", "typeloader", "library"].iter().collect();
2007
2008    let library_paths = HashMap::from([
2009        ("libdir".into(), test_source_path.clone()),
2010        ("libfile.slint".into(), test_source_path.join("lib.slint")),
2011    ]);
2012
2013    let mut compiler_config =
2014        CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter);
2015    compiler_config.library_paths = library_paths;
2016    compiler_config.style = Some("fluent".into());
2017    let mut test_diags = crate::diagnostics::BuildDiagnostics::default();
2018
2019    let doc_node = crate::parser::parse(
2020        r#"
2021/* ... */
2022import { A } from "@libdir";
2023import { B } from "@libdir/unknown.slint";
2024import { C } from "@libfile.slint/unknown.slint";
2025import { D } from "@unknown";
2026import { E } from "@unknown/lib.slint";
2027"#
2028        .into(),
2029        Some(std::path::Path::new("HELLO")),
2030        &mut test_diags,
2031    );
2032
2033    let doc_node: syntax_nodes::Document = doc_node.into();
2034    let global_registry = TypeRegister::builtin();
2035    let registry = Rc::new(RefCell::new(TypeRegister::new(&global_registry)));
2036    let mut build_diagnostics = BuildDiagnostics::default();
2037    let mut loader = TypeLoader::new(global_registry, compiler_config, &mut build_diagnostics);
2038    spin_on::spin_on(loader.load_dependencies_recursively(
2039        &doc_node,
2040        &mut build_diagnostics,
2041        &registry,
2042    ));
2043    assert!(!test_diags.has_errors());
2044    assert!(build_diagnostics.has_errors());
2045    let diags = build_diagnostics.to_string_vec();
2046    assert_eq!(diags.len(), 5);
2047    assert!(diags[0].starts_with(&format!(
2048        "HELLO:3: Error reading requested import \"{}\": ",
2049        test_source_path.to_string_lossy()
2050    )));
2051    assert_eq!(&diags[1], "HELLO:4: Cannot find requested import \"@libdir/unknown.slint\" in the library search path");
2052    assert_eq!(&diags[2], "HELLO:5: Cannot find requested import \"@libfile.slint/unknown.slint\" in the library search path");
2053    assert_eq!(
2054        &diags[3],
2055        "HELLO:6: Cannot find requested import \"@unknown\" in the library search path"
2056    );
2057    assert_eq!(
2058        &diags[4],
2059        "HELLO:7: Cannot find requested import \"@unknown/lib.slint\" in the library search path"
2060    );
2061}
2062
2063#[test]
2064fn test_snapshotting() {
2065    let mut type_loader = TypeLoader::new(
2066        crate::typeregister::TypeRegister::builtin(),
2067        crate::CompilerConfiguration::new(crate::generator::OutputFormat::Interpreter),
2068        &mut BuildDiagnostics::default(),
2069    );
2070
2071    let path = PathBuf::from("/tmp/test.slint");
2072    let mut diag = BuildDiagnostics::default();
2073    spin_on::spin_on(type_loader.load_file(
2074        &path,
2075        &path,
2076        "export component Foobar inherits Rectangle { }".to_string(),
2077        false,
2078        &mut diag,
2079    ));
2080
2081    assert!(!diag.has_errors());
2082
2083    let doc = type_loader.get_document(&path).unwrap();
2084    let c = doc.inner_components.first().unwrap();
2085    assert_eq!(c.id, "Foobar");
2086    let root_element = c.root_element.clone();
2087    assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2088
2089    let copy = snapshot(&type_loader).unwrap();
2090
2091    let doc = copy.get_document(&path).unwrap();
2092    let c = doc.inner_components.first().unwrap();
2093    assert_eq!(c.id, "Foobar");
2094    let root_element = c.root_element.clone();
2095    assert_eq!(root_element.borrow().base_type.to_string(), "Rectangle");
2096}