Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to IIncrementalSourceGenerator #57

Open
wants to merge 39 commits into
base: stable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
307b0fa
Bump dependencies to latest stable version
ProphetLamb Jun 20, 2023
3093850
Bump LangVersion to 11.0
ProphetLamb Jun 20, 2023
69902ef
Bump remaining dependencies
ProphetLamb Jun 20, 2023
60dd991
Migrate to IIncrementalSourceGenerator
ProphetLamb Jun 20, 2023
0c56caa
Downgrade Microsoft.CodeAnalysis.CSharp to 4.4.0 for compatibility
ProphetLamb Jun 20, 2023
881a7d9
Add Verify for SourceGenerator testing
ProphetLamb Jun 20, 2023
f381bb2
EnforceExtendedAnalyzerRules
ProphetLamb Jun 20, 2023
ba586f6
Use namespace qualitfied metadata name
ProphetLamb Jun 20, 2023
357a4c4
Migrate test solution to IIncrementalSourceGenerator
ProphetLamb Jun 20, 2023
316c316
Hack: metadata output for debug builds
ProphetLamb Jun 20, 2023
2287673
Suppress nullable warning
ProphetLamb Jun 21, 2023
f17045d
Bump test from net45 to net 452
ProphetLamb Jun 21, 2023
3be87c5
Make models ValueTypes
ProphetLamb Jun 21, 2023
a527984
Implement diagnosed result sum type
ProphetLamb Jun 21, 2023
5cf5bf7
IDisposable via runtime typename
ProphetLamb Jun 21, 2023
e453d22
Collect diagnostics into array
ProphetLamb Jun 21, 2023
dd8f463
Add Nullable struct extensions to value providers
ProphetLamb Jun 21, 2023
04dce5c
Use diagnosedresult type to carry diagnostics
ProphetLamb Jun 21, 2023
025bbbb
Fix HintName generation
ProphetLamb Jun 21, 2023
2a56501
Force DiagnosedResult value IEquatable
ProphetLamb Jun 21, 2023
72c2dd4
Apply rename HintName
ProphetLamb Jun 21, 2023
09d8830
Make models structs
ProphetLamb Jun 21, 2023
3623b7d
Update src/dotVariant.Generator/Descriptor.cs
ProphetLamb Jul 8, 2023
db60983
Update src/dotVariant.Generator/DiagnosedResult.cs
ProphetLamb Jul 8, 2023
3b17024
Remove `declarationBloom`
ProphetLamb Jul 8, 2023
947724b
Split diagnostics and evaluation
ProphetLamb Jun 21, 2023
0093bb8
Add SelectMany
ProphetLamb Jun 21, 2023
fc3fcec
Add Select, SelectMany, Combine overloads
ProphetLamb Jun 21, 2023
1de740d
Move type Symbol and Syntax to SemanticType
ProphetLamb Jun 21, 2023
6db5393
Split diagnostics and evalutation
ProphetLamb Jul 8, 2023
b3010ae
Extract SyntaxProvider to separate methods
ProphetLamb Jun 21, 2023
84840fb
Fix naming convention violations
ProphetLamb Jul 8, 2023
15c3c76
Collect debug information using DebugInfoCollector
ProphetLamb Jul 8, 2023
61fde25
CreateSyntaxProvider formatting
ProphetLamb Jul 8, 2023
d1feb6b
Merge branch 'stable' into feature/incremental-generator
mknejp Jul 8, 2023
41c93f3
Add DiagnosedResult tests
ProphetLamb Jul 8, 2023
d596a25
migate dotVariant.Generator.Function.Test to net6.0
ProphetLamb Jul 8, 2023
a6a1d42
Fix dotVariant.Generator.Function.Test root namespace
ProphetLamb Jul 8, 2023
a92e45e
Merge branch 'stable' into feature/incremental-generator
mknejp Jul 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="4.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="4.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
59 changes: 59 additions & 0 deletions src/dotVariant.Generator.Function.Test/DiagnosedResultTest.cs
Original file line number Diff line number Diff line change
@@ -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.Test;

