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

Add an experimental API for testing incremental steps. #1094

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
2 changes: 2 additions & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@
<MicrosoftVisualStudioProjectSystemSDKToolsVersion>17.3.195-pre</MicrosoftVisualStudioProjectSystemSDKToolsVersion>
<!-- Libs -->
<DiffPlexVersion>1.5.0</DiffPlexVersion>
<HumanizerCoreVersion>2.14.1</HumanizerCoreVersion>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 I would prefer to remove this dependency before merge, but that's further down the line. I'm mostly focusing on the API itself right now.

<SystemNetHttpVersion>4.3.4</SystemNetHttpVersion>
<!-- Testing -->
<MicrosoftCodeAnalysis2PrimaryTestVersion>2.6.1</MicrosoftCodeAnalysis2PrimaryTestVersion>
<MicrosoftCodeAnalysis3PrimaryTestVersion>3.9.0</MicrosoftCodeAnalysis3PrimaryTestVersion>
<MicrosoftCodeAnalysis4PrimaryTestVersion>4.4.0</MicrosoftCodeAnalysis4PrimaryTestVersion>
<MicrosoftCodeAnalysisTestingVersion>1.0.1-beta1.20374.2</MicrosoftCodeAnalysisTestingVersion>
<xunitassertVersion>$(xunitVersion)</xunitassertVersion>
<XunitCombinatorialVersion>1.2.7</XunitCombinatorialVersion>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;

