Skip to main content

i_slint_compiler/
generator.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5The module responsible for the code generation.
6
7There is one sub module for every language
8*/
9
10// cSpell: ignore deque subcomponent
11
12use smol_str::SmolStr;
13use std::collections::{BTreeSet, HashSet, VecDeque};
14use std::rc::{Rc, Weak};
15
16use crate::CompilerConfiguration;
17use crate::expression_tree::{BindingExpression, Expression};
18use crate::langtype::{BuiltinStruct, ElementType, StructName};
19use crate::namedreference::NamedReference;
20use crate::object_tree::{Component, Document, ElementRc};
21
22pub mod accessor_names;
23
24#[cfg(feature = "cpp")]
25pub mod cpp;
26#[cfg(feature = "cpp")]
27pub mod cpp_live_preview;
28#[cfg(feature = "rust")]
29pub mod rust;
30#[cfg(feature = "rust")]
31pub mod rust_live_preview;
32#[cfg(feature = "slint-sc")]
33pub mod slint_sc;
34
35#[cfg(feature = "python")]
36pub mod python;
37
38#[derive(Clone, Debug, PartialEq)]
39pub enum OutputFormat {
40    #[cfg(feature = "cpp")]
41    Cpp(cpp::Config),
42    #[cfg(feature = "rust")]
43    Rust,
44    /// Safety-critical subset of Slint.  Generates minimal Rust code
45    /// targeting the `slint-sc` runtime crate.
46    #[cfg(feature = "slint-sc")]
47    SlintSc,
48    Interpreter,
49    Llr,
50    #[cfg(feature = "python")]
51    Python,
52}
53
54impl OutputFormat {
55    pub fn guess_from_extension(path: &std::path::Path) -> Option<Self> {
56        match path.extension().and_then(|ext| ext.to_str()) {
57            #[cfg(feature = "cpp")]
58            Some("cpp") | Some("cxx") | Some("h") | Some("hpp") => {
59                Some(Self::Cpp(cpp::Config::default()))
60            }
61            #[cfg(feature = "rust")]
62            Some("rs") => Some(Self::Rust),
63            #[cfg(feature = "python")]
64            Some("py") => Some(Self::Python),
65            _ => None,
66        }
67    }
68}
69
70impl std::str::FromStr for OutputFormat {
71    type Err = String;
72    fn from_str(s: &str) -> Result<Self, Self::Err> {
73        match s {
74            #[cfg(feature = "cpp")]
75            "cpp" => Ok(Self::Cpp(cpp::Config::default())),
76            #[cfg(feature = "rust")]
77            "rust" => Ok(Self::Rust),
78            #[cfg(feature = "slint-sc")]
79            "slint-sc" | "rust-sc" => Ok(Self::SlintSc),
80            "llr" => Ok(Self::Llr),
81            #[cfg(feature = "python")]
82            "python" => Ok(Self::Python),
83            _ => Err(format!("Unknown output format {s}")),
84        }
85    }
86}
87
88pub fn generate(
89    format: OutputFormat,
90    destination: &mut impl std::io::Write,
91    destination_path: Option<&std::path::Path>,
92    doc: &Document,
93    compiler_config: &CompilerConfiguration,
94) -> std::io::Result<()> {
95    #![allow(unused_variables)]
96    #![allow(unreachable_code)]
97
98    match format {
99        #[cfg(feature = "cpp")]
100        OutputFormat::Cpp(config) => {
101            let output = cpp::generate(doc, config, compiler_config)?;
102            write!(destination, "{output}")?;
103        }
104        #[cfg(feature = "rust")]
105        OutputFormat::Rust => {
106            let output = rust::generate(doc, compiler_config)?;
107            write!(destination, "{output}")?;
108        }
109        #[cfg(feature = "slint-sc")]
110        OutputFormat::SlintSc => {
111            let generated = slint_sc::generate(doc, compiler_config)?;
112            write!(destination, "{}", generated.code)?;
113            if let (true, Some(path)) = (compiler_config.coverage, destination_path) {
114                let map = path.with_extension("slintcov");
115                crate::fileaccess::write_file_if_changed(&map, generated.coverage_map.as_bytes())?;
116            }
117        }
118        OutputFormat::Interpreter => {
119            return Err(std::io::Error::other(
120                "Unsupported output format: The interpreter is not a valid output format yet.",
121            )); // Perhaps byte code in the future?
122        }
123        OutputFormat::Llr => {
124            let root = crate::llr::lower_to_item_tree::lower_to_item_tree(doc, compiler_config);
125            let mut output = String::new();
126            crate::llr::pretty_print::pretty_print(&root, &mut output).unwrap();
127            write!(destination, "{output}")?;
128        }
129        #[cfg(feature = "python")]
130        OutputFormat::Python => {
131            let output = python::generate(doc, compiler_config, destination_path)?;
132            write!(destination, "{output}")?;
133        }
134    }
135    Ok(())
136}
137
138/// A reference to this trait is passed to the [`build_item_tree`] function.
139/// It can be used to build the array for the item tree.
140pub trait ItemTreeBuilder {
141    /// Some state that contains the code on how to access some particular component
142    type SubComponentState: Clone;
143
144    fn push_repeated_item(
145        &mut self,
146        item: &crate::object_tree::ElementRc,
147        repeater_count: u32,
148        parent_index: u32,
149        component_state: &Self::SubComponentState,
150    );
151    fn push_native_item(
152        &mut self,
153        item: &ElementRc,
154        children_offset: u32,
155        parent_index: u32,
156        component_state: &Self::SubComponentState,
157    );
158    /// Called when a component is entered, this allow to change the component_state.
159    /// The returned SubComponentState will be used for all the items within that component
160    fn enter_component(
161        &mut self,
162        item: &ElementRc,
163        sub_component: &Rc<Component>,
164        children_offset: u32,
165        component_state: &Self::SubComponentState,
166    ) -> Self::SubComponentState;
167    /// Called before the children of a component are entered.
168    fn enter_component_children(
169        &mut self,
170        item: &ElementRc,
171        repeater_count: u32,
172        component_state: &Self::SubComponentState,
173        sub_component_state: &Self::SubComponentState,
174    );
175}
176
177/// Visit each item in order in which they should appear in the children tree array.
178pub fn build_item_tree<T: ItemTreeBuilder>(
179    root_component: &Rc<Component>,
180    initial_state: &T::SubComponentState,
181    builder: &mut T,
182) {
183    if let Some(sub_component) = root_component.root_element.borrow().sub_component() {
184        assert!(root_component.root_element.borrow().children.is_empty());
185        let sub_compo_state =
186            builder.enter_component(&root_component.root_element, sub_component, 1, initial_state);
187        builder.enter_component_children(
188            &root_component.root_element,
189            0,
190            initial_state,
191            &sub_compo_state,
192        );
193        build_item_tree::<T>(sub_component, &sub_compo_state, builder);
194    } else {
195        let mut repeater_count = 0;
196        visit_item(initial_state, &root_component.root_element, 1, &mut repeater_count, 0, builder);
197
198        visit_children(
199            initial_state,
200            &root_component.root_element.borrow().children,
201            root_component,
202            &root_component.root_element,
203            0,
204            0,
205            1,
206            1,
207            &mut repeater_count,
208            builder,
209        );
210    }
211
212    // Size of the element's children and grand-children including
213    // sub-component children, needed to allocate the correct amount of
214    // index spaces for sub-components.
215    fn item_sub_tree_size(e: &ElementRc) -> usize {
216        let mut count = e.borrow().children.len();
217        if let Some(sub_component) = e.borrow().sub_component() {
218            count += item_sub_tree_size(&sub_component.root_element);
219        }
220        for i in &e.borrow().children {
221            count += item_sub_tree_size(i);
222        }
223        count
224    }
225
226    fn visit_children<T: ItemTreeBuilder>(
227        state: &T::SubComponentState,
228        children: &[ElementRc],
229        _component: &Rc<Component>,
230        parent_item: &ElementRc,
231        parent_index: u32,
232        relative_parent_index: u32,
233        children_offset: u32,
234        relative_children_offset: u32,
235        repeater_count: &mut u32,
236        builder: &mut T,
237    ) {
238        debug_assert_eq!(
239            relative_parent_index,
240            *parent_item.borrow().item_index.get().unwrap_or(&parent_index)
241        );
242
243        // Suppose we have this:
244        // ```
245        // Button := Rectangle { /* some repeater here*/ }
246        // StandardButton := Button { /* no children */ }
247        // App := Dialog { StandardButton { /* no children */ }}
248        // ```
249        // The inlining pass ensures that *if* `StandardButton` had children, `Button` would be inlined, but that's not the case here.
250        //
251        // We are in the stage of visiting the Dialog's children and we'll end up visiting the Button's Rectangle because visit_item()
252        // on the StandardButton - a Dialog's child - follows all the way to the Rectangle as native item. We've also determine that
253        // StandardButton is a sub-component and we'll call visit_children() on it. Now we are here. However as `StandardButton` has no children,
254        // and therefore we would never recurse into `Button`'s children and thus miss the repeater. That is what this condition attempts to
255        // detect and chain the children visitation.
256        if children.is_empty()
257            && let Some(nested_subcomponent) = parent_item.borrow().sub_component()
258        {
259            let sub_component_state =
260                builder.enter_component(parent_item, nested_subcomponent, children_offset, state);
261            visit_children(
262                &sub_component_state,
263                &nested_subcomponent.root_element.borrow().children,
264                nested_subcomponent,
265                &nested_subcomponent.root_element,
266                parent_index,
267                relative_parent_index,
268                children_offset,
269                relative_children_offset,
270                repeater_count,
271                builder,
272            );
273            return;
274        }
275
276        let mut offset = children_offset + children.len() as u32;
277
278        let mut sub_component_states = VecDeque::new();
279
280        for child in children.iter() {
281            if let Some(sub_component) = child.borrow().sub_component() {
282                let sub_component_state =
283                    builder.enter_component(child, sub_component, offset, state);
284                visit_item(
285                    &sub_component_state,
286                    &sub_component.root_element,
287                    offset,
288                    repeater_count,
289                    parent_index,
290                    builder,
291                );
292                sub_component_states.push_back(sub_component_state);
293            } else {
294                visit_item(state, child, offset, repeater_count, parent_index, builder);
295            }
296            offset += item_sub_tree_size(child) as u32;
297        }
298
299        let mut offset = children_offset + children.len() as u32;
300        let mut relative_offset = relative_children_offset + children.len() as u32;
301
302        for (i, e) in children.iter().enumerate() {
303            let index = children_offset + i as u32;
304            let relative_index = relative_children_offset + i as u32;
305            if let Some(sub_component) = e.borrow().sub_component() {
306                let sub_tree_state = sub_component_states.pop_front().unwrap();
307                builder.enter_component_children(e, *repeater_count, state, &sub_tree_state);
308                visit_children(
309                    &sub_tree_state,
310                    &sub_component.root_element.borrow().children,
311                    sub_component,
312                    &sub_component.root_element,
313                    index,
314                    0,
315                    offset,
316                    1,
317                    repeater_count,
318                    builder,
319                );
320            } else {
321                visit_children(
322                    state,
323                    &e.borrow().children,
324                    _component,
325                    e,
326                    index,
327                    relative_index,
328                    offset,
329                    relative_offset,
330                    repeater_count,
331                    builder,
332                );
333            }
334
335            let size = item_sub_tree_size(e) as u32;
336            offset += size;
337            relative_offset += size;
338        }
339    }
340
341    fn visit_item<T: ItemTreeBuilder>(
342        component_state: &T::SubComponentState,
343        item: &ElementRc,
344        children_offset: u32,
345        repeater_count: &mut u32,
346        parent_index: u32,
347        builder: &mut T,
348    ) {
349        if item.borrow().repeated.is_some() {
350            builder.push_repeated_item(item, *repeater_count, parent_index, component_state);
351            *repeater_count += 1;
352        } else {
353            let mut item = item.clone();
354            let mut component_state = component_state.clone();
355            while let Some((base, state)) = {
356                item.borrow().sub_component().map(|c| {
357                    (
358                        c.root_element.clone(),
359                        builder.enter_component(&item, c, children_offset, &component_state),
360                    )
361                })
362            } {
363                item = base;
364                component_state = state;
365            }
366            builder.push_native_item(&item, children_offset, parent_index, &component_state)
367        }
368    }
369}
370
371/// Will call the `handle_property` callback for every property that needs to be initialized.
372/// This function makes sure to call them in order so that if constant binding need to access
373/// constant properties, these are already initialized
374pub fn handle_property_bindings_init(
375    component: &Rc<Component>,
376    mut handle_property: impl FnMut(&ElementRc, &SmolStr, &BindingExpression),
377) {
378    fn handle_property_inner(
379        component: &Weak<Component>,
380        elem: &ElementRc,
381        prop_name: &SmolStr,
382        binding_expression: &BindingExpression,
383        handle_property: &mut impl FnMut(&ElementRc, &SmolStr, &BindingExpression),
384        processed: &mut HashSet<NamedReference>,
385    ) {
386        if elem.borrow().is_component_placeholder {
387            return; // This element does not really exist!
388        }
389        let nr = NamedReference::new(elem, prop_name.clone());
390        if processed.contains(&nr) {
391            return;
392        }
393        processed.insert(nr);
394        if binding_expression.analysis.as_ref().is_some_and(|a| a.is_const) {
395            // We must first handle all dependent properties in case it is a constant property
396
397            binding_expression.expression.visit_recursive(&mut |e| {
398                if let Expression::PropertyReference(nr) = e {
399                    let elem = nr.element();
400                    if Weak::ptr_eq(&elem.borrow().enclosing_component, component)
401                        && let Some(be) = elem.borrow().binding_cell_including_synthetic(nr.name())
402                    {
403                        handle_property_inner(
404                            component,
405                            &elem,
406                            nr.name(),
407                            &be.borrow(),
408                            handle_property,
409                            processed,
410                        );
411                    }
412                }
413            })
414        }
415        handle_property(elem, prop_name, binding_expression);
416    }
417
418    let mut processed = HashSet::new();
419    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |elem: &ElementRc, ()| {
420        for (prop_name, binding_expression) in elem.borrow().bindings_including_synthetic() {
421            handle_property_inner(
422                &Rc::downgrade(component),
423                elem,
424                prop_name,
425                &binding_expression.borrow(),
426                &mut handle_property,
427                &mut processed,
428            );
429        }
430    });
431}
432
433/// Call the given function for each constant property in the Component so one can set
434/// `set_constant` on it.
435pub fn for_each_const_properties(
436    component: &Rc<Component>,
437    mut f: impl FnMut(&ElementRc, &SmolStr),
438) {
439    crate::object_tree::recurse_elem(&component.root_element, &(), &mut |elem: &ElementRc, ()| {
440        if elem.borrow().repeated.is_some() {
441            return;
442        }
443        let mut e = elem.clone();
444        let mut all_prop = BTreeSet::new();
445        loop {
446            all_prop.extend(
447                e.borrow()
448                    .property_declarations
449                    .iter()
450                    .filter(|(_, x)| {
451                        x.property_type.is_property_type() &&
452                            !matches!( &x.property_type, crate::langtype::Type::Struct(s) if matches!(s.name, StructName::Builtin(BuiltinStruct::StateInfo)))
453                    })
454                    .map(|(k, _)| k.clone()),
455            );
456            match &e.clone().borrow().base_type {
457                ElementType::Component(c) => {
458                    e = c.root_element.clone();
459                }
460                ElementType::Native(n) => {
461                    let mut n = n;
462                    loop {
463                        all_prop.extend(
464                            n.properties
465                                .iter()
466                                .filter(|(k, x)| {
467                                    x.ty.is_property_type()
468                                        && (n.class_name != "Flickable"
469                                            || !k.starts_with("content-"))
470                                        && k.as_str() != "commands"
471                                })
472                                .map(|(k, _)| k.clone()),
473                        );
474                        match n.parent.as_ref() {
475                            Some(p) => n = p,
476                            None => break,
477                        }
478                    }
479                    break;
480                }
481                ElementType::Builtin(_) => {
482                    unreachable!("builtin element should have been resolved")
483                }
484                ElementType::Global | ElementType::Interface | ElementType::Error => break,
485            }
486        }
487        for c in all_prop {
488            if NamedReference::new(elem, c.clone()).is_constant() {
489                f(elem, &c);
490            }
491        }
492    });
493}
494
495/// Convert a ascii kebab string to pascal case
496pub fn to_pascal_case(str: &str) -> String {
497    let mut result = Vec::with_capacity(str.len());
498    let mut next_upper = true;
499    for x in str.as_bytes() {
500        if *x == b'-' {
501            next_upper = true;
502        } else if next_upper {
503            result.push(x.to_ascii_uppercase());
504            next_upper = false;
505        } else {
506            result.push(*x);
507        }
508    }
509    String::from_utf8(result).unwrap()
510}
511
512/// Convert a ascii pascal case string to kebab case
513pub fn to_kebab_case(str: &str) -> String {
514    let mut result = Vec::with_capacity(str.len());
515    for x in str.as_bytes() {
516        if x.is_ascii_uppercase() {
517            if !result.is_empty() {
518                result.push(b'-');
519            }
520            result.push(x.to_ascii_lowercase());
521        } else {
522            result.push(*x);
523        }
524    }
525    String::from_utf8(result).unwrap()
526}
527
528/// The number of arguments taken by the accessibility action of the given name, where the name
529/// is the `AccessibilityAction` variant in pascal case (such as `SetSelectionOffsets`).
530///
531/// The `AccessibilityAction` enum of the run-time library mirrors the `accessible-action-*`
532/// callbacks declared in the type register: a variant has one field per callback argument, so
533/// that the generators can bind the fields without knowing about any particular action.
534pub fn accessibility_action_argument_count(action: &str) -> usize {
535    let property_name = format!("accessible-action-{}", to_kebab_case(action));
536    crate::typeregister::reserved_accessibility_properties()
537        .find_map(|(name, ty)| match ty {
538            crate::langtype::Type::Callback(function) if name == property_name => {
539                Some(function.args.len())
540            }
541            _ => None,
542        })
543        .unwrap_or_else(|| panic!("Unknown accessibility action {action}"))
544}
545
546#[test]
547fn case_conversions() {
548    assert_eq!(to_kebab_case("HelloWorld"), "hello-world");
549    assert_eq!(to_pascal_case("hello-world"), "HelloWorld");
550}