Skip to content

Commit 895f368

Browse files
Removed readonly option from both attributes since we can read from the field itself. Now supports nullable fields and sealed classes.
1 parent 0c520f6 commit 895f368

File tree

3 files changed

+52
-27
lines changed

3 files changed

+52
-27
lines changed

src/ThunderDesign.Net-PCL.SourceGenerators/UnifiedPropertyGenerator.cs

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/BindablePropertyAttribute.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,19 @@ namespace ThunderDesign.Net.Threading.Attributes
99
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
1010
public sealed class BindablePropertyAttribute : Attribute
1111
{
12-
public bool ReadOnly { get; }
1312
public bool ThreadSafe { get; }
1413
public bool Notify { get; }
1514
public string[] AlsoNotify { get; }
1615
public AccessorAccessibility Getter { get; }
1716
public AccessorAccessibility Setter { get; }
1817

1918
public BindablePropertyAttribute(
20-
bool readOnly = false,
2119
bool threadSafe = true,
2220
bool notify = true,
2321
string[] alsoNotify = null,
2422
AccessorAccessibility getter = AccessorAccessibility.Public,
2523
AccessorAccessibility setter = AccessorAccessibility.Public)
2624
{
27-
ReadOnly = readOnly;
2825
ThreadSafe = threadSafe;
2926
Notify = notify;
3027
AlsoNotify = alsoNotify ?? Array.Empty<string>();

src/ThunderDesign.Net-PCL.Threading.Shared/Attributes/PropertyAttribute.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,15 @@ namespace ThunderDesign.Net.Threading.Attributes
77
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
88
public sealed class PropertyAttribute : Attribute
99
{
10-
public bool ReadOnly { get; }
1110
public bool ThreadSafe { get; }
1211
public AccessorAccessibility Getter { get; }
1312
public AccessorAccessibility Setter { get; }
1413

1514
public PropertyAttribute(
16-
bool readOnly = false,
1715
bool threadSafe = true,
1816
AccessorAccessibility getter = AccessorAccessibility.Public,
1917
AccessorAccessibility setter = AccessorAccessibility.Public)
2018
{
21-
ReadOnly = readOnly;
2219
ThreadSafe = threadSafe;
2320
Getter = getter;
2421
Setter = setter;

0 commit comments

Comments
 (0)