From 307b0fa872927b620df684a36a3b374df4bb81e3 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 21:01:28 +0200 Subject: [PATCH 01/37] Bump dependencies to latest stable version --- .../dotVariant.Generator.csproj | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dotVariant.Generator/dotVariant.Generator.csproj b/src/dotVariant.Generator/dotVariant.Generator.csproj index 3927a57..477e534 100644 --- a/src/dotVariant.Generator/dotVariant.Generator.csproj +++ b/src/dotVariant.Generator/dotVariant.Generator.csproj @@ -8,13 +8,17 @@ - - - + + + - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + From 309385021591d43ef67fac2f22bcab79c32eb9a7 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 21:14:05 +0200 Subject: [PATCH 02/37] Bump LangVersion to 11.0 --- src/dotVariant.Generator/dotVariant.Generator.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotVariant.Generator/dotVariant.Generator.csproj b/src/dotVariant.Generator/dotVariant.Generator.csproj index 477e534..5a12240 100644 --- a/src/dotVariant.Generator/dotVariant.Generator.csproj +++ b/src/dotVariant.Generator/dotVariant.Generator.csproj @@ -3,7 +3,7 @@ netstandard2.0 enable - 9.0 + 11.0 true From 69902effc1d4de66c0b8f14d25998221d89b3471 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 22:31:12 +0200 Subject: [PATCH 03/37] Bump remaining dependencies --- src/Directory.Build.props | 5 ++++- .../dotVariant.Generator.Test.csproj | 4 ++-- src/dotVariant/dotVariant.csproj | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index fd632b4..9ee125f 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -17,7 +17,10 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj b/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj index d799fba..ac7f0a0 100644 --- a/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj +++ b/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj @@ -7,8 +7,8 @@ - - + + diff --git a/src/dotVariant/dotVariant.csproj b/src/dotVariant/dotVariant.csproj index b1b24f5..0c83de9 100644 --- a/src/dotVariant/dotVariant.csproj +++ b/src/dotVariant/dotVariant.csproj @@ -19,13 +19,13 @@ - + - + From 60dd9914e6047728dcb090856c7a091bfb607a74 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 22:32:10 +0200 Subject: [PATCH 04/37] Migrate to IIncrementalSourceGenerator --- src/dotVariant.Generator/CompilationInfo.cs | 22 ++++ src/dotVariant.Generator/Descriptor.cs | 7 ++ src/dotVariant.Generator/Inspect.cs | 4 +- src/dotVariant.Generator/RenderInfo.cs | 13 +-- src/dotVariant.Generator/SourceGenerator.cs | 114 +++++++++----------- src/dotVariant.Generator/VariantDecl.cs | 17 +++ 6 files changed, 102 insertions(+), 75 deletions(-) create mode 100644 src/dotVariant.Generator/CompilationInfo.cs create mode 100644 src/dotVariant.Generator/VariantDecl.cs diff --git a/src/dotVariant.Generator/CompilationInfo.cs b/src/dotVariant.Generator/CompilationInfo.cs new file mode 100644 index 0000000..4aa3126 --- /dev/null +++ b/src/dotVariant.Generator/CompilationInfo.cs @@ -0,0 +1,22 @@ +// +// Copyright Miro Knejp 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace dotVariant.Generator; + +public readonly record struct CompilationInfo(LanguageVersion LanguageVersion, bool HasReactive, + INamedTypeSymbol DisposableInterface, bool HasHashCode) +{ + public static CompilationInfo FromCompilation(CSharpCompilation compilation) + { + var hasReactive = compilation.GetTypeByMetadataName("System.Reactive.Linq.Observable") is not null; + var disposableInterface = compilation.GetTypeByMetadataName("System.IDisposable")!; + var hasHashCode = compilation.GetTypeByMetadataName("System.HashCode") is not null; + return new(compilation.LanguageVersion, hasReactive, disposableInterface, hasHashCode); + } +} diff --git a/src/dotVariant.Generator/Descriptor.cs b/src/dotVariant.Generator/Descriptor.cs index 4dd41aa..ca110d6 100644 --- a/src/dotVariant.Generator/Descriptor.cs +++ b/src/dotVariant.Generator/Descriptor.cs @@ -24,5 +24,12 @@ public static Descriptor FromDeclaration( var options = Inspect.GetOptions(type); return new(type, syntax, options, nullability); } + + public string SanitizedTypeName => Type.Name + // If the contains type parameters replace angle brackets as those are not allowed in AddSource() + .Replace('<', '{') + .Replace('>', '}') + // Escaped names like @class or @event aren't supported either + .Replace('@', '.'); } } diff --git a/src/dotVariant.Generator/Inspect.cs b/src/dotVariant.Generator/Inspect.cs index 2b8e2e8..485430b 100644 --- a/src/dotVariant.Generator/Inspect.cs +++ b/src/dotVariant.Generator/Inspect.cs @@ -64,11 +64,11 @@ public static bool IsAncestorOf(ITypeSymbol ancestor, ITypeSymbol type) public static Accessibility EffectiveAccessibility(ITypeSymbol type) => type.DeclaredAccessibility; - public static bool ImplementsDispose(ITypeSymbol type, CSharpCompilation compilation) + public static bool ImplementsDispose(ITypeSymbol type, INamedTypeSymbol disposableInterface) { var dispose = FindMethod( - compilation.GetTypeByMetadataName($"{nameof(System)}.{nameof(IDisposable)}")!, + disposableInterface, m => m.Name == nameof(IDisposable.Dispose)); return type.FindImplementationForInterfaceMember(dispose!) is not null; } diff --git a/src/dotVariant.Generator/RenderInfo.cs b/src/dotVariant.Generator/RenderInfo.cs index ef3b3c2..57e923e 100644 --- a/src/dotVariant.Generator/RenderInfo.cs +++ b/src/dotVariant.Generator/RenderInfo.cs @@ -192,14 +192,14 @@ public sealed record ParamInfo( public static RenderInfo FromDescriptor( Descriptor desc, - CSharpCompilation compilation, + CompilationInfo compilation, AnalyzerConfigOptionsProvider options, CancellationToken token) { var maxObjects = desc.Options.Max(NumReferenceFields); var type = desc.Type; var emitNullable = desc.NullableContext.HasFlag(NullableContext.AnnotationsEnabled); - var disposable = compilation.GetTypeByMetadataName(typeof(IDisposable).FullName)!; + var disposable = compilation.DisposableInterface; var paramDescriptors = desc @@ -229,8 +229,8 @@ public static RenderInfo FromDescriptor( ExtensionClassNamespace: ExtensionsNamespace(options, typeNamespace)), Params: paramDescriptors.ToImmutableArray(), Runtime: new( - HasHashCode: compilation.GetTypeByMetadataName("System.HashCode") is not null, - HasSystemReactiveLinq: HasReactive(compilation)), + HasHashCode: compilation.HasHashCode, + HasSystemReactiveLinq: compilation.HasReactive), Variant: new( Accessibility: VariantAccessibility(type), CanBeNull: type.IsReferenceType, @@ -247,7 +247,7 @@ public static RenderInfo FromDescriptor( Type: type.ToDisplayString(TopLevelTypeFormat), UserDefined: new( // If the user defined any method named Dispose() bail out. Too risky! - Dispose: ImplementsDispose(type, compilation) || HasAnyDisposeMethod(type)))); + Dispose: ImplementsDispose(type, compilation.DisposableInterface) || HasAnyDisposeMethod(type)))); } private static string DetermineOutType(IParameterSymbol p, bool emitNullable, LanguageVersion version) @@ -351,9 +351,6 @@ private static int ConvertLanguageVersion(LanguageVersion v) return string.IsNullOrWhiteSpace(value) ? typeNamespace : value; } - private static bool HasReactive(CSharpCompilation compilation) - => compilation.GetTypeByMetadataName("System.Reactive.Linq.Observable") is not null; - public static readonly SymbolDisplayFormat TopLevelTypeFormat = new( globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly, diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index 1b007ef..2e82801 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -7,85 +7,69 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; namespace dotVariant.Generator { [Generator] - public sealed class SourceGenerator : ISourceGenerator + public sealed class SourceGenerator : IIncrementalGenerator { - public void Execute(GeneratorExecutionContext context) + public void Initialize(IncrementalGeneratorInitializationContext generatorContext) { - var receiver = (SyntaxReceiver)context.SyntaxContextReceiver!; - - var decls = - receiver - .VariantDecls - .Select(decl => (decl.Symbol, decl.Syntax, decl.Nullable, Diags: Diagnose.Variant(decl.Symbol, decl.Syntax, context.CancellationToken))) - .Memoize(); - - decls - .SelectMany(desc => desc.Diags) - .ForEach(context.ReportDiagnostic); - - Descriptors = - decls - .Where(decl => !decl.Diags.Any(d => d.Severity == DiagnosticSeverity.Error)) - .Select(decl => Descriptor.FromDeclaration(decl.Symbol, decl.Syntax, decl.Nullable)) - .ToImmutableArray(); - - RenderInfos = - Descriptors - .Select(desc => RenderInfo.FromDescriptor(desc, (CSharpCompilation)context.Compilation, context.AnalyzerConfigOptions, context.CancellationToken)) - .ToImmutableArray(); - - Enumerable - .Zip(Descriptors, RenderInfos, (desc, ri) => (desc, ri)) - .ForEach(v => context.AddSource(SanitizeName(v.desc.Type.ToDisplayString()), Renderer.Render(v.ri))); - - static string SanitizeName(string name) - => name - // If the contains type parameters replace angle brackets as those are not allowed in AddSource() - .Replace('<', '{') - .Replace('>', '}') - // Escaped names like @class or @event aren't supported either - .Replace('@', '.'); - } - - public ImmutableArray Descriptors { get; private set; } - public ImmutableArray RenderInfos { get; private set; } - - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); - } - - private sealed class SyntaxReceiver : ISyntaxContextReceiver - { - public List<(INamedTypeSymbol Symbol, TypeDeclarationSyntax Syntax, NullableContext Nullable)> VariantDecls { get; } = new(); - - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => { - if (context.Node is not TypeDeclarationSyntax tds || tds.AttributeLists.IsEmpty()) + const nuint declarationBloom = (nuint)SyntaxKind.ClassDeclaration | (nuint)SyntaxKind.StructDeclaration | + (nuint)SyntaxKind.RecordDeclaration | (nuint)SyntaxKind.RecordStructDeclaration; + var nodeKind = node.Kind(); + return ((nuint)nodeKind & declarationBloom) != default && nodeKind is SyntaxKind.ClassDeclaration + or SyntaxKind.StructDeclaration + or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; + }, (context, ct) => + { + var sema = context.SemanticModel; + if (context.Node is not TypeDeclarationSyntax syntax) { - return; + return default; } - - var sema = context.SemanticModel; - - if (sema.GetDeclaredSymbol(tds) is not INamedTypeSymbol symbol) + if (sema.GetDeclaredSymbol(syntax, ct) is not { } symbol) { - return; + return default; } - - const string attributeName = nameof(dotVariant) + "." + nameof(VariantAttribute); - if (symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == attributeName)) + if (sema.Compilation is not CSharpCompilation comp) + { + return default; + } + if (!symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == VariantDecl.AttributeName)) { - VariantDecls.Add((symbol, tds, sema.GetNullableContext(context.Node.GetLocation().SourceSpan.Start))); + return default; } - } + + var diagnostics = Diagnose.Variant(symbol, syntax, ct); + + var decl = new VariantDecl(symbol, syntax, sema.GetNullableContext(syntax.GetLocation().SourceSpan.Start), diagnostics); + var compInfo = CompilationInfo.FromCompilation(comp); + (VariantDecl decl, CompilationInfo compInfo)? nullable = (decl, compInfo); + return nullable; + }).Where(t => t.HasValue).Select((t, ct) => t!.Value); + + var descriptorsAndrenderInfos = variantDecls.Combine(generatorContext.AnalyzerConfigOptionsProvider).Select( + (tuple, ct) => + { + var ((decl, compInfo), analyzerOptionProvider) = tuple; + if (decl.Diags.Any(static diag => diag.Severity >= DiagnosticSeverity.Error)) + { + return default; + } + var desc = Descriptor.FromDeclaration(decl.Symbol, decl.Syntax, decl.Nullable); + var renderInfo = RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct); + return (desc, renderInfo); + }); + + generatorContext.RegisterImplementationSourceOutput(descriptorsAndrenderInfos, (context, tuple) => + { + var (desc, renderInfo) = tuple; + context.AddSource(desc.SanitizedTypeName, Renderer.Render(renderInfo)); + }); } } } diff --git a/src/dotVariant.Generator/VariantDecl.cs b/src/dotVariant.Generator/VariantDecl.cs new file mode 100644 index 0000000..b234656 --- /dev/null +++ b/src/dotVariant.Generator/VariantDecl.cs @@ -0,0 +1,17 @@ +// +// Copyright Miro Knejp 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; + +namespace dotVariant.Generator; + +public readonly record struct VariantDecl(INamedTypeSymbol Symbol, TypeDeclarationSyntax Syntax, + NullableContext Nullable, IEnumerable Diags) +{ + public const string AttributeName = nameof(dotVariant) + "." + nameof(VariantAttribute); +} From 0c56caa51ec5d3a6cadf1df0b9e8bb7fa21f836c Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 22:36:05 +0200 Subject: [PATCH 05/37] Downgrade Microsoft.CodeAnalysis.CSharp to 4.4.0 for compatibility --- src/dotVariant.Generator/dotVariant.Generator.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/dotVariant.Generator/dotVariant.Generator.csproj b/src/dotVariant.Generator/dotVariant.Generator.csproj index 5a12240..135591f 100644 --- a/src/dotVariant.Generator/dotVariant.Generator.csproj +++ b/src/dotVariant.Generator/dotVariant.Generator.csproj @@ -9,16 +9,12 @@ - + - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - From 881a7d975100dbd5834e22d4fa758986de87c56a Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 22:40:35 +0200 Subject: [PATCH 06/37] Add Verify for SourceGenerator testing --- .../dotVariant.Generator.Test.csproj | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj b/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj index ac7f0a0..38a19ee 100644 --- a/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj +++ b/src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj @@ -6,13 +6,15 @@ - + - - - - + + + + + + From f381bb2ad28a2599a0d013056f437bfe561d3849 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 22:59:46 +0200 Subject: [PATCH 07/37] EnforceExtendedAnalyzerRules --- src/dotVariant.Generator/dotVariant.Generator.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dotVariant.Generator/dotVariant.Generator.csproj b/src/dotVariant.Generator/dotVariant.Generator.csproj index 135591f..eab1436 100644 --- a/src/dotVariant.Generator/dotVariant.Generator.csproj +++ b/src/dotVariant.Generator/dotVariant.Generator.csproj @@ -5,6 +5,7 @@ enable 11.0 true + true From ba586f6541cfa4512450633e9c445bb637c2f860 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 23:00:29 +0200 Subject: [PATCH 08/37] Use namespace qualitfied metadata name to generate the name hint --- src/dotVariant.Generator/Descriptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotVariant.Generator/Descriptor.cs b/src/dotVariant.Generator/Descriptor.cs index ca110d6..88782ec 100644 --- a/src/dotVariant.Generator/Descriptor.cs +++ b/src/dotVariant.Generator/Descriptor.cs @@ -25,7 +25,7 @@ public static Descriptor FromDeclaration( return new(type, syntax, options, nullability); } - public string SanitizedTypeName => Type.Name + public string SanitizedTypeName => (Type.ContainingNamespace.MetadataName + Type.MetadataName) // If the contains type parameters replace angle brackets as those are not allowed in AddSource() .Replace('<', '{') .Replace('>', '}') From 357a4c4102be9b39cf3dcc71c4e0d5ed34c8b1c5 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 23:01:00 +0200 Subject: [PATCH 09/37] Migrate test solution to IIncrementalSourceGenerator --- src/dotVariant.Generator.Test/GeneratorTools.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dotVariant.Generator.Test/GeneratorTools.cs b/src/dotVariant.Generator.Test/GeneratorTools.cs index eb87592..a622114 100644 --- a/src/dotVariant.Generator.Test/GeneratorTools.cs +++ b/src/dotVariant.Generator.Test/GeneratorTools.cs @@ -21,7 +21,7 @@ namespace dotVariant.Generator.Test { internal static class GeneratorTools { - public static Dictionary GetGeneratedOutput(IDictionary sources, Func makeGenerator, bool failOnInvalidSource = false) + public static Dictionary GetGeneratedOutput(IDictionary sources, Func makeGenerator, bool failOnInvalidSource = false) { var compilation = Compile(sources); @@ -41,10 +41,10 @@ public static Dictionary GetGeneratedOutput(IDictionary GetGeneratedOutput(IDictionary sources, bool failOnInvalidSource = false) - where TGenerator : ISourceGenerator, new() + where TGenerator : IIncrementalGenerator, new() => GetGeneratedOutput(sources, () => new TGenerator(), failOnInvalidSource); - public static ImmutableArray GetGeneratorDiagnostics(IDictionary sources, Func makeGenerator) + public static ImmutableArray GetGeneratorDiagnostics(IDictionary sources, Func makeGenerator) { var compilation = Compile(sources); @@ -57,7 +57,7 @@ public static ImmutableArray GetGeneratorDiagnostics(IDictionary GetGeneratorDiagnostics(IDictionary sources) - where TGenerator : ISourceGenerator, new() + where TGenerator : IIncrementalGenerator, new() => GetGeneratorDiagnostics(sources, () => new TGenerator()); public static CSharpCompilation Compile( From 316c3160934f1e855338ff00e5178d2c80cf1e27 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Tue, 20 Jun 2023 23:01:54 +0200 Subject: [PATCH 10/37] Hack: metadata output for debug builds Note: IIncrementalSourceGenerators should not keep a state for performance reasons. --- src/dotVariant.Generator.Test/RenderInfo.cs | 9 ++++----- src/dotVariant.Generator/SourceGenerator.cs | 8 ++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/dotVariant.Generator.Test/RenderInfo.cs b/src/dotVariant.Generator.Test/RenderInfo.cs index 97dc62b..25d6ef4 100644 --- a/src/dotVariant.Generator.Test/RenderInfo.cs +++ b/src/dotVariant.Generator.Test/RenderInfo.cs @@ -22,12 +22,11 @@ private static ImmutableArray GetRenderInfoFromCompilation( { var compilation = Compile(SupportSources.Add("input", source), version); var generator = new SourceGenerator(); - var driver = CSharpGeneratorDriver.Create( - new[] { generator }, - optionsProvider: new AnalyzerConfigOptionsProvider(msBuildProperties), - parseOptions: new CSharpParseOptions(version)); + var driver = CSharpGeneratorDriver.Create(generator) + .WithUpdatedAnalyzerConfigOptions(new AnalyzerConfigOptionsProvider(msBuildProperties)) + .WithUpdatedParseOptions(new CSharpParseOptions(version)); _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out var _, out var _); - return generator.RenderInfos; + return generator.RenderInfos.ToImmutableArray(); } } } diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index 2e82801..ee57068 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; using System.Linq; namespace dotVariant.Generator @@ -14,6 +15,10 @@ namespace dotVariant.Generator [Generator] public sealed class SourceGenerator : IIncrementalGenerator { +#if DEBUG + public List RenderInfos { get; } = new(); +#endif + public void Initialize(IncrementalGeneratorInitializationContext generatorContext) { var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => @@ -62,6 +67,9 @@ or SyntaxKind.StructDeclaration } var desc = Descriptor.FromDeclaration(decl.Symbol, decl.Syntax, decl.Nullable); var renderInfo = RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct); +#if DEBUG + RenderInfos.Add(renderInfo); +#endif return (desc, renderInfo); }); From 2287673a0c50b8e8509b5325f2ca8eb20652392c Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 12:16:35 +0200 Subject: [PATCH 11/37] Suppress nullable warning nullable annotations are not allowed --- src/dotVariant.Test/Variant+TryMatch.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dotVariant.Test/Variant+TryMatch.cs b/src/dotVariant.Test/Variant+TryMatch.cs index 9f1140f..1f0d807 100644 --- a/src/dotVariant.Test/Variant+TryMatch.cs +++ b/src/dotVariant.Test/Variant+TryMatch.cs @@ -46,7 +46,9 @@ public static void TryMatch_fails_on_inactive_value_1() { var v = new Class_int_float_string(1); Assert.That(v.TryMatch(out float _), Is.False); +#pragma warning disable CS8601 Assert.That(v.TryMatch(out string _), Is.False); +#pragma warning restore CS8601 } [Test] @@ -54,7 +56,9 @@ public static void TryMatch_fails_on_inactive_value_2() { var v = new Class_int_float_string(1f); Assert.That(v.TryMatch(out int _), Is.False); +#pragma warning disable CS8601 Assert.That(v.TryMatch(out string _), Is.False); +#pragma warning restore CS8601 } [Test] From f17045d9f40d20ce8a735f78c88a893b621ca106 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 12:18:37 +0200 Subject: [PATCH 12/37] Bump test from net45 to net 452 --- .../dotVariant.Test-net452.csproj} | 2 +- src/dotVariant.sln | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) rename src/{dotVariant.Test-net45/dotVariant.Test-net45.csproj => dotVariant.Test-net452/dotVariant.Test-net452.csproj} (78%) diff --git a/src/dotVariant.Test-net45/dotVariant.Test-net45.csproj b/src/dotVariant.Test-net452/dotVariant.Test-net452.csproj similarity index 78% rename from src/dotVariant.Test-net45/dotVariant.Test-net45.csproj rename to src/dotVariant.Test-net452/dotVariant.Test-net452.csproj index 912b7f6..5619627 100644 --- a/src/dotVariant.Test-net45/dotVariant.Test-net45.csproj +++ b/src/dotVariant.Test-net452/dotVariant.Test-net452.csproj @@ -1,7 +1,7 @@ - net45 + net452 diff --git a/src/dotVariant.sln b/src/dotVariant.sln index edddd8f..0037505 100644 --- a/src/dotVariant.sln +++ b/src/dotVariant.sln @@ -17,7 +17,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotVariant.Runtime", "dotVa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotVariant.Test-net5.0", "dotVariant.Test-net5.0\dotVariant.Test-net5.0.csproj", "{5FA8381B-0576-498A-9968-006A38839D3C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotVariant.Test-net45", "dotVariant.Test-net45\dotVariant.Test-net45.csproj", "{1305501C-65AB-4338-848F-3D2B919B7255}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotVariant.Test-net452", "dotVariant.Test-net452\dotVariant.Test-net452.csproj", "{1305501C-65AB-4338-848F-3D2B919B7255}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "dotVariant.Test", "dotVariant.Test\dotVariant.Test.shproj", "{193D7A79-92BF-46BE-BB02-60360DA0883D}" EndProject @@ -29,11 +29,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ EndProjectSection EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - dotVariant.Test\dotVariant.Test.projitems*{1305501c-65ab-4338-848f-3d2b919b7255}*SharedItemsImports = 5 - dotVariant.Test\dotVariant.Test.projitems*{193d7a79-92bf-46be-bb02-60360da0883d}*SharedItemsImports = 13 - dotVariant.Test\dotVariant.Test.projitems*{5fa8381b-0576-498a-9968-006a38839d3c}*SharedItemsImports = 5 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -70,4 +65,9 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5601C60A-02E6-42A8-B802-79FCCC6CD6C3} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + dotVariant.Test\dotVariant.Test.projitems*{1305501c-65ab-4338-848f-3d2b919b7255}*SharedItemsImports = 5 + dotVariant.Test\dotVariant.Test.projitems*{193d7a79-92bf-46be-bb02-60360da0883d}*SharedItemsImports = 13 + dotVariant.Test\dotVariant.Test.projitems*{5fa8381b-0576-498a-9968-006a38839d3c}*SharedItemsImports = 5 + EndGlobalSection EndGlobal From 3be87c596d849597426a447948f629dca057fb22 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 13:04:50 +0200 Subject: [PATCH 13/37] Make models ValueTypes --- src/dotVariant.Generator/Descriptor.cs | 2 +- src/dotVariant.Generator/RenderInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotVariant.Generator/Descriptor.cs b/src/dotVariant.Generator/Descriptor.cs index 88782ec..6c4b4a0 100644 --- a/src/dotVariant.Generator/Descriptor.cs +++ b/src/dotVariant.Generator/Descriptor.cs @@ -10,7 +10,7 @@ namespace dotVariant.Generator { - public sealed record Descriptor( + public readonly record struct Descriptor( INamedTypeSymbol Type, TypeDeclarationSyntax Syntax, ImmutableArray Options, diff --git a/src/dotVariant.Generator/RenderInfo.cs b/src/dotVariant.Generator/RenderInfo.cs index 57e923e..748861d 100644 --- a/src/dotVariant.Generator/RenderInfo.cs +++ b/src/dotVariant.Generator/RenderInfo.cs @@ -24,7 +24,7 @@ namespace dotVariant.Generator /// Properties of the parameters provided to VariantOf. /// Information about the .NET runtime we are generating code for. /// Properties of the variant class. - public sealed record RenderInfo( + public readonly record struct RenderInfo( RenderInfo.LanguageInfo Language, RenderInfo.OptionsInfo Options, ImmutableArray Params, From a5279844feb7346a86cebf62a02b0f632362158e Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 14:02:00 +0200 Subject: [PATCH 14/37] Implement diagnosed result sum type --- src/dotVariant.Generator/DiagnosedResult.cs | 66 +++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/dotVariant.Generator/DiagnosedResult.cs diff --git a/src/dotVariant.Generator/DiagnosedResult.cs b/src/dotVariant.Generator/DiagnosedResult.cs new file mode 100644 index 0000000..c275760 --- /dev/null +++ b/src/dotVariant.Generator/DiagnosedResult.cs @@ -0,0 +1,66 @@ +// +// Copyright Miro Knejp 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace dotVariant.Generator; + +/// A result type carrying values and a if any only if no errors are diagnosed. +/// The type of the value +public readonly struct DiagnosedResult : IEquatable> +{ + public DiagnosedResult(ImmutableArray diagnostics, Func valueFactory) + { + HasErrors = diagnostics.Any(static d => d.Severity >= DiagnosticSeverity.Error); + Diagnostics = diagnostics; + ValueOrDefault = HasErrors ? default : valueFactory(); + } + + private DiagnosedResult(ImmutableArray diagnostics, bool hasErrors, TValue? valueOrDefault) + { + HasErrors = hasErrors; + Diagnostics = diagnostics; + ValueOrDefault = valueOrDefault; + } + + public readonly ImmutableArray Diagnostics; + public readonly TValue? ValueOrDefault; + public readonly bool HasErrors; + + public bool TryGetValue(out TValue value) + { + value = ValueOrDefault!; + return !HasErrors; + } + + public DiagnosedResult Select(Func selector) + { + var result = HasErrors ? default : selector(ValueOrDefault!); + return new DiagnosedResult(Diagnostics, HasErrors, result); + } + + public bool Equals(DiagnosedResult other) + { + return EqualityComparer.Default.Equals(ValueOrDefault, other.ValueOrDefault) && HasErrors == other.HasErrors; + } + + public override bool Equals(object? obj) + { + return obj is DiagnosedResult other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return (EqualityComparer.Default.GetHashCode(ValueOrDefault) * 397) ^ HasErrors.GetHashCode(); + } + } +} From 5cf5bf717355880682d3ace1085a6ab01de8fcfb Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 14:02:18 +0200 Subject: [PATCH 15/37] IDisposable via runtime typename --- src/dotVariant.Generator/CompilationInfo.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dotVariant.Generator/CompilationInfo.cs b/src/dotVariant.Generator/CompilationInfo.cs index 4aa3126..983c474 100644 --- a/src/dotVariant.Generator/CompilationInfo.cs +++ b/src/dotVariant.Generator/CompilationInfo.cs @@ -1,4 +1,4 @@ -// +// // Copyright Miro Knejp 2021. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using System; namespace dotVariant.Generator; @@ -15,7 +16,7 @@ public readonly record struct CompilationInfo(LanguageVersion LanguageVersion, b public static CompilationInfo FromCompilation(CSharpCompilation compilation) { var hasReactive = compilation.GetTypeByMetadataName("System.Reactive.Linq.Observable") is not null; - var disposableInterface = compilation.GetTypeByMetadataName("System.IDisposable")!; + var disposableInterface = compilation.GetTypeByMetadataName(typeof(IDisposable).FullName)!; var hasHashCode = compilation.GetTypeByMetadataName("System.HashCode") is not null; return new(compilation.LanguageVersion, hasReactive, disposableInterface, hasHashCode); } From e453d22963ce3d7b0d40b991d7fc70f4042b4ae2 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 14:02:45 +0200 Subject: [PATCH 16/37] Collect diagnostics into array --- src/dotVariant.Generator/VariantDecl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dotVariant.Generator/VariantDecl.cs b/src/dotVariant.Generator/VariantDecl.cs index b234656..68ff825 100644 --- a/src/dotVariant.Generator/VariantDecl.cs +++ b/src/dotVariant.Generator/VariantDecl.cs @@ -1,4 +1,4 @@ -// +// // Copyright Miro Knejp 2021. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) @@ -6,12 +6,12 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; +using System.Collections.Immutable; namespace dotVariant.Generator; public readonly record struct VariantDecl(INamedTypeSymbol Symbol, TypeDeclarationSyntax Syntax, - NullableContext Nullable, IEnumerable Diags) + NullableContext Nullable, ImmutableArray Diags) { public const string AttributeName = nameof(dotVariant) + "." + nameof(VariantAttribute); } From dd8f463614692888276bd790e55a7e33200e9e98 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 14:03:09 +0200 Subject: [PATCH 17/37] Add Nullable struct extensions to value providers --- .../ValueProviderExtensions.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/dotVariant.Generator/ValueProviderExtensions.cs diff --git a/src/dotVariant.Generator/ValueProviderExtensions.cs b/src/dotVariant.Generator/ValueProviderExtensions.cs new file mode 100644 index 0000000..57a7ad6 --- /dev/null +++ b/src/dotVariant.Generator/ValueProviderExtensions.cs @@ -0,0 +1,36 @@ +// +// Copyright Miro Knejp 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace dotVariant.Generator +{ + public static class ValueProviderExtensions + { + public static IncrementalValuesProvider SelectNotNull( + this IncrementalValuesProvider source, Func selector) + where TResult : struct + { + return source.SelectMany((source, ct) => + selector(source, ct) is { } result ? ImmutableArray.Create(result) : ImmutableArray.Empty); + } + + public static IncrementalValuesProvider SelectNotNull(this IncrementalValuesProvider source) + where TSource : struct + { + return source.SelectMany(static (source, _) => + source is { } result ? ImmutableArray.Create(result) : ImmutableArray.Empty); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? AsNullable(this T value) where T : struct => value; + } +} From 04dce5c7c42107ee05971c2ddd654a8c04b96859 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 14:03:46 +0200 Subject: [PATCH 18/37] Use diagnosedresult type to carry diagnostics --- src/dotVariant.Generator/SourceGenerator.cs | 46 +++++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index ee57068..a8f0989 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; namespace dotVariant.Generator @@ -49,34 +50,43 @@ or SyntaxKind.StructDeclaration return default; } - var diagnostics = Diagnose.Variant(symbol, syntax, ct); + var diagnostics = Diagnose.Variant(symbol, syntax, ct).ToImmutableArray(); var decl = new VariantDecl(symbol, syntax, sema.GetNullableContext(syntax.GetLocation().SourceSpan.Start), diagnostics); var compInfo = CompilationInfo.FromCompilation(comp); - (VariantDecl decl, CompilationInfo compInfo)? nullable = (decl, compInfo); - return nullable; - }).Where(t => t.HasValue).Select((t, ct) => t!.Value); + return (decl, compInfo).AsNullable(); + }).SelectNotNull(); - var descriptorsAndrenderInfos = variantDecls.Combine(generatorContext.AnalyzerConfigOptionsProvider).Select( + var descriptors = variantDecls.Select((tuple, _) => + { + var (decl, compInfo) = tuple; + return new DiagnosedResult<(Descriptor, CompilationInfo)>(decl.Diags, + () => (Descriptor.FromDeclaration(decl.Symbol, decl.Syntax, decl.Nullable), compInfo)); + }); + + var renderInfos = descriptors.Combine(generatorContext.AnalyzerConfigOptionsProvider).Select( (tuple, ct) => { - var ((decl, compInfo), analyzerOptionProvider) = tuple; - if (decl.Diags.Any(static diag => diag.Severity >= DiagnosticSeverity.Error)) + var (source, analyzerOptionProvider) = tuple; + return source.Select(tuple => { - return default; - } - var desc = Descriptor.FromDeclaration(decl.Symbol, decl.Syntax, decl.Nullable); - var renderInfo = RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct); -#if DEBUG - RenderInfos.Add(renderInfo); -#endif - return (desc, renderInfo); + var (desc, compInfo) = tuple; + return (desc.SanitizedTypeName, RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct)); + }); }); - generatorContext.RegisterImplementationSourceOutput(descriptorsAndrenderInfos, (context, tuple) => + generatorContext.RegisterImplementationSourceOutput(renderInfos, (context, source) => { - var (desc, renderInfo) = tuple; - context.AddSource(desc.SanitizedTypeName, Renderer.Render(renderInfo)); + source.Diagnostics.ForEach(context.ReportDiagnostic); + if (!source.TryGetValue(out var tuple)) + { + return; + } + var (name, info) = tuple; +#if DEBUG + RenderInfos.Add(info); +#endif + context.AddSource(name, Renderer.Render(info)); }); } } From 025bbbb5e3e022b12542467b4383aea9cd34a18d Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 15:05:00 +0200 Subject: [PATCH 19/37] Fix HintName generation --- src/dotVariant.Generator/Descriptor.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dotVariant.Generator/Descriptor.cs b/src/dotVariant.Generator/Descriptor.cs index 6c4b4a0..039be4f 100644 --- a/src/dotVariant.Generator/Descriptor.cs +++ b/src/dotVariant.Generator/Descriptor.cs @@ -25,11 +25,11 @@ public static Descriptor FromDeclaration( return new(type, syntax, options, nullability); } - public string SanitizedTypeName => (Type.ContainingNamespace.MetadataName + Type.MetadataName) - // If the contains type parameters replace angle brackets as those are not allowed in AddSource() - .Replace('<', '{') - .Replace('>', '}') - // Escaped names like @class or @event aren't supported either - .Replace('@', '.'); + public string HintName => $"{Type.ToString() + // If the contains type parameters replace angle brackets as those are not allowed in AddSource() + .Replace('<', '{') + .Replace('>', '}') + // Escaped names like @class or @event aren't supported either + .Replace('@', '.')}.cs"; } } From 2a565013a6baca69afc0ccd03e597d4e8103567b Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 15:05:22 +0200 Subject: [PATCH 20/37] Force DiagnosedResult value IEquatable --- src/dotVariant.Generator/DiagnosedResult.cs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/dotVariant.Generator/DiagnosedResult.cs b/src/dotVariant.Generator/DiagnosedResult.cs index c275760..cd10d01 100644 --- a/src/dotVariant.Generator/DiagnosedResult.cs +++ b/src/dotVariant.Generator/DiagnosedResult.cs @@ -1,4 +1,4 @@ -// +// // Copyright Miro Knejp 2021. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) @@ -14,33 +14,39 @@ namespace dotVariant.Generator; /// A result type carrying values and a if any only if no errors are diagnosed. /// The type of the value +/// Diagnosed result ignores diagnostics during equality comparison to improve caching consistency. public readonly struct DiagnosedResult : IEquatable> + where TValue: IEquatable { + private readonly bool _noErrors; + public DiagnosedResult(ImmutableArray diagnostics, Func valueFactory) { - HasErrors = diagnostics.Any(static d => d.Severity >= DiagnosticSeverity.Error); + _noErrors = !diagnostics.Any(static d => d.Severity >= DiagnosticSeverity.Error); Diagnostics = diagnostics; ValueOrDefault = HasErrors ? default : valueFactory(); } private DiagnosedResult(ImmutableArray diagnostics, bool hasErrors, TValue? valueOrDefault) { - HasErrors = hasErrors; + _noErrors = !hasErrors; Diagnostics = diagnostics; ValueOrDefault = valueOrDefault; } public readonly ImmutableArray Diagnostics; public readonly TValue? ValueOrDefault; - public readonly bool HasErrors; + + public bool HasErrors => !_noErrors; public bool TryGetValue(out TValue value) { value = ValueOrDefault!; - return !HasErrors; + return _noErrors; } public DiagnosedResult Select(Func selector) + where TResult : IEquatable { var result = HasErrors ? default : selector(ValueOrDefault!); return new DiagnosedResult(Diagnostics, HasErrors, result); @@ -48,7 +54,7 @@ public DiagnosedResult Select(Func selector) public bool Equals(DiagnosedResult other) { - return EqualityComparer.Default.Equals(ValueOrDefault, other.ValueOrDefault) && HasErrors == other.HasErrors; + return HasErrors == other.HasErrors && EqualityComparer.Default.Equals(ValueOrDefault, other.ValueOrDefault); } public override bool Equals(object? obj) @@ -60,7 +66,7 @@ public override int GetHashCode() { unchecked { - return (EqualityComparer.Default.GetHashCode(ValueOrDefault) * 397) ^ HasErrors.GetHashCode(); + return HasErrors ? 1337 : EqualityComparer.Default.GetHashCode(ValueOrDefault); } } } From 72c2dd440330ee10da2aaf9fb5ea9e75d9b63e04 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 15:12:41 +0200 Subject: [PATCH 21/37] Apply rename HintName --- src/dotVariant.Generator/SourceGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index a8f0989..0339261 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -71,7 +71,7 @@ or SyntaxKind.StructDeclaration return source.Select(tuple => { var (desc, compInfo) = tuple; - return (desc.SanitizedTypeName, RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct)); + return (desc.HintName, RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct)); }); }); From 09d8830afeaf8f118dcd5541fb5ec4504bb6f37d Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 16:09:17 +0200 Subject: [PATCH 22/37] Make models structs --- src/dotVariant.Generator/RenderInfo.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dotVariant.Generator/RenderInfo.cs b/src/dotVariant.Generator/RenderInfo.cs index 748861d..af4a961 100644 --- a/src/dotVariant.Generator/RenderInfo.cs +++ b/src/dotVariant.Generator/RenderInfo.cs @@ -37,7 +37,7 @@ public readonly record struct RenderInfo( /// /// Integer of the form ABB where A=major and BB=minor version of the language (i.e. 703 -> 7.3) /// - public sealed record LanguageInfo( + public readonly record struct LanguageInfo( string Nullable, int Version); @@ -45,7 +45,7 @@ public sealed record LanguageInfo( /// The namespace in which to generate extension method implementations. /// If use the global namespace. /// - public sealed record OptionsInfo( + public readonly record struct OptionsInfo( string? ExtensionClassNamespace); /// @@ -54,7 +54,7 @@ public sealed record OptionsInfo( /// /// if namespace is found. /// - public sealed record RuntimeInfo( + public readonly record struct RuntimeInfo( bool HasHashCode, bool HasSystemReactiveLinq); @@ -102,7 +102,7 @@ public sealed record RuntimeInfo( /// /// Contains info about relevant members the user has defined. /// - public sealed record VariantInfo( + public readonly record struct VariantInfo( string? Accessibility, bool CanBeNull, string DiagType, @@ -121,7 +121,7 @@ public sealed record VariantInfo( /// /// if a user-defined exists. /// - public sealed record UserDefinitions( + public readonly record struct UserDefinitions( bool Dispose); /// @@ -130,7 +130,7 @@ public sealed record UserDefinitions( /// /// Identifier of the generic parameter. /// - public sealed record GenericInfo( + public readonly record struct GenericInfo( ImmutableArray Constraints, string Identifier); } @@ -175,7 +175,7 @@ public sealed record GenericInfo( /// /// The fully qualified name of the type including type parameter list, without nullability annotation. /// - public sealed record ParamInfo( + public readonly record struct ParamInfo( bool CanBeNull, string DiagType, bool EmitImplicitCast, From 3623b7d028bf8e8b6b10f83e0e0f82cfdd590d70 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Sat, 8 Jul 2023 17:08:41 +0000 Subject: [PATCH 23/37] Update src/dotVariant.Generator/Descriptor.cs Co-authored-by: Miro Knejp --- src/dotVariant.Generator/Descriptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotVariant.Generator/Descriptor.cs b/src/dotVariant.Generator/Descriptor.cs index 039be4f..63b5262 100644 --- a/src/dotVariant.Generator/Descriptor.cs +++ b/src/dotVariant.Generator/Descriptor.cs @@ -26,7 +26,7 @@ public static Descriptor FromDeclaration( } public string HintName => $"{Type.ToString() - // If the contains type parameters replace angle brackets as those are not allowed in AddSource() + // If the type contains type parameters replace angle brackets as those are not allowed in AddSource() .Replace('<', '{') .Replace('>', '}') // Escaped names like @class or @event aren't supported either From db609833ec75cdcd060e0496afbdc8bf5244da1f Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Sat, 8 Jul 2023 17:08:53 +0000 Subject: [PATCH 24/37] Update src/dotVariant.Generator/DiagnosedResult.cs Co-authored-by: Miro Knejp --- src/dotVariant.Generator/DiagnosedResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotVariant.Generator/DiagnosedResult.cs b/src/dotVariant.Generator/DiagnosedResult.cs index cd10d01..7a9d0e0 100644 --- a/src/dotVariant.Generator/DiagnosedResult.cs +++ b/src/dotVariant.Generator/DiagnosedResult.cs @@ -12,7 +12,7 @@ namespace dotVariant.Generator; -/// A result type carrying values and a if any only if no errors are diagnosed. +/// A result type carrying values and a if and only if no errors are diagnosed. /// The type of the value /// Diagnosed result ignores diagnostics during equality comparison to improve caching consistency. public readonly struct DiagnosedResult : IEquatable> From 3b1702443a0e8861c5462246eccf83fee1d66ddf Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Sat, 8 Jul 2023 17:17:23 +0000 Subject: [PATCH 25/37] Remove `declarationBloom` --- src/dotVariant.Generator/SourceGenerator.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index 0339261..c63bc22 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -24,10 +24,7 @@ public void Initialize(IncrementalGeneratorInitializationContext generatorContex { var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => { - const nuint declarationBloom = (nuint)SyntaxKind.ClassDeclaration | (nuint)SyntaxKind.StructDeclaration | - (nuint)SyntaxKind.RecordDeclaration | (nuint)SyntaxKind.RecordStructDeclaration; - var nodeKind = node.Kind(); - return ((nuint)nodeKind & declarationBloom) != default && nodeKind is SyntaxKind.ClassDeclaration + return node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; }, (context, ct) => From 947724bb92c76ea75d0b2ceef246b7e3a33ea5a5 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 23:02:58 +0200 Subject: [PATCH 26/37] Split diagnostics and evaluation --- src/dotVariant.Generator/DiagnosedResult.cs | 56 +++++++-- src/dotVariant.Generator/SourceGenerator.cs | 26 ++-- .../ValueProviderExtensions.cs | 112 ++++++++++++++---- 3 files changed, 150 insertions(+), 44 deletions(-) diff --git a/src/dotVariant.Generator/DiagnosedResult.cs b/src/dotVariant.Generator/DiagnosedResult.cs index 7a9d0e0..218e837 100644 --- a/src/dotVariant.Generator/DiagnosedResult.cs +++ b/src/dotVariant.Generator/DiagnosedResult.cs @@ -15,16 +15,22 @@ namespace dotVariant.Generator; /// A result type carrying values and a if and only if no errors are diagnosed. /// The type of the value /// Diagnosed result ignores diagnostics during equality comparison to improve caching consistency. -public readonly struct DiagnosedResult : IEquatable> - where TValue: IEquatable +public readonly struct DiagnosedResult { private readonly bool _noErrors; - public DiagnosedResult(ImmutableArray diagnostics, Func valueFactory) + public DiagnosedResult(ImmutableArray diagnostics, TValue value) { - _noErrors = !diagnostics.Any(static d => d.Severity >= DiagnosticSeverity.Error); + _noErrors = !diagnostics.HasErrors(); Diagnostics = diagnostics; - ValueOrDefault = HasErrors ? default : valueFactory(); + ValueOrDefault = HasErrors ? default : value; + } + + public DiagnosedResult(TValue value) + { + _noErrors = true; + Diagnostics = ImmutableArray.Empty; + ValueOrDefault = value; } private DiagnosedResult(ImmutableArray diagnostics, bool hasErrors, TValue? valueOrDefault) @@ -46,10 +52,33 @@ public bool TryGetValue(out TValue value) } public DiagnosedResult Select(Func selector) - where TResult : IEquatable { var result = HasErrors ? default : selector(ValueOrDefault!); - return new DiagnosedResult(Diagnostics, HasErrors, result); + return new(Diagnostics, HasErrors, result); + } + + public ImmutableArray> SelectMany(Func> selector) + { + var diagnostics = Diagnostics; + if (HasErrors) + { + // preserve diagnostics + return ImmutableArray.Create(new DiagnosedResult(diagnostics, true, default!)); + } + + var results = selector(ValueOrDefault!); + return results.Select(r => new DiagnosedResult(diagnostics, false, r)).ToImmutableArray(); + } + + public DiagnosedResult WithDiagnostics(ImmutableArray diagnostics) + { + if (Diagnostics.IsDefaultOrEmpty && diagnostics.IsDefaultOrEmpty) + { + return new(ImmutableArray.Empty, HasErrors, ValueOrDefault); + } + + diagnostics = Diagnostics.IsDefaultOrEmpty ? diagnostics : Diagnostics.AddRange(diagnostics); + return new(diagnostics, ValueOrDefault!); } public bool Equals(DiagnosedResult other) @@ -70,3 +99,16 @@ public override int GetHashCode() } } } + +public static class DiagnosedResultExtensions +{ + public static DiagnosedResult AsDiagnosedResult(this TValue value) + { + return new(value); + } + + public static bool HasErrors(this ImmutableArray diagnostics) + { + return diagnostics.Any(static d => d.Severity >= DiagnosticSeverity.Error); + } +} diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index c63bc22..8771e7b 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -47,38 +47,30 @@ or SyntaxKind.StructDeclaration return default; } - var diagnostics = Diagnose.Variant(symbol, syntax, ct).ToImmutableArray(); - - var decl = new VariantDecl(symbol, syntax, sema.GetNullableContext(syntax.GetLocation().SourceSpan.Start), diagnostics); + var type = new SemanticType(); + var decl = new VariantDecl(type, sema.GetNullableContext(syntax.GetLocation().SourceSpan.Start)); var compInfo = CompilationInfo.FromCompilation(comp); return (decl, compInfo).AsNullable(); - }).SelectNotNull(); + }) + .SelectNotNull() + .Diagnose((tuple, ct) => Diagnose.Variant(tuple.decl.Type, ct).ToImmutableArray()); var descriptors = variantDecls.Select((tuple, _) => { var (decl, compInfo) = tuple; - return new DiagnosedResult<(Descriptor, CompilationInfo)>(decl.Diags, - () => (Descriptor.FromDeclaration(decl.Symbol, decl.Syntax, decl.Nullable), compInfo)); + return (desc: Descriptor.FromDeclaration(decl.Type, decl.Nullable), nested: decl.NestingTrace.Select((type, _) => Descriptor.FromDeclaration(type, decl.Nullable)).ToImmutableArray(), compInfo); }); var renderInfos = descriptors.Combine(generatorContext.AnalyzerConfigOptionsProvider).Select( (tuple, ct) => { var (source, analyzerOptionProvider) = tuple; - return source.Select(tuple => - { - var (desc, compInfo) = tuple; - return (desc.HintName, RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct)); - }); + var (desc, nested, compInfo) = source; + return (desc.HintName, RenderInfo.FromDescriptor(desc, nested, compInfo, analyzerOptionProvider, ct)); }); - generatorContext.RegisterImplementationSourceOutput(renderInfos, (context, source) => + generatorContext.RegisterSourceOutput(renderInfos, (context, tuple) => { - source.Diagnostics.ForEach(context.ReportDiagnostic); - if (!source.TryGetValue(out var tuple)) - { - return; - } var (name, info) = tuple; #if DEBUG RenderInfos.Add(info); diff --git a/src/dotVariant.Generator/ValueProviderExtensions.cs b/src/dotVariant.Generator/ValueProviderExtensions.cs index 57a7ad6..e7b7a11 100644 --- a/src/dotVariant.Generator/ValueProviderExtensions.cs +++ b/src/dotVariant.Generator/ValueProviderExtensions.cs @@ -13,24 +13,96 @@ namespace dotVariant.Generator { - public static class ValueProviderExtensions - { - public static IncrementalValuesProvider SelectNotNull( - this IncrementalValuesProvider source, Func selector) - where TResult : struct - { - return source.SelectMany((source, ct) => - selector(source, ct) is { } result ? ImmutableArray.Create(result) : ImmutableArray.Empty); - } - - public static IncrementalValuesProvider SelectNotNull(this IncrementalValuesProvider source) - where TSource : struct - { - return source.SelectMany(static (source, _) => - source is { } result ? ImmutableArray.Create(result) : ImmutableArray.Empty); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? AsNullable(this T value) where T : struct => value; - } + public static class ValueProviderExtensions + { + public static IncrementalValuesProvider SelectNotNull( + this IncrementalValuesProvider source, Func selector) + where TResult : struct + { + return source.SelectMany((source, ct) => + selector(source, ct) is { } result ? ImmutableArray.Create(result) : ImmutableArray.Empty); + } + + public static IncrementalValuesProvider SelectNotNull(this IncrementalValuesProvider source) + where TSource : struct + { + return source.SelectMany(static (source, _) => + source is { } result ? ImmutableArray.Create(result) : ImmutableArray.Empty); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? AsNullable(this T value) where T : struct => value; + + public static IncrementalValuesProvider> Select( + this IncrementalValuesProvider> source, + Func selector) + { + return source.Select((result, ct) => + { + return result.Select(s => selector(s, ct)); + }); + } + + public static IncrementalValuesProvider> SelectMany( + this IncrementalValuesProvider> source, + Func> selector) + { + return source.SelectMany((result, ct) => + { + return result.SelectMany(s => selector(s, ct)); + }); + } + + public static IncrementalValuesProvider> Combine( + this IncrementalValuesProvider> left, + IncrementalValueProvider right) + { + return IncrementalValueProviderExtensions.Combine(left, right).Select((tuple, ct) => + { + var (lhs, rhs) = tuple; + return lhs.Select(v => (v, rhs)); + }); + } + + public static IncrementalValuesProvider> Diagnose( + this IncrementalValuesProvider> source, + Func> diagnose) + { + return IncrementalValueProviderExtensions.Select(source, (result, ct) => + { + if (result.TryGetValue(out var value)) + { + var diagnostics = diagnose(value, ct); + return result.WithDiagnostics(diagnostics); + } + + return result; + }); + } + + public static IncrementalValuesProvider> Diagnose( + this IncrementalValuesProvider source, + Func> diagnose) + { + return IncrementalValueProviderExtensions.Select(source, (value, ct) => + { + var diagnostics = diagnose(value, ct); + return new DiagnosedResult(diagnostics, value); + }); + } + + public static void RegisterSourceOutput(this IncrementalGeneratorInitializationContext context, + IncrementalValuesProvider> source, + Action action) + { + context.RegisterSourceOutput(source, (context, value) => + { + value.Diagnostics.ForEach(context.ReportDiagnostic); + if (value.TryGetValue(out var v)) + { + action(context, v); + } + }); + } + } } From 0093bb839275f2d0cc4077792004528ae3187ec5 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 22:41:15 +0200 Subject: [PATCH 27/37] Add SelectMany --- src/dotVariant.Generator/DiagnosedResult.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dotVariant.Generator/DiagnosedResult.cs b/src/dotVariant.Generator/DiagnosedResult.cs index 218e837..542254a 100644 --- a/src/dotVariant.Generator/DiagnosedResult.cs +++ b/src/dotVariant.Generator/DiagnosedResult.cs @@ -81,6 +81,19 @@ public DiagnosedResult WithDiagnostics(ImmutableArray diagno return new(diagnostics, ValueOrDefault!); } + public ImmutableArray> SelectMany(Func> selector) + { + var diagnostics = Diagnostics; + if (HasErrors) + { + // preserve diagnostics + return ImmutableArray.Create(new DiagnosedResult(diagnostics, true, default!)); + } + + var results = selector(ValueOrDefault!); + return results.Select(r => new DiagnosedResult(diagnostics, false, r)).ToImmutableArray(); + } + public bool Equals(DiagnosedResult other) { return HasErrors == other.HasErrors && EqualityComparer.Default.Equals(ValueOrDefault, other.ValueOrDefault); From fc3fcecfb35e3d571bc99bf533433ca977322360 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 22:41:34 +0200 Subject: [PATCH 28/37] Add Select, SelectMany, Combine overloads --- .../ValueProviderExtensions.cs | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/src/dotVariant.Generator/ValueProviderExtensions.cs b/src/dotVariant.Generator/ValueProviderExtensions.cs index e7b7a11..94cf945 100644 --- a/src/dotVariant.Generator/ValueProviderExtensions.cs +++ b/src/dotVariant.Generator/ValueProviderExtensions.cs @@ -63,46 +63,5 @@ public static IncrementalValuesProvider> SelectMany (v, rhs)); }); } - - public static IncrementalValuesProvider> Diagnose( - this IncrementalValuesProvider> source, - Func> diagnose) - { - return IncrementalValueProviderExtensions.Select(source, (result, ct) => - { - if (result.TryGetValue(out var value)) - { - var diagnostics = diagnose(value, ct); - return result.WithDiagnostics(diagnostics); - } - - return result; - }); - } - - public static IncrementalValuesProvider> Diagnose( - this IncrementalValuesProvider source, - Func> diagnose) - { - return IncrementalValueProviderExtensions.Select(source, (value, ct) => - { - var diagnostics = diagnose(value, ct); - return new DiagnosedResult(diagnostics, value); - }); - } - - public static void RegisterSourceOutput(this IncrementalGeneratorInitializationContext context, - IncrementalValuesProvider> source, - Action action) - { - context.RegisterSourceOutput(source, (context, value) => - { - value.Diagnostics.ForEach(context.ReportDiagnostic); - if (value.TryGetValue(out var v)) - { - action(context, v); - } - }); - } } } From 1de740d332672130c71739fffe66ab7df36a0845 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 22:41:59 +0200 Subject: [PATCH 29/37] Move type Symbol and Syntax to SemanticType --- src/dotVariant.Generator/Descriptor.cs | 7 +++---- src/dotVariant.Generator/Diagnose.cs | 6 +++--- src/dotVariant.Generator/VariantDecl.cs | 6 ++++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/dotVariant.Generator/Descriptor.cs b/src/dotVariant.Generator/Descriptor.cs index 63b5262..830fec7 100644 --- a/src/dotVariant.Generator/Descriptor.cs +++ b/src/dotVariant.Generator/Descriptor.cs @@ -17,12 +17,11 @@ public readonly record struct Descriptor( NullableContext NullableContext) { public static Descriptor FromDeclaration( - INamedTypeSymbol type, - TypeDeclarationSyntax syntax, + SemanticType type, NullableContext nullability) { - var options = Inspect.GetOptions(type); - return new(type, syntax, options, nullability); + var options = Inspect.GetOptions(type.Symbol); + return new(type.Symbol, type.Syntax, options, nullability); } public string HintName => $"{Type.ToString() diff --git a/src/dotVariant.Generator/Diagnose.cs b/src/dotVariant.Generator/Diagnose.cs index a9cb301..686e6ce 100644 --- a/src/dotVariant.Generator/Diagnose.cs +++ b/src/dotVariant.Generator/Diagnose.cs @@ -17,11 +17,11 @@ namespace dotVariant.Generator { internal static class Diagnose { - public static IEnumerable Variant(ITypeSymbol type, TypeDeclarationSyntax syntax, CancellationToken token) + public static IEnumerable Variant(SemanticType type, CancellationToken token) => new[] { - CheckType(type, syntax, token), - CheckOptions(type, token), + CheckType(type.Symbol, type.Syntax, token), + CheckOptions(type.Symbol, token), } .Concat(); diff --git a/src/dotVariant.Generator/VariantDecl.cs b/src/dotVariant.Generator/VariantDecl.cs index 68ff825..f3eb665 100644 --- a/src/dotVariant.Generator/VariantDecl.cs +++ b/src/dotVariant.Generator/VariantDecl.cs @@ -10,8 +10,10 @@ namespace dotVariant.Generator; -public readonly record struct VariantDecl(INamedTypeSymbol Symbol, TypeDeclarationSyntax Syntax, - NullableContext Nullable, ImmutableArray Diags) +public readonly record struct SemanticType(INamedTypeSymbol Symbol, TypeDeclarationSyntax Syntax); + +public readonly record struct VariantDecl(SemanticType Type, ImmutableArray NestingTrace, + NullableContext Nullable) { public const string AttributeName = nameof(dotVariant) + "." + nameof(VariantAttribute); } From 6db5393380942b68a00901d1b7db49830e9be8c0 Mon Sep 17 00:00:00 2001 From: Prophet Lamb Date: Sat, 8 Jul 2023 19:57:26 +0200 Subject: [PATCH 30/37] Split diagnostics and evalutation --- src/dotVariant.Generator/DiagnosedResult.cs | 13 ------------- src/dotVariant.Generator/SourceGenerator.cs | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/dotVariant.Generator/DiagnosedResult.cs b/src/dotVariant.Generator/DiagnosedResult.cs index 542254a..9b0d3e3 100644 --- a/src/dotVariant.Generator/DiagnosedResult.cs +++ b/src/dotVariant.Generator/DiagnosedResult.cs @@ -57,19 +57,6 @@ public DiagnosedResult Select(Func selector) return new(Diagnostics, HasErrors, result); } - public ImmutableArray> SelectMany(Func> selector) - { - var diagnostics = Diagnostics; - if (HasErrors) - { - // preserve diagnostics - return ImmutableArray.Create(new DiagnosedResult(diagnostics, true, default!)); - } - - var results = selector(ValueOrDefault!); - return results.Select(r => new DiagnosedResult(diagnostics, false, r)).ToImmutableArray(); - } - public DiagnosedResult WithDiagnostics(ImmutableArray diagnostics) { if (Diagnostics.IsDefaultOrEmpty && diagnostics.IsDefaultOrEmpty) diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index 8771e7b..e07224c 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -47,7 +47,7 @@ or SyntaxKind.StructDeclaration return default; } - var type = new SemanticType(); + var type = new SemanticType(symbol, syntax); var decl = new VariantDecl(type, sema.GetNullableContext(syntax.GetLocation().SourceSpan.Start)); var compInfo = CompilationInfo.FromCompilation(comp); return (decl, compInfo).AsNullable(); From b3010ae01e92ba66ae921cb2389cd73229f1dde1 Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Wed, 21 Jun 2023 23:02:58 +0200 Subject: [PATCH 31/37] Extract SyntaxProvider to separate methods --- src/dotVariant.Generator/DiagnosedResult.cs | 22 ++--- src/dotVariant.Generator/SourceGenerator.cs | 92 ++++++++++++------- .../ValueProviderExtensions.cs | 41 +++++++++ 3 files changed, 113 insertions(+), 42 deletions(-) diff --git a/src/dotVariant.Generator/DiagnosedResult.cs b/src/dotVariant.Generator/DiagnosedResult.cs index 9b0d3e3..218e837 100644 --- a/src/dotVariant.Generator/DiagnosedResult.cs +++ b/src/dotVariant.Generator/DiagnosedResult.cs @@ -57,17 +57,6 @@ public DiagnosedResult Select(Func selector) return new(Diagnostics, HasErrors, result); } - public DiagnosedResult WithDiagnostics(ImmutableArray diagnostics) - { - if (Diagnostics.IsDefaultOrEmpty && diagnostics.IsDefaultOrEmpty) - { - return new(ImmutableArray.Empty, HasErrors, ValueOrDefault); - } - - diagnostics = Diagnostics.IsDefaultOrEmpty ? diagnostics : Diagnostics.AddRange(diagnostics); - return new(diagnostics, ValueOrDefault!); - } - public ImmutableArray> SelectMany(Func> selector) { var diagnostics = Diagnostics; @@ -81,6 +70,17 @@ public ImmutableArray> SelectMany(Func new DiagnosedResult(diagnostics, false, r)).ToImmutableArray(); } + public DiagnosedResult WithDiagnostics(ImmutableArray diagnostics) + { + if (Diagnostics.IsDefaultOrEmpty && diagnostics.IsDefaultOrEmpty) + { + return new(ImmutableArray.Empty, HasErrors, ValueOrDefault); + } + + diagnostics = Diagnostics.IsDefaultOrEmpty ? diagnostics : Diagnostics.AddRange(diagnostics); + return new(diagnostics, ValueOrDefault!); + } + public bool Equals(DiagnosedResult other) { return HasErrors == other.HasErrors && EqualityComparer.Default.Equals(ValueOrDefault, other.ValueOrDefault); diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index e07224c..d20184d 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; namespace dotVariant.Generator { @@ -22,36 +23,10 @@ public sealed class SourceGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext generatorContext) { - var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => - { - return node.Kind() is SyntaxKind.ClassDeclaration - or SyntaxKind.StructDeclaration - or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; - }, (context, ct) => - { - var sema = context.SemanticModel; - if (context.Node is not TypeDeclarationSyntax syntax) - { - return default; - } - if (sema.GetDeclaredSymbol(syntax, ct) is not { } symbol) - { - return default; - } - if (sema.Compilation is not CSharpCompilation comp) - { - return default; - } - if (!symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == VariantDecl.AttributeName)) + var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => NodeIsTypeDeclaration(node), (context, ct) => { - return default; - } - - var type = new SemanticType(symbol, syntax); - var decl = new VariantDecl(type, sema.GetNullableContext(syntax.GetLocation().SourceSpan.Start)); - var compInfo = CompilationInfo.FromCompilation(comp); - return (decl, compInfo).AsNullable(); - }) + return TryCreateCompilationInfo(context, ct); + }) .SelectNotNull() .Diagnose((tuple, ct) => Diagnose.Variant(tuple.decl.Type, ct).ToImmutableArray()); @@ -66,11 +41,16 @@ or SyntaxKind.StructDeclaration { var (source, analyzerOptionProvider) = tuple; var (desc, nested, compInfo) = source; - return (desc.HintName, RenderInfo.FromDescriptor(desc, nested, compInfo, analyzerOptionProvider, ct)); + return (desc.HintName, RenderInfo.FromDescriptor(desc, compInfo, analyzerOptionProvider, ct)); }); - generatorContext.RegisterSourceOutput(renderInfos, (context, tuple) => + generatorContext.RegisterSourceOutput(renderInfos, (context, source) => { + source.Diagnostics.ForEach(context.ReportDiagnostic); + if (!source.TryGetValue(out var tuple)) + { + return; + } var (name, info) = tuple; #if DEBUG RenderInfos.Add(info); @@ -78,5 +58,55 @@ or SyntaxKind.StructDeclaration context.AddSource(name, Renderer.Render(info)); }); } + + private static (VariantDecl decl, CompilationInfo compInfo)? TryCreateCompilationInfo(GeneratorSyntaxContext context, CancellationToken ct) + { + var sema = context.SemanticModel; + if (context.Node is not TypeDeclarationSyntax syntax) + { + return default; + } + + if (sema.GetDeclaredSymbol(syntax, ct) is not { } symbol) + { + return default; + } + + if (sema.Compilation is not CSharpCompilation comp) + { + return default; + } + + if (!symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == VariantDecl.AttributeName)) + { + return default; + } + + var type = new SemanticType(symbol, syntax); + var decl = new VariantDecl(type, CreateNestingTrace(type, sema), + sema.GetNullableContext(syntax.GetLocation().SourceSpan.Start)); + var compInfo = CompilationInfo.FromCompilation(comp); + return (decl, compInfo).AsNullable(); + } + + private static bool NodeIsTypeDeclaration(SyntaxNode node) + { + return node.Kind() is SyntaxKind.ClassDeclaration + or SyntaxKind.StructDeclaration + or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration; + } + + private static ImmutableArray CreateNestingTrace(SemanticType semanticType, SemanticModel semanticModel) + { + var trace = ImmutableArray.CreateBuilder(); + var parent = semanticType.Syntax.Parent; + while (parent is TypeDeclarationSyntax current) + { + trace.Add(new(semanticModel.GetDeclaredSymbol(current)!, current)); + parent = current.Parent; + } + trace.Reverse(); + return trace.ToImmutable(); + } } } diff --git a/src/dotVariant.Generator/ValueProviderExtensions.cs b/src/dotVariant.Generator/ValueProviderExtensions.cs index 94cf945..e7b7a11 100644 --- a/src/dotVariant.Generator/ValueProviderExtensions.cs +++ b/src/dotVariant.Generator/ValueProviderExtensions.cs @@ -63,5 +63,46 @@ public static IncrementalValuesProvider> SelectMany (v, rhs)); }); } + + public static IncrementalValuesProvider> Diagnose( + this IncrementalValuesProvider> source, + Func> diagnose) + { + return IncrementalValueProviderExtensions.Select(source, (result, ct) => + { + if (result.TryGetValue(out var value)) + { + var diagnostics = diagnose(value, ct); + return result.WithDiagnostics(diagnostics); + } + + return result; + }); + } + + public static IncrementalValuesProvider> Diagnose( + this IncrementalValuesProvider source, + Func> diagnose) + { + return IncrementalValueProviderExtensions.Select(source, (value, ct) => + { + var diagnostics = diagnose(value, ct); + return new DiagnosedResult(diagnostics, value); + }); + } + + public static void RegisterSourceOutput(this IncrementalGeneratorInitializationContext context, + IncrementalValuesProvider> source, + Action action) + { + context.RegisterSourceOutput(source, (context, value) => + { + value.Diagnostics.ForEach(context.ReportDiagnostic); + if (value.TryGetValue(out var v)) + { + action(context, v); + } + }); + } } } From 84840fba9ff552dd2649a967c9c4056d2a225d15 Mon Sep 17 00:00:00 2001 From: Prophet Lamb Date: Sat, 8 Jul 2023 20:14:39 +0200 Subject: [PATCH 32/37] Fix naming convention violations --- src/dotVariant.Generator/Diagnose.cs | 8 ++++---- src/dotVariant.Generator/Inspect.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dotVariant.Generator/Diagnose.cs b/src/dotVariant.Generator/Diagnose.cs index 686e6ce..2c4f81a 100644 --- a/src/dotVariant.Generator/Diagnose.cs +++ b/src/dotVariant.Generator/Diagnose.cs @@ -157,14 +157,14 @@ public static IEnumerable NotTooManyOptions( ImmutableArray options, CancellationToken token) { - const int max = byte.MaxValue; + const int Max = byte.MaxValue; - if (options.Count() > max) + if (options.Count() > Max) { yield return MakeError( nameof(NotTooManyOptions), - $"'VariantOf()' must not have more than {max} parameters.", - $"Variant types must not have more than {max} parameters in their 'VariantOf()' method.", + $"'VariantOf()' must not have more than {Max} parameters.", + $"Variant types must not have more than {Max} parameters in their 'VariantOf()' method.", LocationOfFirstToken(syntax, SyntaxKind.IdentifierToken)); } } diff --git a/src/dotVariant.Generator/Inspect.cs b/src/dotVariant.Generator/Inspect.cs index 485430b..6d1e652 100644 --- a/src/dotVariant.Generator/Inspect.cs +++ b/src/dotVariant.Generator/Inspect.cs @@ -99,8 +99,8 @@ public static int NumReferenceFields(IParameterSymbol param) public static bool HasImplicitConversionDisabled(IParameterSymbol param) { - const string attributeName = nameof(dotVariant) + "." + nameof(NoImplicitConversionAttribute); - return param.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == attributeName); + const string AttributeName = nameof(dotVariant) + "." + nameof(NoImplicitConversionAttribute); + return param.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == AttributeName); } public static int NumReferenceFields(ITypeSymbol type) From 15c3c76812ba06d7b698a589cddfd0b216715e11 Mon Sep 17 00:00:00 2001 From: Prophet Lamb Date: Sat, 8 Jul 2023 20:25:47 +0200 Subject: [PATCH 33/37] Collect debug information using DebugInfoCollector --- src/dotVariant.Generator.Test/RenderInfo.cs | 2 +- .../DebugInfoCollector.cs | 36 +++++++++++++++++++ src/dotVariant.Generator/SourceGenerator.cs | 9 +---- 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 src/dotVariant.Generator/DebugInfoCollector.cs diff --git a/src/dotVariant.Generator.Test/RenderInfo.cs b/src/dotVariant.Generator.Test/RenderInfo.cs index 25d6ef4..c0342cc 100644 --- a/src/dotVariant.Generator.Test/RenderInfo.cs +++ b/src/dotVariant.Generator.Test/RenderInfo.cs @@ -26,7 +26,7 @@ private static ImmutableArray GetRenderInfoFromCompilation( .WithUpdatedAnalyzerConfigOptions(new AnalyzerConfigOptionsProvider(msBuildProperties)) .WithUpdatedParseOptions(new CSharpParseOptions(version)); _ = driver.RunGeneratorsAndUpdateCompilation(compilation, out var _, out var _); - return generator.RenderInfos.ToImmutableArray(); + return DebugInfoCollector.TakeRenderInfoList().ToImmutableArray(); } } } diff --git a/src/dotVariant.Generator/DebugInfoCollector.cs b/src/dotVariant.Generator/DebugInfoCollector.cs new file mode 100644 index 0000000..53a3be3 --- /dev/null +++ b/src/dotVariant.Generator/DebugInfoCollector.cs @@ -0,0 +1,36 @@ +// +// Copyright Miro Knejp 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace dotVariant.Generator; + +public static class DebugInfoCollector +{ + private static readonly object _lock = new(); + private static List? _renderInfos; + + [Conditional("DEBUG")] + public static void AddRenderInfo(RenderInfo info) + { + lock (_lock) + { + var renderInfos = _renderInfos ??= new List(); + renderInfos.Add(info); + } + } + + public static List TakeRenderInfoList() + { + lock (_lock) + { + var list = Interlocked.Exchange(ref _renderInfos, null); + return list ?? new List(); + } + } +} diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index d20184d..59d960b 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -7,7 +7,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -17,10 +16,6 @@ namespace dotVariant.Generator [Generator] public sealed class SourceGenerator : IIncrementalGenerator { -#if DEBUG - public List RenderInfos { get; } = new(); -#endif - public void Initialize(IncrementalGeneratorInitializationContext generatorContext) { var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => NodeIsTypeDeclaration(node), (context, ct) => @@ -52,9 +47,7 @@ public void Initialize(IncrementalGeneratorInitializationContext generatorContex return; } var (name, info) = tuple; -#if DEBUG - RenderInfos.Add(info); -#endif + DebugInfoCollector.AddRenderInfo(info); context.AddSource(name, Renderer.Render(info)); }); } From 61fde25cda86a631fcbf6f93b2df7604b08fdc4d Mon Sep 17 00:00:00 2001 From: ProphetLamb Date: Sat, 8 Jul 2023 20:34:26 +0000 Subject: [PATCH 34/37] CreateSyntaxProvider formatting Co-authored-by: Miro Knejp --- src/dotVariant.Generator/SourceGenerator.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dotVariant.Generator/SourceGenerator.cs b/src/dotVariant.Generator/SourceGenerator.cs index 59d960b..801f623 100644 --- a/src/dotVariant.Generator/SourceGenerator.cs +++ b/src/dotVariant.Generator/SourceGenerator.cs @@ -18,10 +18,7 @@ public sealed class SourceGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext generatorContext) { - var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => NodeIsTypeDeclaration(node), (context, ct) => - { - return TryCreateCompilationInfo(context, ct); - }) + var variantDecls = generatorContext.SyntaxProvider.CreateSyntaxProvider((node, ct) => NodeIsTypeDeclaration(node), TryCreateCompilationInfo) .SelectNotNull() .Diagnose((tuple, ct) => Diagnose.Variant(tuple.decl.Type, ct).ToImmutableArray()); From 41c93f32516a370ca52102c5b14587f49584ed14 Mon Sep 17 00:00:00 2001 From: Prophet Lamb Date: Sat, 8 Jul 2023 23:06:32 +0200 Subject: [PATCH 35/37] Add DiagnosedResult tests --- .../DiagnosedResultTest.cs | 59 +++++++++++++++++++ .../dotVariant.Generator.Function.Test.csproj | 26 ++++++++ src/dotVariant.sln | 6 ++ 3 files changed, 91 insertions(+) create mode 100644 src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs create mode 100644 src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj diff --git a/src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs b/src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs new file mode 100644 index 0000000..a5962c6 --- /dev/null +++ b/src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs @@ -0,0 +1,59 @@ +// +// Copyright Miro Knejp 2021. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// + +using dotVariant.Generator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace dotVariant.Generator.Function.Te; + +public sealed class DiagnosedResultTest +{ + [Test] + public void DefaultHasError() + { + DiagnosedResult res = default; + Assert.That(res.HasErrors); + } + + public static IEnumerable ValueEmptyDiagnosticsNotHasErrorsSource => new object[] { 23, }; + + [Test, TestCaseSource(nameof(ValueEmptyDiagnosticsNotHasErrorsSource))] + public void ValueEmptyDiagnosticsNotHasErrors(object expectedValue) + { + DiagnosedResult res = new(expectedValue); + Assert.That(!res.HasErrors); + Assert.That(res.TryGetValue(out var value)); + Assert.That(value, Is.EqualTo(expectedValue)); + } + + + private static Diagnostic CreateDummyDiagnostic(DiagnosticSeverity severity) + { + var severityName = severity.ToString(); + return Diagnostic.Create( + new DiagnosticDescriptor("0", severityName, severityName, severityName, severity, true), + Location.Create("Test", new TextSpan(0, 1), + new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 1)))); + } + + public static IEnumerable HasErrorDiagnosticsHasErrorsSource => + Enum.GetValues().Select(CreateDummyDiagnostic).ToArray(); + + [Test, TestCaseSource(nameof(HasErrorDiagnosticsHasErrorsSource))] + public void ValueHasErrorDiagnosticsHasErrors(Diagnostic diagnostic) + { + var res = default(DiagnosedResult).WithDiagnostics(ImmutableArray.Create(diagnostic)); + var isSeverityError = diagnostic.Severity >= DiagnosticSeverity.Error; + Assert.That(res.HasErrors, Is.EqualTo(isSeverityError)); + Assert.That(res.TryGetValue(out _), Is.EqualTo(!isSeverityError)); + } +} diff --git a/src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj b/src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj new file mode 100644 index 0000000..8322913 --- /dev/null +++ b/src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj @@ -0,0 +1,26 @@ + + + + net5.0 + enable + 11 + dotVariant.Generator.Function.Te + + + + + + + + + + + + + + + + + + + diff --git a/src/dotVariant.sln b/src/dotVariant.sln index 06a40af..5010894 100644 --- a/src/dotVariant.sln +++ b/src/dotVariant.sln @@ -28,6 +28,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ ..\.github\workflows\test.yaml = ..\.github\workflows\test.yaml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotVariant.Generator.Function.Test", "dotVariant.Generator.Function.Test\dotVariant.Generator.Function.Test.csproj", "{42329E06-F134-4254-8089-FC4F121BEF82}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +60,10 @@ Global {1305501C-65AB-4338-848F-3D2B919B7255}.Debug|Any CPU.Build.0 = Debug|Any CPU {1305501C-65AB-4338-848F-3D2B919B7255}.Release|Any CPU.ActiveCfg = Release|Any CPU {1305501C-65AB-4338-848F-3D2B919B7255}.Release|Any CPU.Build.0 = Release|Any CPU + {42329E06-F134-4254-8089-FC4F121BEF82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42329E06-F134-4254-8089-FC4F121BEF82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42329E06-F134-4254-8089-FC4F121BEF82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42329E06-F134-4254-8089-FC4F121BEF82}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From d596a2565d7e70d09c05da99c6a2d24a2c6e67d7 Mon Sep 17 00:00:00 2001 From: Prophet Lamb Date: Sat, 8 Jul 2023 23:07:30 +0200 Subject: [PATCH 36/37] migate dotVariant.Generator.Function.Test to net6.0 --- .../dotVariant.Generator.Function.Test.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj b/src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj index 8322913..b690da5 100644 --- a/src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj +++ b/src/dotVariant.Generator.Function.Test/dotVariant.Generator.Function.Test.csproj @@ -1,10 +1,9 @@ - net5.0 + net6.0 enable 11 - dotVariant.Generator.Function.Te From a6a1d42d79ca85fd76bb6f3a469d9067319c9204 Mon Sep 17 00:00:00 2001 From: Prophet Lamb Date: Sat, 8 Jul 2023 23:07:50 +0200 Subject: [PATCH 37/37] Fix dotVariant.Generator.Function.Test root namespace --- src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs b/src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs index a5962c6..084697b 100644 --- a/src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs +++ b/src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs @@ -13,7 +13,7 @@ using System.Collections.Immutable; using System.Linq; -namespace dotVariant.Generator.Function.Te; +namespace dotVariant.Generator.Function.Test; public sealed class DiagnosedResultTest {