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