1use crate::data::{BindingMode, BindingPriority};
2use crate::property_store::{EffectiveValueDyn, IValueEntry};
3use crate::reactive::{Disposable, IDisposable, IObservable};
4use crate::utilities::HandlerList;
5use crate::{
6 AttachedProperty, DirectProperty, DirectPropertyMetadata, FerroObject, FerroPropertyChangedEventArgs,
7 FerroPropertyMetadata, FerroPropertyRegistry, ObjectType, StaticType, StyledProperty, StyledPropertyMetadata,
8 TypeInfo,
9};
10use std::any::{Any, TypeId};
11use std::cell::{Cell, OnceCell};
12use std::fmt;
13use std::hash::{Hash, Hasher};
14use std::rc::Rc;
15use std::sync::atomic::{AtomicU32, Ordering};
16
17pub trait PropertyValue: Clone + PartialEq + 'static {}
19
20impl<T: Clone + PartialEq + 'static> PropertyValue for T {}
21
22pub trait AnyValue: Any {
28 #[doc(hidden)]
36 fn any_value_as_any(&self) -> &dyn Any;
37
38 #[doc(hidden)]
41 fn any_value_eq(&self, other: &dyn AnyValue) -> bool;
42
43 #[doc(hidden)]
46 fn any_value_type_name(&self) -> &'static str;
47}
48
49impl<T: PartialEq + 'static> AnyValue for T {
50 #[inline]
51 fn any_value_as_any(&self) -> &dyn Any {
52 self
53 }
54
55 fn any_value_eq(&self, other: &dyn AnyValue) -> bool {
56 other.any_value_as_any().downcast_ref::<T>().is_some_and(|other| value_equals(self, other))
57 }
58
59 fn any_value_type_name(&self) -> &'static str {
60 std::any::type_name::<T>()
61 }
62}
63
64#[inline]
74pub fn value_equals<T: PartialEq + 'static>(a: &T, b: &T) -> bool {
75 a == b || both_not_a_number(a, b)
76}
77
78#[inline]
79fn both_not_a_number<T: 'static>(a: &T, b: &T) -> bool {
80 let (a, b): (&dyn Any, &dyn Any) = (a, b);
81 if let (Some(a), Some(b)) = (a.downcast_ref::<f64>(), b.downcast_ref::<f64>()) {
82 return a.is_nan() && b.is_nan();
83 }
84 if let (Some(a), Some(b)) = (a.downcast_ref::<f32>(), b.downcast_ref::<f32>()) {
85 return a.is_nan() && b.is_nan();
86 }
87 if let (Some(Some(a)), Some(Some(b))) = (a.downcast_ref::<Option<f64>>(), b.downcast_ref::<Option<f64>>()) {
88 return a.is_nan() && b.is_nan();
89 }
90 if let (Some(Some(a)), Some(Some(b))) = (a.downcast_ref::<Option<f32>>(), b.downcast_ref::<Option<f32>>()) {
91 return a.is_nan() && b.is_nan();
92 }
93 false
94}
95
96impl dyn AnyValue {
97 #[inline]
99 pub fn as_any(&self) -> &dyn Any {
100 self.any_value_as_any()
101 }
102
103 #[inline]
105 pub fn value_type_id(&self) -> TypeId {
106 self.any_value_as_any().type_id()
107 }
108
109 #[inline]
111 pub fn type_name(&self) -> &'static str {
112 self.any_value_type_name()
113 }
114
115 #[inline]
117 pub fn is<T: 'static>(&self) -> bool {
118 self.any_value_as_any().is::<T>()
119 }
120
121 #[inline]
123 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
124 self.any_value_as_any().downcast_ref::<T>()
125 }
126
127 #[inline]
130 pub fn value_eq(&self, other: &dyn AnyValue) -> bool {
131 self.any_value_eq(other)
132 }
133}
134
135impl PartialEq for dyn AnyValue {
136 fn eq(&self, other: &Self) -> bool {
137 self.any_value_eq(other)
138 }
139}
140
141impl fmt::Debug for dyn AnyValue {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 write!(f, "AnyValue<{}>", self.any_value_type_name())
144 }
145}
146
147pub type BoxedValue = Rc<dyn AnyValue>;
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
154pub struct UnsetValueType;
155
156impl fmt::Display for UnsetValueType {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str("(unset)")
159 }
160}
161
162#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164pub struct DoNothingType;
165
166impl fmt::Display for DoNothingType {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 f.write_str("(do nothing)")
169 }
170}
171
172pub type NotifyingCallback = Box<dyn Fn(&FerroObject, bool)>;
175
176pub(crate) trait PropertyRoutes {
178 fn create_effective_value(&'static self, o: &FerroObject) -> Rc<dyn EffectiveValueDyn>;
179 fn route_clear_value(&'static self, o: &FerroObject);
180 fn route_coerce_default_value(&'static self, o: &FerroObject);
181 fn route_get_value(&'static self, o: &FerroObject) -> BoxedValue;
182 fn route_get_base_value(&'static self, o: &FerroObject) -> BoxedValue;
183 fn route_get_default_value(&'static self, type_: &'static TypeInfo) -> BoxedValue;
184 fn route_set_value(&'static self, o: &FerroObject, value: &dyn Any, priority: BindingPriority)
185 -> Option<Rc<dyn IDisposable>>;
186 fn route_set_current_value(&'static self, o: &FerroObject, value: &dyn Any);
187 fn route_set_direct_value_unchecked(&'static self, o: &FerroObject, entry: &dyn IValueEntry);
188 fn route_bind(
189 &'static self,
190 o: &FerroObject,
191 source: Rc<dyn IObservable<BoxedValue>>,
192 priority: BindingPriority,
193 ) -> Rc<dyn IDisposable>;
194 fn route_get_metadata(&'static self, type_: &'static TypeInfo) -> FerroPropertyMetadata;
195 fn route_unregister(&'static self, type_: &'static TypeInfo);
196 fn route_is_valid_value(&'static self, value: &dyn Any) -> bool;
197 fn as_any(&'static self) -> &'static dyn Any;
199 fn route_copy_value(&'static self, value: &dyn Any, out: &mut dyn Any) -> bool;
203 fn route_box_value(&'static self, value: &dyn Any) -> Option<BoxedValue>;
206 fn route_create_binding_entry(
209 &'static self,
210 o: &FerroObject,
211 frame: std::rc::Weak<dyn crate::property_store::ValueFrame>,
212 source: Rc<dyn IObservable<BoxedValue>>,
213 ) -> Rc<dyn IValueEntry>;
214}
215
216type ChangedHandler = dyn Fn(&FerroPropertyChangedEventArgs<'_>);
217
218pub struct PropertyChangedObservable {
220 handlers: HandlerList<ChangedHandler>,
221}
222
223impl PropertyChangedObservable {
224 fn new() -> Self {
225 Self { handlers: HandlerList::new() }
226 }
227
228 pub fn subscribe(&'static self, handler: impl Fn(&FerroPropertyChangedEventArgs<'_>) + 'static) -> Rc<dyn IDisposable> {
230 let token = self.handlers.add(Rc::new(handler));
231 Disposable::create(move || {
232 self.handlers.remove(token);
233 })
234 }
235
236 pub fn add_class_handler<TTarget: ObjectType>(
239 &'static self,
240 handler: impl Fn(&TTarget, &FerroPropertyChangedEventArgs<'_>) + 'static,
241 ) -> Rc<dyn IDisposable> {
242 self.subscribe(move |e| {
243 if let Some(target) = e.sender().downcast_ref::<TTarget>() {
244 handler(target, e);
245 }
246 })
247 }
248
249 #[inline]
250 pub(crate) fn notify(&self, e: &FerroPropertyChangedEventArgs<'_>) {
251 if self.handlers.is_empty() {
252 return;
253 }
254 crate::perf_count!(PropertyChangesWithPropertyHandlers);
255 for (_, handler) in self.handlers.snapshot().iter() {
256 handler(e);
257 }
258 }
259}
260
261static NEXT_ID: AtomicU32 = AtomicU32::new(0);
262
263pub struct FerroProperty {
266 name: Box<str>,
267 property_type: TypeId,
268 property_type_name: &'static str,
269 owner_type: &'static TypeInfo,
270 id: u32,
271 inherits: bool,
272 is_attached: bool,
273 is_direct: bool,
274 is_read_only: bool,
275 assign_binding: Cell<bool>,
276 host_type: Cell<Option<&'static TypeInfo>>,
277 notifying: Option<Rc<dyn Fn(&FerroObject, bool)>>,
278 changed: Rc<PropertyChangedObservable>,
279 routes: OnceCell<&'static dyn PropertyRoutes>,
280}
281
282pub(crate) struct FerroPropertyInit {
283 pub inherits: bool,
284 pub is_attached: bool,
285 pub is_direct: bool,
286 pub is_read_only: bool,
287}
288
289impl FerroProperty {
290 pub(crate) fn new<T: 'static>(
291 name: &str,
292 owner_type: &'static TypeInfo,
293 init: FerroPropertyInit,
294 notifying: Option<NotifyingCallback>,
295 ) -> Self {
296 assert!(!name.contains('.'), "'name' may not contain periods.");
297 Self {
298 name: name.into(),
299 property_type: TypeId::of::<T>(),
300 property_type_name: std::any::type_name::<T>(),
301 owner_type,
302 id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
303 inherits: init.inherits,
304 is_attached: init.is_attached,
305 is_direct: init.is_direct,
306 is_read_only: init.is_read_only,
307 assign_binding: Cell::new(false),
308 host_type: Cell::new(None),
309 notifying: notifying.map(Rc::from),
310 changed: Rc::new(PropertyChangedObservable::new()),
311 routes: OnceCell::new(),
312 }
313 }
314
315 pub(crate) fn for_added_owner(source: &FerroProperty, owner_type: &'static TypeInfo, is_read_only: bool) -> Self {
318 Self {
319 name: source.name.clone(),
320 property_type: source.property_type,
321 property_type_name: source.property_type_name,
322 owner_type,
323 id: source.id,
324 inherits: source.inherits,
325 is_attached: source.is_attached,
326 is_direct: source.is_direct,
327 is_read_only,
328 assign_binding: source.assign_binding.clone(),
329 host_type: source.host_type.clone(),
330 notifying: source.notifying.clone(),
331 changed: source.changed.clone(),
332 routes: OnceCell::new(),
333 }
334 }
335
336 pub(crate) fn set_routes(&self, routes: &'static dyn PropertyRoutes) {
337 if self.routes.set(routes).is_err() {
338 panic!("property routes are already set");
339 }
340 }
341
342 #[inline]
343 pub(crate) fn routes(&self) -> &'static dyn PropertyRoutes {
344 *self.routes.get().expect("property is not fully registered")
345 }
346
347 #[inline]
351 pub fn host_type(&self) -> Option<&'static TypeInfo> {
352 self.host_type.get()
353 }
354
355 #[inline]
357 pub fn name(&self) -> &str {
358 &self.name
359 }
360
361 #[inline]
363 pub fn property_type(&self) -> TypeId {
364 self.property_type
365 }
366
367 #[inline]
369 pub fn property_type_name(&self) -> &'static str {
370 self.property_type_name
371 }
372
373 #[cold]
378 pub(crate) fn invalid_value_message(&self) -> String {
379 format!("Invalid value for property '{}': expected {}.", self.name(), self.property_type_name())
380 }
381
382 #[cold]
384 #[track_caller]
385 pub(crate) fn invalid_value_type(&self) -> ! {
386 panic!("Invalid value for property '{}': expected {}.", self.name(), self.property_type_name())
387 }
388
389 #[cold]
391 #[track_caller]
392 pub(crate) fn value_not_valid(&self) -> ! {
393 panic!("the value is not valid for property '{}'", self.name())
394 }
395
396 #[inline]
398 pub fn owner_type(&self) -> &'static TypeInfo {
399 self.owner_type
400 }
401
402 #[inline]
404 pub fn inherits(&self) -> bool {
405 self.inherits
406 }
407
408 #[inline]
410 pub fn is_attached(&self) -> bool {
411 self.is_attached
412 }
413
414 #[inline]
416 pub fn is_direct(&self) -> bool {
417 self.is_direct
418 }
419
420 #[inline]
422 pub fn is_read_only(&self) -> bool {
423 self.is_read_only
424 }
425
426 #[inline]
432 pub fn assign_binding(&self) -> bool {
433 self.assign_binding.get()
434 }
435
436 pub fn set_assign_binding(&self, value: bool) {
441 self.assign_binding.set(value);
442 }
443
444 #[inline]
447 pub fn id(&self) -> u32 {
448 self.id
449 }
450
451 #[inline]
455 pub fn changed(&'static self) -> &'static PropertyChangedObservable {
456 &self.changed
458 }
459
460 #[inline]
461 pub(crate) fn notify_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
462 self.changed.notify(e)
463 }
464
465 #[inline]
466 pub(crate) fn notifying(&self) -> Option<&dyn Fn(&FerroObject, bool)> {
467 self.notifying.as_deref()
468 }
469
470 pub fn get_metadata(&self, type_: &'static TypeInfo) -> FerroPropertyMetadata {
472 self.routes().route_get_metadata(type_)
473 }
474
475 pub fn get_metadata_for(&self, owner: &FerroObject) -> FerroPropertyMetadata {
477 self.routes().route_get_metadata(owner.get_type())
478 }
479
480 pub fn unregister(&self, type_: &'static TypeInfo) {
483 self.routes().route_unregister(type_)
484 }
485
486 pub fn is_valid_value(&self, value: &dyn Any) -> bool {
489 self.routes().route_is_valid_value(value)
490 }
491
492 pub fn as_styled<T: crate::PropertyValue>(&'static self) -> Option<&'static StyledProperty<T>> {
497 self.routes.get().and_then(|routes| routes.as_any().downcast_ref::<StyledProperty<T>>())
498 }
499
500 pub fn unset_value() -> BoxedValue {
502 crate::ferro_thread_local! {
503 static UNSET: BoxedValue = Rc::new(UnsetValueType);
504 }
505 UNSET.with(Rc::clone)
506 }
507
508 pub fn register<TOwner: ObjectType, TValue: PropertyValue>(
510 name: &str,
511 default_value: TValue,
512 ) -> &'static StyledProperty<TValue> {
513 Self::register_with::<TOwner, TValue>(name, StyledPropertyOptions::new(default_value))
514 }
515
516 pub fn register_with<TOwner: ObjectType, TValue: PropertyValue>(
518 name: &str,
519 options: StyledPropertyOptions<TValue>,
520 ) -> &'static StyledProperty<TValue> {
521 let assign_binding = options.assign_binding;
522 let (metadata, inherits, validate, notifying) = options.into_parts();
523 let result = StyledProperty::create(name, TOwner::TYPE, TOwner::TYPE, metadata, inherits, validate, notifying, false);
524 result.set_assign_binding(assign_binding);
525 FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
526 result
527 }
528
529 pub fn register_attached<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
534 name: &str,
535 default_value: TValue,
536 ) -> &'static AttachedProperty<TValue> {
537 Self::register_attached_with::<TOwner, THost, TValue>(name, StyledPropertyOptions::new(default_value))
538 }
539
540 pub fn register_attached_with<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
542 name: &str,
543 options: StyledPropertyOptions<TValue>,
544 ) -> &'static AttachedProperty<TValue> {
545 let assign_binding = options.assign_binding;
546 let (metadata, inherits, validate, notifying) = options.into_parts();
547 let result = AttachedProperty::create(name, TOwner::TYPE, THost::TYPE, metadata, inherits, validate, notifying);
548 result.set_assign_binding(assign_binding);
549 result.host_type.set(Some(THost::TYPE));
550 let registry = FerroPropertyRegistry::instance();
551 registry.register(TOwner::TYPE, result);
552 registry.register_attached(THost::TYPE, result);
553 result
554 }
555
556 pub fn register_direct<TOwner: ObjectType, TValue: PropertyValue>(
559 name: &str,
560 getter: fn(&TOwner) -> TValue,
561 setter: Option<fn(&TOwner, TValue)>,
562 unset_value: TValue,
563 ) -> &'static DirectProperty<TOwner, TValue> {
564 Self::register_direct_with(name, getter, setter, DirectPropertyMetadata::new(Some(unset_value)))
565 }
566
567 pub fn register_direct_with<TOwner: ObjectType, TValue: PropertyValue>(
569 name: &str,
570 getter: fn(&TOwner) -> TValue,
571 setter: Option<fn(&TOwner, TValue)>,
572 metadata: DirectPropertyMetadata<TValue>,
573 ) -> &'static DirectProperty<TOwner, TValue> {
574 let result = DirectProperty::create(name, getter, setter, metadata);
575 FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
576 result
577 }
578}
579
580impl PartialEq for FerroProperty {
581 #[inline]
582 fn eq(&self, other: &Self) -> bool {
583 self.id == other.id
584 }
585}
586
587impl Eq for FerroProperty {}
588
589impl Hash for FerroProperty {
590 fn hash<H: Hasher>(&self, state: &mut H) {
591 self.id.hash(state)
592 }
593}
594
595impl fmt::Debug for FerroProperty {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 f.write_str(&self.name)
598 }
599}
600
601impl fmt::Display for FerroProperty {
602 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
603 f.write_str(&self.name)
604 }
605}
606
607pub struct StyledPropertyOptions<T> {
609 default_value: T,
610 inherits: bool,
611 default_binding_mode: BindingMode,
612 validate: Option<Box<dyn Fn(&T) -> bool>>,
613 coerce: Option<Rc<dyn Fn(&FerroObject, T) -> T>>,
614 enable_data_validation: bool,
615 assign_binding: bool,
616 notifying: Option<NotifyingCallback>,
617}
618
619impl<T: PropertyValue> StyledPropertyOptions<T> {
620 pub fn new(default_value: T) -> Self {
621 Self {
622 default_value,
623 inherits: false,
624 default_binding_mode: BindingMode::OneWay,
625 validate: None,
626 coerce: None,
627 enable_data_validation: false,
628 assign_binding: false,
629 notifying: None,
630 }
631 }
632
633 pub fn inherits(mut self, inherits: bool) -> Self {
635 self.inherits = inherits;
636 self
637 }
638
639 pub fn default_binding_mode(mut self, mode: BindingMode) -> Self {
641 self.default_binding_mode = mode;
642 self
643 }
644
645 pub fn validate(mut self, validate: impl Fn(&T) -> bool + 'static) -> Self {
648 self.validate = Some(Box::new(validate));
649 self
650 }
651
652 pub fn coerce(mut self, coerce: impl Fn(&FerroObject, T) -> T + 'static) -> Self {
654 self.coerce = Some(Rc::new(coerce));
655 self
656 }
657
658 pub fn enable_data_validation(mut self, enable: bool) -> Self {
660 self.enable_data_validation = enable;
661 self
662 }
663
664 pub fn assign_binding(mut self, assign_binding: bool) -> Self {
667 self.assign_binding = assign_binding;
668 self
669 }
670
671 pub fn notifying(mut self, notifying: impl Fn(&FerroObject, bool) + 'static) -> Self {
673 self.notifying = Some(Box::new(notifying));
674 self
675 }
676
677 #[allow(clippy::type_complexity)]
678 fn into_parts(self) -> (StyledPropertyMetadata<T>, bool, Option<Box<dyn Fn(&T) -> bool>>, Option<NotifyingCallback>) {
679 let mut metadata = StyledPropertyMetadata::new(Some(self.default_value))
680 .with_default_binding_mode(self.default_binding_mode)
681 .with_enable_data_validation(self.enable_data_validation);
682 if let Some(coerce) = self.coerce {
683 metadata = metadata.with_coerce(move |o, v| coerce(o, v));
684 }
685 (metadata, self.inherits, self.validate, self.notifying)
686 }
687}
688
689#[cfg(feature = "compiler-metadata")]
693#[doc(hidden)]
694#[macro_export]
695macro_rules! __record_property_accessor {
696 ($owner:ident, $vis:vis, $name:ident, $property:expr) => {
697 $crate::metadata::record_property_accessor(
698 <$owner as $crate::StaticType>::TYPE,
699 ::std::any::TypeId::of::<Self>(),
700 ::std::stringify!($name),
701 ::std::stringify!($vis),
702 $crate::Registrable::as_registered_property($property),
703 )
704 };
705}
706
707#[cfg(not(feature = "compiler-metadata"))]
710#[doc(hidden)]
711#[macro_export]
712macro_rules! __record_property_accessor {
713 ($owner:ident, $vis:vis, $name:ident, $property:expr) => {};
714}
715
716#[macro_export]
745macro_rules! ferro_property {
746 (for $owner:ident; $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
752 $(#[$meta])*
753 #[inline]
754 $vis fn $name() -> &'static $ty {
755 $crate::ferro_thread_local! {
756 static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
757 const { ::std::cell::Cell::new(::std::option::Option::None) };
758 }
759 match CELL.get() {
760 ::std::option::Option::Some(property) => property,
761 ::std::option::Option::None => {
762 <$owner as $crate::StaticType>::TYPE.ensure_class_init();
763 match CELL.get() {
764 ::std::option::Option::Some(property) => property,
765 ::std::option::Option::None => {
769 let property: &'static $ty = $body;
770 CELL.set(::std::option::Option::Some(property));
771 $crate::__record_property_accessor!($owner, $vis, $name, property);
772 property
773 }
774 }
775 }
776 }
777 }
778 };
779
780 ($(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
781 $(#[$meta])*
782 #[inline]
783 $vis fn $name() -> &'static $ty {
784 $crate::ferro_thread_local! {
785 static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
786 const { ::std::cell::Cell::new(::std::option::Option::None) };
787 }
788 match CELL.get() {
789 ::std::option::Option::Some(property) => property,
790 ::std::option::Option::None => {
791 let property: &'static $ty = $body;
792 CELL.set(::std::option::Option::Some(property));
793 property
794 }
795 }
796 }
797 };
798}
799
800#[macro_export]
856macro_rules! ferro_properties {
857 (@accessors $owner:ident { $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )* }) => {
858 $(
859 $crate::ferro_property!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body);
860 )*
861 };
862
863 (
867 impl $owner:ident $(, also [$($more:path),* $(,)?])? {
868 $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
869 }
870 ) => {
871 impl $owner {
872 $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
873
874 #[doc(hidden)]
877 pub fn __register_properties() {
878 $( let _ = <$owner>::$name(); )+
879 $($( $more(); )*)?
880 }
881 }
882 };
883
884 (
886 impl $owner:ident, fn $part:ident {
887 $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
888 }
889 ) => {
890 impl $owner {
891 $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
892
893 #[doc(hidden)]
896 pub fn $part() {
897 $( let _ = <$owner>::$name(); )+
898 }
899 }
900 };
901
902 (
904 impl $owner:ident, fn $part:ident {
905 $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
906 }
907 ) => {
908 impl $owner {
909 $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
910
911 #[doc(hidden)]
914 pub fn $part() {
915 $( let _ = <$owner>::$name(); )*
916 }
917 }
918 };
919
920 (
922 impl $owner:ident $(, also [$($more:path),* $(,)?])? {
923 $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
924 }
925 ) => {
926 impl $owner {
927 $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
928
929 #[doc(hidden)]
932 pub fn __register_properties() {
933 $( let _ = <$owner>::$name(); )*
934 $($( $more(); )*)?
935 }
936 }
937 };
938}