Skip to content

Commit

Permalink
Don't propagate symbols in TransitiveMembersGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed Sep 10, 2022
1 parent 5f90862 commit 27cd6b4
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,18 @@ public INotifyPropertyChangedGenerator()
}

/// <inheritdoc/>
protected override IncrementalValuesProvider<(INamedTypeSymbol Symbol, INotifyPropertyChangedInfo Info)> GetInfo(
IncrementalGeneratorInitializationContext context,
IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source)
{
static INotifyPropertyChangedInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
{
bool includeAdditionalHelperMethods = attributeData.GetNamedArgument("IncludeAdditionalHelperMethods", true);

return new(includeAdditionalHelperMethods);
}

return source.Select(static (item, _) => (item.Symbol, GetInfo(item.Symbol, item.AttributeData)));
}

/// <inheritdoc/>
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, INotifyPropertyChangedInfo info, out ImmutableArray<Diagnostic> diagnostics)
protected override INotifyPropertyChangedInfo? ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray<Diagnostic> diagnostics)
{
ImmutableArray<Diagnostic>.Builder builder = ImmutableArray.CreateBuilder<Diagnostic>();

INotifyPropertyChangedInfo? info = null;

// Check if the type already implements INotifyPropertyChanged
if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanged")))
{
builder.Add(DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

// Check if the type uses [INotifyPropertyChanged] or [ObservableObject] already (in the type hierarchy too)
Expand All @@ -63,14 +48,17 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, INotifyP
{
builder.Add(InvalidAttributeCombinationForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

bool includeAdditionalHelperMethods = attributeData.GetNamedArgument("IncludeAdditionalHelperMethods", true);

info = new INotifyPropertyChangedInfo(includeAdditionalHelperMethods);

End:
diagnostics = builder.ToImmutable();

return true;
return info;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,7 @@ public ObservableObjectGenerator()
}

/// <inheritdoc/>
protected override IncrementalValuesProvider<(INamedTypeSymbol Symbol, object? Info)> GetInfo(
IncrementalGeneratorInitializationContext context,
IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source)
{
return source.Select(static (item, _) => (item.Symbol, (object?)null));
}

/// <inheritdoc/>
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, object? info, out ImmutableArray<Diagnostic> diagnostics)
protected override object? ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray<Diagnostic> diagnostics)
{
ImmutableArray<Diagnostic>.Builder builder = ImmutableArray.CreateBuilder<Diagnostic>();

Expand All @@ -44,19 +36,15 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, object?
{
builder.Add(DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

// ...or INotifyPropertyChanging
if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanging")))
{
builder.Add(DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

// Check if the type uses [INotifyPropertyChanged] or [ObservableObject] already (in the type hierarchy too)
Expand All @@ -65,14 +53,13 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, object?
{
builder.Add(InvalidAttributeCombinationForObservableObjectAttributeError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

End:
diagnostics = builder.ToImmutable();

return true;
return null;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,63 +30,26 @@ public ObservableRecipientGenerator()
}

/// <inheritdoc/>
protected override IncrementalValuesProvider<(INamedTypeSymbol Symbol, ObservableRecipientInfo Info)> GetInfo(
IncrementalGeneratorInitializationContext context,
IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source)
{
static ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, bool isRequiresUnreferencedCodeAttributeAvailable)
{
string typeName = typeSymbol.Name;
bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true });
bool isAbstract = typeSymbol.IsAbstract;
bool isObservableValidator = typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator");
bool hasOnActivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnActivated" });
bool hasOnDeactivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnDeactivated" });

return new(
typeName,
hasExplicitConstructors,
isAbstract,
isObservableValidator,
isRequiresUnreferencedCodeAttributeAvailable,
hasOnActivatedMethod,
hasOnDeactivatedMethod);
}

// Check whether [RequiresUnreferencedCode] is available
IncrementalValueProvider<bool> isRequiresUnreferencedCodeAttributeAvailable =
context.CompilationProvider
.Select(static (item, _) => item.GetTypeByMetadataName("System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute") is { DeclaredAccessibility: Accessibility.Public });

return
source
.Combine(isRequiresUnreferencedCodeAttributeAvailable)
.Select(static (item, _) => (item.Left.Symbol, GetInfo(item.Left.Symbol, item.Left.AttributeData, item.Right)));
}

/// <inheritdoc/>
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, ObservableRecipientInfo info, out ImmutableArray<Diagnostic> diagnostics)
protected override ObservableRecipientInfo? ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray<Diagnostic> diagnostics)
{
ImmutableArray<Diagnostic>.Builder builder = ImmutableArray.CreateBuilder<Diagnostic>();

ObservableRecipientInfo? info = null;

// Check if the type already inherits from ObservableRecipient
if (typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient"))
{
builder.Add(DuplicateObservableRecipientError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

// Check if the type already inherits [ObservableRecipient]
if (typeSymbol.InheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute"))
{
builder.Add(InvalidAttributeCombinationForObservableRecipientAttributeError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

// In order to use [ObservableRecipient], the target type needs to inherit from ObservableObject,
Expand All @@ -99,14 +62,31 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, Observab
{
builder.Add(MissingBaseObservableObjectFunctionalityError, typeSymbol, typeSymbol);

diagnostics = builder.ToImmutable();

return false;
goto End;
}

// Gather all necessary info to propagate down the pipeline
string typeName = typeSymbol.Name;
bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true });
bool isAbstract = typeSymbol.IsAbstract;
bool isObservableValidator = typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator");
bool isRequiresUnreferencedCodeAttributeAvailable = compilation.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute");
bool hasOnActivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnActivated" });
bool hasOnDeactivatedMethod = typeSymbol.GetMembers().Any(m => m is IMethodSymbol { Parameters.IsEmpty: true, Name: "OnDeactivated" });

info = new ObservableRecipientInfo(
typeName,
hasExplicitConstructors,
isAbstract,
isObservableValidator,
isRequiresUnreferencedCodeAttributeAvailable,
hasOnActivatedMethod,
hasOnDeactivatedMethod);

End:
diagnostics = builder.ToImmutable();

return true;
return info;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;

namespace CommunityToolkit.Mvvm.SourceGenerators;

Expand Down Expand Up @@ -70,46 +69,40 @@ private protected TransitiveMembersGenerator(string attributeType, IEqualityComp
/// <inheritdoc/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Get all class declarations
IncrementalValuesProvider<INamedTypeSymbol> typeSymbols =
// Gather all generation info, and any diagnostics
IncrementalValuesProvider<Result<(HierarchyInfo Hierarchy, bool IsSealed, TInfo? Info)>> generationInfoWithErrors =
context.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 },
static (context, _) =>
(context, token) =>
{
if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8))
{
return default;
}

return (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!;
})
.Where(static item => item is not null)!;
INamedTypeSymbol typeSymbol = (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, token)!;

// Filter the types with the target attribute
IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> typeSymbolsWithAttributeData =
typeSymbols
.Select((item, _) => (
Symbol: item,
Attribute: item.GetAttributes().FirstOrDefault(a => a.AttributeClass?.HasFullyQualifiedName(this.attributeType) == true)))
.Where(static item => item.Attribute is not null)!;
// Filter the types with the target attribute
if (!typeSymbol.TryGetAttributeWithFullyQualifiedName(this.attributeType, out AttributeData? attributeData))
{
return default;
}

// Transform the input data
IncrementalValuesProvider<(INamedTypeSymbol Symbol, TInfo Info)> typeSymbolsWithInfo = GetInfo(context, typeSymbolsWithAttributeData);
// Gather all generation info, and any diagnostics
TInfo? info = ValidateTargetTypeAndGetInfo(typeSymbol, attributeData, context.SemanticModel.Compilation, out ImmutableArray<Diagnostic> diagnostics);

// Gather all generation info, and any diagnostics
IncrementalValuesProvider<Result<(HierarchyInfo Hierarchy, bool IsSealed, TInfo Info)>> generationInfoWithErrors =
typeSymbolsWithInfo.Select((item, _) =>
{
if (ValidateTargetType(item.Symbol, item.Info, out ImmutableArray<Diagnostic> diagnostics))
{
return new Result<(HierarchyInfo, bool, TInfo)>(
(HierarchyInfo.From(item.Symbol), item.Symbol.IsSealed, item.Info),
ImmutableArray<Diagnostic>.Empty);
}
// If there are any diagnostics, there's no need to compute the hierarchy info at all, just return them
if (diagnostics.Length > 0)
{
return new Result<(HierarchyInfo, bool, TInfo?)>(default, diagnostics);
}

HierarchyInfo hierarchy = HierarchyInfo.From(typeSymbol);

return new Result<(HierarchyInfo, bool, TInfo)>(default, diagnostics);
});
return new Result<(HierarchyInfo, bool, TInfo?)>((hierarchy, typeSymbol.IsSealed, info), diagnostics);
})
.Where(static item => item is not null)!;

// Emit the diagnostic, if needed
context.ReportDiagnostics(generationInfoWithErrors.Select(static (item, _) => item.Errors));
Expand All @@ -118,7 +111,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
IncrementalValuesProvider<(HierarchyInfo Hierarchy, bool IsSealed, TInfo Info)> generationInfo =
generationInfoWithErrors
.Where(static item => item.Errors.IsEmpty)
.Select(static (item, _) => item.Value)
.Select(static (item, _) => item.Value)!
.WithComparers(HierarchyInfo.Comparer.Default, EqualityComparer<bool>.Default, this.comparer);

// Generate the required members
Expand All @@ -133,23 +126,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}

/// <summary>
/// Gathers info from a source <see cref="IncrementalValuesProvider{TValues}"/> input.
/// Validates the target type being processes, gets the info if possible and produces all necessary diagnostics.
/// </summary>
/// <param name="context">The <see cref="IncrementalGeneratorInitializationContext"/> instance in use.</param>
/// <param name="source">The source <see cref="IncrementalValuesProvider{TValues}"/> input.</param>
/// <returns>A transformed <see cref="IncrementalValuesProvider{TValues}"/> instance with the gathered data.</returns>
protected abstract IncrementalValuesProvider<(INamedTypeSymbol Symbol, TInfo Info)> GetInfo(
IncrementalGeneratorInitializationContext context,
IncrementalValuesProvider<(INamedTypeSymbol Symbol, AttributeData AttributeData)> source);

/// <summary>
/// Validates a target type being processed.
/// </summary>
/// <param name="typeSymbol">The <see cref="INamedTypeSymbol"/> instance for the target type.</param>
/// <param name="info">The <typeparamref name="TInfo"/> instance with the current processing info.</param>
/// <param name="diagnostics">The resulting diagnostics from the processing operation.</param>
/// <returns>Whether or not the target type is valid and can be processed normally.</returns>
protected abstract bool ValidateTargetType(INamedTypeSymbol typeSymbol, TInfo info, out ImmutableArray<Diagnostic> diagnostics);
/// <param name="typeSymbol">The <see cref="INamedTypeSymbol"/> instance currently being processed.</param>
/// <param name="attributeData">The <see cref="AttributeData"/> instance for the attribute used over <paramref name="typeSymbol"/>.</param>
/// <param name="compilation">The compilation that <paramref name="typeSymbol"/> belongs to.</param>
/// <param name="diagnostics">The resulting diagnostics, if any.</param>
/// <returns>The extracted info for the current type, if possible.</returns>
/// <remarks>If <paramref name="diagnostics"/> is empty, the returned info will always be ignored and no sources will be produced.</remarks>
protected abstract TInfo? ValidateTargetTypeAndGetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData, Compilation compilation, out ImmutableArray<Diagnostic> diagnostics);

/// <summary>
/// Filters the <see cref="MemberDeclarationSyntax"/> nodes to generate from the input parsed tree.
Expand Down

0 comments on commit 27cd6b4

Please sign in to comment.