slint/sixtyfps_compiler/passes/remove_aliases.rs
Olivier Goffart f8f02a13eb Refactor the helper to visit all named reference to take a component
If we want to add more expression or named reference in the component,
we then can just update that function
2020-11-25 17:15:27 +01:00

148 lines
5.6 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
//! This pass removes the property used in a two ways bindings
use crate::diagnostics::BuildDiagnostics;
use crate::expression_tree::{Expression, NamedReference};
use crate::object_tree::*;
use std::collections::{hash_map::Entry, HashMap};
use std::rc::Rc;
type Mapping = HashMap<NamedReference, NamedReference>;
type PropertyReference<'a> = (&'a ElementRc, &'a str);
pub fn remove_aliases(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
// The key will be removed and replaced by the named reference
let mut aliases_to_remove = Mapping::new();
// The key key's binding need to take the binding from the other property
let mut aliases_to_invert = Mapping::new();
// Detects all aliases
recurse_elem_including_sub_components(component, &(), &mut |e, _| {
for (name, expr) in &e.borrow().bindings {
if let Expression::TwoWayBinding(nr, _next) = &expr.expression {
let other_e = nr.element.upgrade().unwrap();
if name == &nr.name && Rc::ptr_eq(e, &other_e) {
diag.push_error("Property cannot alias to itself".into(), expr);
continue;
}
if _next.is_some() {
// FIXME: we could still try to remove this alias if we merge it.
continue;
}
process_alias(
component,
(e, name.as_str()),
(&other_e, nr.name.as_str()),
&mut aliases_to_remove,
&mut aliases_to_invert,
)
}
}
});
// Do the inversions
for (from, to) in aliases_to_invert {
// Move the binding from the `from` to the `to`
debug_assert!(aliases_to_remove.contains_key(&from));
let old = from.element.upgrade().unwrap().borrow_mut().bindings.remove(&from.name);
if let Some(old) = old {
let _x = to.element.upgrade().unwrap().borrow_mut().bindings.insert(to.name, old);
debug_assert!(matches!(_x.unwrap().expression, Expression::TwoWayBinding(..)));
} else {
let o = to.element.upgrade().unwrap().borrow_mut().bindings.remove(&to.name).unwrap();
debug_assert!(matches!(o.expression, Expression::TwoWayBinding(..)));
if let Expression::TwoWayBinding(_, Some(other)) = o.expression {
to.element
.upgrade()
.unwrap()
.borrow_mut()
.bindings
.insert(to.name, (*other).into());
}
};
}
// Do the replacements
visit_all_named_references(&component, &mut |nr: &mut NamedReference| {
if let Some(new) = aliases_to_remove.get(nr) {
*nr = new.clone();
}
});
// Remove the properties
for (remove, to) in aliases_to_remove {
let elem = remove.element.upgrade().unwrap();
let mut elem = elem.borrow_mut();
elem.bindings.remove(&remove.name);
if elem.property_declarations[&remove.name].expose_in_public_api {
elem.property_declarations.get_mut(&remove.name).unwrap().is_alias = Some(to);
} else {
elem.property_declarations.remove(&remove.name);
}
}
}
fn is_declaration(x: &PropertyReference) -> bool {
x.0.borrow().property_declarations.contains_key(x.1)
}
/// `from` is an alias to `to` which may contains further binding.
/// This funciton will fill the aliases_to_remove and aliases_to_invert map
fn process_alias<'a>(
component: &Rc<Component>,
mut from: PropertyReference<'a>,
mut to: PropertyReference<'a>,
aliases_to_remove: &mut Mapping,
aliases_to_invert: &mut Mapping,
) {
// Try to find which is the more canical property
macro_rules! canonical_order {
($x: expr) => {
(
is_declaration(&$x),
!Rc::ptr_eq(&component.root_element, $x.0),
&$x.0.borrow().id,
$x.1,
)
};
}
if canonical_order!(from) < canonical_order!(to) {
std::mem::swap(&mut from, &mut to);
if !is_declaration(&from) {
// Cannot remove if this is not a declaration
return;
}
let k = NamedReference { element: Rc::downgrade(&from.0), name: from.1.to_string() };
match aliases_to_invert.entry(k) {
Entry::Occupied(_) => {
// TODO: maybe there are still way to optimize (three way bindings)
return;
}
Entry::Vacant(e) => {
e.insert(NamedReference { element: Rc::downgrade(&to.0), name: to.1.to_string() });
}
}
} else if !is_declaration(&from) {
// Cannot remove if this is not a declaration
return;
}
let k = NamedReference { element: Rc::downgrade(&from.0), name: from.1.to_string() };
match aliases_to_remove.entry(k) {
Entry::Occupied(_) => {
todo!("TODO: what to do in that case? Does that mean it is an impossible relation")
}
Entry::Vacant(e) => {
e.insert(NamedReference { element: Rc::downgrade(&to.0), name: to.1.to_string() });
}
}
}