Skip to content

Commit

Permalink
Merge pull request CommunityToolkit#435 from CommunityToolkit/dev/no-…
Browse files Browse the repository at this point in the history
…symbols-ivp

Don't use ISymbol in IncrementalValuesProvider<T> nodes
  • Loading branch information
Sergio0694 authored Sep 10, 2022
2 parents 5876242 + b6dd39b commit b3b60f4
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 215 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 @@ -24,38 +24,33 @@ public sealed partial class ObservablePropertyGenerator : IIncrementalGenerator
/// <inheritdoc/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Get all field declarations with at least one attribute
IncrementalValuesProvider<IFieldSymbol> fieldSymbols =
// Gather info for all annotated fields
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result<PropertyInfo?> Info)> propertyInfoWithErrors =
context.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 },
static (context, _) =>
static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } },
static (context, token) =>
{
if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8))
{
return default;
}

return ((FieldDeclarationSyntax)context.Node).Declaration.Variables.Select(v => (IFieldSymbol)context.SemanticModel.GetDeclaredSymbol(v)!);
})
.Where(static items => items is not null)
.SelectMany(static (item, _) => item!)!;
IFieldSymbol fieldSymbol = (IFieldSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, token)!;

// Filter the fields using [ObservableProperty]
IncrementalValuesProvider<IFieldSymbol> fieldSymbolsWithAttribute =
fieldSymbols
.Where(static item => item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"));
// Filter the fields using [ObservableProperty]
if (!fieldSymbol.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute"))
{
return default;
}

// Gather info for all annotated fields
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result<PropertyInfo?> Info)> propertyInfoWithErrors =
fieldSymbolsWithAttribute
.Select(static (item, _) =>
{
HierarchyInfo hierarchy = HierarchyInfo.From(item.ContainingType);
PropertyInfo? propertyInfo = Execute.TryGetInfo(item, out ImmutableArray<Diagnostic> diagnostics);
// Produce the incremental models
HierarchyInfo hierarchy = HierarchyInfo.From(fieldSymbol.ContainingType);
PropertyInfo? propertyInfo = Execute.TryGetInfo(fieldSymbol, out ImmutableArray<Diagnostic> diagnostics);

return (hierarchy, new Result<PropertyInfo?>(propertyInfo, diagnostics));
});
return (Hierarchy: hierarchy, new Result<PropertyInfo?>(propertyInfo, diagnostics));
})
.Where(static item => item.Hierarchy is not null);

// Output the diagnostics
context.ReportDiagnostics(propertyInfoWithErrors.Select(static (item, _) => item.Info.Errors));
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 @@ -21,31 +21,43 @@ public sealed partial class ObservableValidatorValidateAllPropertiesGenerator :
/// <inheritdoc/>
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Get all class declarations. We intentionally skip generating code for abstract types, as that would never be used.
// The methods that are generated by this generator are retrieved through reflection using the type of the invoking
// instance as discriminator, which means a type that is abstract could never be used (since it couldn't be instantiated).
IncrementalValuesProvider<INamedTypeSymbol> typeSymbols =
// Get the types that inherit from ObservableValidator and gather their info
IncrementalValuesProvider<ValidationInfo> validationInfo =
context.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is ClassDeclarationSyntax,
static (context, _) =>
static (node, _) => node is ClassDeclarationSyntax classDeclaration && classDeclaration.HasOrPotentiallyHasBaseTypes(),
static (context, token) =>
{
if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8))
{
return default;
}

return (context.Node, Symbol: (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
})
.Where(static item => item.Symbol is { IsAbstract: false, IsGenericType: false } && item.Node.IsFirstSyntaxDeclarationForSymbol(item.Symbol))
.Select(static (item, _) => item.Symbol);
INamedTypeSymbol typeSymbol = (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, token)!;

// Get the types that inherit from ObservableValidator and gather their info
IncrementalValuesProvider<ValidationInfo> validationInfo =
typeSymbols
.Where(Execute.IsObservableValidator)
.Select(static (item, _) => Execute.GetInfo(item))
.WithComparer(ValidationInfo.Comparer.Default);
// Skip generating code for abstract types, as that would never be used. The methods that are generated by
// this generator are retrieved through reflection using the type of the invoking instance as discriminator,
// which means a type that is abstract could never be used (since it couldn't be instantiated).
if (typeSymbol is not { IsAbstract: false, IsGenericType: false })
{
return default;
}

// Just like in IMessengerRegisterAllGenerator, only select the first declaration for this type symbol
if (!context.Node.IsFirstSyntaxDeclarationForSymbol(typeSymbol))
{
return default;
}

// Only select types inheriting from ObservableValidator
if (!Execute.IsObservableValidator(typeSymbol))
{
return default;
}

return Execute.GetInfo(typeSymbol);
})
.Where(static item => item is not null)!;

// Check whether the header file is needed
IncrementalValueProvider<bool> isHeaderFileNeeded =
Expand Down
Loading

0 comments on commit b3b60f4

Please sign in to comment.