From f16ba7ae08e57de84208a08cff7e932333654507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Thu, 5 Oct 2023 22:26:16 +0200 Subject: [PATCH 01/12] Add first draft of real incremental generator --- global.json | 2 +- .../AutoConstructor.Generator.csproj | 5 +- .../AutoConstructorGenerator.cs | 131 ++--- .../CodeGenerator.cs | 39 +- .../EquatableArray`1.cs | 185 +++++++ src/AutoConstructor.Generator/FieldInfo.cs | 12 +- src/AutoConstructor.Generator/HashCode.cs | 500 ++++++++++++++++++ .../IsExternalInit.cs | 17 + .../NamedTypeSymbolInfo.cs | 37 ++ src/AutoConstructor.Generator/Options.cs | 3 + .../AutoConstructor.Tests.csproj | 2 +- 11 files changed, 847 insertions(+), 86 deletions(-) create mode 100644 src/AutoConstructor.Generator/EquatableArray`1.cs create mode 100644 src/AutoConstructor.Generator/HashCode.cs create mode 100644 src/AutoConstructor.Generator/IsExternalInit.cs create mode 100644 src/AutoConstructor.Generator/NamedTypeSymbolInfo.cs create mode 100644 src/AutoConstructor.Generator/Options.cs diff --git a/global.json b/global.json index 9c9b66d..30e2afc 100644 --- a/global.json +++ b/global.json @@ -4,4 +4,4 @@ "rollForward": "latestMajor", "allowPrerelease": false } -} +} \ No newline at end of file diff --git a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj index f298471..1f7f302 100644 --- a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj +++ b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj @@ -28,6 +28,7 @@ git https://github.com/k94ll13nn3/AutoConstructor https://github.com/k94ll13nn3/AutoConstructor/blob/main/CHANGELOG.md + true @@ -37,9 +38,9 @@ - + - + diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index 42f5328..4ce4610 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -13,6 +13,8 @@ namespace AutoConstructor.Generator; [Generator] public class AutoConstructorGenerator : IIncrementalGenerator { + private static int _counter; + public void Initialize(IncrementalGeneratorInitializationContext context) { // Register the attribute source @@ -23,17 +25,26 @@ public void Initialize(IncrementalGeneratorInitializationContext context) i.AddSource(Source.InjectAttributeFullName, SourceText.From(Source.InjectAttributeText, Encoding.UTF8)); }); - IncrementalValuesProvider classDeclarations = context.SyntaxProvider - .CreateSyntaxProvider(static (s, _) => IsSyntaxTargetForGeneration(s), static (ctx, _) => GetSemanticTargetForGeneration(ctx)) - .Where(static m => m is not null)!; - - IncrementalValueProvider<(Compilation compilation, ImmutableArray classes, AnalyzerConfigOptions options)> valueProvider = - context.CompilationProvider - .Combine(classDeclarations.Collect()) + IncrementalValuesProvider<(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options)> valueProvider = context.SyntaxProvider + .ForAttributeWithMetadataName( + Source.AttributeFullName, + static (node, _) => IsSyntaxTargetForGeneration(node), + static (context, _) => (ClassDeclarationSyntax)context.TargetNode) + .Where(static m => m is not null) + .Collect() .Combine(context.AnalyzerConfigOptionsProvider.Select((c, _) => c.GlobalOptions)) - .Select((c, _) => (compilation: c.Left.Left, classes: c.Left.Right, options: c.Right)); + .Combine(context.CompilationProvider) + .SelectMany((c, _) => + { + (ImmutableArray classes, AnalyzerConfigOptions options, Compilation compilation) = (c.Left.Left, c.Left.Right, c.Right); + return Execute(compilation, classes, new(), options); + }); - context.RegisterSourceOutput(valueProvider, static (spc, source) => Execute(source.compilation, source.classes, spc, source.options)); + context.RegisterSourceOutput(valueProvider, static (context, item) => + { + CompilationUnitSyntax compilationUnit = GenerateAutoConstructor(item.symbol, item.fields, item.options); + context.AddSource($"{item.symbol.Filename}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + }); } private static bool IsSyntaxTargetForGeneration(SyntaxNode node) @@ -41,20 +52,11 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); } - private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context) - { - var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; - - INamedTypeSymbol? symbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); - - return symbol?.HasAttribute(Source.AttributeFullName, context.SemanticModel.Compilation) is true ? classDeclarationSyntax : null; - } - - private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context, AnalyzerConfigOptions options) + private static IEnumerable<(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options)> Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context, AnalyzerConfigOptions analyzerOptions) { if (classes.IsDefaultOrEmpty) { - return; + yield break; } IEnumerable> classesBySymbol = Enumerable.Empty>(); @@ -64,9 +66,25 @@ private static void Execute(Compilation compilation, ImmutableArray groupedClasses in classesBySymbol) { if (context.CancellationToken.IsCancellationRequested) @@ -98,27 +116,21 @@ private static void Execute(Compilation compilation, ImmutableArray concatenatedFields = GetFieldsFromSymbol(compilation, symbol, emitNullChecks); ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); - FieldInfo[] fields = concatenatedFields.ToArray(); + EquatableArray fields = concatenatedFields.ToImmutableArray(); - if (fields.Length == 0) + if (fields.IsEmpty) { // No need to report diagnostic, taken care by the analyzers. continue; } if (fields.GroupBy(x => x.ParameterName).Any(g => - g.Where(c => c.Type is not null).Select(c => c.Type).Distinct(SymbolEqualityComparer.Default).Count() > 1 - || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Distinct(SymbolEqualityComparer.Default).Count() > 1) + g.Where(c => c.Type is not null).Select(c => c.Type).Count() > 1 + || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Count() > 1) )) { foreach (ClassDeclarationSyntax classDeclaration in groupedClasses) @@ -137,28 +149,23 @@ private static void Execute(Compilation compilation, ImmutableArray !d.IsStatic && d.Parameters.Length == 0); - context.AddSource(filename, SourceText.From(GenerateAutoConstructor(symbol, fields, options, hasParameterlessConstructor), Encoding.UTF8)); + yield return (new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options); } } } - private static string GenerateAutoConstructor(INamedTypeSymbol symbol, FieldInfo[] fields, AnalyzerConfigOptions options, bool hasParameterlessConstructor) + private static CompilationUnitSyntax GenerateAutoConstructor(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options) { - bool generateConstructorDocumentation = false; - if (options.TryGetValue("build_property.AutoConstructor_GenerateConstructorDocumentation", out string? generateConstructorDocumentationSwitch)) - { - generateConstructorDocumentation = generateConstructorDocumentationSwitch.Equals("true", StringComparison.OrdinalIgnoreCase); - } - - options.TryGetValue("build_property.AutoConstructor_ConstructorDocumentationComment", out string? constructorDocumentationComment); + bool generateConstructorDocumentation = options.GenerateConstructorDocumentation; + string? constructorDocumentationComment = options.ConstructorDocumentationComment; if (string.IsNullOrWhiteSpace(constructorDocumentationComment)) { - constructorDocumentationComment = "Initializes a new instance of the {0} class."; + constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // Counter: {Interlocked.Increment(ref _counter)}"; } var codeGenerator = new CodeGenerator(); - if (Array.Exists(fields, f => f.Nullable)) + if (fields.Any(f => f.Nullable)) { codeGenerator.AddNullableAnnotation(); } @@ -168,21 +175,21 @@ private static string GenerateAutoConstructor(INamedTypeSymbol symbol, FieldInfo codeGenerator.AddDocumentation(string.Format(CultureInfo.InvariantCulture, constructorDocumentationComment, symbol.Name)); } - if (!symbol.ContainingNamespace.IsGlobalNamespace) + if (!symbol.IsGlobalNamespace) { codeGenerator.AddNamespace(symbol.ContainingNamespace); } - foreach (INamedTypeSymbol containingType in symbol.GetContainingTypes()) + foreach (NamedTypeSymbolInfo containingType in symbol.ContainingTypes) { codeGenerator.AddClass(containingType); } codeGenerator .AddClass(symbol) - .AddConstructor(fields, hasParameterlessConstructor); + .AddConstructor(fields, symbol.HasParameterlessConstructor); - return codeGenerator.ToString(); + return codeGenerator.GetCompilationUnit(); } private static List GetFieldsFromSymbol(Compilation compilation, INamedTypeSymbol symbol, bool emitNullChecks) @@ -243,28 +250,17 @@ private static FieldInfo GetFieldInfo(IFieldSymbol fieldSymbol, Compilation comp } return new FieldInfo( - injectedType, + injectedType?.ToDisplayString(), parameterName, fieldSymbol.AssociatedSymbol?.Name ?? fieldSymbol.Name, initializer, - type, + type.ToDisplayString(), IsNullable(type), summaryText, type.IsReferenceType && type.NullableAnnotation != NullableAnnotation.Annotated && emitNullChecks, FieldType.Initialized); } - private static bool IsNullable(ITypeSymbol typeSymbol) - { - bool isNullable = typeSymbol.IsReferenceType && typeSymbol.NullableAnnotation == NullableAnnotation.Annotated; - if (typeSymbol is INamedTypeSymbol namedSymbol) - { - isNullable |= namedSymbol.TypeArguments.Any(IsNullable); - } - - return isNullable; - } - private static T? GetParameterValue(string parameterName, ImmutableArray parameters, ImmutableArray arguments) where T : class { @@ -304,11 +300,11 @@ private static void ExtractFieldsFromConstructedParent(List concatena else { concatenatedFields.Add(new FieldInfo( - parameter.Type, + parameter.Type.ToDisplayString(), parameter.Name, string.Empty, string.Empty, - parameter.Type, + parameter.Type.ToDisplayString(), IsNullable(parameter.Type), null, false, @@ -334,7 +330,7 @@ private static void ExtractFieldsFromGeneratedParent(Compilation compilation, bo string.Empty, string.Empty, parameter.FallbackType, - IsNullable(parameter.FallbackType), + false,//IsNullable(parameter.FallbackType), null, false, FieldType.PassedToBase)); @@ -343,4 +339,15 @@ private static void ExtractFieldsFromGeneratedParent(Compilation compilation, bo ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); } + + private static bool IsNullable(ITypeSymbol typeSymbol) + { + bool isNullable = typeSymbol.IsReferenceType && typeSymbol.NullableAnnotation == NullableAnnotation.Annotated; + if (typeSymbol is INamedTypeSymbol namedSymbol) + { + isNullable |= namedSymbol.TypeArguments.Any(IsNullable); + } + + return isNullable; + } } diff --git a/src/AutoConstructor.Generator/CodeGenerator.cs b/src/AutoConstructor.Generator/CodeGenerator.cs index 910179e..f07ca3f 100644 --- a/src/AutoConstructor.Generator/CodeGenerator.cs +++ b/src/AutoConstructor.Generator/CodeGenerator.cs @@ -33,22 +33,22 @@ public CodeGenerator AddDocumentation(string? constructorDocumentationComment) return this; } - public CodeGenerator AddNamespace(INamespaceSymbol namespaceSymbol) + public CodeGenerator AddNamespace(string namespaceSymbolDisplayString) { if (_current is not null) { throw new InvalidOperationException($"Method {nameof(AddNamespace)} must be called first."); } - _current = GetNamespace(namespaceSymbol.ToDisplayString(), _addNullableAnnotation); + _current = GetNamespace(namespaceSymbolDisplayString, _addNullableAnnotation); return this; } - public CodeGenerator AddClass(INamedTypeSymbol classSymbol) + public CodeGenerator AddClass(NamedTypeSymbolInfo classSymbol) { string identifier = classSymbol.Name; bool isStatic = classSymbol.IsStatic; - ITypeParameterSymbol[] typeParameterList = classSymbol.TypeParameters.ToArray(); + EquatableArray typeParameterList = classSymbol.TypeParameters; ClassDeclarationSyntax classSyntax = GetClass( identifier, @@ -83,7 +83,7 @@ public CodeGenerator AddClass(INamedTypeSymbol classSymbol) return this; } - public CodeGenerator AddConstructor(FieldInfo[] parameters, bool symbolHasParameterlessConstructor) + public CodeGenerator AddConstructor(EquatableArray parameters, bool symbolHasParameterlessConstructor) { if (_current is ClassDeclarationSyntax classDeclarationSyntax) { @@ -112,6 +112,19 @@ public CodeGenerator AddConstructor(FieldInfo[] parameters, bool symbolHasParame return this; } + public CompilationUnitSyntax GetCompilationUnit() + { + if (_current is null) + { + throw new InvalidOperationException("No class was added to the generator."); + } + + return CompilationUnit() + .AddMembers(_current) + .NormalizeWhitespace() + .WithTrailingTrivia(CarriageReturnLineFeed); + } + public override string ToString() { if (_current is null) @@ -152,7 +165,7 @@ private static BaseNamespaceDeclarationSyntax GetNamespace(string identifier, bo .WithNamespaceKeyword(Token(GetHeaderTrivia(addNullableAnnotation), SyntaxKind.NamespaceKeyword, TriviaList())); } - private static ClassDeclarationSyntax GetClass(string identifier, bool addHeaderTrivia, bool addNullableAnnotation, bool isStatic, ITypeParameterSymbol[] typeParameterList) + private static ClassDeclarationSyntax GetClass(string identifier, bool addHeaderTrivia, bool addNullableAnnotation, bool isStatic, EquatableArray typeParameterList) { SyntaxToken firstModifier = Token(isStatic ? SyntaxKind.StaticKeyword : SyntaxKind.PartialKeyword); if (addHeaderTrivia) @@ -166,15 +179,15 @@ private static ClassDeclarationSyntax GetClass(string identifier, bool addHeader declaration = declaration.AddModifiers(Token(SyntaxKind.PartialKeyword)); } - if (typeParameterList.Length > 0) + if (!typeParameterList.IsEmpty) { - declaration = declaration.AddTypeParameterListParameters(Array.ConvertAll(typeParameterList, GetTypeParameter)); + declaration = declaration.AddTypeParameterListParameters(typeParameterList.Select(GetTypeParameter).ToArray()); } return declaration; } - private static ConstructorDeclarationSyntax GetConstructor(SyntaxToken identifier, FieldInfo[] parameters, string? constructorDocumentationComment, bool generateThisInitializer) + private static ConstructorDeclarationSyntax GetConstructor(SyntaxToken identifier, EquatableArray parameters, string? constructorDocumentationComment, bool generateThisInitializer) { FieldInfo[] constructorParameters = parameters .GroupBy(x => x.ParameterName) @@ -228,15 +241,15 @@ private static DocumentationCommentTriviaSyntax GetDocumentation(string construc private static ParameterSyntax GetParameter(FieldInfo parameter) { - ITypeSymbol parameterType = parameter.Type ?? parameter.FallbackType; + string parameterType = parameter.Type ?? parameter.FallbackType; return Parameter(Identifier(parameter.ParameterName)) - .WithType(ParseTypeName(parameterType.ToDisplayString())); + .WithType(ParseTypeName(parameterType)); } - private static TypeParameterSyntax GetTypeParameter(ITypeParameterSymbol identifier) + private static TypeParameterSyntax GetTypeParameter(string identifierName) { - return TypeParameter(Identifier(identifier.Name)); + return TypeParameter(Identifier(identifierName)); } private static ArgumentSyntax GetArgument(FieldInfo parameter) diff --git a/src/AutoConstructor.Generator/EquatableArray`1.cs b/src/AutoConstructor.Generator/EquatableArray`1.cs new file mode 100644 index 0000000..7eb9ea4 --- /dev/null +++ b/src/AutoConstructor.Generator/EquatableArray`1.cs @@ -0,0 +1,185 @@ +// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System.Collections; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; + +#nullable enable + +namespace AutoConstructor.Generator; + +/// +/// An immutable, equatable array. This is equivalent to but with value equality support. +/// Temporary until this is available in the framework in one form or another. +/// +/// +/// +/// +internal readonly struct EquatableArray : IEquatable>, IEnumerable + where T : IEquatable +{ + /// + /// The underlying array. + /// + private readonly T[]? array; + + /// + /// Creates a new instance. + /// + /// The input to wrap. + public EquatableArray(ImmutableArray array) + { + this.array = Unsafe.As, T[]?>(ref array); + } + + /// + /// Gets a reference to an item at a specified position within the array. + /// + /// The index of the item to retrieve a reference to. + /// A reference to an item at a specified position within the array. + public ref readonly T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref AsImmutableArray().ItemRef(index); + } + + /// + /// Gets a value indicating whether the current array is empty. + /// + public bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => AsImmutableArray().IsEmpty; + } + + /// + public bool Equals(EquatableArray array) + { + return AsSpan().SequenceEqual(array.AsSpan()); + } + + /// + public override bool Equals(object? obj) + { + return obj is EquatableArray array && Equals(this, array); + } + + /// + public override int GetHashCode() + { + if (this.array is not T[] array) + { + return 0; + } + + HashCode hashCode = default; + + foreach (T item in array) + { + hashCode.Add(item); + } + + return hashCode.ToHashCode(); + } + + /// + /// Gets an instance from the current . + /// + /// The from the current . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ImmutableArray AsImmutableArray() + { + return Unsafe.As>(ref Unsafe.AsRef(in this.array)); + } + + /// + /// Creates an instance from a given . + /// + /// The input instance. + /// An instance from a given . + public static EquatableArray FromImmutableArray(ImmutableArray array) + { + return new(array); + } + + /// + /// Returns a wrapping the current items. + /// + /// A wrapping the current items. + public ReadOnlySpan AsSpan() + { + return AsImmutableArray().AsSpan(); + } + + /// + /// Copies the contents of this instance to a mutable array. + /// + /// The newly instantiated array. + public T[] ToArray() + { + return AsImmutableArray().ToArray(); + } + + /// + /// Gets an value to traverse items in the current array. + /// + /// An value to traverse items in the current array. + public ImmutableArray.Enumerator GetEnumerator() + { + return AsImmutableArray().GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)AsImmutableArray()).GetEnumerator(); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator EquatableArray(ImmutableArray array) + { + return FromImmutableArray(array); + } + + /// + /// Implicitly converts an to . + /// + /// An instance from a given . + public static implicit operator ImmutableArray(EquatableArray array) + { + return array.AsImmutableArray(); + } + + /// + /// Checks whether two values are the same. + /// + /// The first value. + /// The second value. + /// Whether and are equal. + public static bool operator ==(EquatableArray left, EquatableArray right) + { + return left.Equals(right); + } + + /// + /// Checks whether two values are not the same. + /// + /// The first value. + /// The second value. + /// Whether and are not equal. + public static bool operator !=(EquatableArray left, EquatableArray right) + { + return !left.Equals(right); + } +} diff --git a/src/AutoConstructor.Generator/FieldInfo.cs b/src/AutoConstructor.Generator/FieldInfo.cs index f86aed2..72d1b21 100644 --- a/src/AutoConstructor.Generator/FieldInfo.cs +++ b/src/AutoConstructor.Generator/FieldInfo.cs @@ -1,15 +1,13 @@ -using Microsoft.CodeAnalysis; - namespace AutoConstructor.Generator; -public class FieldInfo +public record FieldInfo { public FieldInfo( - ITypeSymbol? type, + string? type, string parameterName, string fieldName, string initializer, - ITypeSymbol fallbackType, + string fallbackType, bool nullable, string? comment, bool emitArgumentNullException, @@ -26,7 +24,7 @@ public FieldInfo( FieldType = fieldType; } - public ITypeSymbol? Type { get; } + public string? Type { get; } public string ParameterName { get; } @@ -34,7 +32,7 @@ public FieldInfo( public string Initializer { get; } - public ITypeSymbol FallbackType { get; } + public string FallbackType { get; } public bool Nullable { get; } diff --git a/src/AutoConstructor.Generator/HashCode.cs b/src/AutoConstructor.Generator/HashCode.cs new file mode 100644 index 0000000..06f9afc --- /dev/null +++ b/src/AutoConstructor.Generator/HashCode.cs @@ -0,0 +1,500 @@ +// +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +#pragma warning disable CS0809, IDE0009, IDE1006, IDE0048 +#nullable enable + +namespace AutoConstructor.Generator; + +/// +/// A polyfill type that mirrors some methods from on .NET 6. +/// +internal struct HashCode +{ + private const uint Prime1 = 2654435761U; + private const uint Prime2 = 2246822519U; + private const uint Prime3 = 3266489917U; + private const uint Prime4 = 668265263U; + private const uint Prime5 = 374761393U; + + private static readonly uint seed = GenerateGlobalSeed(); + + private uint v1, v2, v3, v4; + private uint queue1, queue2, queue3; + private uint length; + + /// + /// Initializes the default seed. + /// + /// A random seed. + private static unsafe uint GenerateGlobalSeed() + { + byte[] bytes = new byte[4]; + + RandomNumberGenerator.Create().GetBytes(bytes); + + return BitConverter.ToUInt32(bytes, 0); + } + + /// + /// Combines a value into a hash code. + /// + /// The type of the value to combine into the hash code. + /// The value to combine into the hash code. + /// The hash code that represents the value. + public static int Combine(T1 value) + { + uint hc1 = (uint)(value?.GetHashCode() ?? 0); + uint hash = MixEmptyState(); + + hash += 4; + hash = QueueRound(hash, hc1); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines two values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hash = MixEmptyState(); + + hash += 8; + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines three values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hash = MixEmptyState(); + + hash += 12; + hash = QueueRound(hash, hc1); + hash = QueueRound(hash, hc2); + hash = QueueRound(hash, hc3); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines four values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 16; + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines five values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 20; + hash = QueueRound(hash, hc5); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines six values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The type of the sixth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The sixth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 24; + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines seven values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The type of the sixth value to combine into the hash code. + /// The type of the seventh value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The sixth value to combine into the hash code. + /// The seventh value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 28; + hash = QueueRound(hash, hc5); + hash = QueueRound(hash, hc6); + hash = QueueRound(hash, hc7); + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Combines eight values into a hash code. + /// + /// The type of the first value to combine into the hash code. + /// The type of the second value to combine into the hash code. + /// The type of the third value to combine into the hash code. + /// The type of the fourth value to combine into the hash code. + /// The type of the fifth value to combine into the hash code. + /// The type of the sixth value to combine into the hash code. + /// The type of the seventh value to combine into the hash code. + /// The type of the eigth value to combine into the hash code. + /// The first value to combine into the hash code. + /// The second value to combine into the hash code. + /// The third value to combine into the hash code. + /// The fourth value to combine into the hash code. + /// The fifth value to combine into the hash code. + /// The sixth value to combine into the hash code. + /// The seventh value to combine into the hash code. + /// The eigth value to combine into the hash code. + /// The hash code that represents the values. + public static int Combine(T1 value1, T2 value2, T3 value3, T4 value4, T5 value5, T6 value6, T7 value7, T8 value8) + { + uint hc1 = (uint)(value1?.GetHashCode() ?? 0); + uint hc2 = (uint)(value2?.GetHashCode() ?? 0); + uint hc3 = (uint)(value3?.GetHashCode() ?? 0); + uint hc4 = (uint)(value4?.GetHashCode() ?? 0); + uint hc5 = (uint)(value5?.GetHashCode() ?? 0); + uint hc6 = (uint)(value6?.GetHashCode() ?? 0); + uint hc7 = (uint)(value7?.GetHashCode() ?? 0); + uint hc8 = (uint)(value8?.GetHashCode() ?? 0); + + Initialize(out uint v1, out uint v2, out uint v3, out uint v4); + + v1 = Round(v1, hc1); + v2 = Round(v2, hc2); + v3 = Round(v3, hc3); + v4 = Round(v4, hc4); + + v1 = Round(v1, hc5); + v2 = Round(v2, hc6); + v3 = Round(v3, hc7); + v4 = Round(v4, hc8); + + uint hash = MixState(v1, v2, v3, v4); + + hash += 32; + hash = MixFinal(hash); + + return (int)hash; + } + + /// + /// Adds a single value to the current hash. + /// + /// The type of the value to add into the hash code. + /// The value to add into the hash code. + public void Add(T value) + { + Add(value?.GetHashCode() ?? 0); + } + + /// + /// Adds a single value to the current hash. + /// + /// The type of the value to add into the hash code. + /// The value to add into the hash code. + /// The instance to use. + public void Add(T value, IEqualityComparer? comparer) + { + Add(value is null ? 0 : comparer?.GetHashCode(value) ?? value.GetHashCode()); + } + + /// + /// Adds a span of bytes to the hash code. + /// + /// The span. + public void AddBytes(ReadOnlySpan value) + { + ref byte pos = ref MemoryMarshal.GetReference(value); + ref byte end = ref Unsafe.Add(ref pos, value.Length); + + while ((nint)Unsafe.ByteOffset(ref pos, ref end) >= sizeof(int)) + { + Add(Unsafe.ReadUnaligned(ref pos)); + pos = ref Unsafe.Add(ref pos, sizeof(int)); + } + + while (Unsafe.IsAddressLessThan(ref pos, ref end)) + { + Add((int)pos); + pos = ref Unsafe.Add(ref pos, 1); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4) + { + v1 = seed + Prime1 + Prime2; + v2 = seed + Prime2; + v3 = seed; + v4 = seed - Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Round(uint hash, uint input) + { + return RotateLeft(hash + input * Prime2, 13) * Prime1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint QueueRound(uint hash, uint queuedValue) + { + return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixState(uint v1, uint v2, uint v3, uint v4) + { + return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixEmptyState() + { + return seed + Prime5; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint MixFinal(uint hash) + { + hash ^= hash >> 15; + hash *= Prime2; + hash ^= hash >> 13; + hash *= Prime3; + hash ^= hash >> 16; + + return hash; + } + + private void Add(int value) + { + uint val = (uint)value; + uint previousLength = length++; + uint position = previousLength % 4; + + if (position == 0) + { + queue1 = val; + } + else if (position == 1) + { + queue2 = val; + } + else if (position == 2) + { + queue3 = val; + } + else + { + if (previousLength == 3) + { + Initialize(out v1, out v2, out v3, out v4); + } + + v1 = Round(v1, queue1); + v2 = Round(v2, queue2); + v3 = Round(v3, queue3); + v4 = Round(v4, val); + } + } + + /// + /// Gets the resulting hashcode from the current instance. + /// + /// The resulting hashcode from the current instance. + public readonly int ToHashCode() + { + uint length = this.length; + uint position = length % 4; + uint hash = length < 4 ? MixEmptyState() : MixState(v1, v2, v3, v4); + + hash += length * 4; + + if (position > 0) + { + hash = QueueRound(hash, queue1); + + if (position > 1) + { + hash = QueueRound(hash, queue2); + + if (position > 2) + { + hash = QueueRound(hash, queue3); + } + } + } + + hash = MixFinal(hash); + + return (int)hash; + } + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + { + throw new NotSupportedException(); + } + + /// + [Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + { + throw new NotSupportedException(); + } + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint RotateLeft(uint value, int offset) + { + return value << offset | value >> 32 - offset; + } +} diff --git a/src/AutoConstructor.Generator/IsExternalInit.cs b/src/AutoConstructor.Generator/IsExternalInit.cs new file mode 100644 index 0000000..74f52c0 --- /dev/null +++ b/src/AutoConstructor.Generator/IsExternalInit.cs @@ -0,0 +1,17 @@ +// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices; + +/// +/// Reserved to be used by the compiler for tracking metadata. +/// This class should not be used by developers in source code. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +internal static class IsExternalInit +{ +} diff --git a/src/AutoConstructor.Generator/NamedTypeSymbolInfo.cs b/src/AutoConstructor.Generator/NamedTypeSymbolInfo.cs new file mode 100644 index 0000000..cf07a07 --- /dev/null +++ b/src/AutoConstructor.Generator/NamedTypeSymbolInfo.cs @@ -0,0 +1,37 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace AutoConstructor.Generator; + +internal record NamedTypeSymbolInfo(string Name, bool IsStatic, EquatableArray TypeParameters) +{ + public NamedTypeSymbolInfo(INamedTypeSymbol namedTypeSymbol) + : this(namedTypeSymbol.Name, namedTypeSymbol.IsStatic, namedTypeSymbol.TypeParameters.Select(t => t.Name).ToImmutableArray()) + { + } +} + +internal record MainNamedTypeSymbolInfo( + string Name, + bool IsStatic, + EquatableArray TypeParameters, + bool IsGlobalNamespace, + string ContainingNamespace, + EquatableArray ContainingTypes, + bool HasParameterlessConstructor, + string Filename) + : NamedTypeSymbolInfo(Name, IsStatic, TypeParameters) +{ + public MainNamedTypeSymbolInfo(INamedTypeSymbol namedTypeSymbol, bool hasParameterlessConstructor, string filename) + : this( + namedTypeSymbol.Name, + namedTypeSymbol.IsStatic, + namedTypeSymbol.TypeParameters.Select(t => t.Name).ToImmutableArray(), + namedTypeSymbol.ContainingNamespace.IsGlobalNamespace, + namedTypeSymbol.ContainingNamespace.ToDisplayString(), + namedTypeSymbol.GetContainingTypes().Select(c => new NamedTypeSymbolInfo(c)).ToImmutableArray(), + hasParameterlessConstructor, + filename) + { + } +} diff --git a/src/AutoConstructor.Generator/Options.cs b/src/AutoConstructor.Generator/Options.cs new file mode 100644 index 0000000..ae870bc --- /dev/null +++ b/src/AutoConstructor.Generator/Options.cs @@ -0,0 +1,3 @@ +namespace AutoConstructor.Generator; + +internal record Options(bool GenerateConstructorDocumentation, string? ConstructorDocumentationComment, bool EmitNullChecks); diff --git a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj index ca4ddc7..f8e7e16 100644 --- a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj +++ b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + From 069b970215965a98850e035c3d6a29a0a13d83f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 13:58:49 +0200 Subject: [PATCH 02/12] Fix warnings --- .editorconfig | 4 +- Directory.Build.props | 3 +- .../ClassWithoutFieldsToInjectAnalyzer.cs | 4 +- .../AutoConstructorGenerator.cs | 42 +++++++++++-------- tests/AutoConstructor.Tests/GeneratorTests.cs | 33 +++++++++++++++ 5 files changed, 63 insertions(+), 23 deletions(-) diff --git a/.editorconfig b/.editorconfig index bc9851f..7e09ada 100644 --- a/.editorconfig +++ b/.editorconfig @@ -120,8 +120,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggesti dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:refactoring -dotnet_style_prefer_conditional_expression_over_return = true:refactoring +dotnet_style_prefer_conditional_expression_over_assignment = true:none +dotnet_style_prefer_conditional_expression_over_return = true:none csharp_prefer_simple_default_expression = true:suggestion # Expression-bodied members diff --git a/Directory.Build.props b/Directory.Build.props index 663a08d..55d5723 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,6 +6,7 @@ AllEnabledByDefault enable true + true @@ -25,7 +26,7 @@ - 4.0 + 5.0 minor v dev.0 diff --git a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs index 502e43d..1f71473 100644 --- a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs @@ -54,10 +54,10 @@ private static bool ParentHasFields(Compilation compilation, INamedTypeSymbol sy { INamedTypeSymbol? baseType = symbol.BaseType; - if (baseType?.BaseType is not null && baseType?.Constructors.Count(d => !d.IsStatic) == 1) + if (baseType?.BaseType is not null && baseType.Constructors.Count(d => !d.IsStatic) == 1) { IMethodSymbol constructor = baseType.Constructors.Single(d => !d.IsStatic); - return baseType?.HasAttribute(Source.AttributeFullName, compilation) is true + return baseType.HasAttribute(Source.AttributeFullName, compilation) ? SymbolHasFields(compilation, baseType) || ParentHasFields(compilation, baseType) : constructor.Parameters.Length > 0; } diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index 4ce4610..2e08d24 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -25,7 +25,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) i.AddSource(Source.InjectAttributeFullName, SourceText.From(Source.InjectAttributeText, Encoding.UTF8)); }); - IncrementalValuesProvider<(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options)> valueProvider = context.SyntaxProvider + IncrementalValuesProvider<(MainNamedTypeSymbolInfo? symbol, EquatableArray fields, Options options, EquatableArray diagnostics)> valueProvider = context.SyntaxProvider .ForAttributeWithMetadataName( Source.AttributeFullName, static (node, _) => IsSyntaxTargetForGeneration(node), @@ -37,13 +37,23 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .SelectMany((c, _) => { (ImmutableArray classes, AnalyzerConfigOptions options, Compilation compilation) = (c.Left.Left, c.Left.Right, c.Right); - return Execute(compilation, classes, new(), options); + return Execute(compilation, classes, options); }); context.RegisterSourceOutput(valueProvider, static (context, item) => { - CompilationUnitSyntax compilationUnit = GenerateAutoConstructor(item.symbol, item.fields, item.options); - context.AddSource($"{item.symbol.Filename}.g.cs", compilationUnit.GetText(Encoding.UTF8)); + if (!item.diagnostics.IsEmpty) + { + foreach (Diagnostic diagnostic in item.diagnostics) + { + context.ReportDiagnostic(diagnostic); + } + } + else + { + CodeGenerator codeGenerator = GenerateAutoConstructor(item.symbol!, item.fields, item.options); + context.AddSource($"{item.symbol!.Filename}.g.cs", codeGenerator.ToString()); + } }); } @@ -52,7 +62,7 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); } - private static IEnumerable<(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options)> Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context, AnalyzerConfigOptions analyzerOptions) + private static IEnumerable<(MainNamedTypeSymbolInfo? symbol, EquatableArray fields, Options options, EquatableArray diagnostics)> Execute(Compilation compilation, ImmutableArray classes, AnalyzerConfigOptions analyzerOptions) { if (classes.IsDefaultOrEmpty) { @@ -87,11 +97,6 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) foreach (IGrouping groupedClasses in classesBySymbol) { - if (context.CancellationToken.IsCancellationRequested) - { - break; - } - INamedTypeSymbol? symbol = groupedClasses.Key as INamedTypeSymbol; if (symbol is not null) { @@ -114,8 +119,6 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) filename = $"{symbol.ContainingNamespace.ToDisplayString()}.{filename}"; } - filename += ".g.cs"; - List concatenatedFields = GetFieldsFromSymbol(compilation, symbol, emitNullChecks); ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); @@ -128,6 +131,8 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) continue; } + var diagnotics = new List(); + if (fields.GroupBy(x => x.ParameterName).Any(g => g.Where(c => c.Type is not null).Select(c => c.Type).Count() > 1 || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Count() > 1) @@ -135,9 +140,10 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) { foreach (ClassDeclarationSyntax classDeclaration in groupedClasses) { - context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, classDeclaration.GetLocation())); + diagnotics.Add(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, classDeclaration.GetLocation())); } + yield return (null, fields, options, diagnotics.ToImmutableArray()); continue; } @@ -149,12 +155,12 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) .Count() == 1 && symbol.Constructors.Any(d => !d.IsStatic && d.Parameters.Length == 0); - yield return (new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options); + yield return (new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options, Array.Empty().ToImmutableArray()); } } } - private static CompilationUnitSyntax GenerateAutoConstructor(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options) + private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options) { bool generateConstructorDocumentation = options.GenerateConstructorDocumentation; string? constructorDocumentationComment = options.ConstructorDocumentationComment; @@ -189,7 +195,7 @@ private static CompilationUnitSyntax GenerateAutoConstructor(MainNamedTypeSymbol .AddClass(symbol) .AddConstructor(fields, symbol.HasParameterlessConstructor); - return codeGenerator.GetCompilationUnit(); + return codeGenerator; } private static List GetFieldsFromSymbol(Compilation compilation, INamedTypeSymbol symbol, bool emitNullChecks) @@ -274,10 +280,10 @@ private static void ExtractFieldsFromParent(Compilation compilation, INamedTypeS INamedTypeSymbol? baseType = symbol.BaseType; // Check if base type is not object (ie. its base type is null) and that there is only one constructor. - if (baseType?.BaseType is not null && baseType?.Constructors.Count(d => !d.IsStatic) == 1) + if (baseType?.BaseType is not null && baseType.Constructors.Count(d => !d.IsStatic) == 1) { IMethodSymbol constructor = baseType.Constructors.Single(d => !d.IsStatic); - if (baseType?.HasAttribute(Source.AttributeFullName, compilation) is true) + if (baseType.HasAttribute(Source.AttributeFullName, compilation)) { ExtractFieldsFromGeneratedParent(compilation, emitNullChecks, concatenatedFields, baseType); } diff --git a/tests/AutoConstructor.Tests/GeneratorTests.cs b/tests/AutoConstructor.Tests/GeneratorTests.cs index d2f936b..bc3b0b6 100644 --- a/tests/AutoConstructor.Tests/GeneratorTests.cs +++ b/tests/AutoConstructor.Tests/GeneratorTests.cs @@ -975,6 +975,39 @@ public Test(int i1, int i2) await VerifySourceGenerator.RunAsync(code, generated); } + [Fact] + public async Task Run_MultiplePartialPartsAndAttributesInMultipleParts_ShouldGenerateClass() + { + const string code = @" +namespace Test +{ + [AutoConstructor] + internal partial class Test + { + private readonly int _i1; + } + + internal partial class Test + { + [AutoConstructorInject(parameterName: ""i1"")] + private readonly int _i2; + } +}"; + const string generated = @"namespace Test +{ + partial class Test + { + public Test(int i1) + { + this._i1 = i1; + this._i2 = i1; + } + } +} +"; + await VerifySourceGenerator.RunAsync(code, generated); + } + [Fact] public async Task Run_WithMismatchingTypesWithTwoPartialParts_ShouldReportDiagnosticOnEachPart() { From 89f0bc25b61cb8a840140ed58db70a229372440a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 14:11:12 +0200 Subject: [PATCH 03/12] Fix last test --- .../AutoConstructorGenerator.cs | 140 ++++++++---------- .../GeneratorExectutionResult.cs | 9 ++ .../AutoConstructor.Tests.csproj | 1 + tests/AutoConstructor.Tests/GeneratorTests.cs | 5 +- 4 files changed, 72 insertions(+), 83 deletions(-) create mode 100644 src/AutoConstructor.Generator/GeneratorExectutionResult.cs diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index 2e08d24..b974431 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -25,34 +25,36 @@ public void Initialize(IncrementalGeneratorInitializationContext context) i.AddSource(Source.InjectAttributeFullName, SourceText.From(Source.InjectAttributeText, Encoding.UTF8)); }); - IncrementalValuesProvider<(MainNamedTypeSymbolInfo? symbol, EquatableArray fields, Options options, EquatableArray diagnostics)> valueProvider = context.SyntaxProvider + IncrementalValuesProvider valueProvider = context.SyntaxProvider .ForAttributeWithMetadataName( Source.AttributeFullName, static (node, _) => IsSyntaxTargetForGeneration(node), static (context, _) => (ClassDeclarationSyntax)context.TargetNode) .Where(static m => m is not null) - .Collect() .Combine(context.AnalyzerConfigOptionsProvider.Select((c, _) => c.GlobalOptions)) .Combine(context.CompilationProvider) - .SelectMany((c, _) => + .Select((c, _) => { - (ImmutableArray classes, AnalyzerConfigOptions options, Compilation compilation) = (c.Left.Left, c.Left.Right, c.Right); - return Execute(compilation, classes, options); + (ClassDeclarationSyntax classSyntax, AnalyzerConfigOptions options, Compilation compilation) = (c.Left.Left, c.Left.Right, c.Right); + return Execute(compilation, classSyntax, options); }); context.RegisterSourceOutput(valueProvider, static (context, item) => { - if (!item.diagnostics.IsEmpty) + if (item is not null) { - foreach (Diagnostic diagnostic in item.diagnostics) + if (!item.Diagnostics.IsEmpty) { - context.ReportDiagnostic(diagnostic); + foreach (Diagnostic diagnostic in item.Diagnostics) + { + context.ReportDiagnostic(diagnostic); + } + } + else + { + CodeGenerator codeGenerator = GenerateAutoConstructor(item.Symbol!, item.Fields, item.Options); + context.AddSource($"{item.Symbol!.Filename}.g.cs", codeGenerator.ToString()); } - } - else - { - CodeGenerator codeGenerator = GenerateAutoConstructor(item.symbol!, item.fields, item.options); - context.AddSource($"{item.symbol!.Filename}.g.cs", codeGenerator.ToString()); } }); } @@ -62,23 +64,8 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); } - private static IEnumerable<(MainNamedTypeSymbolInfo? symbol, EquatableArray fields, Options options, EquatableArray diagnostics)> Execute(Compilation compilation, ImmutableArray classes, AnalyzerConfigOptions analyzerOptions) + private static GeneratorExectutionResult? Execute(Compilation compilation, ClassDeclarationSyntax classSyntax, AnalyzerConfigOptions analyzerOptions) { - if (classes.IsDefaultOrEmpty) - { - yield break; - } - - IEnumerable> classesBySymbol = Enumerable.Empty>(); - try - { - classesBySymbol = classes.GroupBy(c => compilation.GetSemanticModel(c.SyntaxTree).GetDeclaredSymbol(c), SymbolEqualityComparer.Default); - } - catch (ArgumentException) - { - yield break; - } - bool generateConstructorDocumentation = false; if (analyzerOptions.TryGetValue("build_property.AutoConstructor_GenerateConstructorDocumentation", out string? generateConstructorDocumentationSwitch)) { @@ -95,69 +82,61 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) Options options = new(generateConstructorDocumentation, constructorDocumentationComment, emitNullChecks); - foreach (IGrouping groupedClasses in classesBySymbol) + INamedTypeSymbol? symbol = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); + if (symbol is null) { - INamedTypeSymbol? symbol = groupedClasses.Key as INamedTypeSymbol; - if (symbol is not null) - { - string filename = string.Empty; + return null; + } - if (symbol.ContainingType is not null) - { - filename = $"{string.Join(".", symbol.GetContainingTypes().Select(c => c.Name))}."; - } + string filename = string.Empty; + if (symbol.ContainingType is not null) + { + filename = $"{string.Join(".", symbol.GetContainingTypes().Select(c => c.Name))}."; + } - filename += $"{symbol.Name}"; + filename += $"{symbol.Name}"; - if (symbol.TypeArguments.Length > 0) - { - filename += string.Concat(symbol.TypeArguments.Select(tp => $".{tp.Name}")); - } + if (symbol.TypeArguments.Length > 0) + { + filename += string.Concat(symbol.TypeArguments.Select(tp => $".{tp.Name}")); + } - if (!symbol.ContainingNamespace.IsGlobalNamespace) - { - filename = $"{symbol.ContainingNamespace.ToDisplayString()}.{filename}"; - } + if (!symbol.ContainingNamespace.IsGlobalNamespace) + { + filename = $"{symbol.ContainingNamespace.ToDisplayString()}.{filename}"; + } - List concatenatedFields = GetFieldsFromSymbol(compilation, symbol, emitNullChecks); + List concatenatedFields = GetFieldsFromSymbol(compilation, symbol, emitNullChecks); - ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); + ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); - EquatableArray fields = concatenatedFields.ToImmutableArray(); + EquatableArray fields = concatenatedFields.ToImmutableArray(); - if (fields.IsEmpty) - { - // No need to report diagnostic, taken care by the analyzers. - continue; - } + if (fields.IsEmpty) + { + // No need to report diagnostic, taken care by the analyzers. + return null; + } - var diagnotics = new List(); + var diagnotics = new List(); - if (fields.GroupBy(x => x.ParameterName).Any(g => - g.Where(c => c.Type is not null).Select(c => c.Type).Count() > 1 - || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Count() > 1) - )) - { - foreach (ClassDeclarationSyntax classDeclaration in groupedClasses) - { - diagnotics.Add(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, classDeclaration.GetLocation())); - } + if (fields.GroupBy(x => x.ParameterName).Any(g => + g.Where(c => c.Type is not null).Select(c => c.Type).Count() > 1 + || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Count() > 1) + )) + { + diagnotics.Add(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, classSyntax.GetLocation())); - yield return (null, fields, options, diagnotics.ToImmutableArray()); - continue; - } + return new(null, fields, options, diagnotics.ToImmutableArray()); + } - bool hasParameterlessConstructor = - groupedClasses - .SelectMany(c => c - .ChildNodes() - .Where(n => n is ConstructorDeclarationSyntax constructor && !constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword)))) - .Count() == 1 - && symbol.Constructors.Any(d => !d.IsStatic && d.Parameters.Length == 0); + bool hasParameterlessConstructor = + classSyntax + .ChildNodes() + .Count(n => n is ConstructorDeclarationSyntax constructor && !constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) == 1 + && symbol.Constructors.Any(d => !d.IsStatic && d.Parameters.Length == 0); - yield return (new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options, Array.Empty().ToImmutableArray()); - } - } + return new(new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options, Array.Empty().ToImmutableArray()); } private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options) @@ -166,7 +145,8 @@ private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo sym string? constructorDocumentationComment = options.ConstructorDocumentationComment; if (string.IsNullOrWhiteSpace(constructorDocumentationComment)) { - constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // Counter: {Interlocked.Increment(ref _counter)}"; + constructorDocumentationComment = "Initializes a new instance of the {0} class."; + //constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // Counter: {Interlocked.Increment(ref _counter)}"; } var codeGenerator = new CodeGenerator(); @@ -336,7 +316,7 @@ private static void ExtractFieldsFromGeneratedParent(Compilation compilation, bo string.Empty, string.Empty, parameter.FallbackType, - false,//IsNullable(parameter.FallbackType), + parameter.Nullable, null, false, FieldType.PassedToBase)); diff --git a/src/AutoConstructor.Generator/GeneratorExectutionResult.cs b/src/AutoConstructor.Generator/GeneratorExectutionResult.cs new file mode 100644 index 0000000..ee20270 --- /dev/null +++ b/src/AutoConstructor.Generator/GeneratorExectutionResult.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis; + +namespace AutoConstructor.Generator; + +internal record GeneratorExectutionResult( + MainNamedTypeSymbolInfo? Symbol, + EquatableArray Fields, + Options Options, + EquatableArray Diagnostics); diff --git a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj index f8e7e16..c8f11ee 100644 --- a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj +++ b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj @@ -3,6 +3,7 @@ net7.0 false + $(NoWarn);CS1591 diff --git a/tests/AutoConstructor.Tests/GeneratorTests.cs b/tests/AutoConstructor.Tests/GeneratorTests.cs index bc3b0b6..b2ff57e 100644 --- a/tests/AutoConstructor.Tests/GeneratorTests.cs +++ b/tests/AutoConstructor.Tests/GeneratorTests.cs @@ -1009,7 +1009,7 @@ public Test(int i1) } [Fact] - public async Task Run_WithMismatchingTypesWithTwoPartialParts_ShouldReportDiagnosticOnEachPart() + public async Task Run_WithMismatchingTypesWithTwoPartialParts_ShouldReportDiagnosticOnFirstPart() { const string code = @" namespace Test @@ -1028,8 +1028,7 @@ internal partial class Test }"; DiagnosticResult diagnosticResultFirstPart = new DiagnosticResult(DiagnosticDescriptors.MistmatchTypesDiagnosticId, DiagnosticSeverity.Error).WithSpan(4, 5, 9, 6); - DiagnosticResult diagnosticResultSecondPart = new DiagnosticResult(DiagnosticDescriptors.MistmatchTypesDiagnosticId, DiagnosticSeverity.Error).WithSpan(11, 5, 14, 6); - await VerifySourceGenerator.RunAsync(code, diagnostics: new[] { diagnosticResultFirstPart, diagnosticResultSecondPart }); + await VerifySourceGenerator.RunAsync(code, diagnostics: new[] { diagnosticResultFirstPart }); } [Theory] From 53b0c3148e82bcf7f8ff9281a5029721d18b2517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 17:53:14 +0200 Subject: [PATCH 04/12] Add GeneratedCodeAttribute --- README.md | 1 + global.json | 4 +-- .../AutoConstructor.Generator.csproj | 2 +- .../AutoConstructorGenerator.cs | 16 ++++-------- .../CodeGenerator.cs | 26 +++++++++++++++++++ ...nResult.cs => GeneratorExecutionResult.cs} | 0 6 files changed, 35 insertions(+), 14 deletions(-) rename src/AutoConstructor.Generator/{GeneratorExectutionResult.cs => GeneratorExecutionResult.cs} (100%) diff --git a/README.md b/README.md index 0053583..7c3a7bf 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ C# source generator that generates a constructor from readonly fields/properties |---------|---------------|----------| | <=1.3.0 | 16.10+ | 5.0.300+ | | >=2.0.0 | 17.0+ | 6.0.100+ | +| >=5.0.0 | 17.6+ | 7.0.302+ | ## Basic usage diff --git a/global.json b/global.json index 30e2afc..4876ef5 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "7.0.100", + "version": "7.0.401", "rollForward": "latestMajor", "allowPrerelease": false } -} \ No newline at end of file +} diff --git a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj index 1f7f302..b4342e4 100644 --- a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj +++ b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj @@ -10,6 +10,7 @@ true true true + true @@ -28,7 +29,6 @@ git https://github.com/k94ll13nn3/AutoConstructor https://github.com/k94ll13nn3/AutoConstructor/blob/main/CHANGELOG.md - true diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index b974431..d37b83f 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -10,11 +10,9 @@ namespace AutoConstructor.Generator; -[Generator] +[Generator(LanguageNames.CSharp)] public class AutoConstructorGenerator : IIncrementalGenerator { - private static int _counter; - public void Initialize(IncrementalGeneratorInitializationContext context) { // Register the attribute source @@ -33,11 +31,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Where(static m => m is not null) .Combine(context.AnalyzerConfigOptionsProvider.Select((c, _) => c.GlobalOptions)) .Combine(context.CompilationProvider) - .Select((c, _) => - { - (ClassDeclarationSyntax classSyntax, AnalyzerConfigOptions options, Compilation compilation) = (c.Left.Left, c.Left.Right, c.Right); - return Execute(compilation, classSyntax, options); - }); + .Select((c, _) => Execute(c.Right, c.Left.Left, c.Left.Right)); context.RegisterSourceOutput(valueProvider, static (context, item) => { @@ -61,7 +55,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private static bool IsSyntaxTargetForGeneration(SyntaxNode node) { - return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); + return node is ClassDeclarationSyntax { AttributeLists.Count: > 0 } classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); } private static GeneratorExectutionResult? Execute(Compilation compilation, ClassDeclarationSyntax classSyntax, AnalyzerConfigOptions analyzerOptions) @@ -145,8 +139,8 @@ private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo sym string? constructorDocumentationComment = options.ConstructorDocumentationComment; if (string.IsNullOrWhiteSpace(constructorDocumentationComment)) { - constructorDocumentationComment = "Initializes a new instance of the {0} class."; - //constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // Counter: {Interlocked.Increment(ref _counter)}"; + //constructorDocumentationComment = "Initializes a new instance of the {0} class."; + constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // {DateTime.UtcNow}"; } var codeGenerator = new CodeGenerator(); diff --git a/src/AutoConstructor.Generator/CodeGenerator.cs b/src/AutoConstructor.Generator/CodeGenerator.cs index f07ca3f..13f17fc 100644 --- a/src/AutoConstructor.Generator/CodeGenerator.cs +++ b/src/AutoConstructor.Generator/CodeGenerator.cs @@ -201,6 +201,7 @@ private static ConstructorDeclarationSyntax GetConstructor(SyntaxToken identifie } ConstructorDeclarationSyntax constructor = ConstructorDeclaration(identifier) + .AddAttributeLists(GetGeneratedCodeAttributeSyntax()) .AddModifiers(modifiers) .AddParameterListParameters(Array.ConvertAll(constructorParameters, GetParameter)) .AddBodyStatements(Array.ConvertAll(parameters.Where(p => p.FieldType.HasFlag(FieldType.Initialized)).ToArray(), GetParameterAssignement)); @@ -282,4 +283,29 @@ private static InvocationExpressionSyntax NameOf(string identifier) return InvocationExpression(IdentifierName(nameofIdentifier)) .AddArgumentListArguments(Argument(IdentifierName(identifier))); } + + private static AttributeListSyntax GetGeneratedCodeAttributeSyntax() + { + QualifiedNameSyntax attributeName = + QualifiedName( + QualifiedName( + QualifiedName( + AliasQualifiedName( + IdentifierName( + Token(SyntaxKind.GlobalKeyword)), + IdentifierName("System")), + IdentifierName("CodeDom")), + IdentifierName("Compiler")), + IdentifierName("GeneratedCodeAttribute")); + + AttributeSyntax attribute = Attribute(attributeName) + .AddArgumentListArguments(GetAttributeArgumentSyntax(nameof(AutoConstructor)), GetAttributeArgumentSyntax("5.0.0.0")); + + return AttributeList(SingletonSeparatedList(attribute)); + + static AttributeArgumentSyntax GetAttributeArgumentSyntax(string value) + { + return AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(value))); + } + } } diff --git a/src/AutoConstructor.Generator/GeneratorExectutionResult.cs b/src/AutoConstructor.Generator/GeneratorExecutionResult.cs similarity index 100% rename from src/AutoConstructor.Generator/GeneratorExectutionResult.cs rename to src/AutoConstructor.Generator/GeneratorExecutionResult.cs From 0ca01fbc75660556b7433112e23216b64a982ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 21:01:45 +0200 Subject: [PATCH 05/12] Reorganize files --- Directory.Build.props | 1 + .../Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs | 2 +- .../Analyzers/ClassWithoutPartialAnalyzer.cs | 2 +- .../IgnoreAttributeOnNonProcessedFieldAnalyzer.cs | 2 +- ...njectAttributeOnClassWithoutAttributeAnalyzer.cs | 2 +- .../InjectAttributeOnIgnoredFieldAnalyzer.cs | 2 +- .../AutoConstructor.Generator.csproj | 1 - .../AutoConstructorGenerator.cs | 4 +++- .../CodeFixes/ClassWithoutPartialCodeFixProvider.cs | 8 ++++---- .../CodeFixes/RemoveAttributeCodeFixProvider.cs | 8 ++++---- src/AutoConstructor.Generator/CodeGenerator.cs | 10 ++++++++-- .../{ => Core}/EquatableArray`1.cs | 2 +- .../{ => Core}/HashCode.cs | 2 +- .../{ => Core}/IsExternalInit.cs | 0 .../{ => Models}/FieldInfo.cs | 4 ++-- .../{ => Models}/FieldType.cs | 4 ++-- .../{ => Models}/GeneratorExecutionResult.cs | 5 +++-- .../MainNamedTypeSymbolInfo.cs} | 13 +++---------- .../Models/NamedTypeSymbolInfo.cs | 13 +++++++++++++ src/AutoConstructor.Generator/Models/Options.cs | 3 +++ src/AutoConstructor.Generator/Options.cs | 3 --- .../AutoConstructor.Tests.csproj | 1 - 22 files changed, 53 insertions(+), 39 deletions(-) rename src/AutoConstructor.Generator/{ => Core}/EquatableArray`1.cs (99%) rename src/AutoConstructor.Generator/{ => Core}/HashCode.cs (99%) rename src/AutoConstructor.Generator/{ => Core}/IsExternalInit.cs (100%) rename src/AutoConstructor.Generator/{ => Models}/FieldInfo.cs (92%) rename src/AutoConstructor.Generator/{ => Models}/FieldType.cs (52%) rename src/AutoConstructor.Generator/{ => Models}/GeneratorExecutionResult.cs (56%) rename src/AutoConstructor.Generator/{NamedTypeSymbolInfo.cs => Models/MainNamedTypeSymbolInfo.cs} (71%) create mode 100644 src/AutoConstructor.Generator/Models/NamedTypeSymbolInfo.cs create mode 100644 src/AutoConstructor.Generator/Models/Options.cs delete mode 100644 src/AutoConstructor.Generator/Options.cs diff --git a/Directory.Build.props b/Directory.Build.props index 55d5723..d7f98ef 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,6 +7,7 @@ enable true true + $(NoWarn);CS1591 diff --git a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs index 1f71473..488e86b 100644 --- a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs @@ -5,7 +5,7 @@ namespace AutoConstructor.Generator.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] -public class ClassWithoutFieldsToInjectAnalyzer : DiagnosticAnalyzer +public sealed class ClassWithoutFieldsToInjectAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.ClassWithoutFieldsToInjectRule); diff --git a/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs index 9c30e92..3c3afd4 100644 --- a/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs @@ -7,7 +7,7 @@ namespace AutoConstructor.Generator.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] -public class ClassWithoutPartialAnalyzer : DiagnosticAnalyzer +public sealed class ClassWithoutPartialAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.ClassWithoutPartialRule); diff --git a/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs index 191bbb0..6af039a 100644 --- a/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs @@ -5,7 +5,7 @@ namespace AutoConstructor.Generator.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] -public class IgnoreAttributeOnNonProcessedFieldAnalyzer : DiagnosticAnalyzer +public sealed class IgnoreAttributeOnNonProcessedFieldAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.IgnoreAttributeOnNonProcessedFieldRule); diff --git a/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs index 323b1cd..6f37cfe 100644 --- a/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs @@ -5,7 +5,7 @@ namespace AutoConstructor.Generator.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] -public class IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer : DiagnosticAnalyzer +public sealed class IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.IgnoreOrInjectAttributeOnClassWithoutAttributeRule); diff --git a/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs index 571888e..4d394a9 100644 --- a/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs @@ -5,7 +5,7 @@ namespace AutoConstructor.Generator.Analyzers; [DiagnosticAnalyzer(LanguageNames.CSharp)] -public class InjectAttributeOnIgnoredFieldAnalyzer : DiagnosticAnalyzer +public sealed class InjectAttributeOnIgnoredFieldAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.InjectAttributeOnIgnoredFieldRule); diff --git a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj index b4342e4..7953654 100644 --- a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj +++ b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj @@ -2,7 +2,6 @@ netstandard2.0 - false false true true diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index d37b83f..8856fe6 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -2,6 +2,8 @@ using System.Globalization; using System.Text; using System.Xml; +using AutoConstructor.Generator.Core; +using AutoConstructor.Generator.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -11,7 +13,7 @@ namespace AutoConstructor.Generator; [Generator(LanguageNames.CSharp)] -public class AutoConstructorGenerator : IIncrementalGenerator +public sealed class AutoConstructorGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { diff --git a/src/AutoConstructor.Generator/CodeFixes/ClassWithoutPartialCodeFixProvider.cs b/src/AutoConstructor.Generator/CodeFixes/ClassWithoutPartialCodeFixProvider.cs index 5befb29..b39d073 100644 --- a/src/AutoConstructor.Generator/CodeFixes/ClassWithoutPartialCodeFixProvider.cs +++ b/src/AutoConstructor.Generator/CodeFixes/ClassWithoutPartialCodeFixProvider.cs @@ -10,16 +10,16 @@ namespace AutoConstructor.Generator.CodeFixes; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(ClassWithoutPartialCodeFixProvider)), Shared] -public class ClassWithoutPartialCodeFixProvider : CodeFixProvider +public sealed class ClassWithoutPartialCodeFixProvider : CodeFixProvider { - public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticDescriptors.ClassWithoutPartialDiagnosticId); + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(DiagnosticDescriptors.ClassWithoutPartialDiagnosticId); - public sealed override FixAllProvider GetFixAllProvider() + public override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + public override async Task RegisterCodeFixesAsync(CodeFixContext context) { SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); diff --git a/src/AutoConstructor.Generator/CodeFixes/RemoveAttributeCodeFixProvider.cs b/src/AutoConstructor.Generator/CodeFixes/RemoveAttributeCodeFixProvider.cs index 91c4bd9..274b61e 100644 --- a/src/AutoConstructor.Generator/CodeFixes/RemoveAttributeCodeFixProvider.cs +++ b/src/AutoConstructor.Generator/CodeFixes/RemoveAttributeCodeFixProvider.cs @@ -9,20 +9,20 @@ namespace AutoConstructor.Generator.CodeFixes; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RemoveAttributeCodeFixProvider)), Shared] -public class RemoveAttributeCodeFixProvider : CodeFixProvider +public sealed class RemoveAttributeCodeFixProvider : CodeFixProvider { - public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create( + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create( DiagnosticDescriptors.ClassWithoutFieldsToInjectDiagnosticId, DiagnosticDescriptors.IgnoreAttributeOnNonProcessedFieldDiagnosticId, DiagnosticDescriptors.InjectAttributeOnIgnoredFieldDiagnosticId, DiagnosticDescriptors.IgnoreOrInjectAttributeOnClassWithoutAttributeDiagnosticId); - public sealed override FixAllProvider GetFixAllProvider() + public override FixAllProvider GetFixAllProvider() { return WellKnownFixAllProviders.BatchFixer; } - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + public override async Task RegisterCodeFixesAsync(CodeFixContext context) { SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); diff --git a/src/AutoConstructor.Generator/CodeGenerator.cs b/src/AutoConstructor.Generator/CodeGenerator.cs index 13f17fc..25b6121 100644 --- a/src/AutoConstructor.Generator/CodeGenerator.cs +++ b/src/AutoConstructor.Generator/CodeGenerator.cs @@ -1,3 +1,5 @@ +using AutoConstructor.Generator.Core; +using AutoConstructor.Generator.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -5,12 +7,16 @@ namespace AutoConstructor.Generator; -internal class CodeGenerator +internal sealed class CodeGenerator { private MemberDeclarationSyntax? _current; private bool _addNullableAnnotation; private string? _constructorDocumentationComment; + internal static readonly string GeneratorVersion = (System.Reflection.Assembly.GetExecutingAssembly() + .GetCustomAttributes(typeof(System.Reflection.AssemblyFileVersionAttribute), false) + .Single() as System.Reflection.AssemblyFileVersionAttribute)!.Version; + public CodeGenerator AddNullableAnnotation() { if (_current is not null) @@ -299,7 +305,7 @@ private static AttributeListSyntax GetGeneratedCodeAttributeSyntax() IdentifierName("GeneratedCodeAttribute")); AttributeSyntax attribute = Attribute(attributeName) - .AddArgumentListArguments(GetAttributeArgumentSyntax(nameof(AutoConstructor)), GetAttributeArgumentSyntax("5.0.0.0")); + .AddArgumentListArguments(GetAttributeArgumentSyntax(nameof(AutoConstructor)), GetAttributeArgumentSyntax(GeneratorVersion)); return AttributeList(SingletonSeparatedList(attribute)); diff --git a/src/AutoConstructor.Generator/EquatableArray`1.cs b/src/AutoConstructor.Generator/Core/EquatableArray`1.cs similarity index 99% rename from src/AutoConstructor.Generator/EquatableArray`1.cs rename to src/AutoConstructor.Generator/Core/EquatableArray`1.cs index 7eb9ea4..b623552 100644 --- a/src/AutoConstructor.Generator/EquatableArray`1.cs +++ b/src/AutoConstructor.Generator/Core/EquatableArray`1.cs @@ -8,7 +8,7 @@ #nullable enable -namespace AutoConstructor.Generator; +namespace AutoConstructor.Generator.Core; /// /// An immutable, equatable array. This is equivalent to but with value equality support. diff --git a/src/AutoConstructor.Generator/HashCode.cs b/src/AutoConstructor.Generator/Core/HashCode.cs similarity index 99% rename from src/AutoConstructor.Generator/HashCode.cs rename to src/AutoConstructor.Generator/Core/HashCode.cs index 06f9afc..af786ef 100644 --- a/src/AutoConstructor.Generator/HashCode.cs +++ b/src/AutoConstructor.Generator/Core/HashCode.cs @@ -7,7 +7,7 @@ #pragma warning disable CS0809, IDE0009, IDE1006, IDE0048 #nullable enable -namespace AutoConstructor.Generator; +namespace AutoConstructor.Generator.Core; /// /// A polyfill type that mirrors some methods from on .NET 6. diff --git a/src/AutoConstructor.Generator/IsExternalInit.cs b/src/AutoConstructor.Generator/Core/IsExternalInit.cs similarity index 100% rename from src/AutoConstructor.Generator/IsExternalInit.cs rename to src/AutoConstructor.Generator/Core/IsExternalInit.cs diff --git a/src/AutoConstructor.Generator/FieldInfo.cs b/src/AutoConstructor.Generator/Models/FieldInfo.cs similarity index 92% rename from src/AutoConstructor.Generator/FieldInfo.cs rename to src/AutoConstructor.Generator/Models/FieldInfo.cs index 72d1b21..05e239e 100644 --- a/src/AutoConstructor.Generator/FieldInfo.cs +++ b/src/AutoConstructor.Generator/Models/FieldInfo.cs @@ -1,6 +1,6 @@ -namespace AutoConstructor.Generator; +namespace AutoConstructor.Generator.Models; -public record FieldInfo +internal sealed record FieldInfo { public FieldInfo( string? type, diff --git a/src/AutoConstructor.Generator/FieldType.cs b/src/AutoConstructor.Generator/Models/FieldType.cs similarity index 52% rename from src/AutoConstructor.Generator/FieldType.cs rename to src/AutoConstructor.Generator/Models/FieldType.cs index eebc6bb..ae3ef9f 100644 --- a/src/AutoConstructor.Generator/FieldType.cs +++ b/src/AutoConstructor.Generator/Models/FieldType.cs @@ -1,7 +1,7 @@ -namespace AutoConstructor.Generator; +namespace AutoConstructor.Generator.Models; [Flags] -public enum FieldType +internal enum FieldType { None = 0, Initialized = 1, diff --git a/src/AutoConstructor.Generator/GeneratorExecutionResult.cs b/src/AutoConstructor.Generator/Models/GeneratorExecutionResult.cs similarity index 56% rename from src/AutoConstructor.Generator/GeneratorExecutionResult.cs rename to src/AutoConstructor.Generator/Models/GeneratorExecutionResult.cs index ee20270..d664945 100644 --- a/src/AutoConstructor.Generator/GeneratorExecutionResult.cs +++ b/src/AutoConstructor.Generator/Models/GeneratorExecutionResult.cs @@ -1,8 +1,9 @@ +using AutoConstructor.Generator.Core; using Microsoft.CodeAnalysis; -namespace AutoConstructor.Generator; +namespace AutoConstructor.Generator.Models; -internal record GeneratorExectutionResult( +internal sealed record GeneratorExectutionResult( MainNamedTypeSymbolInfo? Symbol, EquatableArray Fields, Options Options, diff --git a/src/AutoConstructor.Generator/NamedTypeSymbolInfo.cs b/src/AutoConstructor.Generator/Models/MainNamedTypeSymbolInfo.cs similarity index 71% rename from src/AutoConstructor.Generator/NamedTypeSymbolInfo.cs rename to src/AutoConstructor.Generator/Models/MainNamedTypeSymbolInfo.cs index cf07a07..54080bc 100644 --- a/src/AutoConstructor.Generator/NamedTypeSymbolInfo.cs +++ b/src/AutoConstructor.Generator/Models/MainNamedTypeSymbolInfo.cs @@ -1,17 +1,10 @@ using System.Collections.Immutable; +using AutoConstructor.Generator.Core; using Microsoft.CodeAnalysis; -namespace AutoConstructor.Generator; +namespace AutoConstructor.Generator.Models; -internal record NamedTypeSymbolInfo(string Name, bool IsStatic, EquatableArray TypeParameters) -{ - public NamedTypeSymbolInfo(INamedTypeSymbol namedTypeSymbol) - : this(namedTypeSymbol.Name, namedTypeSymbol.IsStatic, namedTypeSymbol.TypeParameters.Select(t => t.Name).ToImmutableArray()) - { - } -} - -internal record MainNamedTypeSymbolInfo( +internal sealed record MainNamedTypeSymbolInfo( string Name, bool IsStatic, EquatableArray TypeParameters, diff --git a/src/AutoConstructor.Generator/Models/NamedTypeSymbolInfo.cs b/src/AutoConstructor.Generator/Models/NamedTypeSymbolInfo.cs new file mode 100644 index 0000000..fe15a7d --- /dev/null +++ b/src/AutoConstructor.Generator/Models/NamedTypeSymbolInfo.cs @@ -0,0 +1,13 @@ +using System.Collections.Immutable; +using AutoConstructor.Generator.Core; +using Microsoft.CodeAnalysis; + +namespace AutoConstructor.Generator.Models; + +internal record NamedTypeSymbolInfo(string Name, bool IsStatic, EquatableArray TypeParameters) +{ + public NamedTypeSymbolInfo(INamedTypeSymbol namedTypeSymbol) + : this(namedTypeSymbol.Name, namedTypeSymbol.IsStatic, namedTypeSymbol.TypeParameters.Select(t => t.Name).ToImmutableArray()) + { + } +} diff --git a/src/AutoConstructor.Generator/Models/Options.cs b/src/AutoConstructor.Generator/Models/Options.cs new file mode 100644 index 0000000..a64f7cb --- /dev/null +++ b/src/AutoConstructor.Generator/Models/Options.cs @@ -0,0 +1,3 @@ +namespace AutoConstructor.Generator.Models; + +internal sealed record Options(bool GenerateConstructorDocumentation, string? ConstructorDocumentationComment, bool EmitNullChecks); diff --git a/src/AutoConstructor.Generator/Options.cs b/src/AutoConstructor.Generator/Options.cs deleted file mode 100644 index ae870bc..0000000 --- a/src/AutoConstructor.Generator/Options.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace AutoConstructor.Generator; - -internal record Options(bool GenerateConstructorDocumentation, string? ConstructorDocumentationComment, bool EmitNullChecks); diff --git a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj index c8f11ee..f8e7e16 100644 --- a/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj +++ b/tests/AutoConstructor.Tests/AutoConstructor.Tests.csproj @@ -3,7 +3,6 @@ net7.0 false - $(NoWarn);CS1591 From b65e5373d62fd1c0d10a5cb1bcb116379b2c8142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 21:28:13 +0200 Subject: [PATCH 06/12] Update tests --- .../AutoConstructorGenerator.cs | 4 ++-- src/AutoConstructor.Generator/CodeGenerator.cs | 12 ++++++++---- .../Verifiers/CSharpSourceGeneratorVerifier`1.cs | 8 +++++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index 8856fe6..fd824fa 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -141,8 +141,8 @@ private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo sym string? constructorDocumentationComment = options.ConstructorDocumentationComment; if (string.IsNullOrWhiteSpace(constructorDocumentationComment)) { - //constructorDocumentationComment = "Initializes a new instance of the {0} class."; - constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // {DateTime.UtcNow}"; + constructorDocumentationComment = "Initializes a new instance of the {0} class."; + //constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // {DateTime.UtcNow}"; } var codeGenerator = new CodeGenerator(); diff --git a/src/AutoConstructor.Generator/CodeGenerator.cs b/src/AutoConstructor.Generator/CodeGenerator.cs index 25b6121..252e7b3 100644 --- a/src/AutoConstructor.Generator/CodeGenerator.cs +++ b/src/AutoConstructor.Generator/CodeGenerator.cs @@ -200,15 +200,19 @@ private static ConstructorDeclarationSyntax GetConstructor(SyntaxToken identifie .Select(x => x.Any(c => c.Type is not null) ? x.First(c => c.Type is not null) : x.First()) .ToArray(); - SyntaxToken modifiers = Token(SyntaxKind.PublicKeyword); + AttributeListSyntax attribute = GetGeneratedCodeAttributeSyntax(); if (constructorDocumentationComment is string { Length: > 0 }) { - modifiers = Token(TriviaList(Trivia(GetDocumentation(constructorDocumentationComment, constructorParameters))), SyntaxKind.PublicKeyword, TriviaList()); + attribute = attribute.WithOpenBracketToken( + Token( + TriviaList(Trivia(GetDocumentation(constructorDocumentationComment, constructorParameters))), + SyntaxKind.OpenBracketToken, + TriviaList())); } ConstructorDeclarationSyntax constructor = ConstructorDeclaration(identifier) - .AddAttributeLists(GetGeneratedCodeAttributeSyntax()) - .AddModifiers(modifiers) + .AddModifiers(Token(SyntaxKind.PublicKeyword)) + .AddAttributeLists(attribute) .AddParameterListParameters(Array.ConvertAll(constructorParameters, GetParameter)) .AddBodyStatements(Array.ConvertAll(parameters.Where(p => p.FieldType.HasFlag(FieldType.Initialized)).ToArray(), GetParameterAssignement)); diff --git a/tests/AutoConstructor.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs b/tests/AutoConstructor.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs index a04d9e6..6137f52 100644 --- a/tests/AutoConstructor.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs +++ b/tests/AutoConstructor.Tests/Verifiers/CSharpSourceGeneratorVerifier`1.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Text.RegularExpressions; using AutoConstructor.Generator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -8,7 +9,7 @@ namespace AutoConstructor.Tests.Verifiers; -internal static class CSharpSourceGeneratorVerifier +internal static partial class CSharpSourceGeneratorVerifier where TSourceGenerator : IIncrementalGenerator, new() { public static Task RunAsync( @@ -45,6 +46,7 @@ public static async Task RunAsync( LanguageVersion = LanguageVersion.Default, }; + string generatedCodeAttribute = @$"[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""{nameof(AutoConstructor)}"", ""{CodeGenerator.GeneratorVersion}"")]"; foreach ((string? generated, string generatedName) in generatedSources) { if (generated is string { Length: > 0 }) @@ -58,6 +60,7 @@ public static async Task RunAsync( // //------------------------------------------------------------------------------ {generated}"; + generatedWithHeader = BeforeConstructorRegex().Replace(generatedWithHeader, $"$1{generatedCodeAttribute}$1public $2("); test.TestState.GeneratedSources.Add((typeof(AutoConstructorGenerator), generatedName, SourceText.From(generatedWithHeader, Encoding.UTF8))); } } @@ -108,4 +111,7 @@ protected override ParseOptions CreateParseOptions() return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(LanguageVersion); } } + + [GeneratedRegex("(\\s+)public (\\w+)\\(")] + private static partial Regex BeforeConstructorRegex(); } From 07384d8c02b2807dc354264083eb8bd450c954ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 21:29:50 +0200 Subject: [PATCH 07/12] Remove unused code --- src/AutoConstructor.Generator/CodeGenerator.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/AutoConstructor.Generator/CodeGenerator.cs b/src/AutoConstructor.Generator/CodeGenerator.cs index 252e7b3..a7900af 100644 --- a/src/AutoConstructor.Generator/CodeGenerator.cs +++ b/src/AutoConstructor.Generator/CodeGenerator.cs @@ -118,19 +118,6 @@ public CodeGenerator AddConstructor(EquatableArray parameters, bool s return this; } - public CompilationUnitSyntax GetCompilationUnit() - { - if (_current is null) - { - throw new InvalidOperationException("No class was added to the generator."); - } - - return CompilationUnit() - .AddMembers(_current) - .NormalizeWhitespace() - .WithTrailingTrivia(CarriageReturnLineFeed); - } - public override string ToString() { if (_current is null) From afc7c71c888b489c8e370174a4c4b99c8c5029eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 21:33:03 +0200 Subject: [PATCH 08/12] Remove dev code --- src/AutoConstructor.Generator/AutoConstructorGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index fd824fa..fd8dda5 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -142,7 +142,6 @@ private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo sym if (string.IsNullOrWhiteSpace(constructorDocumentationComment)) { constructorDocumentationComment = "Initializes a new instance of the {0} class."; - //constructorDocumentationComment = $"Initializes a new instance of the {{0}} class. // {DateTime.UtcNow}"; } var codeGenerator = new CodeGenerator(); From 5115bd8af8e3afdfd8663f93ba019a928870c94b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sat, 7 Oct 2023 21:35:29 +0200 Subject: [PATCH 09/12] Format code --- tests/AutoConstructor.Tests/Verifiers/CSharpVerifierHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/AutoConstructor.Tests/Verifiers/CSharpVerifierHelper.cs b/tests/AutoConstructor.Tests/Verifiers/CSharpVerifierHelper.cs index cf8878f..66fc396 100644 --- a/tests/AutoConstructor.Tests/Verifiers/CSharpVerifierHelper.cs +++ b/tests/AutoConstructor.Tests/Verifiers/CSharpVerifierHelper.cs @@ -1,4 +1,4 @@ -using System.Collections.Immutable; +using System.Collections.Immutable; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; From c114791a5041b2e32444480fa2ced8286aeb8426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sun, 15 Oct 2023 11:47:20 +0200 Subject: [PATCH 10/12] Remove compiltation from the incremental pipeline --- .../ClassWithoutFieldsToInjectAnalyzer.cs | 2 +- ...oreAttributeOnNonProcessedFieldAnalyzer.cs | 2 +- .../InjectAttributeOnIgnoredFieldAnalyzer.cs | 2 +- .../AutoConstructorGenerator.cs | 81 ++++++----- .../CodeGenerator.cs | 14 +- .../Models/GeneratorExecutionResult.cs | 7 +- .../Models/ReportedDiagnostic.cs | 12 ++ .../SymbolExtension.cs | 19 ++- .../IncrementalGeneratorTests.cs | 127 ++++++++++++++++++ 9 files changed, 206 insertions(+), 60 deletions(-) create mode 100644 src/AutoConstructor.Generator/Models/ReportedDiagnostic.cs create mode 100644 tests/AutoConstructor.Tests/IncrementalGeneratorTests.cs diff --git a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs index 488e86b..ffb8909 100644 --- a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs @@ -43,7 +43,7 @@ private static bool SymbolHasFields(Compilation compilation, INamedTypeSymbol sy { return symbol.GetMembers() .OfType() - .Any(x => x.CanBeInjected(compilation) + .Any(x => x.CanBeInjected() && !x.IsStatic && x.IsReadOnly && !x.IsInitialized() diff --git a/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs index 6af039a..7bfa862 100644 --- a/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs @@ -24,7 +24,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) var symbol = (IFieldSymbol)context.Symbol; if (symbol.GetAttribute(Source.IgnoreAttributeFullName, context.Compilation) is AttributeData attr - && (!symbol.CanBeInjected(context.Compilation) || symbol.IsStatic || !symbol.IsReadOnly || symbol.IsInitialized())) + && (!symbol.CanBeInjected() || symbol.IsStatic || !symbol.IsReadOnly || symbol.IsInitialized())) { SyntaxReference? propertyTypeIdentifier = attr.ApplicationSyntaxReference; if (propertyTypeIdentifier is not null) diff --git a/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs index 4d394a9..5ae82e9 100644 --- a/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs @@ -24,7 +24,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) var symbol = (IFieldSymbol)context.Symbol; if (symbol.GetAttribute(Source.InjectAttributeFullName, context.Compilation) is AttributeData attr - && (!symbol.CanBeInjected(context.Compilation) || symbol.IsStatic || !symbol.IsReadOnly || symbol.IsInitialized() || symbol.HasAttribute(Source.IgnoreAttributeFullName, context.Compilation))) + && (!symbol.CanBeInjected() || symbol.IsStatic || !symbol.IsReadOnly || symbol.IsInitialized() || symbol.HasAttribute(Source.IgnoreAttributeFullName, context.Compilation))) { SyntaxReference? propertyTypeIdentifier = attr.ApplicationSyntaxReference; if (propertyTypeIdentifier is not null) diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index fd8dda5..76e7f04 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -25,31 +25,28 @@ public void Initialize(IncrementalGeneratorInitializationContext context) i.AddSource(Source.InjectAttributeFullName, SourceText.From(Source.InjectAttributeText, Encoding.UTF8)); }); - IncrementalValuesProvider valueProvider = context.SyntaxProvider + IncrementalValuesProvider<(GeneratorExectutionResult? result, Options options)> valueProvider = context.SyntaxProvider .ForAttributeWithMetadataName( Source.AttributeFullName, static (node, _) => IsSyntaxTargetForGeneration(node), - static (context, _) => (ClassDeclarationSyntax)context.TargetNode) + static (context, _) => Execute(context, (ClassDeclarationSyntax)context.TargetNode)) + .WithTrackingName("Execute") .Where(static m => m is not null) - .Combine(context.AnalyzerConfigOptionsProvider.Select((c, _) => c.GlobalOptions)) - .Combine(context.CompilationProvider) - .Select((c, _) => Execute(c.Right, c.Left.Left, c.Left.Right)); + .Combine(context.AnalyzerConfigOptionsProvider.Select((c, _) => ParseOptions(c.GlobalOptions))) + .WithTrackingName("Combine"); context.RegisterSourceOutput(valueProvider, static (context, item) => { - if (item is not null) + if (item.result is not null) { - if (!item.Diagnostics.IsEmpty) + if (item.result.ReportedDiagnostic is ReportedDiagnostic diagnostic) { - foreach (Diagnostic diagnostic in item.Diagnostics) - { - context.ReportDiagnostic(diagnostic); - } + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, Location.Create(diagnostic.FilePath, diagnostic.TextSpan, diagnostic.LineSpan))); } - else + else if (item.result.Symbol is not null) { - CodeGenerator codeGenerator = GenerateAutoConstructor(item.Symbol!, item.Fields, item.Options); - context.AddSource($"{item.Symbol!.Filename}.g.cs", codeGenerator.ToString()); + CodeGenerator codeGenerator = GenerateAutoConstructor(item.result.Symbol!, item.result.Fields, item.options); + context.AddSource($"{item.result.Symbol!.Filename}.g.cs", codeGenerator.ToString()); } } }); @@ -60,7 +57,7 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) return node is ClassDeclarationSyntax { AttributeLists.Count: > 0 } classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); } - private static GeneratorExectutionResult? Execute(Compilation compilation, ClassDeclarationSyntax classSyntax, AnalyzerConfigOptions analyzerOptions) + private static Options ParseOptions(AnalyzerConfigOptions analyzerOptions) { bool generateConstructorDocumentation = false; if (analyzerOptions.TryGetValue("build_property.AutoConstructor_GenerateConstructorDocumentation", out string? generateConstructorDocumentationSwitch)) @@ -76,10 +73,12 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) emitNullChecks = disableNullCheckingSwitch.Equals("false", StringComparison.OrdinalIgnoreCase); } - Options options = new(generateConstructorDocumentation, constructorDocumentationComment, emitNullChecks); + return new(generateConstructorDocumentation, constructorDocumentationComment, emitNullChecks); + } - INamedTypeSymbol? symbol = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax); - if (symbol is null) + private static GeneratorExectutionResult? Execute(GeneratorAttributeSyntaxContext context, ClassDeclarationSyntax classSyntax) + { + if (context.TargetSymbol is not INamedTypeSymbol symbol) { return null; } @@ -102,9 +101,9 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) filename = $"{symbol.ContainingNamespace.ToDisplayString()}.{filename}"; } - List concatenatedFields = GetFieldsFromSymbol(compilation, symbol, emitNullChecks); + List concatenatedFields = GetFieldsFromSymbol(symbol); - ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); + ExtractFieldsFromParent(symbol, concatenatedFields); EquatableArray fields = concatenatedFields.ToImmutableArray(); @@ -114,16 +113,14 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) return null; } - var diagnotics = new List(); - if (fields.GroupBy(x => x.ParameterName).Any(g => g.Where(c => c.Type is not null).Select(c => c.Type).Count() > 1 || (g.All(c => c.Type is null) && g.Select(c => c.FallbackType).Count() > 1) + )) { - diagnotics.Add(Diagnostic.Create(DiagnosticDescriptors.MistmatchTypesRule, classSyntax.GetLocation())); - - return new(null, fields, options, diagnotics.ToImmutableArray()); + Location location = classSyntax.GetLocation(); + return new(null, fields, new(location.SourceTree!.FilePath, location.SourceSpan, location.GetLineSpan().Span)); } bool hasParameterlessConstructor = @@ -132,7 +129,7 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) .Count(n => n is ConstructorDeclarationSyntax constructor && !constructor.Modifiers.Any(m => m.IsKind(SyntaxKind.StaticKeyword))) == 1 && symbol.Constructors.Any(d => !d.IsStatic && d.Parameters.Length == 0); - return new(new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, options, Array.Empty().ToImmutableArray()); + return new(new MainNamedTypeSymbolInfo(symbol, hasParameterlessConstructor, filename), fields, null); } private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo symbol, EquatableArray fields, Options options) @@ -168,29 +165,29 @@ private static CodeGenerator GenerateAutoConstructor(MainNamedTypeSymbolInfo sym codeGenerator .AddClass(symbol) - .AddConstructor(fields, symbol.HasParameterlessConstructor); + .AddConstructor(fields, symbol.HasParameterlessConstructor, options.EmitNullChecks); return codeGenerator; } - private static List GetFieldsFromSymbol(Compilation compilation, INamedTypeSymbol symbol, bool emitNullChecks) + private static List GetFieldsFromSymbol(INamedTypeSymbol symbol) { return symbol.GetMembers().OfType() - .Where(x => x.CanBeInjected(compilation) + .Where(x => x.CanBeInjected() && !x.IsStatic && (x.IsReadOnly || IsPropertyWithExplicitInjection(x)) && !x.IsInitialized() - && !x.HasAttribute(Source.IgnoreAttributeFullName, compilation)) - .Select(x => GetFieldInfo(x, compilation, emitNullChecks)) + && !x.HasAttribute(Source.IgnoreAttributeFullName)) + .Select(GetFieldInfo) .ToList(); - bool IsPropertyWithExplicitInjection(IFieldSymbol x) + static bool IsPropertyWithExplicitInjection(IFieldSymbol x) { - return x.AssociatedSymbol is not null && x.HasAttribute(Source.InjectAttributeFullName, compilation); + return x.AssociatedSymbol is not null && x.HasAttribute(Source.InjectAttributeFullName); } } - private static FieldInfo GetFieldInfo(IFieldSymbol fieldSymbol, Compilation compilation, bool emitNullChecks) + private static FieldInfo GetFieldInfo(IFieldSymbol fieldSymbol) { ITypeSymbol type = fieldSymbol.Type; ITypeSymbol? injectedType = type; @@ -212,7 +209,7 @@ private static FieldInfo GetFieldInfo(IFieldSymbol fieldSymbol, Compilation comp summaryText = document.SelectSingleNode("member/summary")?.InnerText.Trim(); } - AttributeData? attributeData = fieldSymbol.GetAttribute(Source.InjectAttributeFullName, compilation); + AttributeData? attributeData = fieldSymbol.GetAttribute(Source.InjectAttributeFullName); if (attributeData is not null) { ImmutableArray parameters = attributeData.AttributeConstructor?.Parameters ?? ImmutableArray.Create(); @@ -238,7 +235,7 @@ private static FieldInfo GetFieldInfo(IFieldSymbol fieldSymbol, Compilation comp type.ToDisplayString(), IsNullable(type), summaryText, - type.IsReferenceType && type.NullableAnnotation != NullableAnnotation.Annotated && emitNullChecks, + type.IsReferenceType && type.NullableAnnotation != NullableAnnotation.Annotated, FieldType.Initialized); } @@ -250,7 +247,7 @@ private static FieldInfo GetFieldInfo(IFieldSymbol fieldSymbol, Compilation comp : null; } - private static void ExtractFieldsFromParent(Compilation compilation, INamedTypeSymbol symbol, bool emitNullChecks, List concatenatedFields) + private static void ExtractFieldsFromParent(INamedTypeSymbol symbol, List concatenatedFields) { INamedTypeSymbol? baseType = symbol.BaseType; @@ -258,9 +255,9 @@ private static void ExtractFieldsFromParent(Compilation compilation, INamedTypeS if (baseType?.BaseType is not null && baseType.Constructors.Count(d => !d.IsStatic) == 1) { IMethodSymbol constructor = baseType.Constructors.Single(d => !d.IsStatic); - if (baseType.HasAttribute(Source.AttributeFullName, compilation)) + if (baseType.HasAttribute(Source.AttributeFullName)) { - ExtractFieldsFromGeneratedParent(compilation, emitNullChecks, concatenatedFields, baseType); + ExtractFieldsFromGeneratedParent(concatenatedFields, baseType); } else { @@ -294,9 +291,9 @@ private static void ExtractFieldsFromConstructedParent(List concatena } } - private static void ExtractFieldsFromGeneratedParent(Compilation compilation, bool emitNullChecks, List concatenatedFields, INamedTypeSymbol symbol) + private static void ExtractFieldsFromGeneratedParent(List concatenatedFields, INamedTypeSymbol symbol) { - foreach (FieldInfo parameter in GetFieldsFromSymbol(compilation, symbol, emitNullChecks)) + foreach (FieldInfo parameter in GetFieldsFromSymbol(symbol)) { int index = concatenatedFields.FindIndex(p => p.ParameterName == parameter.ParameterName); if (index != -1) @@ -318,7 +315,7 @@ private static void ExtractFieldsFromGeneratedParent(Compilation compilation, bo } } - ExtractFieldsFromParent(compilation, symbol, emitNullChecks, concatenatedFields); + ExtractFieldsFromParent(symbol, concatenatedFields); } private static bool IsNullable(ITypeSymbol typeSymbol) diff --git a/src/AutoConstructor.Generator/CodeGenerator.cs b/src/AutoConstructor.Generator/CodeGenerator.cs index a7900af..06db300 100644 --- a/src/AutoConstructor.Generator/CodeGenerator.cs +++ b/src/AutoConstructor.Generator/CodeGenerator.cs @@ -89,12 +89,12 @@ public CodeGenerator AddClass(NamedTypeSymbolInfo classSymbol) return this; } - public CodeGenerator AddConstructor(EquatableArray parameters, bool symbolHasParameterlessConstructor) + public CodeGenerator AddConstructor(EquatableArray parameters, bool symbolHasParameterlessConstructor, bool emitNullChecks) { if (_current is ClassDeclarationSyntax classDeclarationSyntax) { ClassDeclarationSyntax lastClassSyntax = classDeclarationSyntax.DescendantNodesAndSelf().OfType().LastOrDefault(); - _current = classDeclarationSyntax.ReplaceNode(lastClassSyntax, lastClassSyntax.AddMembers(GetConstructor(lastClassSyntax.Identifier, parameters, _constructorDocumentationComment, symbolHasParameterlessConstructor))); + _current = classDeclarationSyntax.ReplaceNode(lastClassSyntax, lastClassSyntax.AddMembers(GetConstructor(lastClassSyntax.Identifier, parameters, _constructorDocumentationComment, symbolHasParameterlessConstructor, emitNullChecks))); } else if (_current is BaseNamespaceDeclarationSyntax namespaceDeclarationSyntax && namespaceDeclarationSyntax.Members.First() is ClassDeclarationSyntax) { @@ -104,7 +104,7 @@ public CodeGenerator AddConstructor(EquatableArray parameters, bool s throw new InvalidOperationException("No class was added to the generator."); } - _current = namespaceDeclarationSyntax.ReplaceNode(lastClassSyntax, lastClassSyntax.AddMembers(GetConstructor(lastClassSyntax.Identifier, parameters, _constructorDocumentationComment, symbolHasParameterlessConstructor))); + _current = namespaceDeclarationSyntax.ReplaceNode(lastClassSyntax, lastClassSyntax.AddMembers(GetConstructor(lastClassSyntax.Identifier, parameters, _constructorDocumentationComment, symbolHasParameterlessConstructor, emitNullChecks))); } else if (_current is null) { @@ -180,7 +180,7 @@ private static ClassDeclarationSyntax GetClass(string identifier, bool addHeader return declaration; } - private static ConstructorDeclarationSyntax GetConstructor(SyntaxToken identifier, EquatableArray parameters, string? constructorDocumentationComment, bool generateThisInitializer) + private static ConstructorDeclarationSyntax GetConstructor(SyntaxToken identifier, EquatableArray parameters, string? constructorDocumentationComment, bool generateThisInitializer, bool emitNullChecks) { FieldInfo[] constructorParameters = parameters .GroupBy(x => x.ParameterName) @@ -201,7 +201,7 @@ private static ConstructorDeclarationSyntax GetConstructor(SyntaxToken identifie .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddAttributeLists(attribute) .AddParameterListParameters(Array.ConvertAll(constructorParameters, GetParameter)) - .AddBodyStatements(Array.ConvertAll(parameters.Where(p => p.FieldType.HasFlag(FieldType.Initialized)).ToArray(), GetParameterAssignement)); + .AddBodyStatements(Array.ConvertAll(parameters.Where(p => p.FieldType.HasFlag(FieldType.Initialized)).ToArray(), p => GetParameterAssignement(p, emitNullChecks))); if (Array.Exists(constructorParameters, p => p.FieldType.HasFlag(FieldType.PassedToBase))) { @@ -255,12 +255,12 @@ private static ArgumentSyntax GetArgument(FieldInfo parameter) return Argument(IdentifierName(parameter.ParameterName)); } - private static ExpressionStatementSyntax GetParameterAssignement(FieldInfo parameter) + private static ExpressionStatementSyntax GetParameterAssignement(FieldInfo parameter, bool emitNullChecks) { ExpressionSyntax left = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName(parameter.FieldName)); ExpressionSyntax right = IdentifierName(parameter.Initializer); - if (parameter.EmitArgumentNullException) + if (parameter.EmitArgumentNullException && emitNullChecks) { right = BinaryExpression( diff --git a/src/AutoConstructor.Generator/Models/GeneratorExecutionResult.cs b/src/AutoConstructor.Generator/Models/GeneratorExecutionResult.cs index d664945..122bbd8 100644 --- a/src/AutoConstructor.Generator/Models/GeneratorExecutionResult.cs +++ b/src/AutoConstructor.Generator/Models/GeneratorExecutionResult.cs @@ -1,10 +1,5 @@ using AutoConstructor.Generator.Core; -using Microsoft.CodeAnalysis; namespace AutoConstructor.Generator.Models; -internal sealed record GeneratorExectutionResult( - MainNamedTypeSymbolInfo? Symbol, - EquatableArray Fields, - Options Options, - EquatableArray Diagnostics); +internal sealed record GeneratorExectutionResult(MainNamedTypeSymbolInfo? Symbol, EquatableArray Fields, ReportedDiagnostic? ReportedDiagnostic); diff --git a/src/AutoConstructor.Generator/Models/ReportedDiagnostic.cs b/src/AutoConstructor.Generator/Models/ReportedDiagnostic.cs new file mode 100644 index 0000000..35a5f8a --- /dev/null +++ b/src/AutoConstructor.Generator/Models/ReportedDiagnostic.cs @@ -0,0 +1,12 @@ +using Microsoft.CodeAnalysis.Text; + +namespace AutoConstructor.Generator.Models; + +/// +/// Basic diagnostic description for reporting diagnostic inside the incremental pipeline. +/// +/// +/// +/// +/// +internal sealed record ReportedDiagnostic(string FilePath, TextSpan TextSpan, LinePositionSpan LineSpan); diff --git a/src/AutoConstructor.Generator/SymbolExtension.cs b/src/AutoConstructor.Generator/SymbolExtension.cs index 253d1e1..11371c9 100644 --- a/src/AutoConstructor.Generator/SymbolExtension.cs +++ b/src/AutoConstructor.Generator/SymbolExtension.cs @@ -19,6 +19,7 @@ public static bool IsInitialized(this IFieldSymbol symbol) symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is VariableDeclaratorSyntax { Initializer: not null }; } + [Obsolete] public static bool HasAttribute(this ISymbol symbol, string attributeName, Compilation compilation) { _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); @@ -27,6 +28,14 @@ public static bool HasAttribute(this ISymbol symbol, string attributeName, Compi return symbol.GetAttribute(attributeName, compilation) is not null; } + public static bool HasAttribute(this ISymbol symbol, string attributeName) + { + _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); + + return symbol.GetAttribute(attributeName) is not null; + } + + [Obsolete] public static AttributeData? GetAttribute(this ISymbol symbol, string attributeName, Compilation compilation) { _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); @@ -36,10 +45,16 @@ public static bool HasAttribute(this ISymbol symbol, string attributeName, Compi return symbol.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true); } - public static bool CanBeInjected(this ISymbol symbol, Compilation compilation) + public static AttributeData? GetAttribute(this ISymbol symbol, string attributeName) + { + _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); + + return symbol.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.Name == attributeName); + } + + public static bool CanBeInjected(this ISymbol symbol) { _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); - _ = compilation ?? throw new ArgumentNullException(nameof(compilation)); return symbol.CanBeReferencedByName || (!symbol.CanBeReferencedByName diff --git a/tests/AutoConstructor.Tests/IncrementalGeneratorTests.cs b/tests/AutoConstructor.Tests/IncrementalGeneratorTests.cs new file mode 100644 index 0000000..8e8450b --- /dev/null +++ b/tests/AutoConstructor.Tests/IncrementalGeneratorTests.cs @@ -0,0 +1,127 @@ +using System.Reflection; +using AutoConstructor.Generator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace AutoConstructor.Tests; + +public class IncrementalGeneratorTests +{ + [Theory] + [InlineData(@" +namespace Test +{ + [AutoConstructor] + internal partial class Test + { + private readonly int _t; + } +} + +", @" +namespace Test +{ + [AutoConstructor] + internal partial class Test + { + // Test + private readonly int _t; + } +} + +", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Unchanged, IncrementalStepRunReason.Cached)] + [InlineData(@" +namespace Test +{ + [AutoConstructor] + internal partial class Test + { + private readonly int _t; + } +} + +", @" +namespace Test +{ + [AutoConstructor] + internal partial class Test + { + private readonly int _toto; + } +} + +", IncrementalStepRunReason.New, IncrementalStepRunReason.Modified, IncrementalStepRunReason.Modified)] + [InlineData(@" +namespace Test +{ + [AutoConstructor] + internal partial class Test + { + private readonly int _t; + } +} + +", @" +namespace Test +{ + [AutoConstructor] + internal partial class Test + { + private readonly int _t; + private readonly int _toto = 2; + } +} + +", IncrementalStepRunReason.Cached, IncrementalStepRunReason.Unchanged, IncrementalStepRunReason.Cached)] + public void CheckGeneratorIsIncremental( + string source, + string sourceUpdated, + IncrementalStepRunReason sourceStepReason, + IncrementalStepRunReason executeStepReason, + IncrementalStepRunReason combineStepReason) + { + SyntaxTree baseSyntaxTree = CSharpSyntaxTree.ParseText(AppendBaseCode(source)); + + Compilation compilation = CreateCompilation(baseSyntaxTree); + + ISourceGenerator sourceGenerator = new AutoConstructorGenerator().AsSourceGenerator(); + + GeneratorDriver driver = CSharpGeneratorDriver.Create( + generators: new ISourceGenerator[] { sourceGenerator }, + driverOptions: new GeneratorDriverOptions(default, trackIncrementalGeneratorSteps: true)); + + // Run the generator + driver = driver.RunGenerators(compilation); + + // Update the compilation and rerun the generator + compilation = compilation.ReplaceSyntaxTree(baseSyntaxTree, CSharpSyntaxTree.ParseText(AppendBaseCode(sourceUpdated))); + driver = driver.RunGenerators(compilation); + + GeneratorRunResult result = driver.GetRunResult().Results.Single(); + IEnumerable<(object Value, IncrementalStepRunReason Reason)> sourceOuputs = + result.TrackedOutputSteps.SelectMany(outputStep => outputStep.Value).SelectMany(output => output.Outputs); + Assert.Collection(sourceOuputs, output => Assert.Equal(sourceStepReason, output.Reason)); + + Assert.Equal(executeStepReason, result.TrackedSteps["Execute"].Single().Outputs[0].Reason); + Assert.Equal(combineStepReason, result.TrackedSteps["Combine"].Single().Outputs[0].Reason); + } + + private static Compilation CreateCompilation(SyntaxTree syntaxTree) + { + return CSharpCompilation.Create("compilation", + new[] { syntaxTree }, + new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) }, + new CSharpCompilationOptions(OutputKind.ConsoleApplication)); + } + + private static string AppendBaseCode(string value) + { + // Appends the attributes from the generator to the code to be compiled. + string valueWithCode = value; + valueWithCode += $"{Source.AttributeText}\n"; + valueWithCode += $"{Source.IgnoreAttributeText}\n"; + valueWithCode += $"{Source.InjectAttributeText}\n"; + return valueWithCode; + } +} From 4bbfbabada3111618a5fc6d9612b34cc16173684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sun, 15 Oct 2023 11:50:47 +0200 Subject: [PATCH 11/12] Remove obsolete code --- .../ClassWithoutFieldsToInjectAnalyzer.cs | 12 ++++++------ .../Analyzers/ClassWithoutPartialAnalyzer.cs | 2 +- ...oreAttributeOnNonProcessedFieldAnalyzer.cs | 2 +- ...ttributeOnClassWithoutAttributeAnalyzer.cs | 7 +++---- .../InjectAttributeOnIgnoredFieldAnalyzer.cs | 4 ++-- .../SymbolExtension.cs | 19 ------------------- 6 files changed, 13 insertions(+), 33 deletions(-) diff --git a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs index ffb8909..0beed5c 100644 --- a/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/ClassWithoutFieldsToInjectAnalyzer.cs @@ -23,9 +23,9 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) { var symbol = (INamedTypeSymbol)context.Symbol; - if (symbol.GetAttribute(Source.AttributeFullName, context.Compilation) is AttributeData attr) + if (symbol.GetAttribute(Source.AttributeFullName) is AttributeData attr) { - bool hasFields = SymbolHasFields(context.Compilation, symbol) || ParentHasFields(context.Compilation, symbol); + bool hasFields = SymbolHasFields(symbol) || ParentHasFields(context.Compilation, symbol); if (!hasFields) { SyntaxReference? propertyTypeIdentifier = attr.ApplicationSyntaxReference; @@ -39,7 +39,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) } } - private static bool SymbolHasFields(Compilation compilation, INamedTypeSymbol symbol) + private static bool SymbolHasFields(INamedTypeSymbol symbol) { return symbol.GetMembers() .OfType() @@ -47,7 +47,7 @@ private static bool SymbolHasFields(Compilation compilation, INamedTypeSymbol sy && !x.IsStatic && x.IsReadOnly && !x.IsInitialized() - && !x.HasAttribute(Source.IgnoreAttributeFullName, compilation)); + && !x.HasAttribute(Source.IgnoreAttributeFullName)); } private static bool ParentHasFields(Compilation compilation, INamedTypeSymbol symbol) @@ -57,8 +57,8 @@ private static bool ParentHasFields(Compilation compilation, INamedTypeSymbol sy if (baseType?.BaseType is not null && baseType.Constructors.Count(d => !d.IsStatic) == 1) { IMethodSymbol constructor = baseType.Constructors.Single(d => !d.IsStatic); - return baseType.HasAttribute(Source.AttributeFullName, compilation) - ? SymbolHasFields(compilation, baseType) || ParentHasFields(compilation, baseType) + return baseType.HasAttribute(Source.AttributeFullName) + ? SymbolHasFields(baseType) || ParentHasFields(compilation, baseType) : constructor.Parameters.Length > 0; } diff --git a/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs index 3c3afd4..4f4fd08 100644 --- a/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/ClassWithoutPartialAnalyzer.cs @@ -26,7 +26,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) var symbol = (INamedTypeSymbol)context.Symbol; if (symbol.DeclaringSyntaxReferences[0].GetSyntax() is ClassDeclarationSyntax classDeclarationSyntax - && symbol.HasAttribute(Source.AttributeFullName, context.Compilation) + && symbol.HasAttribute(Source.AttributeFullName) && !classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) { var diagnostic = Diagnostic.Create(DiagnosticDescriptors.ClassWithoutPartialRule, classDeclarationSyntax.Identifier.GetLocation()); diff --git a/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs index 7bfa862..9bd9d9d 100644 --- a/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/IgnoreAttributeOnNonProcessedFieldAnalyzer.cs @@ -23,7 +23,7 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) { var symbol = (IFieldSymbol)context.Symbol; - if (symbol.GetAttribute(Source.IgnoreAttributeFullName, context.Compilation) is AttributeData attr + if (symbol.GetAttribute(Source.IgnoreAttributeFullName) is AttributeData attr && (!symbol.CanBeInjected() || symbol.IsStatic || !symbol.IsReadOnly || symbol.IsInitialized())) { SyntaxReference? propertyTypeIdentifier = attr.ApplicationSyntaxReference; diff --git a/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs index 6f37cfe..69433f0 100644 --- a/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/IgnoreOrInjectAttributeOnClassWithoutAttributeAnalyzer.cs @@ -24,17 +24,16 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) var symbol = (INamedTypeSymbol)context.Symbol; var fields = symbol.GetMembers().OfType() - .Where(x => x.HasAttribute(Source.IgnoreAttributeFullName, context.Compilation) || x.HasAttribute(Source.InjectAttributeFullName, context.Compilation)) + .Where(x => x.HasAttribute(Source.IgnoreAttributeFullName) || x.HasAttribute(Source.InjectAttributeFullName)) .ToList(); - if (symbol.GetAttribute(Source.AttributeFullName, context.Compilation) is null - && fields.Count > 0) + if (symbol.GetAttribute(Source.AttributeFullName) is null && fields.Count > 0) { foreach (IFieldSymbol field in fields) { foreach (string attributeName in new[] { Source.IgnoreAttributeFullName, Source.InjectAttributeFullName }) { - if (field.GetAttribute(attributeName, context.Compilation) is AttributeData attr) + if (field.GetAttribute(attributeName) is AttributeData attr) { SyntaxReference? propertyTypeIdentifier = attr.ApplicationSyntaxReference; if (propertyTypeIdentifier is not null) diff --git a/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs b/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs index 5ae82e9..dff43b2 100644 --- a/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs +++ b/src/AutoConstructor.Generator/Analyzers/InjectAttributeOnIgnoredFieldAnalyzer.cs @@ -23,8 +23,8 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) { var symbol = (IFieldSymbol)context.Symbol; - if (symbol.GetAttribute(Source.InjectAttributeFullName, context.Compilation) is AttributeData attr - && (!symbol.CanBeInjected() || symbol.IsStatic || !symbol.IsReadOnly || symbol.IsInitialized() || symbol.HasAttribute(Source.IgnoreAttributeFullName, context.Compilation))) + if (symbol.GetAttribute(Source.InjectAttributeFullName) is AttributeData attr + && (!symbol.CanBeInjected() || symbol.IsStatic || !symbol.IsReadOnly || symbol.IsInitialized() || symbol.HasAttribute(Source.IgnoreAttributeFullName))) { SyntaxReference? propertyTypeIdentifier = attr.ApplicationSyntaxReference; if (propertyTypeIdentifier is not null) diff --git a/src/AutoConstructor.Generator/SymbolExtension.cs b/src/AutoConstructor.Generator/SymbolExtension.cs index 11371c9..67db2d4 100644 --- a/src/AutoConstructor.Generator/SymbolExtension.cs +++ b/src/AutoConstructor.Generator/SymbolExtension.cs @@ -19,15 +19,6 @@ public static bool IsInitialized(this IFieldSymbol symbol) symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() is VariableDeclaratorSyntax { Initializer: not null }; } - [Obsolete] - public static bool HasAttribute(this ISymbol symbol, string attributeName, Compilation compilation) - { - _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); - _ = compilation ?? throw new ArgumentNullException(nameof(compilation)); - - return symbol.GetAttribute(attributeName, compilation) is not null; - } - public static bool HasAttribute(this ISymbol symbol, string attributeName) { _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); @@ -35,16 +26,6 @@ public static bool HasAttribute(this ISymbol symbol, string attributeName) return symbol.GetAttribute(attributeName) is not null; } - [Obsolete] - public static AttributeData? GetAttribute(this ISymbol symbol, string attributeName, Compilation compilation) - { - _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); - _ = compilation ?? throw new ArgumentNullException(nameof(compilation)); - - INamedTypeSymbol? attributeSymbol = compilation.GetTypeByMetadataName(attributeName); - return symbol.GetAttributes().FirstOrDefault(ad => ad.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) == true); - } - public static AttributeData? GetAttribute(this ISymbol symbol, string attributeName) { _ = symbol ?? throw new ArgumentNullException(nameof(symbol)); From ff71661b891ad647f2a072da9b755d22c3dddfc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Gallienne?= Date: Sun, 15 Oct 2023 18:15:58 +0200 Subject: [PATCH 12/12] Fix PR comments --- .../AutoConstructor.Generator.csproj | 4 ++ .../AutoConstructorGenerator.cs | 6 +-- .../CodeGenerator.cs | 4 +- .../Core/IsExternalInit.cs | 17 ------ .../Models/FieldInfo.cs | 52 ++++--------------- 5 files changed, 18 insertions(+), 65 deletions(-) delete mode 100644 src/AutoConstructor.Generator/Core/IsExternalInit.cs diff --git a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj index 7953654..ed91530 100644 --- a/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj +++ b/src/AutoConstructor.Generator/AutoConstructor.Generator.csproj @@ -40,6 +40,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs index 76e7f04..c1d9e62 100644 --- a/src/AutoConstructor.Generator/AutoConstructorGenerator.cs +++ b/src/AutoConstructor.Generator/AutoConstructorGenerator.cs @@ -194,7 +194,7 @@ private static FieldInfo GetFieldInfo(IFieldSymbol fieldSymbol) string parameterName = fieldSymbol.Name.TrimStart('_'); if (fieldSymbol.AssociatedSymbol is not null) { - parameterName = char.ToLowerInvariant(fieldSymbol.AssociatedSymbol.Name[0]) + fieldSymbol.AssociatedSymbol.Name.Substring(1); + parameterName = char.ToLowerInvariant(fieldSymbol.AssociatedSymbol.Name[0]) + fieldSymbol.AssociatedSymbol.Name[1..]; } string initializer = parameterName; @@ -273,7 +273,7 @@ private static void ExtractFieldsFromConstructedParent(List concatena int index = concatenatedFields.FindIndex(p => p.ParameterName == parameter.Name); if (index != -1) { - concatenatedFields[index].FieldType |= FieldType.PassedToBase; + concatenatedFields[index] = concatenatedFields[index] with { FieldType = concatenatedFields[index].FieldType | FieldType.PassedToBase }; } else { @@ -298,7 +298,7 @@ private static void ExtractFieldsFromGeneratedParent(List concatenate int index = concatenatedFields.FindIndex(p => p.ParameterName == parameter.ParameterName); if (index != -1) { - concatenatedFields[index].FieldType |= FieldType.PassedToBase; + concatenatedFields[index] = concatenatedFields[index] with { FieldType = concatenatedFields[index].FieldType | FieldType.PassedToBase }; } else { diff --git a/src/AutoConstructor.Generator/CodeGenerator.cs b/src/AutoConstructor.Generator/CodeGenerator.cs index 06db300..ab2ccf6 100644 --- a/src/AutoConstructor.Generator/CodeGenerator.cs +++ b/src/AutoConstructor.Generator/CodeGenerator.cs @@ -13,9 +13,7 @@ internal sealed class CodeGenerator private bool _addNullableAnnotation; private string? _constructorDocumentationComment; - internal static readonly string GeneratorVersion = (System.Reflection.Assembly.GetExecutingAssembly() - .GetCustomAttributes(typeof(System.Reflection.AssemblyFileVersionAttribute), false) - .Single() as System.Reflection.AssemblyFileVersionAttribute)!.Version; + internal static readonly string GeneratorVersion = typeof(CodeGenerator).Assembly.GetName().Version!.ToString(); public CodeGenerator AddNullableAnnotation() { diff --git a/src/AutoConstructor.Generator/Core/IsExternalInit.cs b/src/AutoConstructor.Generator/Core/IsExternalInit.cs deleted file mode 100644 index 74f52c0..0000000 --- a/src/AutoConstructor.Generator/Core/IsExternalInit.cs +++ /dev/null @@ -1,17 +0,0 @@ -// -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices; - -/// -/// Reserved to be used by the compiler for tracking metadata. -/// This class should not be used by developers in source code. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -internal static class IsExternalInit -{ -} diff --git a/src/AutoConstructor.Generator/Models/FieldInfo.cs b/src/AutoConstructor.Generator/Models/FieldInfo.cs index 05e239e..67cd4b4 100644 --- a/src/AutoConstructor.Generator/Models/FieldInfo.cs +++ b/src/AutoConstructor.Generator/Models/FieldInfo.cs @@ -1,44 +1,12 @@ namespace AutoConstructor.Generator.Models; -internal sealed record FieldInfo -{ - public FieldInfo( - string? type, - string parameterName, - string fieldName, - string initializer, - string fallbackType, - bool nullable, - string? comment, - bool emitArgumentNullException, - FieldType fieldType) - { - Type = type; - ParameterName = parameterName; - FieldName = fieldName; - Initializer = initializer; - FallbackType = fallbackType; - Nullable = nullable; - Comment = comment; - EmitArgumentNullException = emitArgumentNullException; - FieldType = fieldType; - } - - public string? Type { get; } - - public string ParameterName { get; } - - public string FieldName { get; } - - public string Initializer { get; } - - public string FallbackType { get; } - - public bool Nullable { get; } - - public string? Comment { get; } - - public bool EmitArgumentNullException { get; } - - public FieldType FieldType { get; set; } -} +internal sealed record FieldInfo( + string? Type, + string ParameterName, + string FieldName, + string Initializer, + string FallbackType, + bool Nullable, + string? Comment, + bool EmitArgumentNullException, + FieldType FieldType);