@@ -168,8 +168,8 @@ private static void GenerateUnifiedPropertyClass(
168168 voidTypeSymbol ) ;
169169
170170 // Generate properties
171- GenerateBindableProperties ( source , bindableFields , classSymbol ) ;
172- GenerateRegularProperties ( source , propertyFields , classSymbol ) ;
171+ GenerateBindableProperties ( source , bindableFields , classSymbol , compilation ) ;
172+ GenerateRegularProperties ( source , propertyFields , classSymbol , compilation ) ;
173173
174174 source . AppendLine ( "}" ) ;
175175 if ( ! string . IsNullOrEmpty ( classSymbol . ContainingNamespace ? . ToDisplayString ( ) ) )
@@ -256,6 +256,7 @@ private static void GenerateClassHeader(
256256 if ( ! string . IsNullOrEmpty ( ns ) )
257257 source . AppendLine ( $ "namespace { ns } {{") ;
258258
259+ source . AppendLine ( "#nullable enable" ) ;
259260 source . AppendLine ( "using ThunderDesign.Net.Threading.Extentions;" ) ;
260261 source . AppendLine ( "using ThunderDesign.Net.Threading.Objects;" ) ;
261262
@@ -302,18 +303,22 @@ private static void GenerateInfrastructureMembers(
302303 new ITypeSymbol [ ] { stringTypeSymbol } ,
303304 voidTypeSymbol ) )
304305 {
305- source . AppendLine ( @"
306- public virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = """")
307- {
306+ // Only use 'virtual' if the class is not sealed
307+ string virtualModifier = classSymbol . IsSealed ? "" : "virtual " ;
308+
309+ source . AppendLine ( $@ "
310+ public { virtualModifier } void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = """")
311+ {{
308312 this.NotifyPropertyChanged(PropertyChanged, propertyName);
309- }" ) ;
313+ }} " ) ;
310314 }
311315 }
312316
313317 private static void GenerateBindableProperties (
314318 StringBuilder source ,
315319 List < BindableFieldInfo > bindableFields ,
316- INamedTypeSymbol classSymbol )
320+ INamedTypeSymbol classSymbol ,
321+ Compilation compilation )
317322 {
318323 foreach ( var info in bindableFields )
319324 {
@@ -327,17 +332,22 @@ private static void GenerateBindableProperties(
327332
328333 var fieldSymbol = info . FieldSymbol ;
329334 var fieldName = fieldSymbol . Name ;
330- var typeName = fieldSymbol . Type . ToDisplayString ( ) ;
335+
336+ // Use NullableFlowState-aware display string to properly handle nullable types
337+ var typeName = GetNullableAwareTypeName ( fieldSymbol . Type , compilation ) ;
331338
332339 var args = info . AttributeData . ConstructorArguments ;
333- var readOnly = args . Length > 0 && ( bool ) args [ 0 ] . Value ! ;
334- var threadSafe = args . Length > 1 && ( bool ) args [ 1 ] . Value ! ;
335- var notify = args . Length > 2 && ( bool ) args [ 2 ] . Value ! ;
340+
341+ // Check if field is readonly (takes precedence over attribute parameter)
342+ var readOnly = fieldSymbol . IsReadOnly ;
343+
344+ var threadSafe = args . Length > 0 && ( bool ) args [ 0 ] . Value ! ;
345+ var notify = args . Length > 1 && ( bool ) args [ 1 ] . Value ! ;
336346
337347 string [ ] alsoNotify = GetAlsoNotifyProperties ( args ) ;
338348
339- var getterEnum = args . Length > 4 ? args [ 4 ] . Value : null ;
340- var setterEnum = args . Length > 5 ? args [ 5 ] . Value : null ;
349+ var getterEnum = args . Length > 3 ? args [ 3 ] . Value : null ;
350+ var setterEnum = args . Length > 4 ? args [ 4 ] . Value : null ;
341351
342352 // Convert the numeric enum value to its string representation
343353 string getterValue = getterEnum != null ? GetAccessibilityName ( ( int ) getterEnum ) : "Public" ;
@@ -381,10 +391,10 @@ private static void GenerateBindableProperties(
381391
382392 private static string [ ] GetAlsoNotifyProperties ( ImmutableArray < TypedConstant > args )
383393 {
384- if ( args . Length <= 3 )
394+ if ( args . Length <= 2 )
385395 return Array . Empty < string > ( ) ;
386396
387- var arg = args [ 3 ] ;
397+ var arg = args [ 2 ] ;
388398 if ( arg . Kind == TypedConstantKind . Array && arg . Values != null )
389399 {
390400 return arg . Values
@@ -474,7 +484,8 @@ private static void GenerateReadWriteBindableProperty(
474484 private static void GenerateRegularProperties (
475485 StringBuilder source ,
476486 List < PropertyFieldInfo > propertyFields ,
477- INamedTypeSymbol classSymbol )
487+ INamedTypeSymbol classSymbol ,
488+ Compilation compilation )
478489 {
479490 foreach ( var info in propertyFields )
480491 {
@@ -488,13 +499,18 @@ private static void GenerateRegularProperties(
488499
489500 var fieldSymbol = info . FieldSymbol ;
490501 var fieldName = fieldSymbol . Name ;
491- var typeName = fieldSymbol . Type . ToDisplayString ( ) ;
502+
503+ // Use NullableFlowState-aware display string to properly handle nullable types
504+ var typeName = GetNullableAwareTypeName ( fieldSymbol . Type , compilation ) ;
492505
493506 var args = info . AttributeData . ConstructorArguments ;
494- var readOnly = args . Length > 0 && ( bool ) args [ 0 ] . Value ! ;
495- var threadSafe = args . Length > 1 && ( bool ) args [ 1 ] . Value ! ;
496- var getterEnum = args . Length > 2 ? args [ 2 ] . Value : null ;
497- var setterEnum = args . Length > 3 ? args [ 3 ] . Value : null ;
507+
508+ // Check if field is readonly (takes precedence over attribute parameter)
509+ var readOnly = fieldSymbol . IsReadOnly ;
510+
511+ var threadSafe = args . Length > 0 && ( bool ) args [ 0 ] . Value ! ;
512+ var getterEnum = args . Length > 1 ? args [ 1 ] . Value : null ;
513+ var setterEnum = args . Length > 2 ? args [ 2 ] . Value : null ;
498514
499515 // Convert the numeric enum value to its string representation
500516 string getterValue = getterEnum != null ? GetAccessibilityName ( ( int ) getterEnum ) : "Public" ;
@@ -581,6 +597,21 @@ private static void GenerateReadWriteProperty(
581597 }}" ) ;
582598 }
583599
600+ private static string GetNullableAwareTypeName ( ITypeSymbol typeSymbol , Compilation compilation )
601+ {
602+ // Create a SymbolDisplayFormat that includes nullable annotations
603+ var format = new SymbolDisplayFormat (
604+ globalNamespaceStyle : SymbolDisplayGlobalNamespaceStyle . Omitted ,
605+ typeQualificationStyle : SymbolDisplayTypeQualificationStyle . NameAndContainingTypesAndNamespaces ,
606+ genericsOptions : SymbolDisplayGenericsOptions . IncludeTypeParameters ,
607+ miscellaneousOptions : SymbolDisplayMiscellaneousOptions . EscapeKeywordIdentifiers |
608+ SymbolDisplayMiscellaneousOptions . UseSpecialTypes |
609+ SymbolDisplayMiscellaneousOptions . IncludeNullableReferenceTypeModifier
610+ ) ;
611+
612+ return typeSymbol . ToDisplayString ( format ) ;
613+ }
614+
584615 private static bool ImplementsInterface ( INamedTypeSymbol type , string interfaceName )
585616 {
586617 return type . AllInterfaces . Any ( i => i . ToDisplayString ( ) == interfaceName ) ;
0 commit comments