mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 12:54:45 +00:00

In the case of bug #3318, the implicit alias to the window's height or width was removed but the propery was not marked as externaly modified, causing later pass to optimize and inline things that shouldn't Fixes #3318
234 lines
9 KiB
Rust
234 lines
9 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
|
|
|
//! This pass removes the property used in a two ways bindings
|
|
|
|
use crate::diagnostics::BuildDiagnostics;
|
|
use crate::expression_tree::{BindingExpression, Expression, NamedReference};
|
|
use crate::object_tree::*;
|
|
use std::cell::RefCell;
|
|
use std::collections::{btree_map::Entry, HashMap, HashSet};
|
|
use std::rc::Rc;
|
|
|
|
// The property in the key is to be removed, and replaced by the property in the value
|
|
type Mapping = HashMap<NamedReference, NamedReference>;
|
|
|
|
#[derive(Default, Debug)]
|
|
struct PropertySets {
|
|
map: HashMap<NamedReference, Rc<RefCell<HashSet<NamedReference>>>>,
|
|
all_sets: Vec<Rc<RefCell<HashSet<NamedReference>>>>,
|
|
}
|
|
|
|
impl PropertySets {
|
|
fn add_link(&mut self, p1: NamedReference, p2: NamedReference) {
|
|
if !std::rc::Weak::ptr_eq(
|
|
&p1.element().borrow().enclosing_component,
|
|
&p2.element().borrow().enclosing_component,
|
|
) && !p1.element().borrow().enclosing_component.upgrade().unwrap().is_global()
|
|
&& !p2.element().borrow().enclosing_component.upgrade().unwrap().is_global()
|
|
{
|
|
// We can only merge aliases if they are in the same Component. (unless one of them is global)
|
|
// TODO: actually we could still merge two alias in a component pointing to the same
|
|
// property in a parent component
|
|
return;
|
|
}
|
|
if let Some(s1) = self.map.get(&p1).cloned() {
|
|
if let Some(s2) = self.map.get(&p2).cloned() {
|
|
if Rc::ptr_eq(&s1, &s2) {
|
|
return;
|
|
}
|
|
for x in s1.borrow().iter() {
|
|
self.map.insert(x.clone(), s2.clone());
|
|
s2.borrow_mut().insert(x.clone());
|
|
}
|
|
*s1.borrow_mut() = HashSet::new();
|
|
} else {
|
|
s1.borrow_mut().insert(p2.clone());
|
|
self.map.insert(p2, s1);
|
|
}
|
|
} else if let Some(s2) = self.map.get(&p2).cloned() {
|
|
s2.borrow_mut().insert(p1.clone());
|
|
self.map.insert(p1, s2);
|
|
} else {
|
|
let mut set = HashSet::new();
|
|
set.insert(p1.clone());
|
|
set.insert(p2.clone());
|
|
let set = Rc::new(RefCell::new(set));
|
|
self.map.insert(p1, set.clone());
|
|
self.map.insert(p2, set.clone());
|
|
self.all_sets.push(set)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn remove_aliases(doc: &Document, diag: &mut BuildDiagnostics) {
|
|
// collect all sets that are linked together
|
|
let mut property_sets = PropertySets::default();
|
|
|
|
let mut process_element = |e: &ElementRc| {
|
|
'bindings: for (name, binding) in &e.borrow().bindings {
|
|
for nr in &binding.borrow().two_way_bindings {
|
|
let other_e = nr.element();
|
|
if name == nr.name() && Rc::ptr_eq(e, &other_e) {
|
|
diag.push_error("Property cannot alias to itself".into(), &*binding.borrow());
|
|
continue 'bindings;
|
|
}
|
|
property_sets.add_link(NamedReference::new(e, name), nr.clone());
|
|
}
|
|
}
|
|
};
|
|
|
|
for component in (doc.root_component.used_types.borrow().sub_components.iter())
|
|
.chain(doc.root_component.used_types.borrow().globals.iter())
|
|
.chain(std::iter::once(&doc.root_component))
|
|
{
|
|
recurse_elem_including_sub_components(component, &(), &mut |e, &()| process_element(e));
|
|
}
|
|
|
|
// The key will be removed and replaced by the named reference
|
|
let mut aliases_to_remove = Mapping::new();
|
|
|
|
// For each set, find a "master" property. Only reference to this master property will be kept,
|
|
// and only the master property will keep its binding
|
|
for set in property_sets.all_sets {
|
|
let set = set.borrow();
|
|
let mut set_iter = set.iter();
|
|
if let Some(mut best) = set_iter.next().cloned() {
|
|
for candidate in set_iter {
|
|
best = best_property(best.clone(), candidate.clone());
|
|
}
|
|
for x in set.iter() {
|
|
if *x != best {
|
|
aliases_to_remove.insert(x.clone(), best.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for component in (doc.root_component.used_types.borrow().sub_components.iter())
|
|
.chain(doc.root_component.used_types.borrow().globals.iter())
|
|
.chain(std::iter::once(&doc.root_component))
|
|
{
|
|
// 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();
|
|
let to_elem = to.element();
|
|
|
|
// adjust the bindings
|
|
let old_binding = elem.borrow_mut().bindings.remove(remove.name());
|
|
let mut old_binding = old_binding.map(RefCell::into_inner).unwrap_or_else(|| {
|
|
// ensure that we set an expression, because the right hand side of a binding always wins,
|
|
// and if that was not set, we must still kee the default then
|
|
let mut b = BindingExpression::from(Expression::default_value_for_type(&to.ty()));
|
|
b.priority = to_elem
|
|
.borrow_mut()
|
|
.bindings
|
|
.get(to.name())
|
|
.map_or(i32::MAX, |x| x.borrow().priority.saturating_add(1));
|
|
b
|
|
});
|
|
|
|
remove_from_binding_expression(&mut old_binding, &to);
|
|
|
|
let same_component = std::rc::Weak::ptr_eq(
|
|
&elem.borrow().enclosing_component,
|
|
&to_elem.borrow().enclosing_component,
|
|
);
|
|
match to_elem.borrow_mut().bindings.entry(to.name().to_owned()) {
|
|
Entry::Occupied(mut e) => {
|
|
let b = e.get_mut().get_mut();
|
|
remove_from_binding_expression(b, &to);
|
|
if !same_component || b.priority < old_binding.priority || !b.has_binding() {
|
|
b.merge_with(&old_binding);
|
|
} else {
|
|
old_binding.merge_with(b);
|
|
*b = old_binding;
|
|
}
|
|
}
|
|
Entry::Vacant(e) => {
|
|
if same_component && old_binding.has_binding() {
|
|
e.insert(old_binding.into());
|
|
}
|
|
}
|
|
};
|
|
|
|
// Remove the declaration
|
|
{
|
|
let mut elem = elem.borrow_mut();
|
|
let used_externally = elem
|
|
.property_analysis
|
|
.borrow()
|
|
.get(remove.name())
|
|
.map_or(false, |v| v.is_read_externally || v.is_set_externally);
|
|
if let Some(d) = elem.property_declarations.get_mut(remove.name()) {
|
|
if d.expose_in_public_api || used_externally {
|
|
d.is_alias = Some(to.clone());
|
|
drop(elem);
|
|
// one must mark the aliased property as settable from outside
|
|
to.mark_as_set();
|
|
} else {
|
|
elem.property_declarations.remove(remove.name());
|
|
let analysis = elem.property_analysis.borrow().get(remove.name()).cloned();
|
|
if let Some(analysis) = analysis {
|
|
drop(elem);
|
|
to.element()
|
|
.borrow()
|
|
.property_analysis
|
|
.borrow_mut()
|
|
.entry(to.name().to_owned())
|
|
.or_default()
|
|
.merge(&analysis);
|
|
};
|
|
}
|
|
} else {
|
|
// This is not a declaration, we must re-create the binding
|
|
elem.bindings.insert(
|
|
remove.name().to_owned(),
|
|
BindingExpression::new_two_way(to.clone()).into(),
|
|
);
|
|
drop(elem);
|
|
if remove.is_externally_modified() {
|
|
to.mark_as_set();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_declaration(x: &NamedReference) -> bool {
|
|
x.element().borrow().property_declarations.contains_key(x.name())
|
|
}
|
|
|
|
/// Out of two named reference, return the one which is the best to keep.
|
|
fn best_property(p1: NamedReference, p2: NamedReference) -> NamedReference {
|
|
// Try to find which is the more canonical property
|
|
macro_rules! canonical_order {
|
|
($x: expr) => {{
|
|
(
|
|
!$x.element().borrow().enclosing_component.upgrade().unwrap().is_global(),
|
|
is_declaration(&$x),
|
|
$x.element().borrow().id.clone(),
|
|
$x.name(),
|
|
)
|
|
}};
|
|
}
|
|
|
|
if canonical_order!(p1) < canonical_order!(p2) {
|
|
p1
|
|
} else {
|
|
p2
|
|
}
|
|
}
|
|
|
|
/// Remove the `to` from the two_way_bindings
|
|
fn remove_from_binding_expression(expression: &mut BindingExpression, to: &NamedReference) {
|
|
expression.two_way_bindings.retain(|x| x != to);
|
|
}
|