Skip to content

Commit ec1129b

Browse files
authored
Merge pull request #179 from CommunityToolkit/dev/incremental-nullable-generator
Make NullabilityAttributesGenerator incremental
2 parents 4b1adeb + 8c03ffa commit ec1129b

File tree

7 files changed

+78
-57
lines changed

7 files changed

+78
-57
lines changed

CommunityToolkit.Mvvm.SourceGenerators/Attributes/NullabilityAttributesGenerator.cs

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,73 @@
55
using System.IO;
66
using System.Linq;
77
using System.Reflection;
8-
using System.Text;
98
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
109
using Microsoft.CodeAnalysis;
11-
using Microsoft.CodeAnalysis.Text;
1210

1311
namespace CommunityToolkit.Mvvm.SourceGenerators;
1412

1513
/// <summary>
1614
/// A source generator for necessary nullability attributes.
1715
/// </summary>
18-
[Generator]
19-
public sealed class NullabilityAttributesGenerator : ISourceGenerator
16+
[Generator(LanguageNames.CSharp)]
17+
public sealed class NullabilityAttributesGenerator : IIncrementalGenerator
2018
{
21-
/// <inheritdoc/>
22-
public void Initialize(GeneratorInitializationContext context)
23-
{
24-
}
19+
/// <summary>
20+
/// The <c>System.Diagnostics.CodeAnalysis.NotNullAttribute</c> metadata name.
21+
/// </summary>
22+
private const string NotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullAttribute";
23+
24+
/// <summary>
25+
/// The <c>System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute</c> metadata name.
26+
/// </summary>
27+
private const string NotNullIfNotNullAttributeMetadataName = "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute";
2528

2629
/// <inheritdoc/>
27-
public void Execute(GeneratorExecutionContext context)
30+
public void Initialize(IncrementalGeneratorInitializationContext context)
2831
{
29-
AddSourceCodeIfTypeIsNotPresent(context, "System.Diagnostics.CodeAnalysis.NotNullAttribute");
30-
AddSourceCodeIfTypeIsNotPresent(context, "System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute");
32+
// Check that the target attributes are not available in the consuming project. To ensure that this
33+
// works fine both in .NET (Core) and .NET Standard implementations, we also need to check that the
34+
// target types are declared as public (we assume that in this case those types are from the BCL).
35+
// This avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies.
36+
37+
// Check whether [NotNull] is not available
38+
IncrementalValueProvider<bool> isNotNullAttributeNotAvailable =
39+
context.CompilationProvider
40+
.Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullAttributeMetadataName));
41+
42+
// Generate the [NotNull] type
43+
context.RegisterConditionalSourceOutput(isNotNullAttributeNotAvailable, static context =>
44+
{
45+
string source = LoadAttributeSourceWithMetadataName(NotNullAttributeMetadataName);
46+
47+
context.AddSource(NotNullAttributeMetadataName, source);
48+
});
49+
50+
// Check whether [NotNullIfNotNull] is not available
51+
IncrementalValueProvider<bool> isNotNullIfNotNullAttributeNotAvailable =
52+
context.CompilationProvider
53+
.Select(static (item, _) => !item.HasAccessibleTypeWithMetadataName(NotNullIfNotNullAttributeMetadataName));
54+
55+
// Generate the [NotNullIfNotNull] type
56+
context.RegisterConditionalSourceOutput(isNotNullIfNotNullAttributeNotAvailable, static context =>
57+
{
58+
string source = LoadAttributeSourceWithMetadataName(NotNullIfNotNullAttributeMetadataName);
59+
60+
context.AddSource(NotNullIfNotNullAttributeMetadataName, source);
61+
});
3162
}
3263

3364
/// <summary>
34-
/// Adds the source for a given attribute type if it's not present already in the compilation.
65+
/// Gets the generated source for a specified attribute.
3566
/// </summary>
36-
private void AddSourceCodeIfTypeIsNotPresent(GeneratorExecutionContext context, string typeFullName)
67+
private static string LoadAttributeSourceWithMetadataName(string typeFullName)
3768
{
38-
// Check that the target attributes are not available in the consuming project. To ensure that
39-
// this works fine both in .NET (Core) and .NET Standard implementations, we also need to check
40-
// that the target types are declared as public (we assume that in this case those types are from the BCL).
41-
// This avoids issues on .NET Standard with Roslyn also seeing internal types from referenced assemblies.
42-
if (context.Compilation.HasAccessibleTypeWithMetadataName(typeFullName))
43-
{
44-
return;
45-
}
46-
4769
string typeName = typeFullName.Split('.').Last();
4870
string filename = $"CommunityToolkit.Mvvm.SourceGenerators.EmbeddedResources.{typeName}.cs";
4971

5072
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename);
5173
StreamReader reader = new(stream);
5274

53-
string source = reader.ReadToEnd();
54-
55-
context.AddSource($"{typeFullName}.cs", SourceText.From(source, Encoding.UTF8));
75+
return reader.ReadToEnd();
5676
}
5777
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
9595
// Insert all members into the same partial type declaration
9696
CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(memberDeclarations);
9797

98-
context.AddSource(
99-
hintName: $"{item.Hierarchy.FilenameHint}.cs",
100-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
98+
context.AddSource(item.Hierarchy.FilenameHint, compilationUnit.ToFullString());
10199
});
102100

103101
// Gather all property changing names
@@ -115,9 +113,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
115113

