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