// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.0 OR LicenseRef-Slint-commercial //! After inlining and moving declarations, all Element::base_type should be Type::BuiltinElement. This pass resolves them //! to NativeClass and picking a variant that only contains the used properties. use std::collections::HashSet; use std::rc::Rc; use crate::langtype::{ElementType, NativeClass}; use crate::object_tree::{recurse_elem_including_sub_components, Component}; pub fn resolve_native_classes(component: &Component) { recurse_elem_including_sub_components(component, &(), &mut |elem, _| { let new_native_class = { let elem = elem.borrow(); let base_type = match &elem.base_type { ElementType::Component(_) => { // recurse_elem_including_sub_components will recurse into it return; } ElementType::Builtin(b) => b, ElementType::Native(_) => { // already native return; } ElementType::Global | ElementType::Error => panic!("This should not happen"), }; let analysis = elem.property_analysis.borrow(); let native_properties_used: HashSet<_> = elem .bindings .keys() .chain(analysis.iter().filter(|(_, v)| v.is_used()).map(|(k, _)| k)) .filter(|k| { !elem.property_declarations.contains_key(*k) && base_type.as_ref().properties.contains_key(*k) }) .collect(); select_minimal_class_based_on_property_usage( &elem.base_type.as_builtin().native_class, native_properties_used.into_iter(), ) }; elem.borrow_mut().base_type = ElementType::Native(new_native_class); }) } fn lookup_property_distance(mut class: Rc, name: &str) -> (usize, Rc) { let mut distance = 0; loop { if class.properties.contains_key(name) { return (distance, class); } distance += 1; class = class.parent.as_ref().unwrap().clone(); } } fn select_minimal_class_based_on_property_usage<'a>( class: &Rc, properties_used: impl Iterator, ) -> Rc { let mut minimal_class = class.clone(); while let Some(class) = minimal_class.parent.clone() { minimal_class = class; } let (_min_distance, minimal_class) = properties_used.fold( (std::usize::MAX, minimal_class), |(current_distance, current_class), prop_name| { let (prop_distance, prop_class) = lookup_property_distance(class.clone(), prop_name); if prop_distance < current_distance { (prop_distance, prop_class) } else { (current_distance, current_class) } }, ); minimal_class } #[test] fn test_select_minimal_class_based_on_property_usage() { use crate::langtype::{BuiltinPropertyInfo, Type}; let first = Rc::new(NativeClass::new_with_properties( "first_class", [("first_prop".to_owned(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(), )); let mut second = NativeClass::new_with_properties( "second_class", [("second_prop".to_owned(), BuiltinPropertyInfo::new(Type::Int32))].iter().cloned(), ); second.parent = Some(first.clone()); let second = Rc::new(second); let reduce_to_first = select_minimal_class_based_on_property_usage(&second, ["first_prop".to_owned()].iter()); assert_eq!(reduce_to_first.class_name, first.class_name); let reduce_to_second = select_minimal_class_based_on_property_usage(&second, ["second_prop".to_owned()].iter()); assert_eq!(reduce_to_second.class_name, second.class_name); let reduce_to_second = select_minimal_class_based_on_property_usage( &second, ["first_prop".to_owned(), "second_prop".to_owned()].iter(), ); assert_eq!(reduce_to_second.class_name, second.class_name); } #[test] fn select_minimal_class() { let tr = crate::typeregister::TypeRegister::builtin(); let tr = tr.borrow(); let rect = tr.lookup_element("Rectangle").unwrap(); let rect = rect.as_builtin(); assert_eq!( select_minimal_class_based_on_property_usage( &rect.native_class, ["x".to_owned(), "width".to_owned()].iter() ) .class_name, "Empty", ); assert_eq!( select_minimal_class_based_on_property_usage(&rect.native_class, [].iter()).class_name, "Empty", ); assert_eq!( select_minimal_class_based_on_property_usage( &rect.native_class, ["border-width".to_owned()].iter() ) .class_name, "BorderRectangle", ); assert_eq!( select_minimal_class_based_on_property_usage( &rect.native_class, ["border-width".to_owned(), "x".to_owned()].iter() ) .class_name, "BorderRectangle", ); }