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, HashSet};
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
58    /// A type made up of the product of several "unit" types.
59    /// The first parameter is the unit, and the second parameter is the power.
60    /// The vector should be sorted by 1) the power, 2) the unit.
61    UnitProduct(Vec<(Unit, i8)>),
62
63    ElementReference,
64
65    /// This is a `SharedArray<f32>`
66    LayoutCache,
67}
68
69impl core::cmp::PartialEq for Type {
70    fn eq(&self, other: &Self) -> bool {
71        match self {
72            Type::Invalid => matches!(other, Type::Invalid),
73            Type::Void => matches!(other, Type::Void),
74            Type::InferredProperty => matches!(other, Type::InferredProperty),
75            Type::InferredCallback => matches!(other, Type::InferredCallback),
76            Type::Callback(lhs) => {
77                matches!(other, Type::Callback(rhs) if lhs == rhs)
78            }
79            Type::Function(lhs) => {
80                matches!(other, Type::Function(rhs) if lhs == rhs)
81            }
82            Type::ComponentFactory => matches!(other, Type::ComponentFactory),
83            Type::Float32 => matches!(other, Type::Float32),
84            Type::Int32 => matches!(other, Type::Int32),
85            Type::String => matches!(other, Type::String),
86            Type::Color => matches!(other, Type::Color),
87            Type::Duration => matches!(other, Type::Duration),
88            Type::Angle => matches!(other, Type::Angle),
89            Type::PhysicalLength => matches!(other, Type::PhysicalLength),
90            Type::LogicalLength => matches!(other, Type::LogicalLength),
91            Type::Rem => matches!(other, Type::Rem),
92            Type::Percent => matches!(other, Type::Percent),
93            Type::Image => matches!(other, Type::Image),
94            Type::Bool => matches!(other, Type::Bool),
95            Type::Model => matches!(other, Type::Model),
96            Type::PathData => matches!(other, Type::PathData),
97            Type::Easing => matches!(other, Type::Easing),
98            Type::Brush => matches!(other, Type::Brush),
99            Type::Array(a) => matches!(other, Type::Array(b) if a == b),
100            Type::Struct(lhs) => {
101                matches!(other, Type::Struct(rhs) if lhs.fields == rhs.fields && lhs.name == rhs.name)
102            }
103            Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
104            Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),
105            Type::ElementReference => matches!(other, Type::ElementReference),
106            Type::LayoutCache => matches!(other, Type::LayoutCache),
107        }
108    }
109}
110
111impl Display for Type {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match self {
114            Type::Invalid => write!(f, "<error>"),
115            Type::Void => write!(f, "void"),
116            Type::InferredProperty => write!(f, "?"),
117            Type::InferredCallback => write!(f, "callback"),
118            Type::Callback(callback) => {
119                write!(f, "callback")?;
120                if !callback.args.is_empty() {
121                    write!(f, "(")?;
122                    for (i, arg) in callback.args.iter().enumerate() {
123                        if i > 0 {
124                            write!(f, ",")?;
125                        }
126                        write!(f, "{}", arg)?;
127                    }
128                    write!(f, ")")?
129                }
130                write!(f, "-> {}", callback.return_type)?;
131                Ok(())
132            }
133            Type::ComponentFactory => write!(f, "component-factory"),
134            Type::Function(function) => {
135                write!(f, "function(")?;
136                for (i, arg) in function.args.iter().enumerate() {
137                    if i > 0 {
138                        write!(f, ",")?;
139                    }
140                    write!(f, "{}", arg)?;
141                }
142                write!(f, ") -> {}", function.return_type)
143            }
144            Type::Float32 => write!(f, "float"),
145            Type::Int32 => write!(f, "int"),
146            Type::String => write!(f, "string"),
147            Type::Duration => write!(f, "duration"),
148            Type::Angle => write!(f, "angle"),
149            Type::PhysicalLength => write!(f, "physical-length"),
150            Type::LogicalLength => write!(f, "length"),
151            Type::Rem => write!(f, "relative-font-size"),
152            Type::Percent => write!(f, "percent"),
153            Type::Color => write!(f, "color"),
154            Type::Image => write!(f, "image"),
155            Type::Bool => write!(f, "bool"),
156            Type::Model => write!(f, "model"),
157            Type::Array(t) => write!(f, "[{}]", t),
158            Type::Struct(t) => {
159                if let Some(name) = &t.name {
160                    if let Some(separator_pos) = name.rfind("::") {
161                        // write the slint type and not the native type
162                        write!(f, "{}", &name[separator_pos + 2..])
163                    } else {
164                        write!(f, "{}", name)
165                    }
166                } else {
167                    write!(f, "{{ ")?;
168                    for (k, v) in &t.fields {
169                        write!(f, "{}: {},", k, v)?;
170                    }
171                    write!(f, "}}")
172                }
173            }
174            Type::PathData => write!(f, "pathdata"),
175            Type::Easing => write!(f, "easing"),
176            Type::Brush => write!(f, "brush"),
177            Type::Enumeration(enumeration) => write!(f, "enum {}", enumeration.name),
178            Type::UnitProduct(vec) => {
179                const POWERS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
180                let mut x = vec.iter().map(|(unit, power)| {
181                    if *power == 1 {
182                        return unit.to_string();
183                    }
184                    let mut res = format!("{}{}", unit, if *power < 0 { "⁻" } else { "" });
185                    let value = power.abs().to_string();
186                    for x in value.as_bytes() {
187                        res.push(POWERS[(x - b'0') as usize]);
188                    }
189
190                    res
191                });
192                write!(f, "({})", x.join("×"))
193            }
194            Type::ElementReference => write!(f, "element ref"),
195            Type::LayoutCache => write!(f, "layout cache"),
196        }
197    }
198}
199
200impl Type {
201    /// valid type for properties
202    pub fn is_property_type(&self) -> bool {
203        matches!(
204            self,
205            Self::Float32
206                | Self::Int32
207                | Self::String
208                | Self::Color
209                | Self::ComponentFactory
210                | Self::Duration
211                | Self::Angle
212                | Self::PhysicalLength
213                | Self::LogicalLength
214                | Self::Rem
215                | Self::Percent
216                | Self::Image
217                | Self::Bool
218                | Self::Easing
219                | Self::Enumeration(_)
220                | Self::ElementReference
221                | Self::Struct { .. }
222                | Self::Array(_)
223                | Self::Brush
224                | Self::InferredProperty
225        )
226    }
227
228    pub fn ok_for_public_api(&self) -> bool {
229        !matches!(self, Self::Easing)
230    }
231
232    /// Assume it is an enumeration, panic if it isn't
233    pub fn as_enum(&self) -> &Rc<Enumeration> {
234        match self {
235            Type::Enumeration(e) => e,
236            _ => panic!("should be an enumeration, bug in compiler pass"),
237        }
238    }
239
240    /// Return true if the type can be converted to the other type
241    pub fn can_convert(&self, other: &Self) -> bool {
242        let can_convert_struct = |a: &BTreeMap<SmolStr, Type>, b: &BTreeMap<SmolStr, Type>| {
243            // the struct `b` has property that the struct `a` doesn't
244            let mut has_more_property = false;
245            for (k, v) in b {
246                match a.get(k) {
247                    Some(t) if !t.can_convert(v) => return false,
248                    None => has_more_property = true,
249                    _ => (),
250                }
251            }
252            if has_more_property {
253                // we should reject the conversion if `a` has property that `b` doesn't have
254                if a.keys().any(|k| !b.contains_key(k)) {
255                    return false;
256                }
257            }
258            true
259        };
260        match (self, other) {
261            (a, b) if a == b => true,
262            (_, Type::Invalid)
263            | (_, Type::Void)
264            | (Type::Float32, Type::Int32)
265            | (Type::Float32, Type::String)
266            | (Type::Int32, Type::Float32)
267            | (Type::Int32, Type::String)
268            | (Type::Float32, Type::Model)
269            | (Type::Int32, Type::Model)
270            | (Type::PhysicalLength, Type::LogicalLength)
271            | (Type::LogicalLength, Type::PhysicalLength)
272            | (Type::Rem, Type::LogicalLength)
273            | (Type::Rem, Type::PhysicalLength)
274            | (Type::LogicalLength, Type::Rem)
275            | (Type::PhysicalLength, Type::Rem)
276            | (Type::Percent, Type::Float32)
277            | (Type::Brush, Type::Color)
278            | (Type::Color, Type::Brush) => true,
279            (Type::Array(a), Type::Model) if a.is_property_type() => true,
280            (Type::Struct(a), Type::Struct(b)) => can_convert_struct(&a.fields, &b.fields),
281            (Type::UnitProduct(u), o) => match o.as_unit_product() {
282                Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
283                None => false,
284            },
285            (o, Type::UnitProduct(u)) => match o.as_unit_product() {
286                Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
287                None => false,
288            },
289            _ => false,
290        }
291    }
292
293    /// If this is a number type which should be used with an unit, this returns the default unit
294    /// otherwise, returns None
295    pub fn default_unit(&self) -> Option<Unit> {
296        match self {
297            Type::Duration => Some(Unit::Ms),
298            Type::PhysicalLength => Some(Unit::Phx),
299            Type::LogicalLength => Some(Unit::Px),
300            Type::Rem => Some(Unit::Rem),
301            // Unit::Percent is special that it does not combine with other units like
302            Type::Percent => None,
303            Type::Angle => Some(Unit::Deg),
304            Type::Invalid => None,
305            Type::Void => None,
306            Type::InferredProperty | Type::InferredCallback => None,
307            Type::Callback { .. } => None,
308            Type::ComponentFactory => None,
309            Type::Function { .. } => None,
310            Type::Float32 => None,
311            Type::Int32 => None,
312            Type::String => None,
313            Type::Color => None,
314            Type::Image => None,
315            Type::Bool => None,
316            Type::Model => None,
317            Type::PathData => None,
318            Type::Easing => None,
319            Type::Brush => None,
320            Type::Array(_) => None,
321            Type::Struct { .. } => None,
322            Type::Enumeration(_) => None,
323            Type::UnitProduct(_) => None,
324            Type::ElementReference => None,
325            Type::LayoutCache => None,
326        }
327    }
328
329    /// Return a unit product vector even for single scalar
330    pub fn as_unit_product(&self) -> Option<Vec<(Unit, i8)>> {
331        match self {
332            Type::UnitProduct(u) => Some(u.clone()),
333            Type::Float32 | Type::Int32 => Some(Vec::new()),
334            Type::Percent => Some(Vec::new()),
335            _ => self.default_unit().map(|u| vec![(u, 1)]),
336        }
337    }
338}
339
340#[derive(Debug, Clone)]
341
342pub enum BuiltinPropertyDefault {
343    None,
344    Expr(Expression),
345    Fn(fn(&crate::object_tree::ElementRc) -> Expression),
346}
347
348impl BuiltinPropertyDefault {
349    pub fn expr(&self, elem: &crate::object_tree::ElementRc) -> Option<Expression> {
350        match self {
351            BuiltinPropertyDefault::None => None,
352            BuiltinPropertyDefault::Expr(expression) => Some(expression.clone()),
353            BuiltinPropertyDefault::Fn(init_expr) => Some(init_expr(elem)),
354        }
355    }
356}
357
358/// Information about properties in NativeClass
359#[derive(Debug, Clone)]
360pub struct BuiltinPropertyInfo {
361    /// The property type
362    pub ty: Type,
363    /// When != None, this is the initial value that we will have to set if no other binding were specified
364    pub default_value: BuiltinPropertyDefault,
365    pub property_visibility: PropertyVisibility,
366}
367
368impl BuiltinPropertyInfo {
369    pub fn new(ty: Type) -> Self {
370        Self {
371            ty,
372            default_value: BuiltinPropertyDefault::None,
373            property_visibility: PropertyVisibility::InOut,
374        }
375    }
376
377    pub fn is_native_output(&self) -> bool {
378        matches!(self.property_visibility, PropertyVisibility::InOut | PropertyVisibility::Output)
379    }
380}
381
382/// The base of an element
383#[derive(Clone, Debug, derive_more::From)]
384pub enum ElementType {
385    /// The element is based of a component
386    Component(Rc<Component>),
387    /// The element is a builtin element
388    Builtin(Rc<BuiltinElement>),
389    /// The native type was resolved by the resolve_native_class pass.
390    Native(Rc<NativeClass>),
391    /// The base element couldn't be looked up
392    Error,
393    /// This should be the base type of the root element of a global component
394    Global,
395}
396
397impl PartialEq for ElementType {
398    fn eq(&self, other: &Self) -> bool {
399        match (self, other) {
400            (Self::Component(a), Self::Component(b)) => Rc::ptr_eq(a, b),
401            (Self::Builtin(a), Self::Builtin(b)) => Rc::ptr_eq(a, b),
402            (Self::Native(a), Self::Native(b)) => Rc::ptr_eq(a, b),
403            (Self::Error, Self::Error) | (Self::Global, Self::Global) => true,
404            _ => false,
405        }
406    }
407}
408
409impl ElementType {
410    pub fn lookup_property<'a>(&self, name: &'a str) -> PropertyLookupResult<'a> {
411        match self {
412            Self::Component(c) => c.root_element.borrow().lookup_property(name),
413            Self::Builtin(b) => {
414                let resolved_name =
415                    if let Some(alias_name) = b.native_class.lookup_alias(name.as_ref()) {
416                        Cow::Owned(alias_name.to_string())
417                    } else {
418                        Cow::Borrowed(name)
419                    };
420                match b.properties.get(resolved_name.as_ref()) {
421                    None => {
422                        if b.is_non_item_type {
423                            PropertyLookupResult {
424                                resolved_name,
425                                property_type: Type::Invalid,
426                                property_visibility: PropertyVisibility::Private,
427                                declared_pure: None,
428                                is_local_to_component: false,
429                                is_in_direct_base: false,
430                            }
431                        } else {
432                            crate::typeregister::reserved_property(name)
433                        }
434                    }
435                    Some(p) => PropertyLookupResult {
436                        resolved_name,
437                        property_type: p.ty.clone(),
438                        property_visibility: p.property_visibility,
439                        declared_pure: None,
440                        is_local_to_component: false,
441                        is_in_direct_base: false,
442                    },
443                }
444            }
445            Self::Native(n) => {
446                let resolved_name = if let Some(alias_name) = n.lookup_alias(name.as_ref()) {
447                    Cow::Owned(alias_name.to_string())
448                } else {
449                    Cow::Borrowed(name)
450                };
451                let property_type =
452                    n.lookup_property(resolved_name.as_ref()).cloned().unwrap_or_default();
453                PropertyLookupResult {
454                    resolved_name,
455                    property_type,
456                    property_visibility: PropertyVisibility::InOut,
457                    declared_pure: None,
458                    is_local_to_component: false,
459                    is_in_direct_base: false,
460                }
461            }
462            _ => PropertyLookupResult {
463                resolved_name: Cow::Borrowed(name),
464                property_type: Type::Invalid,
465                property_visibility: PropertyVisibility::Private,
466                declared_pure: None,
467                is_local_to_component: false,
468                is_in_direct_base: false,
469            },
470        }
471    }
472
473    /// List of sub properties valid for the auto completion
474    pub fn property_list(&self) -> Vec<(SmolStr, Type)> {
475        match self {
476            Self::Component(c) => {
477                let mut r = c.root_element.borrow().base_type.property_list();
478                r.extend(
479                    c.root_element
480                        .borrow()
481                        .property_declarations
482                        .iter()
483                        .map(|(k, d)| (k.clone(), d.property_type.clone())),
484                );
485                r
486            }
487            Self::Builtin(b) => {
488                b.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
489            }
490            Self::Native(n) => {
491                n.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
492            }
493            _ => Vec::new(),
494        }
495    }
496
497    /// This function looks at the element and checks whether it can have Elements of type `name` as children.
498    /// It returns an Error if that is not possible or an Option of the ElementType if it is.
499    /// The option is unset when the compiler does not know the type well enough to avoid further
500    /// probing.
501    pub fn accepts_child_element(
502        &self,
503        name: &str,
504        tr: &TypeRegister,
505    ) -> Result<Option<ElementType>, String> {
506        match self {
507            Self::Component(component) if component.child_insertion_point.borrow().is_none() => {
508                let base_type = component.root_element.borrow().base_type.clone();
509                if base_type == tr.empty_type() {
510                    return Err(format!("'{}' cannot have children. Only components with @children can have children", component.id));
511                }
512                return base_type.accepts_child_element(name, tr);
513            }
514            Self::Builtin(builtin) => {
515                if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
516                    return Ok(Some(child_type.clone()));
517                }
518                if builtin.disallow_global_types_as_child_elements {
519                    let mut valid_children: Vec<_> =
520                        builtin.additional_accepted_child_types.keys().cloned().collect();
521                    valid_children.sort();
522
523                    let err = if valid_children.is_empty() {
524                        format!("{} cannot have children elements", builtin.native_class.class_name,)
525                    } else {
526                        format!(
527                            "{} is not allowed within {}. Only {} are valid children",
528                            name,
529                            builtin.native_class.class_name,
530                            valid_children.join(" ")
531                        )
532                    };
533
534                    return Err(err);
535                }
536            }
537            _ => {}
538        };
539        Ok(None)
540    }
541
542    /// This function looks at the element and checks whether it can have Elements of type `name` as children.
543    /// In addition to what `accepts_child_element` does, this method also probes the type of `name`.
544    /// It returns an Error if that is not possible or an `ElementType` if it is.
545    pub fn lookup_type_for_child_element(
546        &self,
547        name: &str,
548        tr: &TypeRegister,
549    ) -> Result<ElementType, String> {
550        if let Some(ct) = self.accepts_child_element(name, tr)? {
551            return Ok(ct);
552        }
553
554        tr.lookup_element(name).and_then(|t| {
555            if !tr.expose_internal_types && matches!(&t, Self::Builtin(e) if e.is_internal) {
556                Err(format!("Unknown element '{}'. (The type exist as an internal type, but cannot be accessed in this scope)", name))
557            } else {
558                Ok(t)
559            }
560        }).map_err(|s| {
561            match tr.lookup(name)  {
562                Type::Invalid => s,
563                ty => format!("'{ty}' cannot be used as an element")
564            }
565        })
566    }
567
568    pub fn lookup_member_function(&self, name: &str) -> Option<BuiltinFunction> {
569        match self {
570            Self::Builtin(builtin) => builtin
571                .member_functions
572                .get(name)
573                .cloned()
574                .or_else(|| crate::typeregister::reserved_member_function(name)),
575            Self::Component(component) => {
576                component.root_element.borrow().base_type.lookup_member_function(name)
577            }
578            _ => None,
579        }
580    }
581
582    pub fn collect_contextual_types(
583        &self,
584        context_restricted_types: &mut HashMap<SmolStr, HashSet<SmolStr>>,
585    ) {
586        let builtin = match self {
587            Self::Builtin(ty) => ty,
588            _ => return,
589        };
590        for (accepted_child_type_name, accepted_child_type) in
591            builtin.additional_accepted_child_types.iter()
592        {
593            context_restricted_types
594                .entry(accepted_child_type_name.clone())
595                .or_default()
596                .insert(builtin.native_class.class_name.clone());
597
598            accepted_child_type.collect_contextual_types(context_restricted_types);
599        }
600    }
601
602    /// Assume this is a builtin type, panic if it isn't
603    pub fn as_builtin(&self) -> &BuiltinElement {
604        match self {
605            Self::Builtin(b) => b,
606            Self::Component(_) => panic!("This should not happen because of inlining"),
607            _ => panic!("invalid type"),
608        }
609    }
610
611    /// Assume this is a builtin type, panic if it isn't
612    pub fn as_native(&self) -> &NativeClass {
613        match self {
614            Self::Native(b) => b,
615            Self::Component(_) => {
616                panic!("This should not happen because of native class resolution")
617            }
618            _ => panic!("invalid type"),
619        }
620    }
621
622    /// Assume it is a Component, panic if it isn't
623    pub fn as_component(&self) -> &Rc<Component> {
624        match self {
625            Self::Component(c) => c,
626            _ => panic!("should be a component because of the repeater_component pass"),
627        }
628    }
629
630    /// Returns the Slint type name if applicable (for example `Rectangle` or `MyButton` when `component MyButton {}` is used as `MyButton` element)
631    pub fn type_name(&self) -> Option<&str> {
632        match self {
633            ElementType::Component(component) => Some(&component.id),
634            ElementType::Builtin(b) => Some(&b.name),
635            ElementType::Native(_) => None, // Too late, caller should call this function before the native class lowering
636            ElementType::Error => None,
637            ElementType::Global => None,
638        }
639    }
640}
641
642impl Display for ElementType {
643    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644        match self {
645            Self::Component(c) => c.id.fmt(f),
646            Self::Builtin(b) => b.name.fmt(f),
647            Self::Native(b) => b.class_name.fmt(f),
648            Self::Error => write!(f, "<error>"),
649            Self::Global => Ok(()),
650        }
651    }
652}
653
654impl Default for ElementType {
655    fn default() -> Self {
656        Self::Error
657    }
658}
659
660#[derive(Debug, Clone, Default)]
661pub struct NativeClass {
662    pub parent: Option<Rc<NativeClass>>,
663    pub class_name: SmolStr,
664    pub cpp_vtable_getter: String,
665    pub properties: HashMap<SmolStr, BuiltinPropertyInfo>,
666    pub deprecated_aliases: HashMap<SmolStr, SmolStr>,
667    pub cpp_type: Option<SmolStr>,
668    pub rust_type_constructor: Option<SmolStr>,
669}
670
671impl NativeClass {
672    pub fn new(class_name: &str) -> Self {
673        let cpp_vtable_getter = format!("SLINT_GET_ITEM_VTABLE({}VTable)", class_name);
674        Self {
675            class_name: class_name.into(),
676            cpp_vtable_getter,
677            properties: Default::default(),
678            ..Default::default()
679        }
680    }
681
682    pub fn new_with_properties(
683        class_name: &str,
684        properties: impl IntoIterator<Item = (SmolStr, BuiltinPropertyInfo)>,
685    ) -> Self {
686        let mut class = Self::new(class_name);
687        class.properties = properties.into_iter().collect();
688        class
689    }
690
691    pub fn property_count(&self) -> usize {
692        self.properties.len() + self.parent.clone().map(|p| p.property_count()).unwrap_or_default()
693    }
694
695    pub fn lookup_property(&self, name: &str) -> Option<&Type> {
696        if let Some(bty) = self.properties.get(name) {
697            Some(&bty.ty)
698        } else if let Some(parent_class) = &self.parent {
699            parent_class.lookup_property(name)
700        } else {
701            None
702        }
703    }
704
705    pub fn lookup_alias(&self, name: &str) -> Option<&str> {
706        if let Some(alias_target) = self.deprecated_aliases.get(name) {
707            Some(alias_target)
708        } else if self.properties.contains_key(name) {
709            None
710        } else if let Some(parent_class) = &self.parent {
711            parent_class.lookup_alias(name)
712        } else {
713            None
714        }
715    }
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Default)]
719pub enum DefaultSizeBinding {
720    /// There should not be a default binding for the size
721    #[default]
722    None,
723    /// The size should default to `width:100%; height:100%`
724    ExpandsToParentGeometry,
725    /// The size should default to the item's implicit size
726    ImplicitSize,
727}
728
729#[derive(Debug, Clone, Default)]
730pub struct BuiltinElement {
731    pub name: SmolStr,
732    pub native_class: Rc<NativeClass>,
733    pub properties: BTreeMap<SmolStr, BuiltinPropertyInfo>,
734    pub additional_accepted_child_types: HashMap<SmolStr, ElementType>,
735    pub disallow_global_types_as_child_elements: bool,
736    /// Non-item type do not have reserved properties (x/width/rowspan/...) added to them  (eg: PropertyAnimation)
737    pub is_non_item_type: bool,
738    pub accepts_focus: bool,
739    pub member_functions: HashMap<SmolStr, BuiltinFunction>,
740    pub is_global: bool,
741    pub default_size_binding: DefaultSizeBinding,
742    /// When true this is an internal type not shown in the auto-completion
743    pub is_internal: bool,
744}
745
746impl BuiltinElement {
747    pub fn new(native_class: Rc<NativeClass>) -> Self {
748        Self { name: native_class.class_name.clone(), native_class, ..Default::default() }
749    }
750}
751
752#[derive(PartialEq, Debug)]
753pub struct PropertyLookupResult<'a> {
754    pub resolved_name: std::borrow::Cow<'a, str>,
755    pub property_type: Type,
756    pub property_visibility: PropertyVisibility,
757    pub declared_pure: Option<bool>,
758    /// True if the property is part of the the current component (for visibility purposes)
759    pub is_local_to_component: bool,
760    /// True if the property in the direct base of the component (for visibility purposes)
761    pub is_in_direct_base: bool,
762}
763
764impl<'a> PropertyLookupResult<'a> {
765    pub fn is_valid(&self) -> bool {
766        self.property_type != Type::Invalid
767    }
768
769    /// Can this property be used in an assignment
770    pub fn is_valid_for_assignment(&self) -> bool {
771        !matches!(
772            (self.property_visibility, self.is_local_to_component),
773            (PropertyVisibility::Private, false)
774                | (PropertyVisibility::Input, true)
775                | (PropertyVisibility::Output, false)
776        )
777    }
778}
779
780#[derive(Debug, Clone, PartialEq)]
781pub struct Function {
782    pub return_type: Type,
783    pub args: Vec<Type>,
784    /// The optional names of the arguments (empty string means not set).
785    /// The names are not technically part of the type, but it is good to have them available for auto-completion
786    pub arg_names: Vec<SmolStr>,
787}
788
789#[derive(Debug, Clone)]
790pub struct Struct {
791    pub fields: BTreeMap<SmolStr, Type>,
792    /// When declared in .slint as  `struct Foo := { }`, then the name is "Foo"
793    /// When there is no node, but there is a name, then it is a builtin type
794    pub name: Option<SmolStr>,
795    /// When declared in .slint, this is the node of the declaration.
796    pub node: Option<syntax_nodes::ObjectType>,
797    /// derived
798    pub rust_attributes: Option<Vec<SmolStr>>,
799}
800
801#[derive(Debug, Clone)]
802pub struct Enumeration {
803    pub name: SmolStr,
804    pub values: Vec<SmolStr>,
805    pub default_value: usize, // index in values
806    // For non-builtins enums, this is the declaration node
807    pub node: Option<syntax_nodes::EnumDeclaration>,
808}
809
810impl PartialEq for Enumeration {
811    fn eq(&self, other: &Self) -> bool {
812        self.name.eq(&other.name)
813    }
814}
815
816impl Enumeration {
817    pub fn default_value(self: Rc<Self>) -> EnumerationValue {
818        EnumerationValue { value: self.default_value, enumeration: self.clone() }
819    }
820
821    pub fn try_value_from_string(self: Rc<Self>, value: &str) -> Option<EnumerationValue> {
822        self.values.iter().enumerate().find_map(|(idx, name)| {
823            if name == value {
824                Some(EnumerationValue { value: idx, enumeration: self.clone() })
825            } else {
826                None
827            }
828        })
829    }
830}
831
832#[derive(Clone, Debug)]
833pub struct EnumerationValue {
834    pub value: usize, // index in enumeration.values
835    pub enumeration: Rc<Enumeration>,
836}
837
838impl PartialEq for EnumerationValue {
839    fn eq(&self, other: &Self) -> bool {
840        Rc::ptr_eq(&self.enumeration, &other.enumeration) && self.value == other.value
841    }
842}
843
844impl std::fmt::Display for EnumerationValue {
845    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
846        self.enumeration.values[self.value].fmt(f)
847    }
848}
849
850impl EnumerationValue {
851    pub fn to_pascal_case(&self) -> String {
852        crate::generator::to_pascal_case(&self.enumeration.values[self.value])
853    }
854}
855
856#[derive(Debug, PartialEq)]
857pub struct LengthConversionPowers {
858    pub rem_to_px_power: i8,
859    pub px_to_phx_power: i8,
860}
861
862/// If the `Type::UnitProduct(a)` can be converted to `Type::UnitProduct(a)` by multiplying
863/// by the scale factor, return that scale factor, otherwise, return None
864pub fn unit_product_length_conversion(
865    a: &[(Unit, i8)],
866    b: &[(Unit, i8)],
867) -> Option<LengthConversionPowers> {
868    let mut units = [0i8; 16];
869    for (u, count) in a {
870        units[*u as usize] += count;
871    }
872    for (u, count) in b {
873        units[*u as usize] -= count;
874    }
875
876    if units[Unit::Px as usize] + units[Unit::Phx as usize] + units[Unit::Rem as usize] != 0 {
877        return None;
878    }
879
880    if units[Unit::Rem as usize] != 0
881        && units[Unit::Phx as usize] == -units[Unit::Rem as usize]
882        && units[Unit::Px as usize] == 0
883    {
884        units[Unit::Px as usize] = -units[Unit::Rem as usize];
885        units[Unit::Phx as usize] = -units[Unit::Rem as usize];
886    }
887
888    let result = LengthConversionPowers {
889        rem_to_px_power: if units[Unit::Rem as usize] != 0 { units[Unit::Px as usize] } else { 0 },
890        px_to_phx_power: if units[Unit::Px as usize] != 0 { units[Unit::Phx as usize] } else { 0 },
891    };
892
893    units[Unit::Px as usize] = 0;
894    units[Unit::Phx as usize] = 0;
895    units[Unit::Rem as usize] = 0;
896    units.into_iter().all(|x| x == 0).then_some(result)
897}
898
899#[test]
900fn unit_product_length_conversion_test() {
901    use Option::None;
902    use Unit::*;
903    assert_eq!(
904        unit_product_length_conversion(&[(Px, 1)], &[(Phx, 1)]),
905        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
906    );
907    assert_eq!(
908        unit_product_length_conversion(&[(Phx, -2)], &[(Px, -2)]),
909        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
910    );
911    assert_eq!(
912        unit_product_length_conversion(&[(Px, 1), (Phx, -2)], &[(Phx, -1)]),
913        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
914    );
915    assert_eq!(
916        unit_product_length_conversion(
917            &[(Deg, 3), (Phx, 2), (Ms, -1)],
918            &[(Phx, 4), (Deg, 3), (Ms, -1), (Px, -2)]
919        ),
920        Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
921    );
922    assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
923    assert_eq!(unit_product_length_conversion(&[(Deg, 1), (Phx, -2)], &[(Px, -2)]), None);
924    assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
925
926    assert_eq!(
927        unit_product_length_conversion(&[(Rem, 1)], &[(Px, 1)]),
928        Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: 0 })
929    );
930    assert_eq!(
931        unit_product_length_conversion(&[(Rem, 1)], &[(Phx, 1)]),
932        Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: -1 })
933    );
934    assert_eq!(
935        unit_product_length_conversion(&[(Rem, 2)], &[(Phx, 2)]),
936        Some(LengthConversionPowers { rem_to_px_power: -2, px_to_phx_power: -2 })
937    );
938}