public sealed class DiagnosedResultTest
{
[Test]
public void DefaultHasError()
{
DiagnosedResult<int> res = default;
Assert.That(res.HasErrors);
}

public static IEnumerable<object> ValueEmptyDiagnosticsNotHasErrorsSource => new object[] { 23, };

[Test, TestCaseSource(nameof(ValueEmptyDiagnosticsNotHasErrorsSource))]
public void ValueEmptyDiagnosticsNotHasErrors(object expectedValue)
{
DiagnosedResult<object> 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<Diagnostic> HasErrorDiagnosticsHasErrorsSource =>
Enum.GetValues<DiagnosticSeverity>().Select(CreateDummyDiagnostic).ToArray();

[Test, TestCaseSource(nameof(HasErrorDiagnosticsHasErrorsSource))]
public void ValueHasErrorDiagnosticsHasErrors(Diagnostic diagnostic)
{
var res = default(DiagnosedResult<object>).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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>11</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IsExternalInit" Version="1.0.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="System.Interactive" Version="6.0.1" />
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="Verify.NUnit" Version="20.4.0" />
<PackageReference Include="Verify.SourceGenerators" Version="2.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\dotVariant.Generator\dotVariant.Generator.csproj" />
</ItemGroup>

</Project>
8 changes: 4 additions & 4 deletions src/dotVariant.Generator.Test/GeneratorTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace dotVariant.Generator.Test
{
internal static class GeneratorTools
{
public static Dictionary<string, string> GetGeneratedOutput(IDictionary<string, string> sources, Func<ISourceGenerator> makeGenerator, bool failOnInvalidSource = false)
public static Dictionary<string, string> GetGeneratedOutput(IDictionary<string, string> sources, Func<IIncrementalGenerator> makeGenerator, bool failOnInvalidSource = false)
{
var compilation = Compile(sources);

Expand All @@ -39,10 +39,10 @@ public static Dictionary<string, string> GetGeneratedOutput(IDictionary<string,
}

public static Dictionary<string, string> GetGeneratedOutput<TGenerator>(IDictionary<string, string> sources, bool failOnInvalidSource = false)
where TGenerator : ISourceGenerator, new()
where TGenerator : IIncrementalGenerator, new()
=> GetGeneratedOutput(sources, () => new TGenerator(), failOnInvalidSource);

public static ImmutableArray<Diagnostic> GetGeneratorDiagnostics(IDictionary<string, string> sources, Func<ISourceGenerator> makeGenerator)
public static ImmutableArray<Diagnostic> GetGeneratorDiagnostics(IDictionary<string, string> sources, Func<IIncrementalGenerator> makeGenerator)
{
var compilation = Compile(sources);

Expand All @@ -55,7 +55,7 @@ public static ImmutableArray<Diagnostic> GetGeneratorDiagnostics(IDictionary<str
}

public static ImmutableArray<Diagnostic> GetGeneratorDiagnostics<TGenerator>(IDictionary<string, string> sources)
where TGenerator : ISourceGenerator, new()
where TGenerator : IIncrementalGenerator, new()
=> GetGeneratorDiagnostics(sources, () => new TGenerator());

public static CSharpCompilation Compile(
Expand Down
9 changes: 4 additions & 5 deletions src/dotVariant.Generator.Test/RenderInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ private static ImmutableArray<RenderInfo> 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 DebugInfoCollector.TakeRenderInfoList().ToImmutableArray();
}
}
}
23 changes: 23 additions & 0 deletions src/dotVariant.Generator/CompilationInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// 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;
using System;

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(typeof(IDisposable).FullName)!;
var hasHashCode = compilation.GetTypeByMetadataName("System.HashCode") is not null;
return new(compilation.LanguageVersion, hasReactive, disposableInterface, hasHashCode);
}
}
36 changes: 36 additions & 0 deletions src/dotVariant.Generator/DebugInfoCollector.cs
Original file line number Diff line number Diff line change
@@ -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<RenderInfo>? _renderInfos;

[Conditional("DEBUG")]
public static void AddRenderInfo(RenderInfo info)
{
lock (_lock)
{
var renderInfos = _renderInfos ??= new List<RenderInfo>();
renderInfos.Add(info);
}
}

public static List<RenderInfo> TakeRenderInfoList()
{
lock (_lock)
{
var list = Interlocked.Exchange(ref _renderInfos, null);
return list ?? new List<RenderInfo>();
}
}
}
16 changes: 11 additions & 5 deletions src/dotVariant.Generator/Descriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@

namespace dotVariant.Generator
{
public sealed record Descriptor(
public readonly record struct Descriptor(
INamedTypeSymbol Type,
TypeDeclarationSyntax Syntax,
ImmutableArray<IParameterSymbol> Options,
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()
// 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
.Replace('@', '.')}.cs";
}
}
14 changes: 7 additions & 7 deletions src/dotVariant.Generator/Diagnose.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ namespace dotVariant.Generator
{
internal static class Diagnose
{
public static IEnumerable<Diagnostic> Variant(ITypeSymbol type, TypeDeclarationSyntax syntax, CancellationToken token)
public static IEnumerable<Diagnostic> Variant(SemanticType type, CancellationToken token)
=> new[]
{
CheckType(type, syntax, token),
CheckOptions(type, token),
CheckType(type.Symbol, type.Syntax, token),
CheckOptions(type.Symbol, token),
}
.Concat();

Expand Down Expand Up @@ -155,14 +155,14 @@ public static IEnumerable<Diagnostic> NotTooManyOptions(
ImmutableArray<IParameterSymbol> 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));
}
}
Expand Down
114 changes: 114 additions & 0 deletions src/dotVariant.Generator/DiagnosedResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// 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;

/// <summary>A result type carrying <see cref="Diagnostic"/> values and a <typeparamref name="TValue"/> if and only if no errors are diagnosed.</summary>
/// <typeparam name="TValue">The type of the value</typeparam>
/// <remarks>Diagnosed result ignores diagnostics during equality comparison to improve caching consistency.</remarks>
public readonly struct DiagnosedResult<TValue>
{
private readonly bool _noErrors;
mknejp marked this conversation as resolved.
Show resolved Hide resolved

public DiagnosedResult(ImmutableArray<Diagnostic> diagnostics, TValue value)
{
_noErrors = !diagnostics.HasErrors();
Diagnostics = diagnostics;
ValueOrDefault = HasErrors ? default : value;
}

public DiagnosedResult(TValue value)
{
_noErrors = true;
Diagnostics = ImmutableArray<Diagnostic>.Empty;
ValueOrDefault = value;
}

private DiagnosedResult(ImmutableArray<Diagnostic> diagnostics, bool hasErrors, TValue? valueOrDefault)
{
_noErrors = !hasErrors;
Diagnostics = diagnostics;
ValueOrDefault = valueOrDefault;
}

public readonly ImmutableArray<Diagnostic> Diagnostics;
public readonly TValue? ValueOrDefault;

public bool HasErrors => !_noErrors;

public bool TryGetValue(out TValue value)
{
value = ValueOrDefault!;
return _noErrors;
}

public DiagnosedResult<TResult> Select<TResult>(Func<TValue, TResult> selector)
{
var result = HasErrors ? default : selector(ValueOrDefault!);
return new(Diagnostics, HasErrors, result);
}

public ImmutableArray<DiagnosedResult<TResult>> SelectMany<TResult>(Func<TValue, ImmutableArray<TResult>> selector)
{
var diagnostics = Diagnostics;
if (HasErrors)
{
// preserve diagnostics
return ImmutableArray.Create(new DiagnosedResult<TResult>(diagnostics, true, default!));
}

var results = selector(ValueOrDefault!);
return results.Select(r => new DiagnosedResult<TResult>(diagnostics, false, r)).ToImmutableArray();
}

public DiagnosedResult<TValue> WithDiagnostics(ImmutableArray<Diagnostic> diagnostics)
{
if (Diagnostics.IsDefaultOrEmpty && diagnostics.IsDefaultOrEmpty)
{
return new(ImmutableArray<Diagnostic>.Empty, HasErrors, ValueOrDefault);
}

diagnostics = Diagnostics.IsDefaultOrEmpty ? diagnostics : Diagnostics.AddRange(diagnostics);
return new(diagnostics, ValueOrDefault!);
}

public bool Equals(DiagnosedResult<TValue> other)
{
return HasErrors == other.HasErrors && EqualityComparer<TValue?>.Default.Equals(ValueOrDefault, other.ValueOrDefault);
}

public override bool Equals(object? obj)
{
return obj is DiagnosedResult<TValue> other && Equals(other);
}

public override int GetHashCode()
{
unchecked
{
return HasErrors ? 1337 : EqualityComparer<TValue?>.Default.GetHashCode(ValueOrDefault);
}
}
}

public static class DiagnosedResultExtensions
{
public static DiagnosedResult<TValue> AsDiagnosedResult<TValue>(this TValue value)
{
return new(value);
}

public static bool HasErrors(this ImmutableArray<Diagnostic> diagnostics)
{
return diagnostics.Any(static d => d.Severity >= DiagnosticSeverity.Error);
}
}
Loading
Loading