Skip to main content

i_slint_compiler/
langtype.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 std::borrow::Cow;
5use std::collections::{BTreeMap, HashMap};
6use std::fmt::Display;
7use std::rc::Rc;
8
9use itertools::Itertools;
10
11use smol_str::SmolStr;
12
13use crate::expression_tree::{BuiltinFunction, Expression, Unit};
14use crate::object_tree::{Component, PropertyVisibility};
15use crate::parser::syntax_nodes;
16use crate::typeregister::TypeRegister;
17
18#[derive(Debug, Clone, Default)]
19pub enum Type {
20    /// Correspond to an uninitialized type, or an error
21    #[default]
22    Invalid,
23    /// The type of an expression that return nothing
24    Void,
25    /// The type of a property two way binding whose type was not yet inferred
26    InferredProperty,
27    /// The type of a callback alias whose type was not yet inferred
28    InferredCallback,
29
30    Callback(Rc<Function>),
31    Function(Rc<Function>),
32
33    ComponentFactory,
34
35    // Other property types:
36    Float32,
37    Int32,
38    String,
39    Color,
40    Duration,
41    PhysicalLength,
42    LogicalLength,
43    Rem,
44    Angle,
45    Percent,
46    Image,
47    Bool,
48    /// Fake type that can represent anything that can be converted into a model.
49    Model,
50    PathData, // Either a vector of path elements or a two vectors of events and coordinates
51    Easing,
52    Brush,
53    /// This is usually a model
54    Array(Rc<Type>),
55    Struct(Rc<Struct>),
56    Enumeration(Rc<Enumeration>),
57    Keys,
58    /// `data-transfer` - a special type that handles reading a value from the system with
59    /// some set of available MIME types.
60    DataTransfer,
61
62    /// A type made up of the product of several "unit" types.
63    /// The first parameter is the unit, and the second parameter is the power.
64    /// The vector should be sorted by 1) the power, 2) the unit.
65    UnitProduct(Vec<(Unit, i8)>),
66
67    ElementReference,
68
69    /// This is a `SharedArray<f32>`
70    LayoutCache,
71    /// This is used by GridLayoutOrganizedData
72    ArrayOfU16,
73
74    StyledText,
75}
76
77impl core::cmp::PartialEq for Type {
78    fn eq(&self, other: &Self) -> bool {
79        match self {
80            Type::Invalid => matches!(other, Type::Invalid),
81            Type::Void => matches!(other, Type::Void),
82            Type::InferredProperty => matches!(other, Type::InferredProperty),
83            Type::InferredCallback => matches!(other, Type::InferredCallback),
84            Type::Callback(lhs) => {
85                matches!(other, Type::Callback(rhs) if lhs == rhs)
86            }
87            Type::Function(lhs) => {
88                matches!(other, Type::Function(rhs) if lhs == rhs)
89            }
90            Type::ComponentFactory => matches!(other, Type::ComponentFactory),
91            Type::Float32 => matches!(other, Type::Float32),
92            Type::Int32 => matches!(other, Type::Int32),
93            Type::String => matches!(other, Type::String),
94            Type::Color => matches!(other, Type::Color),
95            Type::Duration => matches!(other, Type::Duration),
96            Type::Angle => matches!(other, Type::Angle),
97            Type::PhysicalLength => matches!(other, Type::PhysicalLength),
98            Type::LogicalLength => matches!(other, Type::LogicalLength),
99            Type::Rem => matches!(other, Type::Rem),
100            Type::Percent => matches!(other, Type::Percent),
101            Type::Image => matches!(other, Type::Image),
102            Type::Bool => matches!(other, Type::Bool),
103            Type::Model => matches!(other, Type::Model),
104            Type::PathData => matches!(other, Type::PathData),
105            Type::Easing => matches!(other, Type::Easing),
106            Type::Brush => matches!(other, Type::Brush),
107            Type::Array(a) => matches!(other, Type::Array(b) if a == b),
108            Type::Struct(lhs) => {
109                matches!(other, Type::Struct(rhs) if lhs.fields == rhs.fields && lhs.name == rhs.name)
110            }
111            Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
112            Type::Keys => matches!(other, Type::Keys),
113            Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),
114            Type::ElementReference => matches!(other, Type::ElementReference),
115            Type::LayoutCache => matches!(other, Type::LayoutCache),
116            Type::ArrayOfU16 => matches!(other, Type::ArrayOfU16),
117            Type::StyledText => matches!(other, Type::StyledText),
118            Type::DataTransfer => matches!(other, Type::DataTransfer),
119        }
120    }
121}
122
123impl Display for Type {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        match self {
126            Type::Invalid => write!(f, "<error>"),
127            Type::Void => write!(f, "void"),
128            Type::InferredProperty => write!(f, "?"),
129            Type::InferredCallback => write!(f, "callback"),
130            Type::Callback(callback) => {
131                write!(f, "callback")?;
132                if !callback.args.is_empty() {
133                    write!(f, "(")?;
134                    for (i, arg) in callback.args.iter().enumerate() {
135                        if i > 0 {
136                            write!(f, ",")?;
137                        }
138                        write!(f, "{arg}")?;
139                    }
140                    write!(f, ")")?
141                }
142                write!(f, "-> {}", callback.return_type)?;
143                Ok(())
144            }
145            Type::ComponentFactory => write!(f, "component-factory"),
146            Type::Function(function) => {
147                write!(f, "function(")?;
148                for (i, arg) in function.args.iter().enumerate() {
149                    if i > 0 {
150                        write!(f, ",")?;
151                    }
152                    write!(f, "{arg}")?;
153                }
154                write!(f, ") -> {}", function.return_type)
155            }
156            Type::Float32 => write!(f, "float"),
157            Type::Int32 => write!(f, "int"),
158            Type::String => write!(f, "string"),
159            Type::Duration => write!(f, "duration"),
160            Type::Angle => write!(f, "angle"),
161            Type::PhysicalLength => write!(f, "physical-length"),
162            Type::LogicalLength => write!(f, "length"),
163            Type::Rem => write!(f, "relative-font-size"),
164            Type::Percent => write!(f, "percent"),
165            Type::Color => write!(f, "color"),
166            Type::Image => write!(f, "image"),
167            Type::Bool => write!(f, "bool"),
168            Type::Model => write!(f, "model"),
169            Type::Array(t) => write!(f, "[{t}]"),
170            Type::Struct(t) => write!(f, "{t}"),
171            Type::PathData => write!(f, "pathdata"),
172            Type::Easing => write!(f, "easing"),
173            Type::Brush => write!(f, "brush"),
174            Type::Enumeration(enumeration) => write!(f, "enum {}", enumeration.name),
175            Type::Keys => write!(f, "keys"),
176            Type::DataTransfer => write!(f, "data-transfer"),
177            Type::UnitProduct(vec) => {
178                const POWERS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
179                let mut x = vec.iter().map(|(unit, power)| {
180                    if *power == 1 {
181                        return unit.to_string();
182                    }
183                    let mut res = format!("{}{}", unit, if *power < 0 { "⁻" } else { "" });
184                    let value = power.abs().to_string();
185                    for x in value.as_bytes() {
186                        res.push(POWERS[(x - b'0') as usize]);
187                    }
188
189                    res
190                });
191                write!(f, "({})", x.join("×"))
192            }
193            Type::ElementReference => write!(f, "element ref"),
194            Type::LayoutCache => write!(f, "layout cache"),
195            Type::ArrayOfU16 => write!(f, "[u16]"),
196            Type::StyledText => write!(f, "styled-text"),
197        }
198    }
199}
200
201impl From<Rc<Struct>> for Type {
202    fn from(value: Rc<Struct>) -> Self {
203        Self::Struct(value)
204    }
205}
206
207impl Type {
208    /// valid type for properties
209    pub fn is_property_type(&self) -> bool {
210        matches!(
211            self,
212            Self::Float32
213                | Self::Int32
214                | Self::String
215                | Self::Color
216                | Self::ComponentFactory
217                | Self::Duration
218                | Self::Angle
219                | Self::PhysicalLength
220                | Self::LogicalLength
221                | Self::Rem
222                | Self::Percent
223                | Self::Image
224                | Self::Bool
225                | Self::Easing
226                | Self::Enumeration(_)
227                | Self::Keys
228                | Self::DataTransfer
229                | Self::ElementReference
230                | Self::Struct { .. }
231                | Self::Array(_)
232                | Self::Brush
233                | Self::InferredProperty
234                | Self::StyledText
235        )
236    }
237
238    pub fn ok_for_public_api(&self) -> bool {
239        !matches!(self, Self::Easing)
240    }
241
242    /// Assume it is an enumeration, panic if it isn't
243    pub fn as_enum(&self) -> &Rc<Enumeration> {
244        match self {
245            Type::Enumeration(e) => e,
246            _ => panic!("should be an enumeration, bug in compiler pass"),
247        }
248    }
249
250    /// Return true if the type can be converted to the other type
251    pub fn can_convert(&self, other: &Self) -> bool {
252        let can_convert_struct = |a: &BTreeMap<SmolStr, Type>, b: &BTreeMap<SmolStr, Type>| {
253            // the struct `b` has property that the struct `a` doesn't
254            let mut has_more_property = false;
255            for (k, v) in b {
256                match a.get(k) {
257                    Some(t) if !t.can_convert(v) => return false,
258                    None => has_more_property = true,
259                    _ => (),
260                }
261            }
262            if has_more_property {
263                // we should reject the conversion if `a` has property that `b` doesn't have
264                if a.keys().any(|k| !b.contains_key(k)) {
265                    return false;
266                }
267            }
268            true
269        };
270        match (self, other) {
271            (a, b) if a == b => true,
272            (_, Type::Invalid)
273            | (_, Type::Void)
274            | (Type::Float32, Type::Int32)
275            | (Type::Float32, Type::String)
276            | (Type::Int32, Type::Float32)
277            | (Type::Int32, Type::String)
278            | (Type::Float32, Type::Model)
279            | (Type::Int32, Type::Model)
280            | (Type::PhysicalLength, Type::LogicalLength)
281            | (Type::LogicalLength, Type::PhysicalLength)
282            | (Type::Rem, Type::LogicalLength)
283            | (Type::Rem, Type::PhysicalLength)
284            | (Type::LogicalLength, Type::Rem)
285            | (Type::PhysicalLength, Type::Rem)
286            | (Type::Percent, Type::Float32)
287            | (Type::Brush, Type::Color)
288            | (Type::Color, Type::Brush) => true,
289            (Type::Array(a), Type::Model) if a.is_property_type() => true,
290            (Type::Struct(a), Type::Struct(b)) => can_convert_struct(&a.fields, &b.fields),
291            (Type::UnitProduct(u), o) => match o.as_unit_product() {
292                Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
293                None => false,
294            },
295            (o, Type::UnitProduct(u)) => match o.as_unit_product() {
296                Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
297                None => false,
298            },
299            _ => false,
300        }
301    }
302
303    /// If this is a number type which should be used with an unit, this returns the default unit
304    /// otherwise, returns None
305    pub fn default_unit(&self) -> Option<Unit> {
306        match self {
307            Type::Duration => Some(Unit::Ms),
308            Type::PhysicalLength => Some(Unit::Phx),
309            Type::LogicalLength => Some(Unit::Px),
310            Type::Rem => Some(Unit::Rem),
311            // Unit::Percent is special that it does not combine with other units like
312            Type::Percent => None,
313            Type::Angle => Some(Unit::Deg),
314            Type::Invalid => None,
315            Type::Void => None,
316            Type::InferredProperty | Type::InferredCallback => None,
317            Type::Callback { .. } => None,
318            Type::ComponentFactory => None,
319            Type::Function { .. } => None,
320            Type::Float32 => None,
321            Type::Int32 => None,
322            Type::String => None,
323            Type::Color => None,
324            Type::Image => None,
325            Type::Bool => None,
326            Type::Model => None,
327            Type::PathData => None,
328            Type::Easing => None,
329            Type::Brush => None,
330            Type::Array(_) => None,
331            Type::Struct { .. } => None,
332            Type::Enumeration(_) => None,
333            Type::Keys => None,
334            Type::DataTransfer => None,
335            Type::UnitProduct(_) => None,
336            Type::ElementReference => None,
337            Type::LayoutCache => None,
338            Type::ArrayOfU16 => None,
339            Type::StyledText => None,
340        }
341    }
342
343    /// Return a unit product vector even for single scalar
344    pub fn as_unit_product(&self) -> Option<Vec<(Unit, i8)>> {
345        match self {
346            Type::UnitProduct(u) => Some(u.clone()),
347            Type::Float32 | Type::Int32 => Some(Vec::new()),
348            Type::Percent => Some(Vec::new()),
349            _ => self.default_unit().map(|u| vec![(u, 1)]),
350        }
351    }
352}
353
354#[derive(Debug, Clone)]
355pub enum BuiltinPropertyDefault {
356    None,
357    Expr(Expression),
358    /// When materializing a property of this type, it will be initialized with an Expression that depends on the ElementRc
359    WithElement(fn(&crate::object_tree::ElementRc) -> Expression),
360    /// The property is actually not a property but a builtin function
361    BuiltinFunction(BuiltinFunction),
362}
363
364impl BuiltinPropertyDefault {
365    pub fn expr(&self, elem: &crate::object_tree::ElementRc) -> Option<Expression> {
366        match self {
367            BuiltinPropertyDefault::None => None,
368            BuiltinPropertyDefault::Expr(expression) => Some(expression.clone()),
369            BuiltinPropertyDefault::WithElement(init_expr) => Some(init_expr(elem)),
370            BuiltinPropertyDefault::BuiltinFunction(..) => {
371                unreachable!("can't get an expression for functions")
372            }
373        }
374    }
375}
376
377/// Information about properties in NativeClass
378#[derive(Debug, Clone)]
379pub struct BuiltinPropertyInfo {
380    /// The property type
381    pub ty: Type,
382    /// When != None, this is the initial value that we will have to set if no other binding were specified
383    pub default_value: BuiltinPropertyDefault,
384    pub property_visibility: PropertyVisibility,
385    /// Raw `///` doc comment from builtins.slint, if any.
386    pub docs: Option<String>,
387    /// True when a component may declare a member of the same name, shadowing this one
388    /// (`//-shadowable` annotation in builtins.slint).
389    /// Members added to a builtin element after its initial release should be marked
390    /// shadowable so that older code that already declares the name keeps compiling —
391    /// unless a compiler pass accesses the member by name, in which case shadowing
392    /// would generate wrong code and the member must not be marked.
393    pub shadowable: bool,
394}
395
396impl BuiltinPropertyInfo {
397    pub fn new(ty: Type) -> Self {
398        Self {
399            ty,
400            default_value: BuiltinPropertyDefault::None,
401            property_visibility: PropertyVisibility::InOut,
402            docs: None,
403            shadowable: false,
404        }
405    }
406
407    pub fn is_native_output(&self) -> bool {
408        matches!(self.property_visibility, PropertyVisibility::InOut | PropertyVisibility::Output)
409    }
410}
411
412impl From<BuiltinFunction> for BuiltinPropertyInfo {
413    fn from(function: BuiltinFunction) -> Self {
414        Self {
415            ty: Type::Function(function.ty()),
416            default_value: BuiltinPropertyDefault::BuiltinFunction(function),
417            property_visibility: PropertyVisibility::Public,
418            docs: None,
419            shadowable: false,
420        }
421    }
422}
423
424/// The base of an element
425#[derive(Clone, Debug, derive_more::From, Default)]
426pub enum ElementType {
427    /// The element is based of a component
428    Component(Rc<Component>),
429    /// The element is a builtin element
430    Builtin(Rc<BuiltinElement>),
431    /// The native type was resolved by the resolve_native_class pass.
432    Native(Rc<NativeClass>),
433    /// The base element couldn't be looked up
434    #[default]
435    Error,
436    /// This should be the base type of the root element of a global component
437    Global,
438    /// This should be the base type of the root element of an interface
439    Interface,
440}
441
442impl PartialEq for ElementType {
443    fn eq(&self, other: &Self) -> bool {
444        match (self, other) {
445            (Self::Component(a), Self::Component(b)) => Rc::ptr_eq(a, b),
446            (Self::Builtin(a), Self::Builtin(b)) => Rc::ptr_eq(a, b),
447            (Self::Native(a), Self::Native(b)) => Rc::ptr_eq(a, b),
448            (Self::Error, Self::Error)
449            | (Self::Global, Self::Global)
450            | (Self::Interface, Self::Interface) => true,
451            _ => false,
452        }
453    }
454}
455
456impl ElementType {
457    pub fn lookup_property<'a>(&self, name: &'a str) -> PropertyLookupResult<'a> {
458        match self {
459            Self::Component(c) => c.root_element.borrow().lookup_property(name),
460            Self::Builtin(b) => {
461                let resolved_name =
462                    if let Some(alias_name) = b.native_class.lookup_alias(name.as_ref()) {
463                        Cow::Owned(alias_name.to_string())
464                    } else {
465                        Cow::Borrowed(name)
466                    };
467                match b.properties.get(resolved_name.as_ref()) {
468                    None => {
469                        if b.is_non_item_type || b.is_global {
470                            PropertyLookupResult::invalid(resolved_name)
471                        } else {
472                            crate::typeregister::reserved_property(resolved_name)
473                        }
474                    }
475                    Some(p) => PropertyLookupResult {
476                        resolved_name,
477                        property_type: p.ty.clone(),
478                        property_visibility: p.property_visibility,
479                        declared_pure: None,
480                        is_local_to_component: false,
481                        is_in_direct_base: false,
482                        is_shadowable: p.shadowable,
483                        builtin_function: match &p.default_value {
484                            BuiltinPropertyDefault::BuiltinFunction(f) => Some(f.clone()),
485                            _ => None,
486                        },
487                    },
488                }
489            }
490            Self::Native(n) => {
491                let resolved_name = if let Some(alias_name) = n.lookup_alias(name.as_ref()) {
492                    Cow::Owned(alias_name.to_string())
493                } else {
494                    Cow::Borrowed(name)
495                };
496                let property_type =
497                    n.lookup_property(resolved_name.as_ref()).cloned().unwrap_or_default();
498                PropertyLookupResult {
499                    resolved_name,
500                    property_type,
501                    property_visibility: PropertyVisibility::InOut,
502                    declared_pure: None,
503                    is_local_to_component: false,
504                    is_in_direct_base: false,
505                    is_shadowable: false,
506                    builtin_function: None,
507                }
508            }
509            _ => PropertyLookupResult::invalid(Cow::Borrowed(name)),
510        }
511    }
512
513    /// List of sub properties valid for the auto completion
514    pub fn property_list(&self) -> Vec<(SmolStr, Type)> {
515        match self {
516            Self::Component(c) => {
517                let mut r = c.root_element.borrow().base_type.property_list();
518                r.extend(
519                    c.root_element
520                        .borrow()
521                        .property_declarations
522                        .iter()
523                        .filter(|(_, d)| d.visibility != PropertyVisibility::Private)
524                        .map(|(k, d)| (k.clone(), d.property_type.clone())),
525                );
526                r
527            }
528            Self::Builtin(b) => {
529                b.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
530            }
531            Self::Native(n) => {
532                n.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
533            }
534            _ => Vec::new(),
535        }
536    }
537
538    /// This function looks at the element and checks whether it can have Elements of type `name` as children.
539    /// In addition to what `accepts_child_element` does, this method also probes the type of `name`.
540    /// It returns an Error if that is not possible or an `ElementType` if it is.
541    pub fn lookup_type_for_child_element(
542        &self,
543        name: &str,
544        tr: &TypeRegister,
545    ) -> Result<ElementType, String> {
546        match self {
547            Self::Component(component) => {
548                let base_type = match &*component.child_insertion_point.borrow() {
549                    Some(insert_in) => insert_in.parent.borrow().base_type.clone(),
550                    None => {
551                        let base_type = component.root_element.borrow().base_type.clone();
552                        if base_type == tr.empty_type() {
553                            let element = tr.lookup_element(name)?;
554                            if matches!(&element, ElementType::Builtin(b) if b.can_be_declared_without_children_slot) {
555                                return Ok(element);
556                            }
557                            return Err(format!("'{}' cannot have children. Only components with @children can have children", component.id));
558                        }
559                        base_type
560                    }
561                };
562                base_type.lookup_type_for_child_element(name, tr)
563            }
564            Self::Builtin(builtin) => {
565                let looked_up = tr.lookup_element(name);
566                if let Ok(ElementType::Builtin(b)) = &looked_up
567                    && b.can_be_declared_without_children_slot
568                {
569                    return Ok(ElementType::Builtin(b.clone()));
570                }
571                if builtin.disallow_global_types_as_child_elements {
572                    if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
573                        return Ok(child_type.clone().into());
574                    } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
575                        return Ok(builtin.clone().into());
576                    }
577                    let mut valid_children: Vec<_> =
578                        builtin.additional_accepted_child_types.keys().cloned().collect();
579                    if builtin.additional_accept_self {
580                        valid_children.push(builtin.native_class.class_name.clone());
581                    }
582                    valid_children.sort();
583
584                    let err = if valid_children.is_empty() {
585                        // No whitelist to suggest from; prefer "Unknown element" for typos.
586                        looked_up?;
587                        format!("{} cannot have children elements", builtin.native_class.class_name,)
588                    } else {
589                        format!(
590                            "{} is not allowed within {}. Only {} are valid children",
591                            name,
592                            builtin.native_class.class_name,
593                            valid_children.join(" ")
594                        )
595                    };
596                    return Err(err);
597                }
598                let err = match looked_up {
599                    Err(e) => e,
600                    Ok(t) => {
601                        if !tr.expose_internal_types
602                            && matches!(&t, Self::Builtin(e) if e.is_internal)
603                        {
604                            format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)")
605                        } else {
606                            return Ok(t);
607                        }
608                    }
609                };
610                if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
611                    return Ok(child_type.clone().into());
612                } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
613                    return Ok(builtin.clone().into());
614                }
615                match tr.lookup(name) {
616                    Type::Invalid => Err(err),
617                    ty => Err(format!("'{ty}' cannot be used as an element")),
618                }
619            }
620            _ => tr.lookup_element(name).and_then(|t| {
621                if !tr.expose_internal_types && matches!(&t, Self::Builtin(e) if e.is_internal) {
622                    Err(format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)"))
623                } else {
624                    Ok(t)
625                }
626            })
627        }
628    }
629
630    /// Assume this is a builtin type, panic if it isn't
631    pub fn as_builtin(&self) -> &BuiltinElement {
632        match self {
633            Self::Builtin(b) => b,
634            Self::Component(_) => panic!("This should not happen because of inlining"),
635            _ => panic!("invalid type"),
636        }
637    }
638
639    /// Assume this is a builtin type, panic if it isn't
640    pub fn as_native(&self) -> &NativeClass {
641        match self {
642            Self::Native(b) => b,
643            Self::Component(_) => {
644                panic!("This should not happen because of native class resolution")
645            }
646            _ => panic!("invalid type"),
647        }
648    }
649
650    /// Assume it is a Component, panic if it isn't
651    pub fn as_component(&self) -> &Rc<Component> {
652        match self {
653            Self::Component(c) => c,
654            _ => panic!("should be a component because of the repeater_component pass"),
655        }
656    }
657
658    /// Returns the Slint type name if applicable (for example `Rectangle` or `MyButton` when `component MyButton {}` is used as `MyButton` element)
659    pub fn type_name(&self) -> Option<&str> {
660        match self {
661            ElementType::Component(component) => Some(&component.id),
662            ElementType::Builtin(b) => Some(&b.name),
663            ElementType::Native(_) => None, // Too late, caller should call this function before the native class lowering
664            ElementType::Error => None,
665            ElementType::Global => None,
666            ElementType::Interface => None,
667        }
668    }
669}
670
671impl Display for ElementType {
672    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673        match self {
674            Self::Component(c) => c.id.fmt(f),
675            Self::Builtin(b) => b.name.fmt(f),
676            Self::Native(b) => b.class_name.fmt(f),
677            Self::Error => write!(f, "<error>"),
678            Self::Global => Ok(()),
679            Self::Interface => Ok(()),
680        }
681    }
682}
683
684macro_rules! define_builtin_struct_enum {
685    ($(
686        $(#[$attr:meta])*
687        $vis:vis struct $Name:ident {
688            $( $(#[$field_attr:meta])* $field:ident : $field_type:ty, )*
689        }
690    )*) => {
691        #[derive(Debug, Clone, PartialEq, strum::EnumString, strum::IntoStaticStr)]
692        pub enum BuiltinStruct {
693            // Generated from for_each_builtin_structs
694            $($Name,)*
695
696            // Public structs not in the macro (registered in typeregister.rs)
697            Color,
698            LogicalPosition,
699            LogicalSize,
700
701            // Path element types, set via `//-builtin_struct:` annotations
702            // in builtins.slint and read through NativeClass.builtin_struct
703            PathMoveTo,
704            PathLineTo,
705            PathArcTo,
706            PathCubicTo,
707            PathQuadraticTo,
708            PathClose,
709            PathElement,
710
711            // Compiler-internal structs (no slint_name, not exposed to .slint)
712
713            // Internal coordinate struct for compiled SVG path data (x/y as Float32,
714            // unlike LogicalPosition which uses LogicalLength)
715            Point,
716            // Return type of ArraySize (width/height as Int32,
717            // unlike LogicalSize which uses LogicalLength)
718            Size,
719            StateInfo,
720            PropertyAnimation,
721            GridLayoutData,
722            GridLayoutInputData,
723            BoxLayoutData,
724            BoxLayoutOrthoData,
725            FlexboxLayoutData,
726            LayoutItemInfo,
727            FlexboxLayoutItemInfo,
728            Padding,
729            LayoutInfo,
730        }
731
732        impl BuiltinStruct {
733            pub fn is_public(&self) -> bool {
734                match self {
735                    // Macro-defined structs: derived from the `pub` visibility keyword
736                    $(Self::$Name => stringify!($vis) == "pub",)*
737                    // Non-macro public structs
738                    Self::Color | Self::LogicalPosition | Self::LogicalSize => true,
739                    _ => false,
740                }
741            }
742
743            /// The name of this struct in the Slint language, or None if it is
744            /// purely internal and not visible in .slint files.
745            pub fn slint_name(&self) -> Option<SmolStr> {
746                match self {
747                    // Macro-defined structs all have a slint name matching their Rust name
748                    $(Self::$Name => {
749                        Some(SmolStr::new_static(stringify!($Name)))
750                    })*
751                    // Non-macro structs with custom slint names
752                    Self::Color => Some(SmolStr::new_static("color")),
753                    Self::LogicalPosition => Some(SmolStr::new_static("Point")),
754                    Self::LogicalSize => Some(SmolStr::new_static("Size")),
755                    _ => None,
756                }
757            }
758        }
759    };
760}
761i_slint_common::for_each_builtin_structs!(define_builtin_struct_enum);
762
763impl BuiltinStruct {
764    pub fn is_layout_data(&self) -> bool {
765        matches!(
766            self,
767            Self::GridLayoutInputData
768                | Self::GridLayoutData
769                | Self::BoxLayoutData
770                | Self::BoxLayoutOrthoData
771                | Self::FlexboxLayoutData
772        )
773    }
774}
775
776#[derive(Debug, Clone, Default)]
777pub struct NativeClass {
778    pub parent: Option<Rc<NativeClass>>,
779    pub class_name: SmolStr,
780    pub cpp_vtable_getter: String,
781    pub properties: HashMap<SmolStr, BuiltinPropertyInfo>,
782    pub deprecated_aliases: HashMap<SmolStr, SmolStr>,
783    /// Type override if class_name is not equal to the name to be used in the
784    /// target language API.
785    pub builtin_struct: Option<BuiltinStruct>,
786}
787
788impl NativeClass {
789    pub fn new(class_name: &str) -> Self {
790        let cpp_vtable_getter = format!("SLINT_GET_ITEM_VTABLE({class_name}VTable)");
791        Self {
792            class_name: class_name.into(),
793            cpp_vtable_getter,
794            properties: Default::default(),
795            ..Default::default()
796        }
797    }
798
799    pub fn new_with_properties(
800        class_name: &str,
801        properties: impl IntoIterator<Item = (SmolStr, BuiltinPropertyInfo)>,
802    ) -> Self {
803        let mut class = Self::new(class_name);
804        class.properties = properties.into_iter().collect();
805        class
806    }
807
808    pub fn property_count(&self) -> usize {
809        self.properties.len() + self.parent.clone().map(|p| p.property_count()).unwrap_or_default()
810    }
811
812    pub fn lookup_property(&self, name: &str) -> Option<&Type> {
813        if let Some(bty) = self.properties.get(name) {
814            Some(&bty.ty)
815        } else if let Some(parent_class) = &self.parent {
816            parent_class.lookup_property(name)
817        } else {
818            None
819        }
820    }
821
822    pub fn lookup_alias(&self, name: &str) -> Option<&str> {
823        if let Some(alias_target) = self.deprecated_aliases.get(name) {
824            Some(alias_target)
825        } else if self.properties.contains_key(name) {
826            None
827        } else if let Some(parent_class) = &self.parent {
828            parent_class.lookup_alias(name)
829        } else {
830            None
831        }
832    }
833}
834
835#[derive(Debug, Clone, Copy, PartialEq, Default)]
836pub enum DefaultSizeBinding {
837    /// There should not be a default binding for the size
838    #[default]
839    None,
840    /// The size should default to `width:100%; height:100%`
841    ExpandsToParentGeometry,
842    /// The size should default to the item's implicit size
843    ImplicitSize,
844}
845
846#[derive(Debug, Clone, Default)]
847pub struct BuiltinElement {
848    pub name: SmolStr,
849    pub native_class: Rc<NativeClass>,
850    pub properties: BTreeMap<SmolStr, BuiltinPropertyInfo>,
851    /// Additional builtin element that can be accepted as child of this element
852    /// (example `Tab` in `TabWidget`, `Row` in `GridLayout` and the path elements in `Path`)
853    pub additional_accepted_child_types: HashMap<SmolStr, Rc<BuiltinElement>>,
854    /// `Self` is conceptually in `additional_accepted_child_types` (which it can't otherwise that'd make a Rc loop)
855    pub additional_accept_self: bool,
856    pub disallow_global_types_as_child_elements: bool,
857    /// Non-item type do not have reserved properties (x/width/rowspan/...) added to them  (eg: PropertyAnimation)
858    pub is_non_item_type: bool,
859    pub accepts_focus: bool,
860    pub is_global: bool,
861    pub default_size_binding: DefaultSizeBinding,
862    /// When true this is an internal type not shown in the auto-completion
863    pub is_internal: bool,
864    /// Documentation sections from builtins.slint, preserving source order.
865    /// `Text` entries come from `///` (element-level) and `//!` (section) comments;
866    /// `Member` entries reference a property, callback, or function by name.
867    pub docs: Vec<crate::doc_comments::ElementDocEntry>,
868    /// When true this builtin can be declared as a child even if the parent element
869    /// does not expose an explicit @children insertion slot.
870    pub can_be_declared_without_children_slot: bool,
871    /// When true this element is part of the Slint SC (safety-critical) subset.
872    pub slint_sc: bool,
873}
874
875impl BuiltinElement {
876    pub fn new(native_class: Rc<NativeClass>) -> Self {
877        Self { name: native_class.class_name.clone(), native_class, ..Default::default() }
878    }
879}
880
881#[derive(PartialEq, Debug)]
882pub struct PropertyLookupResult<'a> {
883    pub resolved_name: std::borrow::Cow<'a, str>,
884    pub property_type: Type,
885    pub property_visibility: PropertyVisibility,
886    pub declared_pure: Option<bool>,
887    /// True if the property is part of the current component (for visibility purposes)
888    pub is_local_to_component: bool,
889    /// True if the property in the direct base of the component (for protected visibility purposes)
890    pub is_in_direct_base: bool,
891    /// True if a local declaration may shadow this member. Only builtin element
892    /// members marked `//-shadowable` in builtins.slint are shadowable.
893    pub is_shadowable: bool,
894
895    /// If the property is a builtin function
896    pub builtin_function: Option<BuiltinFunction>,
897}
898
899impl<'a> PropertyLookupResult<'a> {
900    pub fn is_valid(&self) -> bool {
901        self.property_type != Type::Invalid
902    }
903
904    /// Can this property be used in an assignment
905    pub fn is_valid_for_assignment(&self) -> bool {
906        !matches!(
907            (self.property_visibility, self.is_local_to_component),
908            (PropertyVisibility::Private, false)
909                | (PropertyVisibility::Input, true)
910                | (PropertyVisibility::Output, false)
911        )
912    }
913
914    pub fn invalid(resolved_name: Cow<'a, str>) -> Self {
915        Self {
916            resolved_name,
917            property_type: Type::Invalid,
918            property_visibility: PropertyVisibility::Private,
919            declared_pure: None,
920            is_local_to_component: false,
921            is_in_direct_base: false,
922            is_shadowable: false,
923            builtin_function: None,
924        }
925    }
926}
927
928#[derive(Debug, Clone, PartialEq)]
929pub struct Function {
930    pub return_type: Type,
931    pub args: Vec<Type>,
932    /// The optional names of the arguments (empty string means not set).
933    /// The names are not technically part of the type, but it is good to have them available for auto-completion
934    pub arg_names: Vec<SmolStr>,
935}
936
937#[derive(Debug, Clone)]
938pub enum StructName {
939    None,
940    /// When declared in .slint as  `struct Foo { }`, then the name is "Foo"
941    User {
942        name: SmolStr,
943        /// When declared in .slint, this is the node of the declaration.
944        node: syntax_nodes::ObjectType,
945    },
946    Builtin(BuiltinStruct),
947}
948
949impl PartialEq for StructName {
950    fn eq(&self, other: &Self) -> bool {
951        match (self, other) {
952            (
953                Self::User { name: l_user_name, node: _ },
954                Self::User { name: r_user_name, node: _ },
955            ) => l_user_name == r_user_name,
956            (Self::Builtin(l0), Self::Builtin(r0)) => l0 == r0,
957            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
958        }
959    }
960}
961
962impl StructName {
963    pub fn slint_name(&self) -> Option<SmolStr> {
964        match self {
965            StructName::None => None,
966            StructName::User { name, .. } => Some(name.clone()),
967            StructName::Builtin(builtin) => builtin.slint_name(),
968        }
969    }
970
971    pub fn is_none(&self) -> bool {
972        matches!(self, Self::None)
973    }
974
975    pub fn is_some(&self) -> bool {
976        !matches!(self, Self::None)
977    }
978
979    pub fn or(self, other: Self) -> Self {
980        match self {
981            Self::None => other,
982            this => this,
983        }
984    }
985}
986
987impl From<BuiltinStruct> for StructName {
988    fn from(value: BuiltinStruct) -> Self {
989        Self::Builtin(value)
990    }
991}
992
993#[derive(Debug, Clone)]
994pub struct Struct {
995    pub fields: BTreeMap<SmolStr, Type>,
996    pub name: StructName,
997}
998
999impl Struct {
1000    pub fn node(&self) -> Option<&syntax_nodes::ObjectType> {
1001        match &self.name {
1002            StructName::User { node, .. } => Some(node),
1003            _ => None,
1004        }
1005    }
1006}
1007
1008impl Display for Struct {
1009    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1010        if let Some(name) = &self.name.slint_name() {
1011            write!(f, "{name}")
1012        } else {
1013            write!(f, "{{ ")?;
1014            for (k, v) in &self.fields {
1015                write!(f, "{k}: {v},")?;
1016            }
1017            write!(f, "}}")
1018        }
1019    }
1020}
1021
1022#[derive(Debug, Clone)]
1023pub struct Enumeration {
1024    pub name: SmolStr,
1025    pub values: Vec<SmolStr>,
1026    pub default_value: usize, // index in values
1027    // For non-builtins enums, this is the declaration node
1028    pub node: Option<syntax_nodes::EnumDeclaration>,
1029}
1030
1031impl PartialEq for Enumeration {
1032    fn eq(&self, other: &Self) -> bool {
1033        self.name.eq(&other.name)
1034    }
1035}
1036
1037impl Enumeration {
1038    pub fn default_value(self: Rc<Self>) -> EnumerationValue {
1039        EnumerationValue { value: self.default_value, enumeration: self.clone() }
1040    }
1041
1042    pub fn try_value_from_string(self: Rc<Self>, value: &str) -> Option<EnumerationValue> {
1043        self.values.iter().enumerate().find_map(|(idx, name)| {
1044            if name == value {
1045                Some(EnumerationValue { value: idx, enumeration: self.clone() })
1046            } else {
1047                None
1048            }
1049        })
1050    }
1051}
1052
1053#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
1054pub struct KeyboardModifiers {
1055    pub alt: bool,
1056    pub control: bool,
1057    pub meta: bool,
1058    pub shift: bool,
1059}
1060
1061#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
1062pub struct Keys {
1063    pub key: SmolStr,
1064    pub modifiers: KeyboardModifiers,
1065    pub ignore_shift: bool,
1066    pub ignore_alt: bool,
1067}
1068
1069impl std::fmt::Display for Keys {
1070    // Make sure to keep this in sync with the implementation in core/input.rs
1071    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1072        if self.key.is_empty() {
1073            write!(f, "")
1074        } else {
1075            let alt = self
1076                .ignore_alt
1077                .then_some("Alt?+")
1078                .or(self.modifiers.alt.then_some("Alt+"))
1079                .unwrap_or_default();
1080            let ctrl = if self.modifiers.control { "Control+" } else { "" };
1081            let meta = if self.modifiers.meta { "Meta+" } else { "" };
1082            let shift = self
1083                .ignore_shift
1084                .then_some("Shift?+")
1085                .or(self.modifiers.shift.then_some("Shift+"))
1086                .unwrap_or_default();
1087            let keycode: String = self
1088                .key
1089                .chars()
1090                .flat_map(|character| {
1091                    let mut escaped = vec![];
1092                    if character.is_control() {
1093                        escaped.extend(character.escape_unicode());
1094                    } else {
1095                        escaped.push(character);
1096                    }
1097                    escaped
1098                })
1099                .collect();
1100            write!(f, "{meta}{ctrl}{alt}{shift}\"{keycode}\"")
1101        }
1102    }
1103}
1104
1105#[derive(Clone, Debug)]
1106pub struct EnumerationValue {
1107    pub value: usize, // index in enumeration.values
1108    pub enumeration: Rc<Enumeration>,
1109}
1110
1111impl PartialEq for EnumerationValue {
1112    fn eq(&self, other: &Self) -> bool {
1113        Rc::ptr_eq(&self.enumeration, &other.enumeration) && self.value == other.value
1114    }
1115}
1116
1117impl std::fmt::Display for EnumerationValue {
1118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1119        self.enumeration.values[self.value].fmt(f)
1120    }
1121}
1122
1123impl EnumerationValue {
1124    pub fn to_pascal_case(&self) -> String {
1125        crate::generator::to_pascal_case(&self.enumeration.values[self.value])
1126    }
1127}
1128
1129#[derive(Debug, PartialEq)]
1130pub struct LengthConversionPowers {
1131    pub rem_to_px_power: i8,
1132    pub px_to_phx_power: i8,
1133}
1134
1135/// If the `Type::UnitProduct(a)` can be converted to `Type::UnitProduct(b)` by multiplying
1136/// by the scale factor, return that scale factor, otherwise, return None
1137pub fn unit_product_length_conversion(
1138    a: &[(Unit, i8)],
1139    b: &[(Unit, i8)],
1140) -> Option<LengthConversionPowers> {
1141    // e.g. float to int conversion, no units
1142    if a.is_empty() && b.is_empty() {
1143        return Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: 0 });
1144    }
1145
1146    let mut units = [0i8; 16];
1147    for (u, count) in a {
1148        units[*u as usize] += count;
1149    }
1150    for (u, count) in b {
1151        units[*u as usize] -= count;
1152    }
1153
1154    if units[Unit::Px as usize] + units[Unit::Phx as usize] + units[Unit::Rem as usize] != 0 {
1155        return None;
1156    }
1157
1158    if units[Unit::Rem as usize] != 0
1159        && units[Unit::Phx as usize] == -units[Unit::Rem as usize]
1160        && units[Unit::Px as usize] == 0
1161    {
1162        units[Unit::Px as usize] = -units[Unit::Rem as usize];
1163        units[Unit::Phx as usize] = -units[Unit::Rem as usize];
1164    }
1165
1166    let result = LengthConversionPowers {
1167        rem_to_px_power: if units[Unit::Rem as usize] != 0 { units[Unit::Px as usize] } else { 0 },
1168        px_to_phx_power: if units[Unit::Px as usize] != 0 { units[Unit::Phx as usize] } else { 0 },
1169    };
1170
1171    units[Unit::Px as usize] = 0;
1172    units[Unit::Phx as usize] = 0;
1173    units[Unit::Rem as usize] = 0;
1174    units.into_iter().all(|x| x == 0).then_some(result)
1175}
1176
1177#[test]
1178fn unit_product_length_conversion_test() {
1179    use Option::None;
1180    use Unit::*;
1181    assert_eq!(
1182        unit_product_length_conversion(&[], &[]),
1183        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: 0 })
1184    );
1185    assert_eq!(
1186        unit_product_length_conversion(&[(Px, 1)], &[(Phx, 1)]),
1187        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
1188    );
1189    assert_eq!(
1190        unit_product_length_conversion(&[(Phx, -2)], &[(Px, -2)]),
1191        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
1192    );
1193    assert_eq!(
1194        unit_product_length_conversion(&[(Px, 1), (Phx, -2)], &[(Phx, -1)]),
1195        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
1196    );
1197    assert_eq!(
1198        unit_product_length_conversion(
1199            &[(Deg, 3), (Phx, 2), (Ms, -1)],
1200            &[(Phx, 4), (Deg, 3), (Ms, -1), (Px, -2)]
1201        ),
1202        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
1203    );
1204    assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
1205    assert_eq!(unit_product_length_conversion(&[(Deg, 1), (Phx, -2)], &[(Px, -2)]), None);
1206    assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
1207
1208    assert_eq!(
1209        unit_product_length_conversion(&[(Rem, 1)], &[(Px, 1)]),
1210        Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: 0 })
1211    );
1212    assert_eq!(
1213        unit_product_length_conversion(&[(Rem, 1)], &[(Phx, 1)]),
1214        Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: -1 })
1215    );
1216    assert_eq!(
1217        unit_product_length_conversion(&[(Rem, 2)], &[(Phx, 2)]),
1218        Some(LengthConversionPowers { rem_to_px_power: -2, px_to_phx_power: -2 })
1219    );
1220}