slint/internal/compiler/passes/const_propagation.rs
Olivier Goffart 28ae8f7bc4 Refactoring: split ElementType away from the types used as property type
These are two different concept, and it is confusing to keep them in the
same enum

We want to support component without any base element, and Void is
already used for global component, so do this refactoring before
2022-10-26 14:50:44 +02:00

168 lines
6.9 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint-ui.com>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
//! Try to simplify property bindings by propagating constant expressions
use crate::expression_tree::*;
use crate::langtype::ElementType;
use crate::langtype::Type;
use crate::object_tree::*;
pub fn const_propagation(component: &Component) {
visit_all_expressions(component, |expr, ty| {
if matches!(ty(), Type::Callback { .. }) {
return;
}
simplify_expression(expr);
});
}
/// Returns false if the expression still contains a reference to an element
fn simplify_expression(expr: &mut Expression) -> bool {
match expr {
Expression::PropertyReference(nr) => {
if nr.is_constant()
&& !matches!(nr.ty(), Type::Struct { name: Some(name), .. } if name.ends_with("::StateInfo"))
{
// Inline the constant value
if let Some(result) = extract_constant_property_reference(nr) {
*expr = result;
return true;
}
}
false
}
Expression::BinaryExpression { lhs, op, rhs } => {
let mut can_inline = simplify_expression(lhs);
can_inline &= simplify_expression(rhs);
let new = match (*op, &mut **lhs, &mut **rhs) {
('+', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
Some(Expression::StringLiteral(format!("{}{}", a, b)))
}
('+', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
if un1 == un2 =>
{
Some(Expression::NumberLiteral(*a + *b, *un1))
}
('-', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
if un1 == un2 =>
{
Some(Expression::NumberLiteral(*a - *b, *un1))
}
('*', Expression::NumberLiteral(a, un1), Expression::NumberLiteral(b, un2))
if *un1 == Unit::None || *un2 == Unit::None =>
{
let preserved_unit = if *un1 == Unit::None { *un2 } else { *un1 };
Some(Expression::NumberLiteral(*a * *b, preserved_unit))
}
(
'/',
Expression::NumberLiteral(a, un1),
Expression::NumberLiteral(b, Unit::None),
) => Some(Expression::NumberLiteral(*a / *b, *un1)),
// TODO: take care of * and / when both numbers have units
('=' | '!', Expression::NumberLiteral(a, _), Expression::NumberLiteral(b, _)) => {
Some(Expression::BoolLiteral((a == b) == (*op == '=')))
}
('=' | '!', Expression::StringLiteral(a), Expression::StringLiteral(b)) => {
Some(Expression::BoolLiteral((a == b) == (*op == '=')))
}
('=' | '!', Expression::EnumerationValue(a), Expression::EnumerationValue(b)) => {
Some(Expression::BoolLiteral((a == b) == (*op == '=')))
}
// TODO: more types and more comparison operators
('&', Expression::BoolLiteral(false), _) => {
can_inline = true;
Some(Expression::BoolLiteral(false))
}
('&', _, Expression::BoolLiteral(false)) => {
can_inline = true;
Some(Expression::BoolLiteral(false))
}
('&', Expression::BoolLiteral(true), e) => Some(std::mem::take(e)),
('&', e, Expression::BoolLiteral(true)) => Some(std::mem::take(e)),
('|', Expression::BoolLiteral(true), _) => {
can_inline = true;
Some(Expression::BoolLiteral(true))
}
('|', _, Expression::BoolLiteral(true)) => {
can_inline = true;
Some(Expression::BoolLiteral(true))
}
('|', Expression::BoolLiteral(false), e) => Some(std::mem::take(e)),
('|', e, Expression::BoolLiteral(false)) => Some(std::mem::take(e)),
_ => None,
};
if let Some(new) = new {
*expr = new;
}
can_inline
}
Expression::Cast { from, to } => {
let can_inline = simplify_expression(from);
let new = if from.ty() == *to {
Some(std::mem::take(&mut **from))
} else {
match (&**from, to) {
(Expression::NumberLiteral(x, Unit::None), Type::String) => {
Some(Expression::StringLiteral((*x).to_string()))
}
_ => None,
}
};
if let Some(new) = new {
*expr = new;
}
can_inline
}
Expression::CallbackReference { .. } => false,
Expression::ElementReference { .. } => false,
// FIXME
Expression::LayoutCacheAccess { .. } => false,
Expression::SolveLayout { .. } => false,
Expression::ComputeLayoutInfo { .. } => false,
_ => {
let mut result = true;
expr.visit_mut(|expr| result &= simplify_expression(expr));
result
}
}
}
/// Will extract the property binding from the given named reference
/// and propagate constant expression within it. If that's possible,
/// return the new expression
fn extract_constant_property_reference(nr: &NamedReference) -> Option<Expression> {
debug_assert!(nr.is_constant());
// find the binding.
let mut element = nr.element();
let mut expression = loop {
if let Some(binding) = element.borrow().bindings.get(nr.name()) {
let binding = binding.borrow();
if !binding.two_way_bindings.is_empty() {
// TODO: In practice, we should still find out what the real binding is
// and solve that.
return None;
}
if !matches!(binding.expression, Expression::Invalid) {
break binding.expression.clone();
}
};
if let Some(decl) = element.clone().borrow().property_declarations.get(nr.name()) {
if let Some(alias) = &decl.is_alias {
return extract_constant_property_reference(alias);
}
} else if let ElementType::Component(c) = &element.clone().borrow().base_type {
element = c.root_element.clone();
continue;
}
// There is no binding for this property, return the default value
return Some(Expression::default_value_for_type(&nr.ty()));
};
if !(simplify_expression(&mut expression)) {
return None;
}
Some(expression)
}