Skip to content

Commit 164a8d9

Browse files
committed
Fix member generation in sealed types
1 parent 74c60c7 commit 164a8d9

File tree

7 files changed

+74
-18
lines changed

7 files changed

+74
-18
lines changed

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Collections.Generic;
56
using System.Collections.Immutable;
67
using System.Linq;
78
using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
89
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
910
using CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
1011
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CSharp;
1113
using Microsoft.CodeAnalysis.CSharp.Syntax;
1214
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
1315

@@ -30,12 +32,10 @@ public INotifyPropertyChangedGenerator()
3032
/// <inheritdoc/>
3133
protected override INotifyPropertyChangedInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
3234
{
33-
if (attributeData.TryGetNamedArgument("IncludeAdditionalHelperMethods", out bool includeAdditionalHelperMethods))
34-
{
35-
return new(includeAdditionalHelperMethods);
36-
}
35+
bool includeAdditionalHelperMethods = attributeData.GetNamedArgument<bool>("IncludeAdditionalHelperMethods", true);
36+
bool isSealed = typeSymbol.IsSealed;
3737

38-
return new(false);
38+
return new(includeAdditionalHelperMethods, isSealed);
3939
}
4040

4141
/// <inheritdoc/>
@@ -61,15 +61,26 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, INotifyP
6161
/// <inheritdoc/>
6262
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(INotifyPropertyChangedInfo info, ClassDeclarationSyntax classDeclaration)
6363
{
64+
IEnumerable<MemberDeclarationSyntax> memberDeclarations;
65+
6466
// If requested, only include the event and the basic methods to raise it, but not the additional helpers
65-
if (!info.IncludeAdditionalHelperMethods)
67+
if (info.IncludeAdditionalHelperMethods)
68+
{
69+
memberDeclarations = classDeclaration.Members;
70+
}
71+
else
6672
{
67-
return classDeclaration.Members.Where(
68-
static member => member
69-
is EventFieldDeclarationSyntax
70-
or MethodDeclarationSyntax { Identifier.ValueText: "OnPropertyChanged" }).ToImmutableArray();
73+
memberDeclarations = classDeclaration.Members.Where(static member => member
74+
is EventFieldDeclarationSyntax
75+
or MethodDeclarationSyntax { Identifier.ValueText: "OnPropertyChanged" });
7176
}
7277

73-
return classDeclaration.Members.ToImmutableArray();
78+
// If the target class is sealed, make protected members private and remove the virtual modifier
79+
return
80+
memberDeclarations
81+
.Select(static member => member
82+
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
83+
.RemoveModifier(SyntaxKind.VirtualKeyword))
84+
.ToImmutableArray();
7485
}
7586
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
88
/// A model with gathered info on a given <c>INotifyPropertyChangedAttribute</c> instance.
99
/// </summary>
1010
/// <param name="IncludeAdditionalHelperMethods">Whether to also generate helper methods in the target type.</param>
11-
public sealed record INotifyPropertyChangedInfo(bool IncludeAdditionalHelperMethods);
11+
/// <param name="IsSealed">Whether the target type is sealed.</param>
12+
public sealed record INotifyPropertyChangedInfo(bool IncludeAdditionalHelperMethods, bool IsSealed);

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ObservableRecipientInfo.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
1010
/// <param name="TypeName">The type name of the type being annotated.</param>
1111
/// <param name="HasExplicitConstructors">Whether or not the target type has explicit constructors.</param>
1212
/// <param name="IsAbstract">Whether or not the target type is abstract.</param>
13+
/// <param name="IsSealed">Whether the target type is sealed.</param>
1314
/// <param name="IsObservableValidator">Whether or not the target type inherits from <c>ObservableValidator</c>.</param>
1415
public sealed record ObservableRecipientInfo(
1516
string TypeName,
1617
bool HasExplicitConstructors,
1718
bool IsAbstract,
19+
bool IsSealed,
1820
bool IsObservableValidator);

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
88
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
99
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
1011
using Microsoft.CodeAnalysis.CSharp.Syntax;
1112
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
1213

