Skip to content

Commit 9297335

Browse files
committed
Fix partial methods support with Roslyn 4.0
Also ensure a consistent order for forwarded attributes
1 parent a845ad4 commit 9297335

File tree

3 files changed

+20
-9
lines changed

3 files changed

+20
-9
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,18 +1016,20 @@ static void GatherForwardedAttributes(
10161016
}
10171017
}
10181018

1019-
// Gather attributes from the method declaration
1020-
GatherForwardedAttributes(methodSymbol, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
1021-
10221019
// If the method is a partial definition, also gather attributes from the implementation part
1023-
if (methodSymbol is { IsPartialDefinition: true, PartialImplementationPart: { } partialImplementation })
1020+
if (methodSymbol is { IsPartialDefinition: true } or { PartialDefinitionPart: not null })
10241021
{
1022+
IMethodSymbol partialDefinition = methodSymbol.PartialDefinitionPart ?? methodSymbol;
1023+
IMethodSymbol partialImplementation = methodSymbol.PartialImplementationPart ?? methodSymbol;
1024+
1025+
// We always give priority to the partial definition, to ensure a predictable and testable ordering
1026+
GatherForwardedAttributes(partialDefinition, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
10251027
GatherForwardedAttributes(partialImplementation, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
10261028
}
1027-
else if (methodSymbol is { IsPartialDefinition: false, PartialDefinitionPart: { } partialDefinition })
1029+
else
10281030
{
1029-
// If the method is a partial implementation, also gather attributes from the definition part
1030-
GatherForwardedAttributes(partialDefinition, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
1031+
// If the method is not a partial definition/implementation, just gather attributes from the method with no modifications
1032+
GatherForwardedAttributes(methodSymbol, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
10311033
}
10321034

10331035
fieldAttributes = fieldAttributesInfo.ToImmutable();

src/CommunityToolkit.Mvvm.SourceGenerators/Polyfills/SyntaxValueProviderExtensions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ public static IncrementalValuesProvider<T> ForAttributeWithMetadataName<T>(
5959
return null;
6060
}
6161

62+
// Edge case: if the symbol is a partial method, skip the implementation part and only process the partial method
63+
// definition. This is needed because attributes will be reported as available on both the definition and the
64+
// implementation part. To avoid generating duplicate files, we only give priority to the definition part.
65+
// On Roslyn 4.3+, ForAttributeWithMetadataName will already only return the symbol the attribute was located on.
66+
if (symbol is IMethodSymbol { IsPartialDefinition: false, PartialDefinitionPart: not null })
67+
{
68+
return null;
69+
}
70+
6271
// Create the GeneratorAttributeSyntaxContext value to pass to the input transform. The attributes array
6372
// will only ever have a single value, but that's fine with the attributes the various generators look for.
6473
GeneratorAttributeSyntaxContext syntaxContext = new(

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,14 @@ partial class MyViewModel
322322
{
323323
/// <summary>The backing field for <see cref="Test1Command"/>.</summary>
324324
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
325-
[global::MyApp.ValueAttribute(0)]
326325
[global::MyApp.ValueAttribute(2)]
326+
[global::MyApp.ValueAttribute(0)]
327327
private global::CommunityToolkit.Mvvm.Input.RelayCommand? test1Command;
328328
/// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test1"/>.</summary>
329329
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
330330
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
331-
[global::MyApp.ValueAttribute(1)]
332331
[global::MyApp.ValueAttribute(3)]
332+
[global::MyApp.ValueAttribute(1)]
333333
public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test1Command => test1Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test1));
334334
}
335335
}

0 commit comments

Comments
 (0)