Skip to content

Commit cc2aa2d

Browse files
authored
Merge pull request #915 from CommunityToolkit/dev/trim-generated-code
Don't generate 'INotifyPropertyChanging' code if not requested
2 parents 2c2a1c3 + 761a22c commit cc2aa2d

File tree

6 files changed

+110
-3
lines changed

6 files changed

+110
-3
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<Compile Include="$(MSBuildThisFileDirectory)Extensions\CompilationExtensions.cs" />
5656
<Compile Include="$(MSBuildThisFileDirectory)Extensions\DiagnosticsExtensions.cs" />
5757
<Compile Include="$(MSBuildThisFileDirectory)Extensions\INamedTypeSymbolExtensions.cs" />
58+
<Compile Include="$(MSBuildThisFileDirectory)Extensions\GeneratorAttributeSyntaxContextWithOptions.cs" />
5859
<Compile Include="$(MSBuildThisFileDirectory)Extensions\IncrementalGeneratorInitializationContextExtensions.cs" />
5960
<Compile Include="$(MSBuildThisFileDirectory)Extensions\IncrementalValuesProviderExtensions.cs" />
6061
<Compile Include="$(MSBuildThisFileDirectory)Extensions\MethodDeclarationSyntaxExtensions.cs" />

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Microsoft.CodeAnalysis;
1717
using Microsoft.CodeAnalysis.CSharp;
1818
using Microsoft.CodeAnalysis.CSharp.Syntax;
19+
using Microsoft.CodeAnalysis.Diagnostics;
1920
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
2021
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
2122

