Skip to content

Commit bc0f1b0

Browse files
Make config binding gen incremental (#89587) (#92730)
* Make config binding gen incremental * Iterate on implementation * Add incremental tests & driver * Make incremental tests pass and revert functional regression * Address failing tests * Make tests pass * Suppress diagnostic * Address feedback on diag info creation * Refactor member access expr parsing to indicate assumptions * Address feedback & do misc clean up * Adjust model to minimize baseline diff / misc clean up Co-authored-by: Carlos Sánchez López <[email protected]>
1 parent ef6283a commit bc0f1b0

File tree

62 files changed

+2677
-1390
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2677
-1390
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Linq;
6+
using System.Numerics.Hashing;
7+
using Microsoft.CodeAnalysis;
8+
9+
namespace SourceGenerators;
10+
11+
/// <summary>
12+
/// Descriptor for diagnostic instances using structural equality comparison.
13+
/// Provides a work-around for https://github.com/dotnet/roslyn/issues/68291.
14+
/// </summary>
15+
internal readonly struct DiagnosticInfo : IEquatable<DiagnosticInfo>
16+
{
17+
public DiagnosticDescriptor Descriptor { get; private init; }
18+
public object?[] MessageArgs { get; private init; }
19+
public Location? Location { get; private init; }
20+
21+
public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location? location, object?[]? messageArgs)
22+
{
23+
Location? trimmedLocation = location is null ? null : GetTrimmedLocation(location);
24+
25+
return new DiagnosticInfo
26+
{
27+
Descriptor = descriptor,
28+
Location = trimmedLocation,
29+
MessageArgs = messageArgs ?? Array.Empty<object?>()
30+
};
31+
32+
// Creates a copy of the Location instance that does not capture a reference to Compilation.
33+
static Location GetTrimmedLocation(Location location)
34+
=> Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span);
35+
}
36+
37+
public Diagnostic CreateDiagnostic()
38+
=> Diagnostic.Create(Descriptor, Location, MessageArgs);
39+
40+
public override readonly bool Equals(object? obj) => obj is DiagnosticInfo info && Equals(info);
41+
42+
public readonly bool Equals(DiagnosticInfo other)
43+
{
44+
return Descriptor.Equals(other.Descriptor) &&
45+
MessageArgs.SequenceEqual(other.MessageArgs) &&
46+
Location == other.Location;
47+
}
48+
49+
public override readonly int GetHashCode()
50+
{
51+
int hashCode = Descriptor.GetHashCode();
52+
foreach (object? messageArg in MessageArgs)
53+
{
54+
hashCode = HashHelpers.Combine(hashCode, messageArg?.GetHashCode() ?? 0);
55+
}
56+
57+
hashCode = HashHelpers.Combine(hashCode, Location?.GetHashCode() ?? 0);
58+
return hashCode;
59+
}
60+
}

src/libraries/System.Text.Json/gen/Helpers/ImmutableEquatableArray.cs renamed to src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections;
56
using System.Collections.Generic;
67
using System.Linq;
78
using System.Numerics.Hashing;
89

9-
namespace System.Text.Json.SourceGeneration
10+
namespace SourceGenerators
1011
{
1112
/// <summary>
1213
/// Provides an immutable list implementation which implements sequence equality.
@@ -72,15 +73,9 @@ public bool MoveNext()
7273
}
7374
}
7475

75-
public static class ImmutableEquatableArray
76+
internal static class ImmutableEquatableArray
7677
{
77-
public static ImmutableEquatableArray<T> Empty<T>() where T : IEquatable<T>
78-
=> ImmutableEquatableArray<T>.Empty;
79-
8078
public static ImmutableEquatableArray<T> ToImmutableEquatableArray<T>(this IEnumerable<T> values) where T : IEquatable<T>
8179
=> new(values);
82-
83-
public static ImmutableEquatableArray<T> Create<T>(params T[] values) where T : IEquatable<T>
84-
=> values is { Length: > 0 } ? new(values) : ImmutableEquatableArray<T>.Empty;
8580
}
8681
}

src/libraries/Common/src/SourceGenerators/TypeModelHelper.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using Microsoft.CodeAnalysis;
55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
7+
using System.Linq;
68

79
namespace SourceGenerators
810
{
@@ -32,5 +34,7 @@ void TraverseContainingTypes(INamedTypeSymbol current)
3234
}
3335
}
3436
}
37+
38+
public static string GetFullyQualifiedName(this ITypeSymbol type) => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
3539
}
3640
}

src/libraries/System.Text.Json/gen/Model/TypeRef.cs renamed to src/libraries/Common/src/SourceGenerators/TypeRef.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Diagnostics;
56
using Microsoft.CodeAnalysis;
67