@@ -16,7 +17,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
1617
/// A source generator for the <c>ObservableObjectAttribute</c> type.
1718
/// </summary>
1819
[Generator(LanguageNames.CSharp)]
19-
public sealed class ObservableObjectGenerator : TransitiveMembersGenerator<object?>
20+
public sealed class ObservableObjectGenerator : TransitiveMembersGenerator<bool>
2021
{
2122
/// <summary>
2223
/// Initializes a new instance of the <see cref="ObservableObjectGenerator"/> class.
@@ -27,13 +28,13 @@ public ObservableObjectGenerator()
2728
}
2829

2930
/// <inheritdoc/>
30-
protected override object? GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
31+
protected override bool GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
3132
{
32-
return null;
33+
return typeSymbol.IsSealed;
3334
}
3435

3536
/// <inheritdoc/>
36-
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, object? info, out ImmutableArray<Diagnostic> diagnostics)
37+
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, bool info, out ImmutableArray<Diagnostic> diagnostics)
3738
{
3839
ImmutableArray<Diagnostic>.Builder builder = ImmutableArray.CreateBuilder<Diagnostic>();
3940

@@ -63,8 +64,19 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, object?
6364
}
6465

6566
/// <inheritdoc/>
66-
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(object? info, ClassDeclarationSyntax classDeclaration)
67+
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(bool info, ClassDeclarationSyntax classDeclaration)
6768
{
69+
// If the target class is sealed, make protected members private and remove the virtual modifier
70+
if (info)
71+
{
72+
return
73+
classDeclaration.Members
74+
.Select(static member => member
75+
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
76+
.RemoveModifier(SyntaxKind.VirtualKeyword))
77+
.ToImmutableArray();
78+
}
79+
6880
return classDeclaration.Members.ToImmutableArray();
6981
}
7082
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ protected override ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol,
3535
string typeName = typeSymbol.Name;
3636
bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true });
3737
bool isAbstract = typeSymbol.IsAbstract;
38+
bool isSealed = typeSymbol.IsSealed;
3839
bool isObservableValidator = typeSymbol.InheritsFrom("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator");
3940

4041
return new(
4142
typeName,
4243
hasExplicitConstructors,
4344
isAbstract,
45+
isSealed,
4446
isObservableValidator);
4547
}
4648

@@ -123,6 +125,17 @@ protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers
123125
builder.Add(member);
124126
}
125127

128+
// If the target class is sealed, make protected members private and remove the virtual modifier
129+
if (info.IsSealed)
130+
{
131+
return
132+
builder
133+
.Select(static member => member
134+
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
135+
.RemoveModifier(SyntaxKind.VirtualKeyword))
136+
.ToImmutableArray();
137+
}
138+
126139
return builder.ToImmutable();
127140
}
128141
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
2323
/// </summary>
2424
/// <typeparam name="TInfo">The type of info gathered for each target type to process.</typeparam>
2525
public abstract partial class TransitiveMembersGenerator<TInfo> : IIncrementalGenerator
26-
where TInfo : class?
2726
{
2827
/// <summary>
2928
/// The fully qualified name of the attribute type to look for.

CommunityToolkit.Mvvm.SourceGenerators/Extensions/AttributeDataExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ properties.Value.Value is T argumentValue &&
4343
return false;
4444
}
4545

46+
/// <summary>
47+
/// Gets a given named argument value from an <see cref="AttributeData"/> instance, or a fallback value.
48+
/// </summary>
49+
/// <typeparam name="T">The type of argument to check.</typeparam>
50+
/// <param name="attributeData">The target <see cref="AttributeData"/> instance to check.</param>
51+
/// <param name="name">The name of the argument to check.</param>
52+
/// <param name="fallback">The fallback value to use if the named argument is not present.</param>
53+
/// <returns>The argument named <paramref name="name"/>, or a fallback value.</returns>
54+
public static T? GetNamedArgument<T>(this AttributeData attributeData, string name, T? fallback = default)
55+
{
56+
if (attributeData.TryGetNamedArgument(name, out T? value))
57+
{
58+
return value;
59+
}
60+
61+
return fallback;
62+
}
63+
4664
/// <summary>
4765
/// Tries to get a given named argument value from an <see cref="AttributeData"/> instance, if present.
4866
/// </summary>

0 commit comments

Comments
 (0)