namespace Microsoft.CodeAnalysis.Testing
{
public class IncrementalGeneratorExpectedState
{
internal Dictionary<string, List<IncrementalGeneratorExpectedStepState>> ExpectedStepStates { get; } = new Dictionary<string, List<IncrementalGeneratorExpectedStepState>>();

public List<IncrementalGeneratorExpectedStepState> this[string stepName]
{
get
{
return ExpectedStepStates.GetOrAdd(stepName, () => new List<IncrementalGeneratorExpectedStepState>());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;

namespace Microsoft.CodeAnalysis.Testing
{
public class IncrementalGeneratorExpectedStepState
{
public List<IncrementalStepExpectedRunReason> InputRunReasons { get; } = new List<IncrementalStepExpectedRunReason>();

public List<IncrementalStepExpectedRunReason> OutputRunReasons { get; } = new List<IncrementalStepExpectedRunReason>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace Microsoft.CodeAnalysis.Testing
{
/// <summary>
/// The state of the output of a given executed incremental source generator step.
/// </summary>
public enum IncrementalStepExpectedRunReason
{
/// <summary>
/// The output of this step is a new output produced from a new input.
/// </summary>
New,

/// <summary>
/// The input to this step was modified from a previous run, and it produced a different value than the previous run.
/// </summary>
Modified,

/// <summary>
/// The input to this step was modified from a previous run, but it produced an equal value to the previous run.
/// </summary>
Unchanged,

/// <summary>
/// The output of this step was pulled from this step's cache since the inputs was unchanged from the previous run.
/// </summary>
Cached,

/// <summary>
/// The input that this output is generated from was removed from the input step's outputs, so this value will be removed from the output step results.
/// </summary>
Removed,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,62 @@ public static Func<T, TResult> CreatePropertyAccessor<T, TResult>(Type? type, st
return expression.Compile();
}

/// <summary>
/// Generates a compiled accessor method for a field which cannot be bound at compile time.
/// </summary>
/// <typeparam name="T">The compile-time type representing the instance on which the field is defined. This
/// may be a superclass of the actual type on which the field is declared if the declaring type is not
/// available at compile time.</typeparam>
/// <typeparam name="TResult">The compile-type type representing the result of the field. This may be a
/// superclass of the actual type of the field if the field type is not available at compile
/// time.</typeparam>
/// <param name="type">The runtime time on which the field is defined. If this value is null, the runtime
/// time is assumed to not exist, and a fallback accessor returning <paramref name="defaultValue"/> will be
/// generated.</param>
/// <param name="fieldName">The name of the field to access.</param>
/// <param name="defaultValue">The value to return if the field is not available at runtime.</param>
/// <returns>An accessor method to access the specified runtime field.</returns>
public static Func<T, TResult> CreateFieldAccessor<T, TResult>(Type? type, string fieldName, TResult defaultValue)
{
if (fieldName is null)
{
throw new ArgumentNullException(nameof(fieldName));
}

if (type == null)
{
return CreateFallbackAccessor<T, TResult>(defaultValue);
}

if (!typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
{
throw new InvalidOperationException($"Type '{type}' is not assignable to type '{typeof(T)}'");
}

var field = type.GetTypeInfo().GetDeclaredField(fieldName);
if (field == null)
{
return CreateFallbackAccessor<T, TResult>(defaultValue);
}

if (!typeof(TResult).GetTypeInfo().IsAssignableFrom(field.FieldType.GetTypeInfo()))
{
throw new InvalidOperationException($"Property '{field}' produces a value of type '{field.FieldType}', which is not assignable to type '{typeof(TResult)}'");
}

var parameter = Expression.Parameter(typeof(T), GenerateParameterName(typeof(T)));
Expression instance =
type.GetTypeInfo().IsAssignableFrom(typeof(T).GetTypeInfo())
? (Expression)parameter
: Expression.Convert(parameter, type);

Expression<Func<T, TResult>> expression =
Expression.Lambda<Func<T, TResult>>(
Expression.Convert(Expression.Field(instance, field), typeof(TResult)),
parameter);
return expression.Compile();
}

private static string GenerateParameterName(Type parameterType)
{
var typeName = parameterType.Name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,6 @@

<!-- Use PrivateAssets=compile to avoid exposing our DiffPlex dependency downstream as public API. -->
<PackageReference Include="DiffPlex" Version="$(DiffPlexVersion)" PrivateAssets="compile" />
<PackageReference Include="Humanizer.Core" Version="$(HumanizerCoreVersion)" PrivateAssets="compile" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.ExpectedDiagnostics.get -
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.FormatVerifierMessage(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer> analyzers, Microsoft.CodeAnalysis.Diagnostic actual, Microsoft.CodeAnalysis.Testing.DiagnosticResult expected, string message) -> string
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.GetNameAndFoldersFromPath(string projectPathPrefix, string path) -> (string fileName, System.Collections.Generic.IEnumerable<string> folders)
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.GetSortedDiagnosticsAsync(Microsoft.CodeAnalysis.Solution solution, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer> analyzers, System.Collections.Immutable.ImmutableArray<(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.Diagnostic diagnostic)> additionalDiagnostics, Microsoft.CodeAnalysis.Testing.CompilerDiagnostics compilerDiagnostics, Microsoft.CodeAnalysis.Testing.IVerifier verifier, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<System.Collections.Immutable.ImmutableArray<(Microsoft.CodeAnalysis.Project project, Microsoft.CodeAnalysis.Diagnostic diagnostic)>>
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.IncrementalGeneratorStates.get -> System.Collections.Generic.Dictionary<System.Type, Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedState>
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.IncrementalGeneratorTransforms.get -> System.Collections.Generic.List<System.Func<Microsoft.CodeAnalysis.Solution, Microsoft.CodeAnalysis.ProjectId, Microsoft.CodeAnalysis.Solution>>
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.MarkupOptions.get -> Microsoft.CodeAnalysis.Testing.MarkupOptions
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.MarkupOptions.set -> void
Microsoft.CodeAnalysis.Testing.AnalyzerTest<TVerifier>.MatchDiagnosticsTimeout.get -> System.TimeSpan
Expand Down Expand Up @@ -111,6 +113,19 @@ Microsoft.CodeAnalysis.Testing.IVerifier.PushContext(string context) -> Microsof
Microsoft.CodeAnalysis.Testing.IVerifier.SequenceEqual<T>(System.Collections.Generic.IEnumerable<T> expected, System.Collections.Generic.IEnumerable<T> actual, System.Collections.Generic.IEqualityComparer<T> equalityComparer = null, string message = null) -> void
Microsoft.CodeAnalysis.Testing.IVerifier.True(bool assert, string message = null) -> void
Microsoft.CodeAnalysis.Testing.IVerifierExtensions
Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedState
Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedState.IncrementalGeneratorExpectedState() -> void
Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedState.this[string stepName].get -> System.Collections.Generic.List<Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedStepState>
Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedStepState
Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedStepState.IncrementalGeneratorExpectedStepState() -> void
Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedStepState.InputRunReasons.get -> System.Collections.Generic.List<Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason>
Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedStepState.OutputRunReasons.get -> System.Collections.Generic.List<Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason>
Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason
Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason.Cached = 3 -> Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason
Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason.Modified = 1 -> Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason
Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason.New = 0 -> Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason
Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason.Removed = 4 -> Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason
Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason.Unchanged = 2 -> Microsoft.CodeAnalysis.Testing.IncrementalStepExpectedRunReason
Microsoft.CodeAnalysis.Testing.MarkupMode
Microsoft.CodeAnalysis.Testing.MarkupMode.Allow = 3 -> Microsoft.CodeAnalysis.Testing.MarkupMode
Microsoft.CodeAnalysis.Testing.MarkupMode.Ignore = 1 -> Microsoft.CodeAnalysis.Testing.MarkupMode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Testing;

Expand All @@ -27,5 +28,19 @@ protected override CompilationOptions CreateCompilationOptions()

protected override ParseOptions CreateParseOptions()
=> new CSharpParseOptions(DefaultLanguageVersion, DocumentationMode.Diagnose);

public IncrementalGeneratorExpectedState IncrementalGeneratorState
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 I think we can remove this in favor of changing the type of IncrementalGeneratorStepStates to a new type derived from Dictionary<Type, IncrementalGeneratorExpectedState> that provides a new indexer that creates the value on demand. In practice in means you'd have to type this, but it seems acceptable:

IncrementalGeneratorStepStates =
{
    [typeof(TSourceGenerator)] =
    {
        ExpectedStepStates =
        {
            // ...
        },
    },
},

{
get
{
if (!IncrementalGeneratorStates.TryGetValue(typeof(TSourceGenerator), out var value))
{
value = new IncrementalGeneratorExpectedState();
IncrementalGeneratorStates.Add(typeof(TSourceGenerator), value);
}

return value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>
Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.CSharpSourceGeneratorTest() -> void
Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.IncrementalGeneratorState.get -> Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedState
Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorVerifier<TSourceGenerator, TVerifier>
Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorVerifier<TSourceGenerator, TVerifier>.CSharpSourceGeneratorVerifier() -> void
override Microsoft.CodeAnalysis.CSharp.Testing.CSharpSourceGeneratorTest<TSourceGenerator, TVerifier>.CreateCompilationOptions() -> Microsoft.CodeAnalysis.CompilationOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier)
Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).IncrementalGeneratorState() -> Microsoft.CodeAnalysis.Testing.IncrementalGeneratorExpectedState
Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorTest(Of TSourceGenerator, TVerifier).New() -> Void
Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, TVerifier)
Microsoft.CodeAnalysis.VisualBasic.Testing.VisualBasicSourceGeneratorVerifier(Of TSourceGenerator, TVerifier).New() -> Void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,16 @@ Public Class VisualBasicSourceGeneratorTest(Of TSourceGenerator As New, TVerifie
Protected Overrides Function GetSourceGenerators() As IEnumerable(Of Type)
Return New Type() {GetType(TSourceGenerator)}
End Function

Public ReadOnly Property IncrementalGeneratorState As IncrementalGeneratorExpectedState
Get
Dim state As IncrementalGeneratorExpectedState = Nothing

If Not IncrementalGeneratorStates.TryGetValue(GetType(TSourceGenerator), state) Then
state = New IncrementalGeneratorExpectedState
IncrementalGeneratorStates.Add(GetType(TSourceGenerator), state)
End If
Return state
End Get
End Property
End Class
4 changes: 2 additions & 2 deletions tests/Microsoft.CodeAnalysis.Testing/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<PropertyGroup>
<TestTargetFrameworks>netcoreapp3.1;net472</TestTargetFrameworks>
<SourceGeneratorTestTargetFrameworks>netcoreapp3.1;net472</SourceGeneratorTestTargetFrameworks>
<TestTargetFrameworks>netcoreapp3.1;net472;net6.0</TestTargetFrameworks>
<SourceGeneratorTestTargetFrameworks>netcoreapp3.1;net472;net6.0</SourceGeneratorTestTargetFrameworks>

<!-- Workaround dependencies that do not yet support netcoreapp3.1 https://github.com/dotnet/roslyn/issues/45114 -->
<NoWarn>NU1701;$(NoWarn)</NoWarn>
Expand Down
Loading