7-
namespace System.Text.Json.SourceGeneration
8+
namespace SourceGenerators
89
{
910
/// <summary>
1011
/// An equatable value representing type identity.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Reflection;
9+
using Xunit;
10+
11+
namespace SourceGenerators.Tests
12+
{
13+
internal static class GeneratorTestHelpers
14+
{
15+
/// <summary>
16+
/// Asserts for structural equality, returning a path to the mismatching data when not equal.
17+
/// </summary>
18+
public static void AssertStructurallyEqual<T>(T expected, T actual)
19+
{
20+
CheckAreEqualCore(expected, actual, new());
21+
static void CheckAreEqualCore(object expected, object actual, Stack<string> path)
22+
{
23+
if (expected is null || actual is null)
24+
{
25+
if (expected is not null || actual is not null)
26+
{
27+
FailNotEqual();
28+
}
29+
30+
return;
31+
}
32+
33+
Type type = expected.GetType();
34+
if (type != actual.GetType())
35+
{
36+
FailNotEqual();
37+
return;
38+
}
39+
40+
if (expected is IEnumerable leftCollection)
41+
{
42+
if (actual is not IEnumerable rightCollection)
43+
{
44+
FailNotEqual();
45+
return;
46+
}
47+
48+
object?[] expectedValues = leftCollection.Cast<object?>().ToArray();
49+
object?[] actualValues = rightCollection.Cast<object?>().ToArray();
50+
51+
for (int i = 0; i < Math.Max(expectedValues.Length, actualValues.Length); i++)
52+
{
53+
object? expectedElement = i < expectedValues.Length ? expectedValues[i] : "<end of collection>";
54+
object? actualElement = i < actualValues.Length ? actualValues[i] : "<end of collection>";
55+
56+
path.Push($"[{i}]");
57+
CheckAreEqualCore(expectedElement, actualElement, path);
58+
path.Pop();
59+
}
60+
}
61+
62+
if (type.GetProperty("EqualityContract", BindingFlags.Instance | BindingFlags.NonPublic, null, returnType: typeof(Type), types: Array.Empty<Type>(), null) != null)
63+
{
64+
// Type is a C# record, run pointwise equality comparison.
65+
foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
66+
{
67+
path.Push("." + property.Name);
68+
CheckAreEqualCore(property.GetValue(expected), property.GetValue(actual), path);
69+
path.Pop();
70+
}
71+
72+
return;
73+
}
74+
75+
if (!expected.Equals(actual))
76+
{
77+
FailNotEqual();
78+
}
79+
80+
void FailNotEqual() => Assert.Fail($"Value not equal in ${string.Join("", path.Reverse())}: expected {expected}, but was {actual}.");
81+
}
82+
}
83+
}
84+
}

src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Immutable;
54
using Microsoft.CodeAnalysis;
65
using SourceGenerators;
76

@@ -11,19 +10,22 @@ public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerato
1110
{
1211
private sealed partial class Emitter
1312
{
14-
private readonly SourceProductionContext _context;
15-
private readonly SourceGenerationSpec _sourceGenSpec;
13+
private readonly InterceptorInfo _interceptorInfo;
14+
private readonly BindingHelperInfo _bindingHelperInfo;
15+
private readonly TypeIndex _typeIndex;
16+
1617
private readonly SourceWriter _writer = new();
1718

18-
public Emitter(SourceProductionContext context, SourceGenerationSpec sourceGenSpec)
19+
public Emitter(SourceGenerationSpec sourceGenSpec)
1920
{
20-
_context = context;
21-
_sourceGenSpec = sourceGenSpec;
21+
_interceptorInfo = sourceGenSpec.InterceptorInfo;
22+
_bindingHelperInfo = sourceGenSpec.BindingHelperInfo;
23+
_typeIndex = new TypeIndex(sourceGenSpec.ConfigTypes);
2224
}
2325

24-
public void Emit()
26+
public void Emit(SourceProductionContext context)
2527
{
26-
if (!ShouldEmitBindingExtensions())
28+
if (!ShouldEmitMethods(MethodsToGen.Any))
2729
{
2830
return;
2931
}
@@ -52,7 +54,7 @@ file static class {{Identifier.BindingExtensions}}
5254

5355
EmitEndBlock(); // Binding namespace.
5456

55-
_context.AddSource($"{Identifier.BindingExtensions}.g.cs", _writer.ToSourceText());
57+
context.AddSource($"{Identifier.BindingExtensions}.g.cs", _writer.ToSourceText());
5658
}
5759

5860
private void EmitInterceptsLocationAttrDecl()
@@ -79,7 +81,7 @@ public InterceptsLocationAttribute(string filePath, int line, int column)
7981

8082
private void EmitUsingStatements()
8183
{
82-
foreach (string @namespace in _sourceGenSpec.Namespaces.ToImmutableSortedSet())
84+
foreach (string @namespace in _bindingHelperInfo.Namespaces)
8385
{
8486
_writer.WriteLine($"using {@namespace};");
8587
}

0 commit comments

Comments
 (0)