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