1use std::borrow::Cow;
5use std::collections::{BTreeMap, HashMap};
6use std::fmt::Display;
7use std::rc::Rc;
8use std::sync::Arc;
9
10use itertools::Itertools;
11
12use smol_str::SmolStr;
13
14use crate::diagnostics::SourceLocation;
15use crate::expression_tree::{BuiltinFunction, Expression, Unit};
16use crate::object_tree::{Component, DEFAULT_SLOT_NAME, PropertyVisibility};
17use crate::parser::SyntaxNode;
18use crate::typeregister::TypeRegister;
19
20#[derive(Debug, Clone, Default)]
21pub enum Type {
22 #[default]
24 Invalid,
25 Void,
27 InferredProperty,
29 InferredCallback,
31
32 Callback(Arc<Function>),
33 Function(Arc<Function>),
34
35 ComponentFactory,
36
37 Float32,
39 Int32,
40 String,
41 Color,
42 Duration,
43 PhysicalLength,
44 LogicalLength,
45 Rem,
46 Angle,
47 Percent,
48 Image,
49 Bool,
50 Model,
52 PathData, Easing,
54 Brush,
55 Array(Arc<Type>),
57 Struct(Arc<Struct>),
58 Enumeration(Arc<Enumeration>),
59 Keys,
60 DataTransfer,
63
64 UnitProduct(Vec<(Unit, i8)>),
68
69 ElementReference,
70
71 LayoutCache,
73 ArrayOfU16,
75
76 StyledText,
77 MouseCursor,
78 Closure,
79}
80
81impl core::cmp::PartialEq for Type {
82 fn eq(&self, other: &Self) -> bool {
83 match self {
84 Type::Invalid => matches!(other, Type::Invalid),
85 Type::Void => matches!(other, Type::Void),
86 Type::InferredProperty => matches!(other, Type::InferredProperty),
87 Type::InferredCallback => matches!(other, Type::InferredCallback),
88 Type::Callback(lhs) => {
89 matches!(other, Type::Callback(rhs) if lhs == rhs)
90 }
91 Type::Function(lhs) => {
92 matches!(other, Type::Function(rhs) if lhs == rhs)
93 }
94 Type::ComponentFactory => matches!(other, Type::ComponentFactory),
95 Type::Float32 => matches!(other, Type::Float32),
96 Type::Int32 => matches!(other, Type::Int32),
97 Type::String => matches!(other, Type::String),
98 Type::Color => matches!(other, Type::Color),
99 Type::Duration => matches!(other, Type::Duration),
100 Type::Angle => matches!(other, Type::Angle),
101 Type::PhysicalLength => matches!(other, Type::PhysicalLength),
102 Type::LogicalLength => matches!(other, Type::LogicalLength),
103 Type::Rem => matches!(other, Type::Rem),
104 Type::Percent => matches!(other, Type::Percent),
105 Type::Image => matches!(other, Type::Image),
106 Type::Bool => matches!(other, Type::Bool),
107 Type::Model => matches!(other, Type::Model),
108 Type::PathData => matches!(other, Type::PathData),
109 Type::Easing => matches!(other, Type::Easing),
110 Type::MouseCursor => matches!(other, Type::MouseCursor),
111 Type::Brush => matches!(other, Type::Brush),
112 Type::Array(a) => matches!(other, Type::Array(b) if a == b),
113 Type::Struct(lhs) => {
114 matches!(other, Type::Struct(rhs) if lhs.fields == rhs.fields && lhs.name == rhs.name)
115 }
116 Type::Enumeration(lhs) => matches!(other, Type::Enumeration(rhs) if lhs == rhs),
117 Type::Keys => matches!(other, Type::Keys),
118 Type::UnitProduct(a) => matches!(other, Type::UnitProduct(b) if a == b),
119 Type::ElementReference => matches!(other, Type::ElementReference),
120 Type::LayoutCache => matches!(other, Type::LayoutCache),
121 Type::ArrayOfU16 => matches!(other, Type::ArrayOfU16),
122 Type::StyledText => matches!(other, Type::StyledText),
123 Type::DataTransfer => matches!(other, Type::DataTransfer),
124 Type::Closure => matches!(other, Type::Closure),
125 }
126 }
127}
128
129impl Display for Type {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 Type::Invalid => write!(f, "<error>"),
133 Type::Void => write!(f, "void"),
134 Type::InferredProperty => write!(f, "?"),
135 Type::InferredCallback => write!(f, "callback"),
136 Type::Callback(callback) => {
137 write!(f, "callback{}", callback)
138 }
139 Type::ComponentFactory => write!(f, "component-factory"),
140 Type::Function(function) => {
141 write!(f, "function{}", function)
142 }
143 Type::Float32 => write!(f, "float"),
144 Type::Int32 => write!(f, "int"),
145 Type::String => write!(f, "string"),
146 Type::Duration => write!(f, "duration"),
147 Type::Angle => write!(f, "angle"),
148 Type::PhysicalLength => write!(f, "physical-length"),
149 Type::LogicalLength => write!(f, "length"),
150 Type::Rem => write!(f, "relative-font-size"),
151 Type::Percent => write!(f, "percent"),
152 Type::Color => write!(f, "color"),
153 Type::Image => write!(f, "image"),
154 Type::Bool => write!(f, "bool"),
155 Type::Model => write!(f, "model"),
156 Type::Array(t) => write!(f, "[{t}]"),
157 Type::Struct(t) => write!(f, "{t}"),
158 Type::PathData => write!(f, "pathdata"),
159 Type::Easing => write!(f, "easing"),
160 Type::MouseCursor => write!(f, "MouseCursor"),
161 Type::Brush => write!(f, "brush"),
162 Type::Enumeration(enumeration) => write!(f, "{}", enumeration.name),
163 Type::Keys => write!(f, "keys"),
164 Type::DataTransfer => write!(f, "data-transfer"),
165 Type::UnitProduct(vec) => {
166 const POWERS: &[char] = &['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
167 let mut x = vec.iter().map(|(unit, power)| {
168 if *power == 1 {
169 return unit.to_string();
170 }
171 let mut res = format!("{}{}", unit, if *power < 0 { "⁻" } else { "" });
172 let value = power.abs().to_string();
173 for x in value.as_bytes() {
174 res.push(POWERS[(x - b'0') as usize]);
175 }
176
177 res
178 });
179 write!(f, "({})", x.join("×"))
180 }
181 Type::ElementReference => write!(f, "element ref"),
182 Type::LayoutCache => write!(f, "layout cache"),
183 Type::ArrayOfU16 => write!(f, "[u16]"),
184 Type::StyledText => write!(f, "styled-text"),
185 Type::Closure => write!(f, "closure"),
186 }
187 }
188}
189
190impl From<Arc<Struct>> for Type {
191 fn from(value: Arc<Struct>) -> Self {
192 Self::Struct(value)
193 }
194}
195
196impl Type {
197 pub fn is_slint_sc(&self) -> bool {
200 #[cfg(feature = "slint-sc")]
201 return match self {
202 Self::Int32 | Self::LogicalLength | Self::Color | Self::Bool | Self::Image => true,
203 Self::Enumeration(en) => en.node.is_some(),
205 Self::Struct(s) => matches!(&s.name, StructName::User { .. }),
208 _ => false,
209 };
210 #[cfg(not(feature = "slint-sc"))]
211 false
212 }
213
214 pub fn is_property_type(&self) -> bool {
216 matches!(
217 self,
218 Self::Float32
219 | Self::Int32
220 | Self::String
221 | Self::Color
222 | Self::ComponentFactory
223 | Self::Duration
224 | Self::Angle
225 | Self::PhysicalLength
226 | Self::LogicalLength
227 | Self::Rem
228 | Self::Percent
229 | Self::Image
230 | Self::Bool
231 | Self::Easing
232 | Self::MouseCursor
233 | Self::Enumeration(_)
234 | Self::Keys
235 | Self::DataTransfer
236 | Self::ElementReference
237 | Self::Struct { .. }
238 | Self::Array(_)
239 | Self::Brush
240 | Self::InferredProperty
241 | Self::StyledText
242 )
243 }
244
245 pub fn ok_for_public_api(&self) -> bool {
246 !matches!(self, Self::Easing)
247 }
248
249 pub fn as_enum(&self) -> &Arc<Enumeration> {
251 match self {
252 Type::Enumeration(e) => e,
253 _ => panic!("should be an enumeration, bug in compiler pass"),
254 }
255 }
256
257 pub fn can_convert(&self, other: &Self) -> bool {
259 let can_convert_struct = |a: &BTreeMap<SmolStr, Type>, b: &BTreeMap<SmolStr, Type>| {
260 let mut has_more_property = false;
262 for (k, v) in b {
263 match a.get(k) {
264 Some(t) if !t.can_convert(v) => return false,
265 None => has_more_property = true,
266 _ => (),
267 }
268 }
269 if has_more_property {
270 if a.keys().any(|k| !b.contains_key(k)) {
272 return false;
273 }
274 }
275 true
276 };
277 match (self, other) {
278 (a, b) if a == b => true,
279 (_, Type::Invalid)
280 | (_, Type::Void)
281 | (Type::Float32, Type::Int32)
282 | (Type::Float32, Type::String)
283 | (Type::Int32, Type::Float32)
284 | (Type::Int32, Type::String)
285 | (Type::Float32, Type::Model)
286 | (Type::Int32, Type::Model)
287 | (Type::PhysicalLength, Type::LogicalLength)
288 | (Type::LogicalLength, Type::PhysicalLength)
289 | (Type::Rem, Type::LogicalLength)
290 | (Type::Rem, Type::PhysicalLength)
291 | (Type::LogicalLength, Type::Rem)
292 | (Type::PhysicalLength, Type::Rem)
293 | (Type::Percent, Type::Float32)
294 | (Type::Brush, Type::Color)
295 | (Type::Color, Type::Brush) => true,
296 (Type::Array(a), Type::Model) if a.is_property_type() => true,
297 (Type::Struct(a), Type::Struct(b)) => can_convert_struct(&a.fields, &b.fields),
298 (Type::UnitProduct(u), o) => match o.as_unit_product() {
299 Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
300 None => false,
301 },
302 (o, Type::UnitProduct(u)) => match o.as_unit_product() {
303 Some(o) => unit_product_length_conversion(u.as_slice(), o.as_slice()).is_some(),
304 None => false,
305 },
306 _ => false,
307 }
308 }
309
310 pub fn default_unit(&self) -> Option<Unit> {
313 match self {
314 Type::Duration => Some(Unit::Ms),
315 Type::PhysicalLength => Some(Unit::Phx),
316 Type::LogicalLength => Some(Unit::Px),
317 Type::Rem => Some(Unit::Rem),
318 Type::Percent => None,
320 Type::Angle => Some(Unit::Deg),
321 Type::Invalid => None,
322 Type::Void => None,
323 Type::InferredProperty | Type::InferredCallback => None,
324 Type::Callback { .. } => None,
325 Type::ComponentFactory => None,
326 Type::Function { .. } => None,
327 Type::Float32 => None,
328 Type::Int32 => None,
329 Type::String => None,
330 Type::Color => None,
331 Type::Image => None,
332 Type::Bool => None,
333 Type::Model => None,
334 Type::PathData => None,
335 Type::Easing => None,
336 Type::MouseCursor => None,
337 Type::Brush => None,
338 Type::Array(_) => None,
339 Type::Struct { .. } => None,
340 Type::Enumeration(_) => None,
341 Type::Keys => None,
342 Type::DataTransfer => None,
343 Type::UnitProduct(_) => None,
344 Type::ElementReference => None,
345 Type::LayoutCache => None,
346 Type::ArrayOfU16 => None,
347 Type::StyledText => None,
348 Type::Closure => None,
349 }
350 }
351
352 pub fn as_unit_product(&self) -> Option<Vec<(Unit, i8)>> {
354 match self {
355 Type::UnitProduct(u) => Some(u.clone()),
356 Type::Float32 | Type::Int32 => Some(Vec::new()),
357 Type::Percent => Some(Vec::new()),
358 _ => self.default_unit().map(|u| vec![(u, 1)]),
359 }
360 }
361}
362
363#[derive(Debug, Clone)]
364pub enum BuiltinPropertyDefault {
365 None,
366 Expr(ConstantExpression),
367 ElementFunction(BuiltinFunction),
369 RuntimeValue(BuiltinFunction),
372 BuiltinFunction(BuiltinFunction),
374}
375
376impl BuiltinPropertyDefault {
377 pub fn expr_without_element(&self) -> Option<Expression> {
380 match self {
381 BuiltinPropertyDefault::None => None,
382 BuiltinPropertyDefault::Expr(constant) => Some(constant.to_expression()),
383 BuiltinPropertyDefault::RuntimeValue(function) => Some(Expression::FunctionCall {
384 function: function.clone().into(),
385 arguments: Vec::new(),
386 source_location: None,
387 }),
388 BuiltinPropertyDefault::ElementFunction(..)
391 | BuiltinPropertyDefault::BuiltinFunction(..) => None,
392 }
393 }
394
395 pub fn expr(&self, elem: &crate::object_tree::ElementRc) -> Option<Expression> {
396 match self {
397 BuiltinPropertyDefault::ElementFunction(function) => Some(Expression::FunctionCall {
398 function: function.clone().into(),
399 arguments: vec![Expression::ElementReference(Rc::downgrade(elem))],
400 source_location: None,
401 }),
402 other => other.expr_without_element(),
403 }
404 }
405}
406
407#[derive(Debug, Clone)]
409pub struct BuiltinPropertyInfo {
410 pub ty: Type,
412 pub default_value: BuiltinPropertyDefault,
414 pub property_visibility: PropertyVisibility,
415 pub docs: Option<String>,
417 pub slint_sc: bool,
420 pub shadowable: bool,
427 pub pure: bool,
432}
433
434impl BuiltinPropertyInfo {
435 pub fn new(ty: Type) -> Self {
436 Self {
437 ty,
438 default_value: BuiltinPropertyDefault::None,
439 property_visibility: PropertyVisibility::InOut,
440 docs: None,
441 shadowable: false,
442 pure: false,
443 slint_sc: false,
444 }
445 }
446
447 pub fn is_native_output(&self) -> bool {
448 matches!(self.property_visibility, PropertyVisibility::InOut | PropertyVisibility::Output)
449 }
450
451 pub fn declared_pure(&self) -> Option<bool> {
453 matches!(self.ty, Type::Function(_) | Type::Callback(_)).then_some(self.pure)
454 }
455}
456
457impl From<BuiltinFunction> for BuiltinPropertyInfo {
458 fn from(function: BuiltinFunction) -> Self {
459 Self {
460 ty: Type::Function(function.ty()),
461 property_visibility: PropertyVisibility::Public,
462 docs: None,
463 shadowable: false,
464 pure: function.is_pure(),
465 default_value: BuiltinPropertyDefault::BuiltinFunction(function),
466 slint_sc: false,
467 }
468 }
469}
470
471#[derive(Clone, Debug, derive_more::From, Default)]
473pub enum ElementType {
474 Component(Rc<Component>),
476 Builtin(Rc<BuiltinElement>),
478 Native(Arc<NativeClass>),
480 #[default]
482 Error,
483 Global,
485 Interface,
487}
488
489impl PartialEq for ElementType {
490 fn eq(&self, other: &Self) -> bool {
491 match (self, other) {
492 (Self::Component(a), Self::Component(b)) => Rc::ptr_eq(a, b),
493 (Self::Builtin(a), Self::Builtin(b)) => Rc::ptr_eq(a, b),
494 (Self::Native(a), Self::Native(b)) => Arc::ptr_eq(a, b),
495 (Self::Error, Self::Error)
496 | (Self::Global, Self::Global)
497 | (Self::Interface, Self::Interface) => true,
498 _ => false,
499 }
500 }
501}
502
503impl ElementType {
504 pub fn lookup_property<'a>(
509 &self,
510 name: &'a str,
511 mode: PropertyLookupMode,
512 ) -> PropertyLookupResult<'a> {
513 match self {
514 Self::Component(c) => c.root_element.borrow().lookup_property(name, mode),
515 Self::Builtin(b) => {
516 let resolved_name =
517 if let Some(alias_name) = b.native_class.lookup_alias(name.as_ref()) {
518 Cow::Owned(alias_name.to_string())
519 } else {
520 Cow::Borrowed(name)
521 };
522 match b.properties.get(resolved_name.as_ref()) {
523 None => {
524 if b.is_non_item_type || b.is_global {
525 PropertyLookupResult::invalid(resolved_name)
526 } else {
527 crate::typeregister::reserved_property(resolved_name)
528 }
529 }
530 Some(p) => PropertyLookupResult {
531 resolved_name,
532 property_type: p.ty.clone(),
533 property_visibility: p.property_visibility,
534 declared_pure: p.declared_pure(),
535 is_local_to_component: false,
536 is_in_direct_base: false,
537 is_shadowable: p.shadowable,
538 builtin_function: match &p.default_value {
539 BuiltinPropertyDefault::BuiltinFunction(f) => Some(f.clone()),
540 _ => None,
541 },
542 is_slint_sc: p.slint_sc,
543 internal_name: None,
544 deprecated: None,
545 },
546 }
547 }
548 Self::Native(n) => {
549 let resolved_name = if let Some(alias_name) = n.lookup_alias(name.as_ref()) {
550 Cow::Owned(alias_name.to_string())
551 } else {
552 Cow::Borrowed(name)
553 };
554 let info = n.lookup_property_info(resolved_name.as_ref());
555 PropertyLookupResult {
556 resolved_name,
557 property_type: info.map(|p| p.ty.clone()).unwrap_or_default(),
558 property_visibility: PropertyVisibility::InOut,
559 declared_pure: info.and_then(|p| p.declared_pure()),
560 is_local_to_component: false,
561 is_in_direct_base: false,
562 is_shadowable: false,
563 builtin_function: None,
564 is_slint_sc: false,
565 internal_name: None,
566 deprecated: None,
567 }
568 }
569 _ => PropertyLookupResult::invalid(Cow::Borrowed(name)),
570 }
571 }
572
573 pub fn property_declaration_node(&self, name: &str) -> Option<SyntaxNode> {
575 match self {
576 Self::Component(c) => c.root_element.borrow().property_declaration_node(name),
577 _ => None,
578 }
579 }
580
581 pub fn property_list(&self) -> Vec<(SmolStr, Type)> {
583 match self {
584 Self::Component(c) => {
585 let root = c.root_element.borrow();
586 let mut r = root.base_type.property_list();
587 if !root.shadowing_members.is_empty() {
589 let hidden: std::collections::HashSet<_> =
590 root.visible_shadowing_members().collect();
591 r.retain(|(name, _)| !hidden.contains(name));
592 }
593 r.extend(
594 root.property_declarations
595 .iter()
596 .filter(|(_, d)| d.visibility != PropertyVisibility::Private)
597 .map(|(k, d)| (d.declared_name(k).clone(), d.property_type.clone())),
598 );
599 r
600 }
601 Self::Builtin(b) => {
602 b.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
603 }
604 Self::Native(n) => {
605 n.properties.iter().map(|(k, t)| (k.clone(), t.ty.clone())).collect()
606 }
607 _ => Vec::new(),
608 }
609 }
610
611 pub fn lookup_type_for_child_element(
615 &self,
616 name: &str,
617 tr: &TypeRegister,
618 ) -> Result<ElementType, String> {
619 match self {
620 Self::Component(component) => {
621 let base_type = match component.child_insertion_points.borrow().get(DEFAULT_SLOT_NAME) {
622 Some(insert_in) => insert_in.parent.borrow().base_type.clone(),
623 None => {
624 let base_type = component.root_element.borrow().base_type.clone();
625 if base_type == tr.empty_type() {
626 let element = tr.lookup_element(name)?;
627 if matches!(&element, ElementType::Builtin(b) if b.can_be_declared_without_children_slot) {
628 return Ok(element);
629 }
630 return Err(format!("'{}' cannot have children. Only components with @children can have children", component.id));
631 }
632 base_type
633 }
634 };
635 base_type.lookup_type_for_child_element(name, tr)
636 }
637 Self::Builtin(builtin) => {
638 let looked_up = tr.lookup_element(name);
639 if let Ok(ElementType::Builtin(b)) = &looked_up
640 && b.can_be_declared_without_children_slot
641 {
642 return Ok(ElementType::Builtin(b.clone()));
643 }
644 if builtin.disallow_global_types_as_child_elements {
645 if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
646 return Ok(child_type.clone().into());
647 } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
648 return Ok(builtin.clone().into());
649 }
650 let mut valid_children: Vec<_> =
651 builtin.additional_accepted_child_types.keys().cloned().collect();
652 if builtin.additional_accept_self {
653 valid_children.push(builtin.native_class.class_name.clone());
654 }
655 valid_children.sort();
656
657 let err = if valid_children.is_empty() {
658 looked_up?;
660 format!("{} cannot have children elements", builtin.native_class.class_name,)
661 } else {
662 format!(
663 "{} is not allowed within {}. Only {} are valid children",
664 name,
665 builtin.native_class.class_name,
666 valid_children.join(" ")
667 )
668 };
669 return Err(err);
670 }
671 let err = match looked_up {
672 Err(e) => e,
673 Ok(t) => {
674 if !tr.expose_internal_types
675 && matches!(&t, Self::Builtin(e) if e.is_internal)
676 {
677 format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)")
678 } else {
679 return Ok(t);
680 }
681 }
682 };
683 if let Some(child_type) = builtin.additional_accepted_child_types.get(name) {
684 return Ok(child_type.clone().into());
685 } else if builtin.additional_accept_self && name == builtin.native_class.class_name {
686 return Ok(builtin.clone().into());
687 }
688 match tr.lookup(name) {
689 Type::Invalid => Err(err),
690 ty => Err(format!("'{ty}' cannot be used as an element")),
691 }
692 }
693 _ => tr.lookup_element(name).and_then(|t| {
694 if !tr.expose_internal_types && matches!(&t, Self::Builtin(e) if e.is_internal) {
695 Err(format!("Unknown element '{name}'. (The type exists as an internal type, but cannot be accessed in this scope)"))
696 } else {
697 Ok(t)
698 }
699 })
700 }
701 }
702
703 pub fn as_builtin(&self) -> &BuiltinElement {
705 match self {
706 Self::Builtin(b) => b,
707 Self::Component(_) => panic!("This should not happen because of inlining"),
708 _ => panic!("invalid type"),
709 }
710 }
711
712 pub fn as_native(&self) -> &NativeClass {
714 match self {
715 Self::Native(b) => b,
716 Self::Component(_) => {
717 panic!("This should not happen because of native class resolution")
718 }
719 _ => panic!("invalid type"),
720 }
721 }
722
723 pub fn as_component(&self) -> &Rc<Component> {
725 match self {
726 Self::Component(c) => c,
727 _ => panic!("should be a component because of the repeater_component pass"),
728 }
729 }
730
731 pub fn type_name(&self) -> Option<&str> {
733 match self {
734 ElementType::Component(component) => Some(&component.id),
735 ElementType::Builtin(b) => Some(&b.name),
736 ElementType::Native(_) => None, ElementType::Error => None,
738 ElementType::Global => None,
739 ElementType::Interface => None,
740 }
741 }
742}
743
744impl Display for ElementType {
745 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
746 match self {
747 Self::Component(c) => c.id.fmt(f),
748 Self::Builtin(b) => b.name.fmt(f),
749 Self::Native(b) => b.class_name.fmt(f),
750 Self::Error => write!(f, "<error>"),
751 Self::Global => Ok(()),
752 Self::Interface => Ok(()),
753 }
754 }
755}
756
757macro_rules! define_builtin_struct_enum {
758 ($(
759 $(#[$attr:meta])*
760 $vis:vis struct $Name:ident {
761 $( $(#[$field_attr:meta])* $field:ident : $field_type:ty $(= $field_default:expr)?, )*
762 }
763 )*) => {
764 #[derive(Debug, Clone, PartialEq, strum::EnumString, strum::IntoStaticStr)]
765 pub enum BuiltinStruct {
766 $($Name,)*
768
769 Color,
771 LogicalPosition,
772 LogicalSize,
773
774 PathMoveTo,
777 PathLineTo,
778 PathArcTo,
779 PathCubicTo,
780 PathQuadraticTo,
781 PathClose,
782 PathElement,
783
784 Point,
789 Size,
792 StateInfo,
793 PropertyAnimation,
794 GridLayoutData,
795 GridLayoutInputData,
796 BoxLayoutData,
797 BoxLayoutOrthoData,
798 FlexboxLayoutData,
799 LayoutItemInfo,
800 FlexboxLayoutItemInfo,
801 FlexItemProps,
802 Padding,
803 LayoutInfo,
804 }
805
806 impl BuiltinStruct {
807 pub fn is_public(&self) -> bool {
808 match self {
809 $(Self::$Name => stringify!($vis) == "pub",)*
811 Self::Color | Self::LogicalPosition | Self::LogicalSize => true,
813 _ => false,
814 }
815 }
816
817 pub fn slint_name(&self) -> Option<SmolStr> {
820 match self {
821 $(Self::$Name => {
823 Some(SmolStr::new_static(stringify!($Name)))
824 })*
825 Self::Color => Some(SmolStr::new_static("color")),
827 Self::LogicalPosition => Some(SmolStr::new_static("Point")),
828 Self::LogicalSize => Some(SmolStr::new_static("Size")),
829 _ => None,
830 }
831 }
832 }
833 };
834}
835i_slint_common::for_each_builtin_structs!(define_builtin_struct_enum);
836
837impl BuiltinStruct {
838 pub fn is_layout_data(&self) -> bool {
839 matches!(
840 self,
841 Self::GridLayoutInputData
842 | Self::GridLayoutData
843 | Self::BoxLayoutData
844 | Self::BoxLayoutOrthoData
845 | Self::FlexboxLayoutData
846 )
847 }
848}
849
850#[derive(Debug, Clone, Default)]
851pub struct NativeClass {
852 pub parent: Option<Arc<NativeClass>>,
853 pub class_name: SmolStr,
854 pub cpp_vtable_getter: String,
855 pub properties: BTreeMap<SmolStr, BuiltinPropertyInfo>,
856 pub deprecated_aliases: HashMap<SmolStr, SmolStr>,
857 pub builtin_struct: Option<BuiltinStruct>,
860}
861
862impl NativeClass {
863 pub fn new(class_name: &str) -> Self {
864 let cpp_vtable_getter = format!("SLINT_GET_ITEM_VTABLE({class_name}VTable)");
865 Self {
866 class_name: class_name.into(),
867 cpp_vtable_getter,
868 properties: Default::default(),
869 ..Default::default()
870 }
871 }
872
873 pub fn new_with_properties(
874 class_name: &str,
875 properties: impl IntoIterator<Item = (SmolStr, BuiltinPropertyInfo)>,
876 ) -> Self {
877 let mut class = Self::new(class_name);
878 class.properties = properties.into_iter().collect();
879 class
880 }
881
882 pub fn property_count(&self) -> usize {
883 self.properties.len() + self.parent.clone().map(|p| p.property_count()).unwrap_or_default()
884 }
885
886 pub fn lookup_property(&self, name: &str) -> Option<&Type> {
887 self.lookup_property_info(name).map(|info| &info.ty)
888 }
889
890 pub fn lookup_property_info(&self, name: &str) -> Option<&BuiltinPropertyInfo> {
892 self.properties
893 .get(name)
894 .or_else(|| self.parent.as_ref().and_then(|parent| parent.lookup_property_info(name)))
895 }
896
897 pub fn lookup_alias(&self, name: &str) -> Option<&str> {
898 if let Some(alias_target) = self.deprecated_aliases.get(name) {
899 Some(alias_target)
900 } else if self.properties.contains_key(name) {
901 None
902 } else if let Some(parent_class) = &self.parent {
903 parent_class.lookup_alias(name)
904 } else {
905 None
906 }
907 }
908}
909
910#[derive(Debug, Clone, Copy, PartialEq, Default)]
911pub enum DefaultSizeBinding {
912 #[default]
914 None,
915 ExpandsToParentGeometry,
917 ImplicitSize,
919}
920
921#[derive(Debug, Clone)]
923pub enum ElementDocEntry {
924 Text(String),
926 Member(SmolStr),
928}
929
930#[derive(Debug, Clone, Default)]
931pub struct BuiltinElement {
932 pub name: SmolStr,
933 pub native_class: Arc<NativeClass>,
934 pub properties: BTreeMap<SmolStr, BuiltinPropertyInfo>,
935 pub additional_accepted_child_types: BTreeMap<SmolStr, Rc<BuiltinElement>>,
938 pub additional_accept_self: bool,
940 pub disallow_global_types_as_child_elements: bool,
941 pub is_non_item_type: bool,
943 pub accepts_focus: bool,
944 pub is_global: bool,
945 pub default_size_binding: DefaultSizeBinding,
946 pub is_internal: bool,
948 pub docs: Vec<ElementDocEntry>,
952 pub can_be_declared_without_children_slot: bool,
955 pub slint_sc: bool,
957}
958
959#[derive(Copy, Clone, PartialEq, Debug)]
961pub enum PropertyLookupMode {
962 ComponentLocal,
964 FromOutside,
967 InternalName,
969}
970
971#[derive(PartialEq, Debug)]
972pub struct PropertyLookupResult<'a> {
973 pub resolved_name: std::borrow::Cow<'a, str>,
974 pub property_type: Type,
975 pub property_visibility: PropertyVisibility,
976 pub declared_pure: Option<bool>,
977 pub is_local_to_component: bool,
979 pub is_in_direct_base: bool,
981 pub is_shadowable: bool,
983
984 pub internal_name: Option<SmolStr>,
989
990 pub builtin_function: Option<BuiltinFunction>,
992
993 pub is_slint_sc: bool,
996
997 pub deprecated: Option<SmolStr>,
1001}
1002
1003impl<'a> PropertyLookupResult<'a> {
1004 pub fn is_valid(&self) -> bool {
1005 self.property_type != Type::Invalid
1006 }
1007
1008 pub fn is_valid_for_assignment(&self) -> bool {
1010 !matches!(
1011 (self.property_visibility, self.is_local_to_component),
1012 (PropertyVisibility::Private, false)
1013 | (PropertyVisibility::Input, true)
1014 | (PropertyVisibility::Output, false)
1015 )
1016 }
1017
1018 #[cfg(feature = "slint-sc")]
1021 pub fn check_slint_sc(
1022 &self,
1023 name: &dyn Display,
1024 source: &dyn crate::diagnostics::Spanned,
1025 diag: &mut crate::diagnostics::BuildDiagnostics,
1026 ) {
1027 if self.is_valid() && !self.is_slint_sc {
1028 diag.slint_sc_error(&format!("The property '{name}' is"), source);
1029 }
1030 }
1031
1032 pub fn internal_or_resolved_name(&self) -> SmolStr {
1035 self.internal_name.clone().unwrap_or_else(|| self.resolved_name.as_ref().into())
1036 }
1037
1038 pub fn invalid(resolved_name: Cow<'a, str>) -> Self {
1039 Self {
1040 resolved_name,
1041 property_type: Type::Invalid,
1042 property_visibility: PropertyVisibility::Private,
1043 declared_pure: None,
1044 is_local_to_component: false,
1045 is_in_direct_base: false,
1046 is_shadowable: false,
1047 builtin_function: None,
1048 is_slint_sc: false,
1049 internal_name: None,
1050 deprecated: None,
1051 }
1052 }
1053}
1054
1055#[derive(Debug, Clone, PartialEq)]
1056pub struct Function {
1057 pub return_type: Type,
1058 pub args: Vec<Type>,
1059 pub arg_names: Vec<SmolStr>,
1062}
1063
1064impl Display for Function {
1065 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1066 write!(formatter, "(")?;
1067 for (i, arg) in self.args.iter().enumerate() {
1068 if i > 0 {
1069 write!(formatter, ", ")?;
1070 }
1071 write!(formatter, "{arg}")?;
1072 }
1073 let return_type = if self.return_type == Type::Void {
1074 String::new()
1075 } else {
1076 format!(" -> {}", self.return_type)
1077 };
1078 write!(formatter, "){return_type}")
1079 }
1080}
1081
1082#[derive(Debug, Clone)]
1083pub enum StructName {
1084 None,
1086 User {
1088 name: SmolStr,
1089 node: SourceLocation,
1091 rust_attributes: Vec<SmolStr>,
1094 field_order: Vec<SmolStr>,
1098 },
1099 Builtin(BuiltinStruct),
1100}
1101
1102impl PartialEq for StructName {
1103 fn eq(&self, other: &Self) -> bool {
1104 match (self, other) {
1105 (Self::User { name: l_user_name, .. }, Self::User { name: r_user_name, .. }) => {
1106 l_user_name == r_user_name
1107 }
1108 (Self::Builtin(l0), Self::Builtin(r0)) => l0 == r0,
1109 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
1110 }
1111 }
1112}
1113
1114impl StructName {
1115 pub fn slint_name(&self) -> Option<SmolStr> {
1116 match self {
1117 StructName::None => None,
1118 StructName::User { name, .. } => Some(name.clone()),
1119 StructName::Builtin(builtin) => builtin.slint_name(),
1120 }
1121 }
1122
1123 pub fn is_none(&self) -> bool {
1124 matches!(self, Self::None)
1125 }
1126
1127 pub fn is_some(&self) -> bool {
1128 !matches!(self, Self::None)
1129 }
1130
1131 pub fn or(self, other: Self) -> Self {
1132 match self {
1133 Self::None => other,
1134 this => this,
1135 }
1136 }
1137}
1138
1139impl From<BuiltinStruct> for StructName {
1140 fn from(value: BuiltinStruct) -> Self {
1141 Self::Builtin(value)
1142 }
1143}
1144
1145#[derive(Debug, Clone)]
1146pub struct Struct {
1147 pub fields: BTreeMap<SmolStr, Type>,
1148 pub field_defaults: BTreeMap<SmolStr, ConstantExpression>,
1152 pub name: StructName,
1153}
1154
1155impl Struct {
1156 pub fn new(fields: BTreeMap<SmolStr, Type>, name: impl Into<StructName>) -> Self {
1158 Self { fields, field_defaults: Default::default(), name: name.into() }
1159 }
1160
1161 pub fn node(&self) -> Option<&SourceLocation> {
1163 match &self.name {
1164 StructName::User { node, .. } => Some(node),
1165 _ => None,
1166 }
1167 }
1168
1169 pub fn rust_attributes(&self) -> &[SmolStr] {
1171 match &self.name {
1172 StructName::User { rust_attributes, .. } => rust_attributes,
1173 _ => &[],
1174 }
1175 }
1176
1177 pub fn field_order(&self) -> &[SmolStr] {
1179 match &self.name {
1180 StructName::User { field_order, .. } => field_order,
1181 _ => &[],
1182 }
1183 }
1184
1185 pub fn default_value_for_field(&self, name: &SmolStr) -> Expression {
1188 self.field_defaults.get(name).map(ConstantExpression::to_expression).unwrap_or_else(|| {
1189 Expression::default_value_for_type(
1190 self.fields.get(name).expect("default value requested for unknown struct field"),
1191 )
1192 })
1193 }
1194}
1195
1196#[derive(Debug, Clone)]
1209pub enum ConstantExpression {
1210 StringLiteral(SmolStr),
1211 NumberLiteral(f64, Unit),
1213 BoolLiteral(bool),
1214 EnumerationValue(EnumerationValue),
1215 Cast {
1216 from: Box<ConstantExpression>,
1217 to: Type,
1218 },
1219 UnaryOp {
1220 sub: Box<ConstantExpression>,
1221 op: char,
1222 },
1223 Struct {
1224 ty: Arc<Struct>,
1225 values: BTreeMap<SmolStr, ConstantExpression>,
1226 },
1227 Array {
1228 element_ty: Type,
1229 values: Vec<ConstantExpression>,
1230 },
1231}
1232
1233impl ConstantExpression {
1234 pub fn from_expression(expression: &Expression) -> Option<Self> {
1237 Some(match expression {
1238 Expression::StringLiteral(s) => Self::StringLiteral(s.clone()),
1239 Expression::NumberLiteral(n, unit) => Self::NumberLiteral(*n, *unit),
1240 Expression::BoolLiteral(b) => Self::BoolLiteral(*b),
1241 Expression::EnumerationValue(e) => Self::EnumerationValue(e.clone()),
1242 Expression::Cast { from, to } => {
1243 if *to == Type::String {
1248 return None;
1249 }
1250 Self::Cast { from: Box::new(Self::from_expression(from)?), to: to.clone() }
1251 }
1252 Expression::UnaryOp { sub, op } => {
1253 Self::UnaryOp { sub: Box::new(Self::from_expression(sub)?), op: *op }
1254 }
1255 Expression::Struct { ty, values } => Self::Struct {
1256 ty: ty.clone(),
1257 values: values
1258 .iter()
1259 .map(|(k, v)| Some((k.clone(), Self::from_expression(v)?)))
1260 .collect::<Option<_>>()?,
1261 },
1262 Expression::Array { element_ty, values } => Self::Array {
1263 element_ty: element_ty.clone(),
1264 values: values.iter().map(Self::from_expression).collect::<Option<_>>()?,
1265 },
1266 _ => return None,
1267 })
1268 }
1269
1270 pub fn to_expression(&self) -> Expression {
1272 match self {
1273 Self::StringLiteral(s) => Expression::StringLiteral(s.clone()),
1274 Self::NumberLiteral(n, unit) => Expression::NumberLiteral(*n, *unit),
1275 Self::BoolLiteral(b) => Expression::BoolLiteral(*b),
1276 Self::EnumerationValue(e) => Expression::EnumerationValue(e.clone()),
1277 Self::Cast { from, to } => {
1278 Expression::Cast { from: Box::new(from.to_expression()), to: to.clone() }
1279 }
1280 Self::UnaryOp { sub, op } => {
1281 Expression::UnaryOp { sub: Box::new(sub.to_expression()), op: *op }
1282 }
1283 Self::Struct { ty, values } => Expression::Struct {
1284 ty: ty.clone(),
1285 values: values.iter().map(|(k, v)| (k.clone(), v.to_expression())).collect(),
1286 },
1287 Self::Array { element_ty, values } => Expression::Array {
1288 element_ty: element_ty.clone(),
1289 values: values.iter().map(Self::to_expression).collect(),
1290 },
1291 }
1292 }
1293}
1294
1295impl Display for Struct {
1296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1297 if let Some(name) = &self.name.slint_name() {
1298 write!(f, "{name}")
1299 } else {
1300 write!(f, "{{ ")?;
1301 for (k, v) in &self.fields {
1302 write!(f, "{k}: {v},")?;
1303 }
1304 write!(f, "}}")
1305 }
1306 }
1307}
1308
1309pub(crate) fn visit_declared_types(ty: &Type, visitor: &mut impl FnMut(&SmolStr, &Type)) {
1312 match ty {
1313 Type::Struct(s) => {
1314 if let StructName::User { name, .. } = &s.name {
1315 visitor(name, ty);
1316 }
1317 for sub_ty in s.fields.values() {
1318 visit_declared_types(sub_ty, visitor);
1319 }
1320 }
1321 Type::Array(x) => visit_declared_types(x, visitor),
1322 Type::Function(function) | Type::Callback(function) => {
1323 visit_declared_types(&function.return_type, visitor);
1324 for a in &function.args {
1325 visit_declared_types(a, visitor);
1326 }
1327 }
1328 Type::Enumeration(en) if en.node.is_some() => visitor(&en.name, ty),
1329 _ => {}
1330 }
1331}
1332
1333#[derive(Debug, Clone)]
1334pub struct Enumeration {
1335 pub name: SmolStr,
1336 pub values: Vec<SmolStr>,
1337 pub default_value: usize, pub node: Option<SourceLocation>,
1340 pub rust_attributes: Vec<SmolStr>,
1343}
1344
1345impl PartialEq for Enumeration {
1346 fn eq(&self, other: &Self) -> bool {
1347 self.name.eq(&other.name)
1348 }
1349}
1350
1351impl Enumeration {
1352 pub fn default_value(self: Arc<Self>) -> EnumerationValue {
1353 EnumerationValue { value: self.default_value, enumeration: self.clone() }
1354 }
1355
1356 pub fn try_value_from_string(self: Arc<Self>, value: &str) -> Option<EnumerationValue> {
1357 self.values.iter().enumerate().find_map(|(idx, name)| {
1358 if name == value {
1359 Some(EnumerationValue { value: idx, enumeration: self.clone() })
1360 } else {
1361 None
1362 }
1363 })
1364 }
1365}
1366
1367#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
1368pub struct KeyboardModifiers {
1369 pub alt: bool,
1370 pub control: bool,
1371 pub meta: bool,
1372 pub shift: bool,
1373}
1374
1375#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
1376pub struct Keys {
1377 pub key: SmolStr,
1378 pub modifiers: KeyboardModifiers,
1379 pub ignore_shift: bool,
1380 pub ignore_alt: bool,
1381}
1382
1383impl std::fmt::Display for Keys {
1384 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1386 if self.key.is_empty() {
1387 write!(f, "")
1388 } else {
1389 let alt = self
1390 .ignore_alt
1391 .then_some("Alt?+")
1392 .or(self.modifiers.alt.then_some("Alt+"))
1393 .unwrap_or_default();
1394 let ctrl = if self.modifiers.control { "Control+" } else { "" };
1395 let meta = if self.modifiers.meta { "Meta+" } else { "" };
1396 let shift = self
1397 .ignore_shift
1398 .then_some("Shift?+")
1399 .or(self.modifiers.shift.then_some("Shift+"))
1400 .unwrap_or_default();
1401 let keycode: String = self
1402 .key
1403 .chars()
1404 .flat_map(|character| {
1405 let mut escaped = vec![];
1406 if character.is_control() {
1407 escaped.extend(character.escape_unicode());
1408 } else {
1409 escaped.push(character);
1410 }
1411 escaped
1412 })
1413 .collect();
1414 write!(f, "{meta}{ctrl}{alt}{shift}\"{keycode}\"")
1415 }
1416 }
1417}
1418
1419#[derive(Clone, Debug)]
1420pub struct EnumerationValue {
1421 pub value: usize, pub enumeration: Arc<Enumeration>,
1423}
1424
1425impl PartialEq for EnumerationValue {
1426 fn eq(&self, other: &Self) -> bool {
1427 Arc::ptr_eq(&self.enumeration, &other.enumeration) && self.value == other.value
1428 }
1429}
1430
1431impl std::fmt::Display for EnumerationValue {
1432 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1433 self.enumeration.values[self.value].fmt(f)
1434 }
1435}
1436
1437impl EnumerationValue {
1438 pub fn to_pascal_case(&self) -> String {
1439 crate::generator::to_pascal_case(&self.enumeration.values[self.value])
1440 }
1441}
1442
1443#[derive(Debug, PartialEq)]
1444pub struct LengthConversionPowers {
1445 pub rem_to_px_power: i8,
1446 pub px_to_phx_power: i8,
1447}
1448
1449pub fn unit_product_length_conversion(
1452 a: &[(Unit, i8)],
1453 b: &[(Unit, i8)],
1454) -> Option<LengthConversionPowers> {
1455 if a.is_empty() && b.is_empty() {
1457 return Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: 0 });
1458 }
1459
1460 let mut units = [0i8; 16];
1461 for (u, count) in a {
1462 units[*u as usize] += count;
1463 }
1464 for (u, count) in b {
1465 units[*u as usize] -= count;
1466 }
1467
1468 if units[Unit::Px as usize] + units[Unit::Phx as usize] + units[Unit::Rem as usize] != 0 {
1469 return None;
1470 }
1471
1472 if units[Unit::Rem as usize] != 0
1473 && units[Unit::Phx as usize] == -units[Unit::Rem as usize]
1474 && units[Unit::Px as usize] == 0
1475 {
1476 units[Unit::Px as usize] = -units[Unit::Rem as usize];
1477 units[Unit::Phx as usize] = -units[Unit::Rem as usize];
1478 }
1479
1480 let result = LengthConversionPowers {
1481 rem_to_px_power: if units[Unit::Rem as usize] != 0 { units[Unit::Px as usize] } else { 0 },
1482 px_to_phx_power: if units[Unit::Px as usize] != 0 { units[Unit::Phx as usize] } else { 0 },
1483 };
1484
1485 units[Unit::Px as usize] = 0;
1486 units[Unit::Phx as usize] = 0;
1487 units[Unit::Rem as usize] = 0;
1488 units.into_iter().all(|x| x == 0).then_some(result)
1489}
1490
1491#[test]
1492fn unit_product_length_conversion_test() {
1493 use Option::None;
1494 use Unit::*;
1495 assert_eq!(
1496 unit_product_length_conversion(&[], &[]),
1497 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: 0 })
1498 );
1499 assert_eq!(
1500 unit_product_length_conversion(&[(Px, 1)], &[(Phx, 1)]),
1501 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
1502 );
1503 assert_eq!(
1504 unit_product_length_conversion(&[(Phx, -2)], &[(Px, -2)]),
1505 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
1506 );
1507 assert_eq!(
1508 unit_product_length_conversion(&[(Px, 1), (Phx, -2)], &[(Phx, -1)]),
1509 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -1 })
1510 );
1511 assert_eq!(
1512 unit_product_length_conversion(
1513 &[(Deg, 3), (Phx, 2), (Ms, -1)],
1514 &[(Phx, 4), (Deg, 3), (Ms, -1), (Px, -2)]
1515 ),
1516 Some(LengthConversionPowers { rem_to_px_power: 0, px_to_phx_power: -2 })
1517 );
1518 assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
1519 assert_eq!(unit_product_length_conversion(&[(Deg, 1), (Phx, -2)], &[(Px, -2)]), None);
1520 assert_eq!(unit_product_length_conversion(&[(Px, 1)], &[(Phx, -1)]), None);
1521
1522 assert_eq!(
1523 unit_product_length_conversion(&[(Rem, 1)], &[(Px, 1)]),
1524 Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: 0 })
1525 );
1526 assert_eq!(
1527 unit_product_length_conversion(&[(Rem, 1)], &[(Phx, 1)]),
1528 Some(LengthConversionPowers { rem_to_px_power: -1, px_to_phx_power: -1 })
1529 );
1530 assert_eq!(
1531 unit_product_length_conversion(&[(Rem, 2)], &[(Phx, 2)]),
1532 Some(LengthConversionPowers { rem_to_px_power: -2, px_to_phx_power: -2 })
1533 );
1534}