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 #[cfg(feature = "testing")]
486 #[doc(hidden)]
487 pub fn default_value_boxed(&self, type_: &'static TypeInfo) -> BoxedValue {
488 self.routes().route_get_default_value(type_)
489 }
490
491 pub fn unregister(&self, type_: &'static TypeInfo) {
494 self.routes().route_unregister(type_)
495 }
496
497 pub fn is_valid_value(&self, value: &dyn Any) -> bool {
500 self.routes().route_is_valid_value(value)
501 }
502
503 pub fn as_styled<T: crate::PropertyValue>(&'static self) -> Option<&'static StyledProperty<T>> {
508 self.routes.get().and_then(|routes| routes.as_any().downcast_ref::<StyledProperty<T>>())
509 }
510
511 pub fn unset_value() -> BoxedValue {
513 crate::ferro_thread_local! {
514 static UNSET: BoxedValue = Rc::new(UnsetValueType);
515 }
516 UNSET.with(Rc::clone)
517 }
518
519 pub fn register<TOwner: ObjectType, TValue: PropertyValue>(
521 name: &str,
522 default_value: TValue,
523 ) -> &'static StyledProperty<TValue> {
524 Self::register_with::<TOwner, TValue>(name, StyledPropertyOptions::new(default_value))
525 }
526
527 pub fn register_with<TOwner: ObjectType, TValue: PropertyValue>(
529 name: &str,
530 options: StyledPropertyOptions<TValue>,
531 ) -> &'static StyledProperty<TValue> {
532 let assign_binding = options.assign_binding;
533 let (metadata, inherits, validate, notifying) = options.into_parts();
534 let result = StyledProperty::create(name, TOwner::TYPE, TOwner::TYPE, metadata, inherits, validate, notifying, false);
535 result.set_assign_binding(assign_binding);
536 FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
537 result
538 }
539
540 pub fn register_attached<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
545 name: &str,
546 default_value: TValue,
547 ) -> &'static AttachedProperty<TValue> {
548 Self::register_attached_with::<TOwner, THost, TValue>(name, StyledPropertyOptions::new(default_value))
549 }
550
551 pub fn register_attached_with<TOwner: StaticType, THost: ObjectType, TValue: PropertyValue>(
553 name: &str,
554 options: StyledPropertyOptions<TValue>,
555 ) -> &'static AttachedProperty<TValue> {
556 let assign_binding = options.assign_binding;
557 let (metadata, inherits, validate, notifying) = options.into_parts();
558 let result = AttachedProperty::create(name, TOwner::TYPE, THost::TYPE, metadata, inherits, validate, notifying);
559 result.set_assign_binding(assign_binding);
560 result.host_type.set(Some(THost::TYPE));
561 let registry = FerroPropertyRegistry::instance();
562 registry.register(TOwner::TYPE, result);
563 registry.register_attached(THost::TYPE, result);
564 result
565 }
566
567 pub fn register_direct<TOwner: ObjectType, TValue: PropertyValue>(
570 name: &str,
571 getter: fn(&TOwner) -> TValue,
572 setter: Option<fn(&TOwner, TValue)>,
573 unset_value: TValue,
574 ) -> &'static DirectProperty<TOwner, TValue> {
575 Self::register_direct_with(name, getter, setter, DirectPropertyMetadata::new(Some(unset_value)))
576 }
577
578 pub fn register_direct_with<TOwner: ObjectType, TValue: PropertyValue>(
580 name: &str,
581 getter: fn(&TOwner) -> TValue,
582 setter: Option<fn(&TOwner, TValue)>,
583 metadata: DirectPropertyMetadata<TValue>,
584 ) -> &'static DirectProperty<TOwner, TValue> {
585 let result = DirectProperty::create(name, getter, setter, metadata);
586 FerroPropertyRegistry::instance().register(TOwner::TYPE, result);
587 result
588 }
589}
590
591impl PartialEq for FerroProperty {
592 #[inline]
593 fn eq(&self, other: &Self) -> bool {
594 self.id == other.id
595 }
596}
597
598impl Eq for FerroProperty {}
599
600impl Hash for FerroProperty {
601 fn hash<H: Hasher>(&self, state: &mut H) {
602 self.id.hash(state)
603 }
604}
605
606impl fmt::Debug for FerroProperty {
607 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608 f.write_str(&self.name)
609 }
610}
611
612impl fmt::Display for FerroProperty {
613 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
614 f.write_str(&self.name)
615 }
616}
617
618pub struct StyledPropertyOptions<T> {
620 default_value: T,
621 inherits: bool,
622 default_binding_mode: BindingMode,
623 validate: Option<Box<dyn Fn(&T) -> bool>>,
624 coerce: Option<Rc<dyn Fn(&FerroObject, T) -> T>>,
625 enable_data_validation: bool,
626 assign_binding: bool,
627 notifying: Option<NotifyingCallback>,
628}
629
630impl<T: PropertyValue> StyledPropertyOptions<T> {
631 pub fn new(default_value: T) -> Self {
632 Self {
633 default_value,
634 inherits: false,
635 default_binding_mode: BindingMode::OneWay,
636 validate: None,
637 coerce: None,
638 enable_data_validation: false,
639 assign_binding: false,
640 notifying: None,
641 }
642 }
643
644 pub fn inherits(mut self, inherits: bool) -> Self {
646 self.inherits = inherits;
647 self
648 }
649
650 pub fn default_binding_mode(mut self, mode: BindingMode) -> Self {
652 self.default_binding_mode = mode;
653 self
654 }
655
656 pub fn validate(mut self, validate: impl Fn(&T) -> bool + 'static) -> Self {
659 self.validate = Some(Box::new(validate));
660 self
661 }
662
663 pub fn coerce(mut self, coerce: impl Fn(&FerroObject, T) -> T + 'static) -> Self {
665 self.coerce = Some(Rc::new(coerce));
666 self
667 }
668
669 pub fn enable_data_validation(mut self, enable: bool) -> Self {
671 self.enable_data_validation = enable;
672 self
673 }
674
675 pub fn assign_binding(mut self, assign_binding: bool) -> Self {
678 self.assign_binding = assign_binding;
679 self
680 }
681
682 pub fn notifying(mut self, notifying: impl Fn(&FerroObject, bool) + 'static) -> Self {
684 self.notifying = Some(Box::new(notifying));
685 self
686 }
687
688 #[allow(clippy::type_complexity)]
689 fn into_parts(self) -> (StyledPropertyMetadata<T>, bool, Option<Box<dyn Fn(&T) -> bool>>, Option<NotifyingCallback>) {
690 let mut metadata = StyledPropertyMetadata::new(Some(self.default_value))
691 .with_default_binding_mode(self.default_binding_mode)
692 .with_enable_data_validation(self.enable_data_validation);
693 if let Some(coerce) = self.coerce {
694 metadata = metadata.with_coerce(move |o, v| coerce(o, v));
695 }
696 (metadata, self.inherits, self.validate, self.notifying)
697 }
698}
699
700#[cfg(feature = "compiler-metadata")]
704#[doc(hidden)]
705#[macro_export]
706macro_rules! __record_property_accessor {
707 ($owner:ident, $vis:vis, $name:ident, $property:expr) => {
708 $crate::metadata::record_property_accessor(
709 <$owner as $crate::StaticType>::TYPE,
710 ::std::any::TypeId::of::<Self>(),
711 ::std::stringify!($name),
712 ::std::stringify!($vis),
713 $crate::Registrable::as_registered_property($property),
714 )
715 };
716}
717
718#[cfg(not(feature = "compiler-metadata"))]
721#[doc(hidden)]
722#[macro_export]
723macro_rules! __record_property_accessor {
724 ($owner:ident, $vis:vis, $name:ident, $property:expr) => {};
725}
726
727#[macro_export]
756macro_rules! ferro_property {
757 (for $owner:ident; $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
763 $(#[$meta])*
764 #[inline]
765 $vis fn $name() -> &'static $ty {
766 $crate::ferro_thread_local! {
767 static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
768 const { ::std::cell::Cell::new(::std::option::Option::None) };
769 }
770 match CELL.get() {
771 ::std::option::Option::Some(property) => property,
772 ::std::option::Option::None => {
773 <$owner as $crate::StaticType>::TYPE.ensure_class_init();
774 match CELL.get() {
775 ::std::option::Option::Some(property) => property,
776 ::std::option::Option::None => {
780 let property: &'static $ty = $body;
781 CELL.set(::std::option::Option::Some(property));
782 $crate::__record_property_accessor!($owner, $vis, $name, property);
783 property
784 }
785 }
786 }
787 }
788 }
789 };
790
791 ($(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block) => {
792 $(#[$meta])*
793 #[inline]
794 $vis fn $name() -> &'static $ty {
795 $crate::ferro_thread_local! {
796 static CELL: ::std::cell::Cell<::std::option::Option<&'static $ty>> =
797 const { ::std::cell::Cell::new(::std::option::Option::None) };
798 }
799 match CELL.get() {
800 ::std::option::Option::Some(property) => property,
801 ::std::option::Option::None => {
802 let property: &'static $ty = $body;
803 CELL.set(::std::option::Option::Some(property));
804 property
805 }
806 }
807 }
808 };
809}
810
811#[macro_export]
867macro_rules! ferro_properties {
868 (@accessors $owner:ident { $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )* }) => {
869 $(
870 $crate::ferro_property!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body);
871 )*
872 };
873
874 (
878 impl $owner:ident $(, also [$($more:path),* $(,)?])? {
879 $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
880 }
881 ) => {
882 impl $owner {
883 $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
884
885 #[doc(hidden)]
888 pub fn __register_properties() {
889 $( let _ = <$owner>::$name(); )+
890 $($( $more(); )*)?
891 }
892 }
893 };
894
895 (
897 impl $owner:ident, fn $part:ident {
898 $( $macro_:ident ! ( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block ) ; )+
899 }
900 ) => {
901 impl $owner {
902 $( $macro_!(for $owner; $(#[$meta])* $vis fn $name() -> $ty $body); )+
903
904 #[doc(hidden)]
907 pub fn $part() {
908 $( let _ = <$owner>::$name(); )+
909 }
910 }
911 };
912
913 (
915 impl $owner:ident, fn $part:ident {
916 $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
917 }
918 ) => {
919 impl $owner {
920 $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
921
922 #[doc(hidden)]
925 pub fn $part() {
926 $( let _ = <$owner>::$name(); )*
927 }
928 }
929 };
930
931 (
933 impl $owner:ident $(, also [$($more:path),* $(,)?])? {
934 $( $(#[$meta:meta])* $vis:vis fn $name:ident() -> $ty:ty $body:block )*
935 }
936 ) => {
937 impl $owner {
938 $crate::ferro_properties!(@accessors $owner { $( $(#[$meta])* $vis fn $name() -> $ty $body )* });
939
940 #[doc(hidden)]
943 pub fn __register_properties() {
944 $( let _ = <$owner>::$name(); )*
945 $($( $more(); )*)?
946 }
947 }
948 };
949}