@@ -165,7 +165,8 @@ private static void GenerateUnifiedPropertyClass(
165165 classSymbol ,
166166 propertyChangedEventType ,
167167 stringTypeSymbol ,
168- voidTypeSymbol ) ;
168+ voidTypeSymbol ,
169+ propertyFields ) ; // Pass propertyFields to check for static properties
169170
170171 // Generate properties
171172 GenerateBindableProperties ( source , bindableFields , classSymbol , compilation ) ;
@@ -193,6 +194,14 @@ private static bool ValidateFields(
193194 // Check bindable fields
194195 foreach ( var info in bindableFields )
195196 {
197+ // Add this validation for static fields with BindableProperty
198+ if ( info . FieldSymbol . IsStatic )
199+ {
200+ PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) ,
201+ $ "Static fields cannot use [BindableProperty]. Use [Property] instead for field '{ info . FieldSymbol . Name } '.")
202+ ; return false ;
203+ }
204+
196205 if ( ! PropertyGeneratorHelpers . IsPartial ( classSymbol ) )
197206 {
198207 PropertyGeneratorHelpers . ReportDiagnostic ( context , info . FieldDeclaration . GetLocation ( ) ,
@@ -281,8 +290,12 @@ private static void GenerateInfrastructureMembers(
281290 INamedTypeSymbol classSymbol ,
282291 INamedTypeSymbol propertyChangedEventType ,
283292 ITypeSymbol stringTypeSymbol ,
284- ITypeSymbol voidTypeSymbol )
293+ ITypeSymbol voidTypeSymbol ,
294+ List < PropertyFieldInfo > propertyFields )
285295 {
296+ // Check if we have any static property fields
297+ bool hasStaticProperties = propertyFields . Any ( p => p . FieldSymbol . IsStatic ) ;
298+
286299 // Add event if needed
287300 if ( bindableFields . Count > 0 && ! implementsINotify &&
288301 ! PropertyGeneratorHelpers . EventExists ( classSymbol , "PropertyChanged" , propertyChangedEventType ) )
@@ -296,6 +309,12 @@ private static void GenerateInfrastructureMembers(
296309 source . AppendLine ( " protected readonly object _Locker = new object();" ) ;
297310 }
298311
312+ // Add static _StaticLocker if we have static properties
313+ if ( hasStaticProperties && ! PropertyGeneratorHelpers . FieldExists ( classSymbol , "_StaticLocker" ) )
314+ {
315+ source . AppendLine ( " static readonly object _StaticLocker = new object();" ) ;
316+ }
317+
299318 // Add OnPropertyChanged if needed
300319 if ( bindableFields . Count > 0 && ! implementsIBindable && ! PropertyGeneratorHelpers . MethodExists (
301320 classSymbol ,
@@ -312,6 +331,12 @@ private static void GenerateInfrastructureMembers(
312331 this.NotifyPropertyChanged(PropertyChanged, propertyName);
313332 }}" ) ;
314333 }
334+
335+ // Add static property helper methods if we have static properties
336+ if ( hasStaticProperties )
337+ {
338+ GenerateStaticPropertyHelpers ( source , classSymbol ) ;
339+ }
315340 }
316341
317342 private static void GenerateBindableProperties (
@@ -501,6 +526,7 @@ private static void GenerateRegularProperties(
501526
502527 var fieldSymbol = info . FieldSymbol ;
503528 var fieldName = fieldSymbol . Name ;
529+ var isStatic = fieldSymbol . IsStatic ;
504530
505531 // Use NullableFlowState-aware display string to properly handle nullable types
506532 var typeName = GetNullableAwareTypeName ( fieldSymbol . Type , compilation ) ;
@@ -523,36 +549,115 @@ private static void GenerateRegularProperties(
523549 string propertyAccessRaw = readOnly ? getterValue : GetWidestAccessibility ( getterValue , setterValue ) ;
524550 string propertyAccessibilityStr = ToPropertyAccessibilityString ( propertyAccessRaw ) ;
525551
526- var lockerArg = threadSafe ? "_Locker" : "null" ;
552+ var lockerArg = isStatic ? ( threadSafe ? "_StaticLocker" : "null" ) : ( threadSafe ? " _Locker" : "null" ) ;
527553
528- if ( readOnly )
554+ if ( isStatic )
529555 {
530- GenerateReadOnlyProperty (
531- source ,
532- propertyAccessibilityStr ,
533- typeName ,
534- propertyName ,
535- getterValue ,
536- propertyAccessRaw ,
537- fieldName ,
538- lockerArg ) ;
556+ if ( readOnly )
557+ {
558+ GenerateReadOnlyStaticProperty (
559+ source ,
560+ propertyAccessibilityStr ,
561+ typeName ,
562+ propertyName ,
563+ getterValue ,
564+ propertyAccessRaw ,
565+ fieldName ,
566+ lockerArg ) ;
567+ }
568+ else
569+ {
570+ GenerateReadWriteStaticProperty (
571+ source ,
572+ propertyAccessibilityStr ,
573+ typeName ,
574+ propertyName ,
575+ getterValue ,
576+ propertyAccessRaw ,
577+ fieldName ,
578+ lockerArg ,
579+ setterValue ) ;
580+ }
539581 }
540582 else
541583 {
542- GenerateReadWriteProperty (
543- source ,
544- propertyAccessibilityStr ,
545- typeName ,
546- propertyName ,
547- getterValue ,
548- propertyAccessRaw ,
549- fieldName ,
550- lockerArg ,
551- setterValue ) ;
584+ if ( readOnly )
585+ {
586+ GenerateReadOnlyProperty (
587+ source ,
588+ propertyAccessibilityStr ,
589+ typeName ,
590+ propertyName ,
591+ getterValue ,
592+ propertyAccessRaw ,
593+ fieldName ,
594+ lockerArg ) ;
595+ }
596+ else
597+ {
598+ GenerateReadWriteProperty (
599+ source ,
600+ propertyAccessibilityStr ,
601+ typeName ,
602+ propertyName ,
603+ getterValue ,
604+ propertyAccessRaw ,
605+ fieldName ,
606+ lockerArg ,
607+ setterValue ) ;
608+ }
552609 }
553610 }
554611 }
555612
613+ private static void GenerateReadOnlyStaticProperty (
614+ StringBuilder source ,
615+ string propertyAccessibilityStr ,
616+ string typeName ,
617+ string propertyName ,
618+ string getterValue ,
619+ string propertyAccessRaw ,
620+ string fieldName ,
621+ string lockerArg )
622+ {
623+ string getterModifier = getterValue . Equals ( propertyAccessRaw , StringComparison . OrdinalIgnoreCase )
624+ ? ""
625+ : ToPropertyAccessibilityString ( getterValue ) ;
626+
627+ source . AppendLine ( $@ "
628+ { propertyAccessibilityStr } static { typeName } { propertyName }
629+ {{
630+ { getterModifier } get {{ return GetStaticProperty(ref { fieldName } , { lockerArg } ); }}
631+ }}" ) ;
632+ }
633+
634+ private static void GenerateReadWriteStaticProperty (
635+ StringBuilder source ,
636+ string propertyAccessibilityStr ,
637+ string typeName ,
638+ string propertyName ,
639+ string getterValue ,
640+ string propertyAccessRaw ,
641+ string fieldName ,
642+ string lockerArg ,
643+ string setterValue )
644+ {
645+ string getterModifier = getterValue . Equals ( propertyAccessRaw , StringComparison . OrdinalIgnoreCase )
646+ ? ""
647+ : ToPropertyAccessibilityString ( getterValue ) ;
648+
649+ string setterModifier = setterValue . Equals ( propertyAccessRaw , StringComparison . OrdinalIgnoreCase )
650+ ? ""
651+ : ToPropertyAccessibilityString ( setterValue ) ;
652+
653+ source . AppendLine ( $@ "
654+ { propertyAccessibilityStr } static { typeName } { propertyName }
655+ {{
656+ { getterModifier } get {{ return GetStaticProperty(ref { fieldName } , { lockerArg } ); }}
657+ { setterModifier } set {{ SetStaticProperty(ref { fieldName } , value, { lockerArg } ); }}
658+ }}" ) ;
659+ }
660+
556661 private static void GenerateReadOnlyProperty (
557662 StringBuilder source ,
558663 string propertyAccessibilityStr ,
@@ -638,6 +743,73 @@ private static string GetWidestAccessibility(string getter, string setter)
638743 return getterRank >= setterRank ? getter : setter ;
639744 }
640745
746+ private static void GenerateStaticPropertyHelpers ( StringBuilder source , INamedTypeSymbol classSymbol )
747+ {
748+ // Check if GetStaticProperty method already exists
749+ var genericMethodExists = classSymbol . GetMembers ( )
750+ . OfType < IMethodSymbol > ( )
751+ . Any ( m => m . Name == "GetStaticProperty" && m . IsStatic && m . IsGenericMethod ) ;
752+
753+ if ( ! genericMethodExists )
754+ {
755+ source . AppendLine ( @"
756+ public static T GetStaticProperty<T>(
757+ ref T backingStore,
758+ object? lockObj = null)
759+ {
760+ bool lockWasTaken = false;
761+ try
762+ {
763+ if (lockObj != null)
764+ System.Threading.Monitor.Enter(lockObj, ref lockWasTaken);
765+ return backingStore;
766+ }
767+ finally
768+ {
769+ if (lockWasTaken)
770+ System.Threading.Monitor.Exit(lockObj!);
771+ }
772+ }" ) ;
773+ }
774+
775+ // Check if SetStaticProperty method already exists
776+ var setMethodExists = classSymbol . GetMembers ( )
777+ . OfType < IMethodSymbol > ( )
778+ . Any ( m => m . Name == "SetStaticProperty" && m . IsStatic && m . IsGenericMethod ) ;
779+
780+ if ( ! setMethodExists )
781+ {
782+ source . AppendLine ( @"
783+ public static bool SetStaticProperty<T>(
784+ ref T backingStore,
785+ T value,
786+ object? lockObj = null,
787+ [System.Runtime.CompilerServices.CallerMemberName] string propertyName = """")
788+ {
789+ bool lockWasTaken = false;
790+ try
791+ {
792+ if (lockObj != null)
793+ System.Threading.Monitor.Enter(lockObj, ref lockWasTaken);
794+ if (System.Collections.Generic.EqualityComparer<T>.Default.Equals(backingStore, value))
795+ {
796+ return false;
797+ }
798+ else
799+ {
800+ backingStore = value;
801+ return true;
802+ }
803+ }
804+ finally
805+ {
806+ if (lockWasTaken)
807+ System.Threading.Monitor.Exit(lockObj!);
808+ }
809+ }" ) ;
810+ }
811+ }
812+
641813 private struct BindableFieldInfo
642814 {
643815 public IFieldSymbol FieldSymbol { get ; set ; }
0 commit comments