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 22 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 @@ -17,7 +17,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="3.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeStyle" Version="4.4.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</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 @@ -21,7 +21,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 @@ -41,10 +41,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 @@ -57,7 +57,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 @@ -22,12 +22,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 generator.RenderInfos.ToImmutableArray();
}
}
}
16 changes: 9 additions & 7 deletions src/dotVariant.Generator.Test/dotVariant.Generator.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IsExternalInit" Version="1.0.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="System.Interactive" Version="5.0.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
<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>
Expand Down
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);
}
}
9 changes: 8 additions & 1 deletion src/dotVariant.Generator/Descriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace dotVariant.Generator
{
public sealed record Descriptor(
public readonly record struct Descriptor(
INamedTypeSymbol Type,
TypeDeclarationSyntax Syntax,
ImmutableArray<IParameterSymbol> Options,
Expand All @@ -24,5 +24,12 @@ public static Descriptor FromDeclaration(
var options = Inspect.GetOptions(type);
return new(type, syntax, options, nullability);
}

public string HintName => $"{Type.ToString()
// If the contains type parameters replace angle brackets as those are not allowed in AddSource()
ProphetLamb marked this conversation as resolved.
Show resolved Hide resolved
.Replace('<', '{')
.Replace('>', '}')
// Escaped names like @class or @event aren't supported either
.Replace('@', '.')}.cs";
}
}
72 changes: 72 additions & 0 deletions src/dotVariant.Generator/DiagnosedResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// 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 any only if no errors are diagnosed.</summary>
ProphetLamb marked this conversation as resolved.
Show resolved Hide resolved
/// <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> : IEquatable<DiagnosedResult<TValue>>
where TValue: IEquatable<TValue>
{
private readonly bool _noErrors;
mknejp marked this conversation as resolved.
Show resolved Hide resolved

public DiagnosedResult(ImmutableArray<Diagnostic> diagnostics, Func<TValue> valueFactory)
{
_noErrors = !diagnostics.Any(static d => d.Severity >= DiagnosticSeverity.Error);
Diagnostics = diagnostics;
ValueOrDefault = HasErrors ? default : valueFactory();
}

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)
where TResult : IEquatable<TResult>
{
var result = HasErrors ? default : selector(ValueOrDefault!);
return new DiagnosedResult<TResult>(Diagnostics, HasErrors, result);
}

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);
}
}
}
4 changes: 2 additions & 2 deletions src/dotVariant.Generator/Inspect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
29 changes: 13 additions & 16 deletions src/dotVariant.Generator/RenderInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace dotVariant.Generator
/// <param name="Params">Properties of the parameters provided to <c>VariantOf</c>.</param>
/// <param name="Runtime">Information about the .NET runtime we are generating code for.</param>
/// <param name="Variant">Properties of the variant class.</param>
public sealed record RenderInfo(
public readonly record struct RenderInfo(
RenderInfo.LanguageInfo Language,
RenderInfo.OptionsInfo Options,
ImmutableArray<RenderInfo.ParamInfo> Params,
Expand All @@ -37,15 +37,15 @@ public sealed record RenderInfo(
/// <param name="Version">
/// Integer of the form ABB where A=major and BB=minor version of the language (i.e. 703 -> 7.3)
/// </param>
public sealed record LanguageInfo(
public readonly record struct LanguageInfo(
string Nullable,
int Version);

/// <param name="ExtensionClassNamespace">
/// The namespace in which to generate extension method implementations.
/// If <see langword="null"/> use the global namespace.
/// </param>
public sealed record OptionsInfo(
public readonly record struct OptionsInfo(
string? ExtensionClassNamespace);

/// <param name="HasHashCode">
Expand All @@ -54,7 +54,7 @@ public sealed record OptionsInfo(
/// <param name="HasSystemReactiveLinq">
/// <see langword="true"/> if <see cref="System.Reactive.Linq"/> namespace is found.
/// </param>
public sealed record RuntimeInfo(
public readonly record struct RuntimeInfo(
bool HasHashCode,
bool HasSystemReactiveLinq);

Expand Down Expand Up @@ -102,7 +102,7 @@ public sealed record RuntimeInfo(
/// <param name="UserDefined">
/// Contains info about relevant members the user has defined.
/// </param>
public sealed record VariantInfo(
public readonly record struct VariantInfo(
string? Accessibility,
bool CanBeNull,
string DiagType,
Expand All @@ -121,7 +121,7 @@ public sealed record VariantInfo(
/// <param name="Dispose">
/// <see langword="true"/> if a user-defined <see cref="IDisposable.Dispose()"/> exists.
/// </param>
public sealed record UserDefinitions(
public readonly record struct UserDefinitions(
bool Dispose);

/// <param name="Constraints">
Expand All @@ -130,7 +130,7 @@ public sealed record UserDefinitions(
/// <param name="Identifier">
/// Identifier of the generic parameter.
/// </param>
public sealed record GenericInfo(
public readonly record struct GenericInfo(
ImmutableArray<string> Constraints,
string Identifier);
}
Expand Down Expand Up @@ -175,7 +175,7 @@ public sealed record GenericInfo(
/// <param name="Type">
/// The fully qualified name of the type including type parameter list, without nullability annotation.
/// </param>
public sealed record ParamInfo(
public readonly record struct ParamInfo(
bool CanBeNull,
string DiagType,
bool EmitImplicitCast,
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
Loading