Skip to main content

i_slint_compiler/
typeloader.rs

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