| 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 | |
| 4 | use super::{ |
| 5 | GlobalIdx, PropertyReference, RepeatedElementIdx, SubComponentIdx, SubComponentInstanceIdx, |
| 6 | }; |
| 7 | use crate::expression_tree::{BuiltinFunction, MinMaxOp, OperatorClass}; |
| 8 | use crate::langtype::Type; |
| 9 | use crate::layout::Orientation; |
| 10 | use core::num::NonZeroUsize; |
| 11 | use itertools::Either; |
| 12 | use smol_str::SmolStr; |
| 13 | use std::collections::BTreeMap; |
| 14 | use std::rc::Rc; |
| 15 | |
| 16 | #[derive (Debug, Clone)] |
| 17 | pub enum Expression { |
| 18 | /// A string literal. The .0 is the content of the string, without the quotes |
| 19 | StringLiteral(SmolStr), |
| 20 | /// Number |
| 21 | NumberLiteral(f64), |
| 22 | /// Bool |
| 23 | BoolLiteral(bool), |
| 24 | |
| 25 | /// Reference to a property (which can also be a callback) or an element (property name is empty then). |
| 26 | PropertyReference(PropertyReference), |
| 27 | |
| 28 | /// Reference the parameter at the given index of the current function. |
| 29 | FunctionParameterReference { |
| 30 | index: usize, |
| 31 | //ty: Type, |
| 32 | }, |
| 33 | |
| 34 | /// Should be directly within a CodeBlock expression, and store the value of the expression in a local variable |
| 35 | StoreLocalVariable { |
| 36 | name: SmolStr, |
| 37 | value: Box<Expression>, |
| 38 | }, |
| 39 | |
| 40 | /// a reference to the local variable with the given name. The type system should ensure that a variable has been stored |
| 41 | /// with this name and this type before in one of the statement of an enclosing codeblock |
| 42 | ReadLocalVariable { |
| 43 | name: SmolStr, |
| 44 | ty: Type, |
| 45 | }, |
| 46 | |
| 47 | /// Access to a field of the given name within a struct. |
| 48 | StructFieldAccess { |
| 49 | /// This expression should have [`Type::Struct`] type |
| 50 | base: Box<Expression>, |
| 51 | name: SmolStr, |
| 52 | }, |
| 53 | |
| 54 | /// Access to a index within an array. |
| 55 | ArrayIndex { |
| 56 | /// This expression should have [`Type::Array`] type |
| 57 | array: Box<Expression>, |
| 58 | index: Box<Expression>, |
| 59 | }, |
| 60 | |
| 61 | /// Cast an expression to the given type |
| 62 | Cast { |
| 63 | from: Box<Expression>, |
| 64 | to: Type, |
| 65 | }, |
| 66 | |
| 67 | /// a code block with different expression |
| 68 | CodeBlock(Vec<Expression>), |
| 69 | |
| 70 | /// A function call |
| 71 | BuiltinFunctionCall { |
| 72 | function: BuiltinFunction, |
| 73 | arguments: Vec<Expression>, |
| 74 | }, |
| 75 | CallBackCall { |
| 76 | callback: PropertyReference, |
| 77 | arguments: Vec<Expression>, |
| 78 | }, |
| 79 | FunctionCall { |
| 80 | function: PropertyReference, |
| 81 | arguments: Vec<Expression>, |
| 82 | }, |
| 83 | ItemMemberFunctionCall { |
| 84 | function: PropertyReference, |
| 85 | }, |
| 86 | |
| 87 | /// A BuiltinFunctionCall, but the function is not yet in the `BuiltinFunction` enum |
| 88 | /// TODO: merge in BuiltinFunctionCall |
| 89 | ExtraBuiltinFunctionCall { |
| 90 | return_ty: Type, |
| 91 | function: String, |
| 92 | arguments: Vec<Expression>, |
| 93 | }, |
| 94 | |
| 95 | /// An assignment of a value to a property |
| 96 | PropertyAssignment { |
| 97 | property: PropertyReference, |
| 98 | value: Box<Expression>, |
| 99 | }, |
| 100 | /// an assignment of a value to the model data |
| 101 | ModelDataAssignment { |
| 102 | // how deep in the parent hierarchy we go |
| 103 | level: usize, |
| 104 | value: Box<Expression>, |
| 105 | }, |
| 106 | /// An assignment done with the `foo[idx] = ...` |
| 107 | ArrayIndexAssignment { |
| 108 | array: Box<Expression>, |
| 109 | index: Box<Expression>, |
| 110 | value: Box<Expression>, |
| 111 | }, |
| 112 | |
| 113 | BinaryExpression { |
| 114 | lhs: Box<Expression>, |
| 115 | rhs: Box<Expression>, |
| 116 | /// '+', '-', '/', '*', '=', '!', '<', '>', '≤', '≥', '&', '|' |
| 117 | op: char, |
| 118 | }, |
| 119 | |
| 120 | UnaryOp { |
| 121 | sub: Box<Expression>, |
| 122 | /// '+', '-', '!' |
| 123 | op: char, |
| 124 | }, |
| 125 | |
| 126 | ImageReference { |
| 127 | resource_ref: crate::expression_tree::ImageReference, |
| 128 | nine_slice: Option<[u16; 4]>, |
| 129 | }, |
| 130 | |
| 131 | Condition { |
| 132 | condition: Box<Expression>, |
| 133 | true_expr: Box<Expression>, |
| 134 | false_expr: Box<Expression>, |
| 135 | }, |
| 136 | |
| 137 | Array { |
| 138 | element_ty: Type, |
| 139 | values: Vec<Expression>, |
| 140 | /// When true, this should be converted to a model. When false, this should stay as a slice |
| 141 | as_model: bool, |
| 142 | }, |
| 143 | Struct { |
| 144 | ty: Rc<crate::langtype::Struct>, |
| 145 | values: BTreeMap<SmolStr, Expression>, |
| 146 | }, |
| 147 | |
| 148 | EasingCurve(crate::expression_tree::EasingCurve), |
| 149 | |
| 150 | LinearGradient { |
| 151 | angle: Box<Expression>, |
| 152 | /// First expression in the tuple is a color, second expression is the stop position |
| 153 | stops: Vec<(Expression, Expression)>, |
| 154 | }, |
| 155 | |
| 156 | RadialGradient { |
| 157 | /// First expression in the tuple is a color, second expression is the stop position |
| 158 | stops: Vec<(Expression, Expression)>, |
| 159 | }, |
| 160 | |
| 161 | EnumerationValue(crate::langtype::EnumerationValue), |
| 162 | |
| 163 | LayoutCacheAccess { |
| 164 | layout_cache_prop: PropertyReference, |
| 165 | index: usize, |
| 166 | /// When set, this is the index within a repeater, and the index is then the location of another offset. |
| 167 | /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index]` |
| 168 | repeater_index: Option<Box<Expression>>, |
| 169 | }, |
| 170 | /// Will call the sub_expression, with the cell variable set to the |
| 171 | /// array of BoxLayoutCellData from the elements |
| 172 | BoxLayoutFunction { |
| 173 | /// The local variable (as read with [`Self::ReadLocalVariable`]) that contains the sell |
| 174 | cells_variable: String, |
| 175 | /// The name for the local variable that contains the repeater indices |
| 176 | repeater_indices: Option<SmolStr>, |
| 177 | /// Either an expression of type BoxLayoutCellData, or an index to the repeater |
| 178 | elements: Vec<Either<Expression, RepeatedElementIdx>>, |
| 179 | orientation: Orientation, |
| 180 | sub_expression: Box<Expression>, |
| 181 | }, |
| 182 | |
| 183 | ComputeDialogLayoutCells { |
| 184 | /// The local variable where the slice of cells is going to be stored |
| 185 | cells_variable: String, |
| 186 | roles: Box<Expression>, |
| 187 | /// This is an Expression::Array |
| 188 | unsorted_cells: Box<Expression>, |
| 189 | }, |
| 190 | |
| 191 | MinMax { |
| 192 | ty: Type, |
| 193 | op: MinMaxOp, |
| 194 | lhs: Box<Expression>, |
| 195 | rhs: Box<Expression>, |
| 196 | }, |
| 197 | |
| 198 | EmptyComponentFactory, |
| 199 | |
| 200 | /// A reference to bundled translated string |
| 201 | TranslationReference { |
| 202 | /// An expression of type array of strings |
| 203 | format_args: Box<Expression>, |
| 204 | string_index: usize, |
| 205 | /// The `n` value to use for the plural form if it is a plural form |
| 206 | plural: Option<Box<Expression>>, |
| 207 | }, |
| 208 | } |
| 209 | |
| 210 | impl Expression { |
| 211 | pub fn default_value_for_type(ty: &Type) -> Option<Self> { |
| 212 | Some(match ty { |
| 213 | Type::Invalid |
| 214 | | Type::Callback { .. } |
| 215 | | Type::Function { .. } |
| 216 | | Type::Void |
| 217 | | Type::InferredProperty |
| 218 | | Type::InferredCallback |
| 219 | | Type::ElementReference |
| 220 | | Type::LayoutCache => return None, |
| 221 | Type::Float32 |
| 222 | | Type::Duration |
| 223 | | Type::Int32 |
| 224 | | Type::Angle |
| 225 | | Type::PhysicalLength |
| 226 | | Type::LogicalLength |
| 227 | | Type::Rem |
| 228 | | Type::UnitProduct(_) => Expression::NumberLiteral(0.), |
| 229 | Type::Percent => Expression::NumberLiteral(1.), |
| 230 | Type::String => Expression::StringLiteral(SmolStr::default()), |
| 231 | Type::Color => { |
| 232 | Expression::Cast { from: Box::new(Expression::NumberLiteral(0.)), to: ty.clone() } |
| 233 | } |
| 234 | Type::Image => Expression::ImageReference { |
| 235 | resource_ref: crate::expression_tree::ImageReference::None, |
| 236 | nine_slice: None, |
| 237 | }, |
| 238 | Type::Bool => Expression::BoolLiteral(false), |
| 239 | Type::Model => return None, |
| 240 | Type::PathData => return None, |
| 241 | Type::Array(element_ty) => Expression::Array { |
| 242 | element_ty: (**element_ty).clone(), |
| 243 | values: vec![], |
| 244 | as_model: true, |
| 245 | }, |
| 246 | Type::Struct(s) => Expression::Struct { |
| 247 | ty: s.clone(), |
| 248 | values: s |
| 249 | .fields |
| 250 | .iter() |
| 251 | .map(|(k, v)| Some((k.clone(), Expression::default_value_for_type(v)?))) |
| 252 | .collect::<Option<_>>()?, |
| 253 | }, |
| 254 | Type::Easing => Expression::EasingCurve(crate::expression_tree::EasingCurve::default()), |
| 255 | Type::Brush => Expression::Cast { |
| 256 | from: Box::new(Expression::default_value_for_type(&Type::Color)?), |
| 257 | to: Type::Brush, |
| 258 | }, |
| 259 | Type::Enumeration(enumeration) => { |
| 260 | Expression::EnumerationValue(enumeration.clone().default_value()) |
| 261 | } |
| 262 | Type::ComponentFactory => Expression::EmptyComponentFactory, |
| 263 | }) |
| 264 | } |
| 265 | |
| 266 | pub fn ty(&self, ctx: &dyn TypeResolutionContext) -> Type { |
| 267 | match self { |
| 268 | Self::StringLiteral(_) => Type::String, |
| 269 | Self::NumberLiteral(_) => Type::Float32, |
| 270 | Self::BoolLiteral(_) => Type::Bool, |
| 271 | Self::PropertyReference(prop) => ctx.property_ty(prop).clone(), |
| 272 | Self::FunctionParameterReference { index } => ctx.arg_type(*index).clone(), |
| 273 | Self::StoreLocalVariable { .. } => Type::Void, |
| 274 | Self::ReadLocalVariable { ty, .. } => ty.clone(), |
| 275 | Self::StructFieldAccess { base, name } => match base.ty(ctx) { |
| 276 | Type::Struct(s) => s.fields[name].clone(), |
| 277 | _ => unreachable!(), |
| 278 | }, |
| 279 | Self::ArrayIndex { array, .. } => match array.ty(ctx) { |
| 280 | Type::Array(ty) => (*ty).clone(), |
| 281 | _ => unreachable!(), |
| 282 | }, |
| 283 | Self::Cast { to, .. } => to.clone(), |
| 284 | Self::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty(ctx)), |
| 285 | Self::BuiltinFunctionCall { function, .. } => function.ty().return_type.clone(), |
| 286 | Self::CallBackCall { callback, .. } => match ctx.property_ty(callback) { |
| 287 | Type::Callback(callback) => callback.return_type.clone(), |
| 288 | _ => Type::Invalid, |
| 289 | }, |
| 290 | Self::FunctionCall { function, .. } => ctx.property_ty(function).clone(), |
| 291 | Self::ItemMemberFunctionCall { function } => match ctx.property_ty(function) { |
| 292 | Type::Function(function) => function.return_type.clone(), |
| 293 | _ => Type::Invalid, |
| 294 | }, |
| 295 | Self::ExtraBuiltinFunctionCall { return_ty, .. } => return_ty.clone(), |
| 296 | Self::PropertyAssignment { .. } => Type::Void, |
| 297 | Self::ModelDataAssignment { .. } => Type::Void, |
| 298 | Self::ArrayIndexAssignment { .. } => Type::Void, |
| 299 | Self::BinaryExpression { lhs, rhs: _, op } => { |
| 300 | if crate::expression_tree::operator_class(*op) != OperatorClass::ArithmeticOp { |
| 301 | Type::Bool |
| 302 | } else { |
| 303 | lhs.ty(ctx) |
| 304 | } |
| 305 | } |
| 306 | Self::UnaryOp { sub, .. } => sub.ty(ctx), |
| 307 | Self::ImageReference { .. } => Type::Image, |
| 308 | Self::Condition { false_expr, .. } => false_expr.ty(ctx), |
| 309 | Self::Array { element_ty, .. } => Type::Array(element_ty.clone().into()), |
| 310 | Self::Struct { ty, .. } => ty.clone().into(), |
| 311 | Self::EasingCurve(_) => Type::Easing, |
| 312 | Self::LinearGradient { .. } => Type::Brush, |
| 313 | Self::RadialGradient { .. } => Type::Brush, |
| 314 | Self::EnumerationValue(e) => Type::Enumeration(e.enumeration.clone()), |
| 315 | Self::LayoutCacheAccess { .. } => Type::LogicalLength, |
| 316 | Self::BoxLayoutFunction { sub_expression, .. } => sub_expression.ty(ctx), |
| 317 | Self::ComputeDialogLayoutCells { .. } => { |
| 318 | Type::Array(super::lower_expression::grid_layout_cell_data_ty().into()) |
| 319 | } |
| 320 | Self::MinMax { ty, .. } => ty.clone(), |
| 321 | Self::EmptyComponentFactory => Type::ComponentFactory, |
| 322 | Self::TranslationReference { .. } => Type::String, |
| 323 | } |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | macro_rules! visit_impl { |
| 328 | ($self:ident, $visitor:ident, $as_ref:ident, $iter:ident, $values:ident) => { |
| 329 | match $self { |
| 330 | Expression::StringLiteral(_) => {} |
| 331 | Expression::NumberLiteral(_) => {} |
| 332 | Expression::BoolLiteral(_) => {} |
| 333 | Expression::PropertyReference(_) => {} |
| 334 | Expression::FunctionParameterReference { .. } => {} |
| 335 | Expression::StoreLocalVariable { value, .. } => $visitor(value), |
| 336 | Expression::ReadLocalVariable { .. } => {} |
| 337 | Expression::StructFieldAccess { base, .. } => $visitor(base), |
| 338 | Expression::ArrayIndex { array, index } => { |
| 339 | $visitor(array); |
| 340 | $visitor(index); |
| 341 | } |
| 342 | Expression::Cast { from, .. } => $visitor(from), |
| 343 | Expression::CodeBlock(b) => b.$iter().for_each($visitor), |
| 344 | Expression::BuiltinFunctionCall { arguments, .. } |
| 345 | | Expression::CallBackCall { arguments, .. } |
| 346 | | Expression::FunctionCall { arguments, .. } => arguments.$iter().for_each($visitor), |
| 347 | Expression::ItemMemberFunctionCall { function: _ } => {} |
| 348 | Expression::ExtraBuiltinFunctionCall { arguments, .. } => { |
| 349 | arguments.$iter().for_each($visitor) |
| 350 | } |
| 351 | Expression::PropertyAssignment { value, .. } => $visitor(value), |
| 352 | Expression::ModelDataAssignment { value, .. } => $visitor(value), |
| 353 | Expression::ArrayIndexAssignment { array, index, value } => { |
| 354 | $visitor(array); |
| 355 | $visitor(index); |
| 356 | $visitor(value); |
| 357 | } |
| 358 | Expression::BinaryExpression { lhs, rhs, .. } => { |
| 359 | $visitor(lhs); |
| 360 | $visitor(rhs); |
| 361 | } |
| 362 | Expression::UnaryOp { sub, .. } => { |
| 363 | $visitor(sub); |
| 364 | } |
| 365 | Expression::ImageReference { .. } => {} |
| 366 | Expression::Condition { condition, true_expr, false_expr } => { |
| 367 | $visitor(condition); |
| 368 | $visitor(true_expr); |
| 369 | $visitor(false_expr); |
| 370 | } |
| 371 | Expression::Array { values, .. } => values.$iter().for_each($visitor), |
| 372 | Expression::Struct { values, .. } => values.$values().for_each($visitor), |
| 373 | Expression::EasingCurve(_) => {} |
| 374 | Expression::LinearGradient { angle, stops } => { |
| 375 | $visitor(angle); |
| 376 | for (a, b) in stops { |
| 377 | $visitor(a); |
| 378 | $visitor(b); |
| 379 | } |
| 380 | } |
| 381 | Expression::RadialGradient { stops } => { |
| 382 | for (a, b) in stops { |
| 383 | $visitor(a); |
| 384 | $visitor(b); |
| 385 | } |
| 386 | } |
| 387 | Expression::EnumerationValue(_) => {} |
| 388 | Expression::LayoutCacheAccess { repeater_index, .. } => { |
| 389 | if let Some(repeater_index) = repeater_index { |
| 390 | $visitor(repeater_index); |
| 391 | } |
| 392 | } |
| 393 | Expression::BoxLayoutFunction { elements, sub_expression, .. } => { |
| 394 | $visitor(sub_expression); |
| 395 | elements.$iter().filter_map(|x| x.$as_ref().left()).for_each($visitor); |
| 396 | } |
| 397 | Expression::ComputeDialogLayoutCells { roles, unsorted_cells, .. } => { |
| 398 | $visitor(roles); |
| 399 | $visitor(unsorted_cells); |
| 400 | } |
| 401 | Expression::MinMax { ty: _, op: _, lhs, rhs } => { |
| 402 | $visitor(lhs); |
| 403 | $visitor(rhs); |
| 404 | } |
| 405 | Expression::EmptyComponentFactory => {} |
| 406 | Expression::TranslationReference { format_args, plural, string_index: _ } => { |
| 407 | $visitor(format_args); |
| 408 | if let Some(plural) = plural { |
| 409 | $visitor(plural); |
| 410 | } |
| 411 | } |
| 412 | } |
| 413 | }; |
| 414 | } |
| 415 | |
| 416 | impl Expression { |
| 417 | /// Call the visitor for each sub-expression (not recursive) |
| 418 | pub fn visit(&self, mut visitor: impl FnMut(&Self)) { |
| 419 | visit_impl!(self, visitor, as_ref, iter, values) |
| 420 | } |
| 421 | |
| 422 | /// Call the visitor for each sub-expression (not recursive) |
| 423 | pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) { |
| 424 | visit_impl!(self, visitor, as_mut, iter_mut, values_mut) |
| 425 | } |
| 426 | |
| 427 | /// Visit itself and each sub expression recursively |
| 428 | pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) { |
| 429 | visitor(self); |
| 430 | self.visit(|e| e.visit_recursive(visitor)); |
| 431 | } |
| 432 | |
| 433 | pub fn visit_property_references( |
| 434 | &self, |
| 435 | ctx: &EvaluationContext, |
| 436 | visitor: &mut dyn FnMut(&PropertyReference, &EvaluationContext), |
| 437 | ) { |
| 438 | self.visit_recursive(&mut |expr| { |
| 439 | let p = match expr { |
| 440 | Expression::PropertyReference(p) => p, |
| 441 | Expression::CallBackCall { callback, .. } => callback, |
| 442 | Expression::PropertyAssignment { property, .. } => { |
| 443 | if let Some((a, map)) = &ctx.property_info(property).animation { |
| 444 | let ctx2 = map.map_context(ctx); |
| 445 | a.visit_property_references(&ctx2, visitor); |
| 446 | } |
| 447 | property |
| 448 | } |
| 449 | // FIXME (should be fine anyway because we mark these as not optimizable) |
| 450 | Expression::ModelDataAssignment { .. } => return, |
| 451 | Expression::LayoutCacheAccess { layout_cache_prop, .. } => layout_cache_prop, |
| 452 | _ => return, |
| 453 | }; |
| 454 | visitor(p, ctx) |
| 455 | }); |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | pub trait TypeResolutionContext { |
| 460 | /// The type of the property. |
| 461 | /// |
| 462 | /// For reference to function, this is the return type |
| 463 | fn property_ty(&self, _: &PropertyReference) -> &Type; |
| 464 | // The type of the specified argument when evaluating a callback |
| 465 | fn arg_type(&self, _index: usize) -> &Type { |
| 466 | unimplemented!() |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | pub struct ParentCtx<'a, T = ()> { |
| 471 | pub ctx: &'a EvaluationContext<'a, T>, |
| 472 | // Index of the repeater within the ctx.current_sub_component |
| 473 | pub repeater_index: Option<RepeatedElementIdx>, |
| 474 | } |
| 475 | |
| 476 | impl<T> Clone for ParentCtx<'_, T> { |
| 477 | fn clone(&self) -> Self { |
| 478 | *self |
| 479 | } |
| 480 | } |
| 481 | impl<T> Copy for ParentCtx<'_, T> {} |
| 482 | |
| 483 | impl<'a, T> ParentCtx<'a, T> { |
| 484 | pub fn new( |
| 485 | ctx: &'a EvaluationContext<'a, T>, |
| 486 | repeater_index: Option<RepeatedElementIdx>, |
| 487 | ) -> Self { |
| 488 | Self { ctx, repeater_index } |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | #[derive (Clone)] |
| 493 | pub struct EvaluationContext<'a, T = ()> { |
| 494 | pub compilation_unit: &'a super::CompilationUnit, |
| 495 | pub current_sub_component: Option<SubComponentIdx>, |
| 496 | pub current_global: Option<GlobalIdx>, |
| 497 | pub generator_state: T, |
| 498 | /// The repeater parent |
| 499 | pub parent: Option<ParentCtx<'a, T>>, |
| 500 | |
| 501 | /// The callback argument types |
| 502 | pub argument_types: &'a [Type], |
| 503 | } |
| 504 | |
| 505 | impl<'a, T> EvaluationContext<'a, T> { |
| 506 | pub fn new_sub_component( |
| 507 | compilation_unit: &'a super::CompilationUnit, |
| 508 | sub_component: SubComponentIdx, |
| 509 | generator_state: T, |
| 510 | parent: Option<ParentCtx<'a, T>>, |
| 511 | ) -> Self { |
| 512 | Self { |
| 513 | compilation_unit, |
| 514 | current_sub_component: Some(sub_component), |
| 515 | current_global: None, |
| 516 | generator_state, |
| 517 | parent, |
| 518 | argument_types: &[], |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | pub fn new_global( |
| 523 | compilation_unit: &'a super::CompilationUnit, |
| 524 | global: GlobalIdx, |
| 525 | generator_state: T, |
| 526 | ) -> Self { |
| 527 | Self { |
| 528 | compilation_unit, |
| 529 | current_sub_component: None, |
| 530 | current_global: Some(global), |
| 531 | generator_state, |
| 532 | parent: None, |
| 533 | argument_types: &[], |
| 534 | } |
| 535 | } |
| 536 | |
| 537 | pub(crate) fn property_info<'b>(&'b self, prop: &PropertyReference) -> PropertyInfoResult<'b> { |
| 538 | fn match_in_sub_component<'b>( |
| 539 | cu: &'b super::CompilationUnit, |
| 540 | sc: &'b super::SubComponent, |
| 541 | prop: &PropertyReference, |
| 542 | map: ContextMap, |
| 543 | ) -> PropertyInfoResult<'b> { |
| 544 | let property_decl = || { |
| 545 | if let PropertyReference::Local { property_index, sub_component_path } = &prop { |
| 546 | let mut sc = sc; |
| 547 | for i in sub_component_path { |
| 548 | sc = &cu.sub_components[sc.sub_components[*i].ty]; |
| 549 | } |
| 550 | Some(&sc.properties[*property_index]) |
| 551 | } else { |
| 552 | None |
| 553 | } |
| 554 | }; |
| 555 | let animation = sc.animations.get(prop).map(|a| (a, map.clone())); |
| 556 | let analysis = sc.prop_analysis.get(prop); |
| 557 | if let Some(a) = &analysis { |
| 558 | if let Some(init) = a.property_init { |
| 559 | return PropertyInfoResult { |
| 560 | analysis: Some(&a.analysis), |
| 561 | binding: Some((&sc.property_init[init].1, map)), |
| 562 | animation, |
| 563 | property_decl: property_decl(), |
| 564 | }; |
| 565 | } |
| 566 | } |
| 567 | let apply_analysis = |mut r: PropertyInfoResult<'b>| -> PropertyInfoResult<'b> { |
| 568 | if animation.is_some() { |
| 569 | r.animation = animation |
| 570 | }; |
| 571 | if let Some(a) = analysis { |
| 572 | r.analysis = Some(&a.analysis); |
| 573 | } |
| 574 | r |
| 575 | }; |
| 576 | match prop { |
| 577 | PropertyReference::Local { sub_component_path, property_index } => { |
| 578 | if !sub_component_path.is_empty() { |
| 579 | let prop2 = PropertyReference::Local { |
| 580 | sub_component_path: sub_component_path[1..].to_vec(), |
| 581 | property_index: *property_index, |
| 582 | }; |
| 583 | let idx = sub_component_path[0]; |
| 584 | return apply_analysis(match_in_sub_component( |
| 585 | cu, |
| 586 | &cu.sub_components[sc.sub_components[idx].ty], |
| 587 | &prop2, |
| 588 | map.deeper_in_sub_component(idx), |
| 589 | )); |
| 590 | } |
| 591 | } |
| 592 | PropertyReference::InNativeItem { item_index, sub_component_path, prop_name } => { |
| 593 | if !sub_component_path.is_empty() { |
| 594 | let prop2 = PropertyReference::InNativeItem { |
| 595 | sub_component_path: sub_component_path[1..].to_vec(), |
| 596 | prop_name: prop_name.clone(), |
| 597 | item_index: *item_index, |
| 598 | }; |
| 599 | let idx = sub_component_path[0]; |
| 600 | return apply_analysis(match_in_sub_component( |
| 601 | cu, |
| 602 | &cu.sub_components[sc.sub_components[idx].ty], |
| 603 | &prop2, |
| 604 | map.deeper_in_sub_component(idx), |
| 605 | )); |
| 606 | } |
| 607 | } |
| 608 | _ => unreachable!(), |
| 609 | } |
| 610 | apply_analysis(PropertyInfoResult { |
| 611 | property_decl: property_decl(), |
| 612 | ..Default::default() |
| 613 | }) |
| 614 | } |
| 615 | |
| 616 | match prop { |
| 617 | PropertyReference::Local { property_index, .. } => { |
| 618 | if let Some(g) = self.current_global() { |
| 619 | PropertyInfoResult { |
| 620 | analysis: Some(&g.prop_analysis[*property_index]), |
| 621 | binding: g.init_values[*property_index] |
| 622 | .as_ref() |
| 623 | .map(|b| (b, ContextMap::Identity)), |
| 624 | animation: None, |
| 625 | property_decl: Some(&g.properties[*property_index]), |
| 626 | } |
| 627 | } else if let Some(sc) = self.current_sub_component() { |
| 628 | match_in_sub_component(self.compilation_unit, sc, prop, ContextMap::Identity) |
| 629 | } else { |
| 630 | unreachable!() |
| 631 | } |
| 632 | } |
| 633 | PropertyReference::InNativeItem { .. } => match_in_sub_component( |
| 634 | self.compilation_unit, |
| 635 | self.current_sub_component().unwrap(), |
| 636 | prop, |
| 637 | ContextMap::Identity, |
| 638 | ), |
| 639 | PropertyReference::Global { global_index, property_index } => { |
| 640 | let g = &self.compilation_unit.globals[*global_index]; |
| 641 | PropertyInfoResult { |
| 642 | analysis: Some(&g.prop_analysis[*property_index]), |
| 643 | animation: None, |
| 644 | binding: g |
| 645 | .init_values |
| 646 | .get(*property_index) |
| 647 | .and_then(Option::as_ref) |
| 648 | .map(|b| (b, ContextMap::InGlobal(*global_index))), |
| 649 | property_decl: Some(&g.properties[*property_index]), |
| 650 | } |
| 651 | } |
| 652 | PropertyReference::InParent { level, parent_reference } => { |
| 653 | let mut ctx = self; |
| 654 | for _ in 0..level.get() { |
| 655 | ctx = ctx.parent.as_ref().unwrap().ctx; |
| 656 | } |
| 657 | let mut ret = ctx.property_info(parent_reference); |
| 658 | match &mut ret.binding { |
| 659 | Some((_, m @ ContextMap::Identity)) => { |
| 660 | *m = ContextMap::InSubElement { |
| 661 | path: Default::default(), |
| 662 | parent: level.get(), |
| 663 | }; |
| 664 | } |
| 665 | Some((_, ContextMap::InSubElement { parent, .. })) => { |
| 666 | *parent += level.get(); |
| 667 | } |
| 668 | _ => {} |
| 669 | } |
| 670 | ret |
| 671 | } |
| 672 | PropertyReference::Function { .. } | PropertyReference::GlobalFunction { .. } => { |
| 673 | unreachable!() |
| 674 | } |
| 675 | } |
| 676 | } |
| 677 | |
| 678 | pub fn current_sub_component(&self) -> Option<&super::SubComponent> { |
| 679 | self.current_sub_component.and_then(|i| self.compilation_unit.sub_components.get(i)) |
| 680 | } |
| 681 | |
| 682 | pub fn current_global(&self) -> Option<&super::GlobalComponent> { |
| 683 | self.current_global.and_then(|i| self.compilation_unit.globals.get(i)) |
| 684 | } |
| 685 | } |
| 686 | |
| 687 | impl<T> TypeResolutionContext for EvaluationContext<'_, T> { |
| 688 | fn property_ty(&self, prop: &PropertyReference) -> &Type { |
| 689 | match prop { |
| 690 | PropertyReference::Local { sub_component_path, property_index } => { |
| 691 | if let Some(mut sub_component) = self.current_sub_component() { |
| 692 | for i in sub_component_path { |
| 693 | sub_component = &self.compilation_unit.sub_components |
| 694 | [sub_component.sub_components[*i].ty]; |
| 695 | } |
| 696 | &sub_component.properties[*property_index].ty |
| 697 | } else if let Some(current_global) = self.current_global() { |
| 698 | ¤t_global.properties[*property_index].ty |
| 699 | } else { |
| 700 | unreachable!() |
| 701 | } |
| 702 | } |
| 703 | PropertyReference::InNativeItem { sub_component_path, item_index, prop_name } => { |
| 704 | if prop_name == "elements" { |
| 705 | // The `Path::elements` property is not in the NativeClass |
| 706 | return &Type::PathData; |
| 707 | } |
| 708 | |
| 709 | let mut sub_component = self.current_sub_component().unwrap(); |
| 710 | for i in sub_component_path { |
| 711 | sub_component = |
| 712 | &self.compilation_unit.sub_components[sub_component.sub_components[*i].ty]; |
| 713 | } |
| 714 | |
| 715 | sub_component.items[*item_index].ty.lookup_property(prop_name).unwrap() |
| 716 | } |
| 717 | PropertyReference::InParent { level, parent_reference } => { |
| 718 | let mut ctx = self; |
| 719 | for _ in 0..level.get() { |
| 720 | ctx = ctx.parent.as_ref().unwrap().ctx; |
| 721 | } |
| 722 | ctx.property_ty(parent_reference) |
| 723 | } |
| 724 | PropertyReference::Global { global_index, property_index } => { |
| 725 | &self.compilation_unit.globals[*global_index].properties[*property_index].ty |
| 726 | } |
| 727 | PropertyReference::Function { sub_component_path, function_index } => { |
| 728 | if let Some(mut sub_component) = self.current_sub_component() { |
| 729 | for i in sub_component_path { |
| 730 | sub_component = &self.compilation_unit.sub_components |
| 731 | [sub_component.sub_components[*i].ty]; |
| 732 | } |
| 733 | &sub_component.functions[*function_index].ret_ty |
| 734 | } else if let Some(current_global) = self.current_global() { |
| 735 | ¤t_global.functions[*function_index].ret_ty |
| 736 | } else { |
| 737 | unreachable!() |
| 738 | } |
| 739 | } |
| 740 | PropertyReference::GlobalFunction { global_index, function_index } => { |
| 741 | &self.compilation_unit.globals[*global_index].functions[*function_index].ret_ty |
| 742 | } |
| 743 | } |
| 744 | } |
| 745 | |
| 746 | fn arg_type(&self, index: usize) -> &Type { |
| 747 | &self.argument_types[index] |
| 748 | } |
| 749 | } |
| 750 | |
| 751 | #[derive (Default, Debug)] |
| 752 | pub(crate) struct PropertyInfoResult<'a> { |
| 753 | pub analysis: Option<&'a crate::object_tree::PropertyAnalysis>, |
| 754 | pub binding: Option<(&'a super::BindingExpression, ContextMap)>, |
| 755 | pub animation: Option<(&'a Expression, ContextMap)>, |
| 756 | pub property_decl: Option<&'a super::Property>, |
| 757 | } |
| 758 | |
| 759 | /// Maps between two evaluation context. |
| 760 | /// This allows to go from the current subcomponent's context, to the context |
| 761 | /// relative to the binding we want to inline |
| 762 | #[derive (Debug, Clone)] |
| 763 | pub(crate) enum ContextMap { |
| 764 | Identity, |
| 765 | InSubElement { path: Vec<SubComponentInstanceIdx>, parent: usize }, |
| 766 | InGlobal(GlobalIdx), |
| 767 | } |
| 768 | |
| 769 | impl ContextMap { |
| 770 | fn deeper_in_sub_component(self, sub: SubComponentInstanceIdx) -> Self { |
| 771 | match self { |
| 772 | ContextMap::Identity => ContextMap::InSubElement { parent: 0, path: vec![sub] }, |
| 773 | ContextMap::InSubElement { mut path, parent } => { |
| 774 | path.push(sub); |
| 775 | ContextMap::InSubElement { path, parent } |
| 776 | } |
| 777 | ContextMap::InGlobal(_) => panic!(), |
| 778 | } |
| 779 | } |
| 780 | |
| 781 | pub fn map_property_reference(&self, p: &PropertyReference) -> PropertyReference { |
| 782 | match self { |
| 783 | ContextMap::Identity => p.clone(), |
| 784 | ContextMap::InSubElement { path, parent } => { |
| 785 | let map_sub_path = |sub_component_path: &[SubComponentInstanceIdx]| -> Vec<SubComponentInstanceIdx> { |
| 786 | path.iter().chain(sub_component_path.iter()).copied().collect() |
| 787 | }; |
| 788 | |
| 789 | let p2 = match p { |
| 790 | PropertyReference::Local { sub_component_path, property_index } => { |
| 791 | PropertyReference::Local { |
| 792 | sub_component_path: map_sub_path(sub_component_path), |
| 793 | property_index: *property_index, |
| 794 | } |
| 795 | } |
| 796 | PropertyReference::Function { sub_component_path, function_index } => { |
| 797 | PropertyReference::Function { |
| 798 | sub_component_path: map_sub_path(sub_component_path), |
| 799 | function_index: *function_index, |
| 800 | } |
| 801 | } |
| 802 | PropertyReference::InNativeItem { |
| 803 | sub_component_path, |
| 804 | item_index, |
| 805 | prop_name, |
| 806 | } => PropertyReference::InNativeItem { |
| 807 | item_index: *item_index, |
| 808 | prop_name: prop_name.clone(), |
| 809 | sub_component_path: map_sub_path(sub_component_path), |
| 810 | }, |
| 811 | PropertyReference::InParent { level, parent_reference } => { |
| 812 | return PropertyReference::InParent { |
| 813 | level: (parent + level.get()).try_into().unwrap(), |
| 814 | parent_reference: parent_reference.clone(), |
| 815 | } |
| 816 | } |
| 817 | PropertyReference::Global { .. } | PropertyReference::GlobalFunction { .. } => { |
| 818 | return p.clone() |
| 819 | } |
| 820 | }; |
| 821 | if let Some(level) = NonZeroUsize::new(*parent) { |
| 822 | PropertyReference::InParent { level, parent_reference: p2.into() } |
| 823 | } else { |
| 824 | p2 |
| 825 | } |
| 826 | } |
| 827 | ContextMap::InGlobal(global_index) => match p { |
| 828 | PropertyReference::Local { sub_component_path, property_index } => { |
| 829 | assert!(sub_component_path.is_empty()); |
| 830 | PropertyReference::Global { |
| 831 | global_index: *global_index, |
| 832 | property_index: *property_index, |
| 833 | } |
| 834 | } |
| 835 | g @ PropertyReference::Global { .. } => g.clone(), |
| 836 | _ => unreachable!(), |
| 837 | }, |
| 838 | } |
| 839 | } |
| 840 | |
| 841 | pub fn map_expression(&self, e: &mut Expression) { |
| 842 | match e { |
| 843 | Expression::PropertyReference(p) |
| 844 | | Expression::CallBackCall { callback: p, .. } |
| 845 | | Expression::PropertyAssignment { property: p, .. } |
| 846 | | Expression::LayoutCacheAccess { layout_cache_prop: p, .. } => { |
| 847 | *p = self.map_property_reference(p); |
| 848 | } |
| 849 | _ => (), |
| 850 | } |
| 851 | e.visit_mut(|e| self.map_expression(e)) |
| 852 | } |
| 853 | |
| 854 | pub fn map_context<'a>(&self, ctx: &EvaluationContext<'a>) -> EvaluationContext<'a> { |
| 855 | match self { |
| 856 | ContextMap::Identity => ctx.clone(), |
| 857 | ContextMap::InSubElement { path, parent } => { |
| 858 | let mut ctx = ctx; |
| 859 | for _ in 0..*parent { |
| 860 | ctx = ctx.parent.unwrap().ctx; |
| 861 | } |
| 862 | if path.is_empty() { |
| 863 | ctx.clone() |
| 864 | } else { |
| 865 | let mut e = ctx.current_sub_component.unwrap(); |
| 866 | for i in path { |
| 867 | e = ctx.compilation_unit.sub_components[e].sub_components[*i].ty; |
| 868 | } |
| 869 | EvaluationContext::new_sub_component(ctx.compilation_unit, e, (), None) |
| 870 | } |
| 871 | } |
| 872 | ContextMap::InGlobal(g) => EvaluationContext::new_global(ctx.compilation_unit, *g, ()), |
| 873 | } |
| 874 | } |
| 875 | } |
| 876 | |