use super::*;
use crate::*;
use std::cell::RefCell;
crate::ferro_thread_local! {
static LOG: RefCell<Vec<&'static str>> = const { RefCell::new(Vec::new()) };
}
fn log(entry: &'static str) {
LOG.with(|log| log.borrow_mut().push(entry));
}
fn take_log() -> Vec<&'static str> {
LOG.with(|log| std::mem::take(&mut *log.borrow_mut()))
}
#[repr(C)]
pub struct DispatchRoot {
base: FerroObject,
}
ferro_class! {
DispatchRoot: FerroObject, virtuals DispatchRootImpl: FerroObjectImpl {
fn visit(this, count: i32) -> i32;
fn name(this) -> String;
}
}
impl FerroObjectImpl for DispatchRoot {
fn on_property_changed_core(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
log("root.core>");
Self::parent_on_property_changed_core(this, change);
log("root.core<");
}
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
log("root.changed");
Self::parent_on_property_changed(this, change);
}
}
impl DispatchRootImpl for DispatchRoot {
fn visit(_this: &Self, count: i32) -> i32 {
log("root");
count + 1
}
fn name(_this: &Self) -> String {
s("root")
}
}
impl DispatchRoot {
ferro_property!(pub fn foo_property() -> StyledProperty<String> {
FerroProperty::register::<DispatchRoot, _>("Foo", s("foodefault"))
});
fn construct() -> Self {
Self { base: FerroObject::construct() }
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
}
macro_rules! dispatch_class {
($name:ident : $base:ident) => {
#[repr(C)]
pub struct $name {
base: $base,
}
ferro_class!($name: $base);
impl $name {
fn construct() -> Self {
Self { base: $base::construct() }
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
}
};
}
dispatch_class!(DispatchLevel1: DispatchRoot);
ferro_impl_classes!(DispatchLevel1: FerroObjectImpl);
impl DispatchRootImpl for DispatchLevel1 {
fn visit(this: &Self, count: i32) -> i32 {
let count = Self::parent_visit(this, count);
log("l1");
count + 1
}
}
dispatch_class!(DispatchLevel2: DispatchLevel1);
ferro_impl_classes!(DispatchLevel2: FerroObjectImpl, DispatchRootImpl);
dispatch_class!(DispatchLevel3: DispatchLevel2);
ferro_overrides! { impl FerroObjectImpl for DispatchLevel3 {
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
log("l3.changed>");
Self::parent_on_property_changed(this, change);
log("l3.changed<");
}
} }
ferro_overrides! { impl DispatchRootImpl for DispatchLevel3 {
fn name(this: &Self) -> String {
format!("l3:{}", Self::parent_name(this))
}
} }
dispatch_class!(DispatchLevel4: DispatchLevel3);
impl FerroObjectImpl for DispatchLevel4 {}
impl DispatchRootImpl for DispatchLevel4 {}
dispatch_class!(DispatchLevel5: DispatchLevel4);
ferro_impl_classes!(DispatchLevel5: FerroObjectImpl, DispatchRootImpl);
dispatch_class!(DispatchLevel6: DispatchLevel5);
ferro_overrides! { impl FerroObjectImpl for DispatchLevel6 {
fn constructed(this: &Self) {
Self::parent_constructed(this);
log("l6.constructed");
}
} }
ferro_overrides! { impl DispatchRootImpl for DispatchLevel6 {
fn visit(this: &Self, count: i32) -> i32 {
log("l6");
Self::parent_visit(this, count) + 1
}
} }
fn slots<T: ObjectType<VTable = DispatchRootVTable>>() -> [usize; 6] {
let table = T::vtable();
[
table.visit as usize,
table.name as usize,
table.base.constructed as usize,
table.base.on_property_changed_core as usize,
table.base.on_property_changed as usize,
table.base.update_data_validation as usize,
]
}
const VISIT: usize = 0;
const NAME: usize = 1;
const CONSTRUCTED: usize = 2;
const ON_PROPERTY_CHANGED_CORE: usize = 3;
const ON_PROPERTY_CHANGED: usize = 4;
const UPDATE_DATA_VALIDATION: usize = 5;
#[test]
fn call_reaches_the_most_derived_override_and_each_base_once() {
let root = DispatchRoot::new();
assert_eq!(1, root.visit(0));
assert_eq!(vec!["root"], take_log());
let level1 = DispatchLevel1::new();
assert_eq!(2, level1.visit(0));
assert_eq!(vec!["root", "l1"], take_log());
assert_eq!(2, DispatchLevel2::new().visit(0));
assert_eq!(vec!["root", "l1"], take_log());
assert_eq!(2, DispatchLevel3::new().visit(0));
assert_eq!(vec!["root", "l1"], take_log());
assert_eq!(2, DispatchLevel4::new().visit(0));
assert_eq!(vec!["root", "l1"], take_log());
assert_eq!(2, DispatchLevel5::new().visit(0));
assert_eq!(vec!["root", "l1"], take_log());
let level6 = DispatchLevel6::new();
assert_eq!(vec!["l6.constructed"], take_log());
assert_eq!(3, level6.visit(0));
assert_eq!(vec!["l6", "root", "l1"], take_log());
}
#[test]
fn call_through_a_base_class_reaches_the_same_override() {
let level6 = DispatchLevel6::new();
take_log();
let as_root: Ref<DispatchRoot> = level6.clone().upcast();
assert_eq!(3, as_root.visit(0));
assert_eq!(vec!["l6", "root", "l1"], take_log());
let as_level3: Ref<DispatchLevel3> = level6.clone().upcast();
assert_eq!(3, as_level3.visit(0));
assert_eq!(vec!["l6", "root", "l1"], take_log());
assert_eq!("l3:root", as_level3.name());
}
#[test]
fn override_in_the_middle_is_inherited_by_the_classes_below_it() {
assert_eq!("root", DispatchRoot::new().name());
assert_eq!("root", DispatchLevel1::new().name());
assert_eq!("root", DispatchLevel2::new().name());
assert_eq!("l3:root", DispatchLevel3::new().name());
assert_eq!("l3:root", DispatchLevel4::new().name());
assert_eq!("l3:root", DispatchLevel5::new().name());
assert_eq!("l3:root", DispatchLevel6::new().name());
}
#[test]
fn constructed_runs_the_override_of_the_class_once() {
DispatchLevel5::new();
assert!(take_log().is_empty());
DispatchLevel6::new();
assert_eq!(vec!["l6.constructed"], take_log());
}
#[test]
fn property_change_reaches_the_overrides_through_every_form_of_class() {
let level2 = DispatchLevel2::new();
level2.set_value(DispatchRoot::foo_property(), s("a"));
assert_eq!(vec!["root.core>", "root.changed", "root.core<"], take_log());
for target in [
DispatchLevel3::new().upcast::<DispatchRoot>(),
DispatchLevel4::new().upcast::<DispatchRoot>(),
DispatchLevel5::new().upcast::<DispatchRoot>(),
DispatchLevel6::new().upcast::<DispatchRoot>(),
] {
take_log();
target.set_value(DispatchRoot::foo_property(), s("a"));
assert_eq!(vec!["root.core>", "l3.changed>", "root.changed", "l3.changed<", "root.core<"], take_log());
}
}
#[test]
fn implementation_states_the_members_it_overrides() {
fn names(overrides: Option<&'static [&'static str]>) -> Option<Vec<&'static str>> {
overrides.map(|names| names.to_vec())
}
assert_eq!(None, names(<DispatchRoot as FerroObjectImpl>::__OVERRIDES));
assert_eq!(None, names(<DispatchLevel1 as DispatchRootImpl>::__OVERRIDES));
assert_eq!(None, names(<DispatchLevel4 as FerroObjectImpl>::__OVERRIDES));
assert_eq!(None, names(<DispatchLevel4 as DispatchRootImpl>::__OVERRIDES));
assert_eq!(Some(vec![]), names(<DispatchLevel1 as FerroObjectImpl>::__OVERRIDES));
assert_eq!(Some(vec![]), names(<DispatchLevel2 as DispatchRootImpl>::__OVERRIDES));
assert_eq!(Some(vec![]), names(<DispatchLevel5 as FerroObjectImpl>::__OVERRIDES));
assert_eq!(Some(vec!["on_property_changed"]), names(<DispatchLevel3 as FerroObjectImpl>::__OVERRIDES));
assert_eq!(Some(vec!["name"]), names(<DispatchLevel3 as DispatchRootImpl>::__OVERRIDES));
assert_eq!(Some(vec!["constructed"]), names(<DispatchLevel6 as FerroObjectImpl>::__OVERRIDES));
assert_eq!(Some(vec!["visit"]), names(<DispatchLevel6 as DispatchRootImpl>::__OVERRIDES));
}
#[test]
fn slot_of_a_member_stated_as_not_overridden_is_the_slot_of_the_base_class() {
let root = slots::<DispatchRoot>();
let level1 = slots::<DispatchLevel1>();
let level2 = slots::<DispatchLevel2>();
let level3 = slots::<DispatchLevel3>();
let level4 = slots::<DispatchLevel4>();
let level5 = slots::<DispatchLevel5>();
let level6 = slots::<DispatchLevel6>();
assert_eq!(root[CONSTRUCTED..], level1[CONSTRUCTED..]);
assert_eq!(level1, level2);
for member in [VISIT, CONSTRUCTED, ON_PROPERTY_CHANGED_CORE, UPDATE_DATA_VALIDATION] {
assert_eq!(level2[member], level3[member]);
}
assert_ne!(level2[NAME], level3[NAME]);
assert_ne!(level2[ON_PROPERTY_CHANGED], level3[ON_PROPERTY_CHANGED]);
assert_eq!(level4, level5);
for member in [NAME, ON_PROPERTY_CHANGED_CORE, ON_PROPERTY_CHANGED, UPDATE_DATA_VALIDATION] {
assert_eq!(level5[member], level6[member]);
}
assert_ne!(level5[VISIT], level6[VISIT]);
assert_ne!(level5[CONSTRUCTED], level6[CONSTRUCTED]);
}
#[test]
fn slot_is_not_taken_from_a_base_class_that_has_no_such_member() {
assert!(!__forwards_to_parent::<DispatchLevel1, DispatchRootVTable>(None, "visit"));
assert!(__forwards_to_parent::<DispatchLevel1, DispatchRootVTable>(Some(&[]), "visit"));
assert!(!__forwards_to_parent::<DispatchLevel1, DispatchRootVTable>(Some(&["visit"]), "visit"));
assert!(__forwards_to_parent::<DispatchLevel1, DispatchRootVTable>(Some(&["visit"]), "name"));
assert!(!__forwards_to_parent::<DispatchRoot, DispatchRootVTable>(Some(&[]), "visit"));
assert!(__forwards_to_parent::<DispatchRoot, FerroObjectVTable>(Some(&[]), "constructed"));
}