116114
if (compilationUnit is not null)
117115
{
118-
context.AddSource(
119-
hintName: "__KnownINotifyPropertyChangingArgs.cs",
120-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
116+
context.AddSource("__KnownINotifyPropertyChangingArgs", compilationUnit.ToFullString());
121117
}
122118
});
123119

@@ -136,9 +132,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
136132

137133
if (compilationUnit is not null)
138134
{
139-
context.AddSource(
140-
hintName: "__KnownINotifyPropertyChangedArgs.cs",
141-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
135+
context.AddSource("__KnownINotifyPropertyChangedArgs", compilationUnit.ToFullString());
142136
}
143137
});
144138
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
5656
{
5757
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
5858

59-
context.AddSource(
60-
hintName: "__ObservableValidatorExtensions.cs",
61-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
59+
context.AddSource("__ObservableValidatorExtensions", compilationUnit.ToFullString());
6260
});
6361

6462
// Generate the class with all validation methods
6563
context.RegisterImplementationSourceOutput(validationInfo, static (context, item) =>
6664
{
6765
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
6866

69-
context.AddSource(
70-
hintName: $"{item.FilenameHint}.cs",
71-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
67+
context.AddSource(item.FilenameHint, compilationUnit.ToFullString());
7268
});
7369
}
7470
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
124124
ImmutableArray<MemberDeclarationSyntax> filteredMemberDeclarations = FilterDeclaredMembers(item.Info, sourceMemberDeclarations);
125125
CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(filteredMemberDeclarations, this.classDeclaration.BaseList);
126126

127-
context.AddSource(
128-
hintName: $"{item.Hierarchy.FilenameHint}.cs",
129-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
127+
context.AddSource(item.Hierarchy.FilenameHint, compilationUnit.ToFullString());
130128
});
131129
}
132130

CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,9 @@ public static void FilterWithLanguageVersion<T>(
5050
.Select(static (item, _) => item.Length > 0);
5151

5252
// Report them to the output
53-
context.RegisterSourceOutput(isUnsupportedAttributeUsed, (context, diagnostic) =>
53+
context.RegisterConditionalSourceOutput(isUnsupportedAttributeUsed, context =>
5454
{
55-
if (diagnostic)
56-
{
57-
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, null));
58-
}
55+
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, null));
5956
});
6057

6158
// Only let data through if the minimum language version is supported
@@ -65,10 +62,32 @@ public static void FilterWithLanguageVersion<T>(
6562
.Select(static (item, _) => item.Data);
6663
}
6764

65+
/// <summary>
66+
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterSourceOutput{TSource}(IncrementalValueProvider{TSource}, Action{SourceProductionContext, TSource})"/>
67+
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>.
68+
/// </summary>
69+
/// <param name="context">The input <see cref="IncrementalGeneratorInitializationContext"/> value being used.</param>
70+
/// <param name="source">The source <see cref="IncrementalValueProvider{TValues}"/> instance.</param>
71+
/// <param name="action">The conditional <see cref="Action"/> to invoke.</param>
72+
public static void RegisterConditionalSourceOutput(
73+
this IncrementalGeneratorInitializationContext context,
74+
IncrementalValueProvider<bool> source,
75+
Action<SourceProductionContext> action)
76+
{
77+
context.RegisterSourceOutput(source, (context, condition) =>
78+
{
79+
if (condition)
80+
{
81+
action(context);
82+
}
83+
});
84+
}
85+
6886
/// <summary>
6987
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterImplementationSourceOutput{TSource}(IncrementalValueProvider{TSource}, Action{SourceProductionContext, TSource})"/>
7088
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>, and also supplying a given input state.
7189
/// </summary>
90+
/// <typeparam name="T">The type of state to pass to the source production callback to invoke.</typeparam>
7291
/// <param name="context">The input <see cref="IncrementalGeneratorInitializationContext"/> value being used.</param>
7392
/// <param name="source">The source <see cref="IncrementalValueProvider{TValues}"/> instance.</param>
7493
/// <param name="action">The conditional <see cref="Action{T}"/> to invoke.</param>

CommunityToolkit.Mvvm.SourceGenerators/Input/ICommandGenerator.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
7171
ImmutableArray<MemberDeclarationSyntax> memberDeclarations = Execute.GetSyntax(item.Info);
7272
CompilationUnitSyntax compilationUnit = item.Hierarchy.GetCompilationUnit(memberDeclarations);
7373

74-
context.AddSource(
75-
hintName: $"{item.Hierarchy.FilenameHint}.{item.Info.MethodName}.cs",
76-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
74+
context.AddSource($"{item.Hierarchy.FilenameHint}.{item.Info.MethodName}", compilationUnit.ToFullString());
7775
});
7876
}
7977
}

CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,15 @@ item.Symbol.DeclaringSyntaxReferences[0] is SyntaxReference syntaxReference &&
7171
{
7272
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
7373

74-
context.AddSource(
75-
hintName: "__IMessengerExtensions.cs",
76-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
74+
context.AddSource("__IMessengerExtensions", compilationUnit.ToFullString());
7775
});
7876

7977
// Generate the class with all registration methods
8078
context.RegisterImplementationSourceOutput(recipientInfo, static (context, item) =>
8179
{
8280
CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item);
8381

84-
context.AddSource(
85-
hintName: $"{item.FilenameHint}.cs",
86-
sourceText: SourceText.From(compilationUnit.ToFullString(), Encoding.UTF8));
82+
context.AddSource(item.FilenameHint, compilationUnit.ToFullString());
8783
});
8884
}
8985
}

0 commit comments

Comments
 (0)