Skip to content

Commit fc975d1

Browse files
committed
Generalize forwarding all property modifiers
1 parent 28c6a8c commit fc975d1

File tree

3 files changed

+75
-8
lines changed

3 files changed

+75
-8
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
1515
/// <param name="TypeNameWithNullabilityAnnotations">The type name for the generated property, including nullability annotations.</param>
1616
/// <param name="FieldName">The field name.</param>
1717
/// <param name="PropertyName">The generated property name.</param>
18+
/// <param name="PropertyModifers">The list of additional modifiers for the property (they are <see cref="SyntaxKind"/> values).</param>
1819
/// <param name="PropertyAccessibility">The accessibility of the property.</param>
1920
/// <param name="GetterAccessibility">The accessibility of the <see langword="get"/> accessor.</param>
2021
/// <param name="SetterAccessibility">The accessibility of the <see langword="set"/> accessor.</param>
@@ -23,7 +24,6 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
2324
/// <param name="NotifiedCommandNames">The sequence of commands to notify.</param>
2425
/// <param name="NotifyPropertyChangedRecipients">Whether or not the generated property also broadcasts changes.</param>
2526
/// <param name="NotifyDataErrorInfo">Whether or not the generated property also validates its value.</param>
26-
/// <param name="IsRequired">Whether or not the generated property should be marked as required.</param>
2727
/// <param name="IsOldPropertyValueDirectlyReferenced">Whether the old property value is being directly referenced.</param>
2828
/// <param name="IsReferenceTypeOrUnconstrainedTypeParameter">Indicates whether the property is of a reference type or an unconstrained type parameter.</param>
2929
/// <param name="IncludeMemberNotNullOnSetAccessor">Indicates whether to include nullability annotations on the setter.</param>
@@ -34,6 +34,7 @@ internal sealed record PropertyInfo(
3434
string TypeNameWithNullabilityAnnotations,
3535
string FieldName,
3636
string PropertyName,
37+
EquatableArray<ushort> PropertyModifers,
3738
Accessibility PropertyAccessibility,
3839
Accessibility GetterAccessibility,
3940
Accessibility SetterAccessibility,
@@ -42,7 +43,6 @@ internal sealed record PropertyInfo(
4243
EquatableArray<string> NotifiedCommandNames,
4344
bool NotifyPropertyChangedRecipients,
4445
bool NotifyDataErrorInfo,
45-
bool IsRequired,
4646
bool IsOldPropertyValueDirectlyReferenced,
4747
bool IsReferenceTypeOrUnconstrainedTypeParameter,
4848
bool IncludeMemberNotNullOnSetAccessor,

src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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;
56
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.ComponentModel;
@@ -362,6 +363,9 @@ public static bool TryGetInfo(
362363

363364
token.ThrowIfCancellationRequested();
364365

366+
// Get all additional modifiers for the member
367+
ImmutableArray<SyntaxKind> propertyModifiers = GetPropertyModifiers(memberSyntax);
368+
365369
// Retrieve the accessibility values for all components
366370
if (!TryGetAccessibilityModifiers(
367371
memberSyntax,
@@ -388,6 +392,7 @@ public static bool TryGetInfo(
388392
typeNameWithNullabilityAnnotations,
389393
fieldName,
390394
propertyName,
395+
propertyModifiers.AsUnderlyingType(),
391396
propertyAccessibility,
392397
getterAccessibility,
393398
setterAccessibility,
@@ -396,7 +401,6 @@ public static bool TryGetInfo(
396401
notifiedCommandNames.ToImmutable(),
397402
notifyRecipients,
398403
notifyDataErrorInfo,
399-
isRequired,
400404
isOldPropertyValueDirectlyReferenced,
401405
isReferenceTypeOrUnconstrainedTypeParameter,
402406
includeMemberNotNullOnSetAccessor,
@@ -970,6 +974,45 @@ private static void GatherLegacyForwardedAttributes(
970974
}
971975
}
972976

977+
/// <summary>
978+
/// Gathers all allowed property modifiers that should be forwarded to the generated property.
979+
/// </summary>
980+
/// <param name="memberSyntax">The <see cref="MemberDeclarationSyntax"/> instance to process.</param>
981+
/// <returns>The returned set of property modifiers, if any.</returns>
982+
private static ImmutableArray<SyntaxKind> GetPropertyModifiers(MemberDeclarationSyntax memberSyntax)
983+
{
984+
// Fields never need to carry additional modifiers along
985+
if (memberSyntax.IsKind(SyntaxKind.FieldDeclaration))
986+
{
987+
return ImmutableArray<SyntaxKind>.Empty;
988+
}
989+
990+
// We only allow a subset of all possible modifiers (aside from the accessibility modifiers)
991+
ReadOnlySpan<SyntaxKind> candidateKinds =
992+
[
993+
SyntaxKind.NewKeyword,
994+
SyntaxKind.VirtualKeyword,
995+
SyntaxKind.SealedKeyword,
996+
SyntaxKind.OverrideKeyword,
997+
#if ROSLYN_4_3_1_OR_GREATER
998+
SyntaxKind.RequiredKeyword
999+
#endif
1000+
];
1001+
1002+
using ImmutableArrayBuilder<SyntaxKind> builder = ImmutableArrayBuilder<SyntaxKind>.Rent();
1003+
1004+
// Track all modifiers from the allowed set on the input property declaration
1005+
foreach (SyntaxKind kind in candidateKinds)
1006+
{
1007+
if (memberSyntax.Modifiers.Any(kind))
1008+
{
1009+
builder.Add(kind);
1010+
}
1011+
}
1012+
1013+
return builder.ToImmutable();
1014+
}
1015+
9731016
/// <summary>
9741017
/// Tries to get the accessibility of the property and accessors, if possible.
9751018
/// If the target member is not a property, it will use the defaults.
@@ -1395,13 +1438,11 @@ private static SyntaxTokenList GetPropertyModifiers(PropertyInfo propertyInfo)
13951438
{
13961439
SyntaxTokenList propertyModifiers = propertyInfo.PropertyAccessibility.ToSyntaxTokenList();
13971440

1398-
#if ROSLYN_4_3_1_OR_GREATER
1399-
// Add the 'required' modifier if the original member also had it
1400-
if (propertyInfo.IsRequired)
1441+
// Add all gathered modifiers
1442+
foreach (SyntaxKind modifier in propertyInfo.PropertyModifers.AsImmutableArray().FromUnderlyingType())
14011443
{
1402-
propertyModifiers = propertyModifiers.Add(Token(SyntaxKind.RequiredKeyword));
1444+
propertyModifiers = propertyModifiers.Add(Token(modifier));
14031445
}
1404-
#endif
14051446

14061447
// Add the 'partial' modifier if the original member is a partial property
14071448
if (propertyInfo.AnnotatedMemberKind is SyntaxKind.PropertyDeclaration)

src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/SyntaxKindExtensions.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Collections.Immutable;
7+
using System.Runtime.CompilerServices;
68
using Microsoft.CodeAnalysis.CSharp;
79

810
namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
@@ -12,6 +14,30 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
1214
/// </summary>
1315
internal static class SyntaxKindExtensions
1416
{
17+
/// <summary>
18+
/// Converts an <see cref="ImmutableArray{T}"/> of <see cref="SyntaxKind"/> values to one of their underlying type.
19+
/// </summary>
20+
/// <param name="array">The input <see cref="ImmutableArray{T}"/> value.</param>
21+
/// <returns>The resulting <see cref="ImmutableArray{T}"/> of <see cref="ushort"/> values.</returns>
22+
public static ImmutableArray<ushort> AsUnderlyingType(this ImmutableArray<SyntaxKind> array)
23+
{
24+
ushort[]? underlyingArray = (ushort[]?)(object?)Unsafe.As<ImmutableArray<SyntaxKind>, SyntaxKind[]?>(ref array);
25+
26+
return Unsafe.As<ushort[]?, ImmutableArray<ushort>>(ref underlyingArray);
27+
}
28+
29+
/// <summary>
30+
/// Converts an <see cref="ImmutableArray{T}"/> of <see cref="ushort"/> values to one of their real type.
31+
/// </summary>
32+
/// <param name="array">The input <see cref="ImmutableArray{T}"/> value.</param>
33+
/// <returns>The resulting <see cref="ImmutableArray{T}"/> of <see cref="SyntaxKind"/> values.</returns>
34+
public static ImmutableArray<SyntaxKind> FromUnderlyingType(this ImmutableArray<ushort> array)
35+
{
36+
SyntaxKind[]? typedArray = (SyntaxKind[]?)(object?)Unsafe.As<ImmutableArray<ushort>, ushort[]?>(ref array);
37+
38+
return Unsafe.As<SyntaxKind[]?, ImmutableArray<SyntaxKind>>(ref typedArray);
39+
}
40+
1541
/// <summary>
1642
/// Converts a <see cref="SyntaxKind"/> value to either "field" or "property" based on the kind.
1743
/// </summary>

0 commit comments

Comments
 (0)