@@ -35,6 +36,7 @@ internal static class Execute
3536
/// <param name="fieldSyntax">The <see cref="FieldDeclarationSyntax"/> instance to process.</param>
3637
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
3738
/// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
39+
/// <param name="options">The options in use for the generator.</param>
3840
/// <param name="token">The cancellation token for the current operation.</param>
3941
/// <param name="propertyInfo">The resulting <see cref="PropertyInfo"/> value, if successfully retrieved.</param>
4042
/// <param name="diagnostics">The resulting diagnostics from the processing operation.</param>
@@ -43,6 +45,7 @@ public static bool TryGetInfo(
4345
FieldDeclarationSyntax fieldSyntax,
4446
IFieldSymbol fieldSymbol,
4547
SemanticModel semanticModel,
48+
AnalyzerConfigOptions options,
4649
CancellationToken token,
4750
[NotNullWhen(true)] out PropertyInfo? propertyInfo,
4851
out ImmutableArray<DiagnosticInfo> diagnostics)
@@ -66,6 +69,11 @@ public static bool TryGetInfo(
6669

6770
token.ThrowIfCancellationRequested();
6871

72+
// Override the property changing support if explicitly disabled
73+
shouldInvokeOnPropertyChanging &= GetEnableINotifyPropertyChangingSupport(options);
74+
75+
token.ThrowIfCancellationRequested();
76+
6977
// Get the property type and name
7078
string typeNameWithNullabilityAnnotations = fieldSymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations();
7179
string fieldName = fieldSymbol.Name;
@@ -320,6 +328,27 @@ public static bool TryGetInfo(
320328
return true;
321329
}
322330

331+
/// <summary>
332+
/// Gets the value for the "MvvmToolkitEnableINotifyPropertyChangingSupport" property.
333+
/// </summary>
334+
/// <param name="options">The options in use for the generator.</param>
335+
/// <returns>The value for the "MvvmToolkitEnableINotifyPropertyChangingSupport" property.</returns>
336+
public static bool GetEnableINotifyPropertyChangingSupport(AnalyzerConfigOptions options)
337+
{
338+
if (options.TryGetValue("build_property.MvvmToolkitEnableINotifyPropertyChangingSupport", out string? propertyValue))
339+
{
340+
if (bool.TryParse(propertyValue, out bool enableINotifyPropertyChangingSupport))
341+
{
342+
return enableINotifyPropertyChangingSupport;
343+
}
344+
}
345+
346+
// This setting is enabled by default, for backwards compatibility.
347+
// Note that this path should never be reached, as the default
348+
// value is also set in a .targets file bundled in the package.
349+
return true;
350+
}
351+
323352
/// <summary>
324353
/// Validates the containing type for a given field being annotated.
325354
/// </summary>

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2525
{
2626
// Gather info for all annotated fields
2727
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result<PropertyInfo?> Info)> propertyInfoWithErrors =
28-
context.SyntaxProvider
29-
.ForAttributeWithMetadataName(
28+
context.ForAttributeWithMetadataNameAndOptions(
3029
"CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute",
3130
static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } },
3231
static (context, token) =>
@@ -44,7 +43,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
4443

4544
token.ThrowIfCancellationRequested();
4645

47-
_ = Execute.TryGetInfo(fieldDeclaration, fieldSymbol, context.SemanticModel, token, out PropertyInfo? propertyInfo, out ImmutableArray<DiagnosticInfo> diagnostics);
46+
_ = Execute.TryGetInfo(
47+
fieldDeclaration,
48+
fieldSymbol,
49+
context.SemanticModel,
50+
context.GlobalOptions,
51+
token,
52+
out PropertyInfo? propertyInfo,
53+
out ImmutableArray<DiagnosticInfo> diagnostics);
4854

4955
token.ThrowIfCancellationRequested();
5056

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Immutable;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
9+
namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
10+
11+
/// <summary>
12+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext" path="/summary/node()"/>
13+
/// </summary>
14+
/// <param name="syntaxContext">The original <see cref="GeneratorAttributeSyntaxContext"/> value.</param>
15+
/// <param name="globalOptions">The original <see cref="AnalyzerConfigOptions"/> value.</param>
16+
internal readonly struct GeneratorAttributeSyntaxContextWithOptions(
17+
GeneratorAttributeSyntaxContext syntaxContext,
18+
AnalyzerConfigOptions globalOptions)
19+
{
20+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetNode"/>
21+
public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode;
22+
23+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetSymbol"/>
24+
public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol;
25+
26+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.SemanticModel"/>
27+
public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;
28+
29+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.Attributes"/>
30+
public ImmutableArray<AttributeData> Attributes { get; } = syntaxContext.Attributes;
31+
32+
/// <inheritdoc cref="AnalyzerConfigOptionsProvider.GlobalOptions"/>
33+
public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
34+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
using System;
66
using System.Collections.Immutable;
7+
using System.Threading;
78
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
11+
#pragma warning disable CS1574
812

913
namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
1014

@@ -13,6 +17,31 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;
1317
/// </summary>
1418
internal static class IncrementalGeneratorInitializationContextExtensions
1519
{
20+
/// <inheritdoc cref="SyntaxValueProvider.ForAttributeWithMetadataName"/>
21+
public static IncrementalValuesProvider<T> ForAttributeWithMetadataNameAndOptions<T>(
22+
this IncrementalGeneratorInitializationContext context,
23+
string fullyQualifiedMetadataName,
24+
Func<SyntaxNode, CancellationToken, bool> predicate,
25+
Func<GeneratorAttributeSyntaxContextWithOptions, CancellationToken, T> transform)
26+
{
27+
// Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
28+
IncrementalValuesProvider<GeneratorAttributeSyntaxContext> syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName(
29+
fullyQualifiedMetadataName,
30+
predicate,
31+
static (context, token) => context);
32+
33+
// Do the same for the analyzer config options
34+
IncrementalValueProvider<AnalyzerConfigOptions> configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions);
35+
36+
// Merge the two and invoke the provided transform on these two values. Neither value
37+
// is equatable, meaning the pipeline will always re-run until this point. This is
38+
// intentional: we don't want any symbols or other expensive objects to be kept alive
39+
// across incremental steps, especially if they could cause entire compilations to be
40+
// rooted, which would significantly increase memory use and introduce more GC pauses.
41+
// In this specific case, flowing non equatable values in a pipeline is therefore fine.
42+
return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorAttributeSyntaxContextWithOptions(input.Left, input.Right), token));
43+
}
44+
1645
/// <summary>
1746
/// Conditionally invokes <see cref="IncrementalGeneratorInitializationContext.RegisterSourceOutput{TSource}(IncrementalValueProvider{TSource}, Action{SourceProductionContext, TSource})"/>
1847
/// if the value produced by the input <see cref="IncrementalValueProvider{TValue}"/> is <see langword="true"/>.

src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.FeatureSwitches.targets

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
<MvvmToolkitEnableINotifyPropertyChangingSupport Condition="'$(MvvmToolkitEnableINotifyPropertyChangingSupport)' == ''">true</MvvmToolkitEnableINotifyPropertyChangingSupport>
66
</PropertyGroup>
77

8+
<!--
9+
Make the properties visible to the source generators as well, to allow
10+
them to emit optimized codegen ahead of time depending on their values.
11+
-->
12+
<ItemGroup>
13+
<CompilerVisibleProperty Include="MvvmToolkitEnableINotifyPropertyChangingSupport" />
14+
</ItemGroup>
15+
816
<!--
917
Configuration for the feature switches (to support IL trimming).
1018
See the 'ILLink.Substitutions.xml' file for more details on that.

0 commit comments

Comments
 (0)