Skip to content

Commit 55e0dea

Browse files
authored
Avoid caching compilation data and use value equality for SyntaxNodes (#79051)
Fixes #78242 Creates separate types for InteropAttributeData: one that holds compilation data (InteropAttributeCompilationData), and one that holds only the data necessary for the model to create the generated code (InteropAttributeModelData). This uncovered some issues with record equality in records that use SyntaxNode. For those, we need to override Equals or wrap the SyntaxNode in a type that overrides Equals to use IsEquivalentTo on the SyntaxNode. There are probably more places where we use SyntaxNode that aren't caught in the current tests. To make sure every record has the right equality, I wasn't sure if it would be better to override Equals for each of the records, or create a wrapper record struct for each SyntaxNode that implements the equality we want (and implicit casts to and from the SyntaxNode). Then we wouldn't have to explicitly override the equality in each record that has a SyntaxNode. I also overrode both Equals and GetHashCode, but I'm not confident in my GetHashCode implementation. It could also be done with IEquatable.Equals without needing GetHashCode, but that would require implementing the TypeSyntax equality for every type that inherits from ManagedTypeInfo.
1 parent 795fcec commit 55e0dea

File tree

10 files changed

+86
-33
lines changed

10 files changed

+86
-33
lines changed

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodIndexData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Microsoft.Interop
88
/// <summary>
99
/// VirtualMethodIndexAttribute data
1010
/// </summary>
11-
internal sealed record VirtualMethodIndexData(int Index) : InteropAttributeData
11+
internal sealed record VirtualMethodIndexData(int Index) : InteropAttributeCompilationData
1212
{
1313
public bool ImplicitThisParameter { get; init; }
1414

src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Microsoft.Interop
88
{
99
internal static class DefaultMarshallingInfoParser
1010
{
11-
public static MarshallingInfoParser Create(StubEnvironment env, IGeneratorDiagnostics diagnostics, IMethodSymbol method, InteropAttributeData interopAttributeData, AttributeData unparsedAttributeData)
11+
public static MarshallingInfoParser Create(StubEnvironment env, IGeneratorDiagnostics diagnostics, IMethodSymbol method, InteropAttributeCompilationData interopAttributeData, AttributeData unparsedAttributeData)
1212
{
1313

1414
// Compute the current default string encoding value.

src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ private static bool HasUnsupportedMarshalAsInfo(TypePositionInfo info)
146146
|| unmanagedType == UnmanagedType.SafeArray;
147147
}
148148

149-
private static InteropAttributeData CreateInteropAttributeDataFromDllImport(DllImportData dllImportData)
149+
private static InteropAttributeCompilationData CreateInteropAttributeDataFromDllImport(DllImportData dllImportData)
150150
{
151-
InteropAttributeData interopData = new();
151+
InteropAttributeCompilationData interopData = new();
152152
if (dllImportData.SetLastError)
153153
{
154154
interopData = interopData with { IsUserDefined = interopData.IsUserDefined | InteropAttributeMember.SetLastError, SetLastError = true };

src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportData.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
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;
5-
using System.Runtime.InteropServices;
6-
using Microsoft.CodeAnalysis;
7-
84
namespace Microsoft.Interop
95
{
106
/// <summary>
11-
/// LibraryImportAttribute data
7+
/// Contains the data related to a LibraryImportAttribute, without references to Roslyn symbols.
8+
/// See <seealso cref="LibraryImportCompilationData"/> for a type with a reference to the StringMarshallingCustomType
129
/// </summary>
1310
internal sealed record LibraryImportData(string ModuleName) : InteropAttributeData
11+
{
12+
public string EntryPoint { get; init; }
13+
14+
public static LibraryImportData From(LibraryImportCompilationData libraryImport)
15+
=> new LibraryImportData(libraryImport.ModuleName) with
16+
{
17+
EntryPoint = libraryImport.EntryPoint,
18+
IsUserDefined = libraryImport.IsUserDefined,
19+
SetLastError = libraryImport.SetLastError,
20+
StringMarshalling = libraryImport.StringMarshalling
21+
};
22+
}
23+
24+
/// <summary>
25+
/// Contains the data related to a LibraryImportAttribute, with references to Roslyn symbols.
26+
/// Use <seealso cref="LibraryImportData"/> instead when using for incremental compilation state to avoid keeping a compilation alive
27+
/// </summary>
28+
internal sealed record LibraryImportCompilationData(string ModuleName) : InteropAttributeCompilationData
1429
{
1530
public string EntryPoint { get; init; }
1631
}

src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Diagnostics;
88
using System.Linq;
99
using System.Runtime.InteropServices;
10-
using System.Text;
1110
using System.Threading;
1211
using Microsoft.CodeAnalysis;
1312
using Microsoft.CodeAnalysis.CSharp;
@@ -181,7 +180,7 @@ private static MemberDeclarationSyntax PrintGeneratedSource(
181180
.WithBody(stubCode);
182181
}
183182

184-
private static LibraryImportData? ProcessLibraryImportAttribute(AttributeData attrData)
183+
private static LibraryImportCompilationData? ProcessLibraryImportAttribute(AttributeData attrData)
185184
{
186185
// Found the LibraryImport, but it has an error so report the error.
187186
// This is most likely an issue with targeting an incorrect TFM.
@@ -198,7 +197,7 @@ private static MemberDeclarationSyntax PrintGeneratedSource(
198197
ImmutableDictionary<string, TypedConstant> namedArguments = ImmutableDictionary.CreateRange(attrData.NamedArguments);
199198

200199
string? entryPoint = null;
201-
if (namedArguments.TryGetValue(nameof(LibraryImportData.EntryPoint), out TypedConstant entryPointValue))
200+
if (namedArguments.TryGetValue(nameof(LibraryImportCompilationData.EntryPoint), out TypedConstant entryPointValue))
202201
{
203202
if (entryPointValue.Value is not string)
204203
{
@@ -207,7 +206,7 @@ private static MemberDeclarationSyntax PrintGeneratedSource(
207206
entryPoint = (string)entryPointValue.Value!;
208207
}
209208

210-
return new LibraryImportData(attrData.ConstructorArguments[0].Value!.ToString())
209+
return new LibraryImportCompilationData(attrData.ConstructorArguments[0].Value!.ToString())
211210
{
212211
EntryPoint = entryPoint,
213212
}.WithValuesFromNamedArguments(namedArguments);
@@ -261,9 +260,9 @@ private static IncrementalStubGenerationContext CalculateStubInformation(
261260
var generatorDiagnostics = new GeneratorDiagnostics();
262261

263262
// Process the LibraryImport attribute
264-
LibraryImportData libraryImportData =
263+
LibraryImportCompilationData libraryImportData =
265264
ProcessLibraryImportAttribute(generatedDllImportAttr!) ??
266-
new LibraryImportData("INVALID_CSHARP_SYNTAX");
265+
new LibraryImportCompilationData("INVALID_CSHARP_SYNTAX");
267266

268267
if (libraryImportData.IsUserDefined.HasFlag(InteropAttributeMember.StringMarshalling))
269268
{
@@ -302,7 +301,7 @@ private static IncrementalStubGenerationContext CalculateStubInformation(
302301
methodSyntaxTemplate,
303302
new MethodSignatureDiagnosticLocations(originalSyntax),
304303
new SequenceEqualImmutableArray<AttributeSyntax>(additionalAttributes.ToImmutableArray(), SyntaxEquivalentComparer.Instance),
305-
libraryImportData,
304+
LibraryImportData.From(libraryImportData),
306305
LibraryImportGeneratorHelpers.CreateGeneratorFactory(environment, options),
307306
new SequenceEqualImmutableArray<Diagnostic>(generatorDiagnostics.Diagnostics.ToImmutableArray())
308307
);

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/InteropAttributeData.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Collections.Immutable;
6-
using System.Runtime.InteropServices;
76
using Microsoft.CodeAnalysis;
87

98
namespace Microsoft.Interop
@@ -22,9 +21,24 @@ public enum InteropAttributeMember
2221
}
2322

2423
/// <summary>
25-
/// Common data for all source-generated-interop trigger attributes
24+
/// Common data for all source-generated-interop trigger attributes.
25+
/// This type and derived types should not have any reference that would keep a compilation alive.
2626
/// </summary>
2727
public record InteropAttributeData
28+
{
29+
/// <summary>
30+
/// Value set by the user on the original declaration.
31+
/// </summary>
32+
public InteropAttributeMember IsUserDefined { get; init; }
33+
public bool SetLastError { get; init; }
34+
public StringMarshalling StringMarshalling { get; init; }
35+
}
36+
37+
/// <summary>
38+
/// Common data for all source-generated-interop trigger attributes that also includes a reference to the Roslyn symbol for StringMarshallingCustomType.
39+
/// See <seealso cref="InteropAttributeData"/> for a type that doesn't keep a compilation alive.
40+
/// </summary>
41+
public record InteropAttributeCompilationData
2842
{
2943
/// <summary>
3044
/// Value set by the user on the original declaration.
@@ -37,14 +51,14 @@ public record InteropAttributeData
3751

3852
public static class InteropAttributeDataExtensions
3953
{
40-
public static T WithValuesFromNamedArguments<T>(this T t, ImmutableDictionary<string, TypedConstant> namedArguments) where T : InteropAttributeData
54+
public static T WithValuesFromNamedArguments<T>(this T t, ImmutableDictionary<string, TypedConstant> namedArguments) where T : InteropAttributeCompilationData
4155
{
4256
InteropAttributeMember userDefinedValues = InteropAttributeMember.None;
4357
bool setLastError = false;
4458
StringMarshalling stringMarshalling = StringMarshalling.Custom;
4559
INamedTypeSymbol? stringMarshallingCustomType = null;
4660

47-
if (namedArguments.TryGetValue(nameof(InteropAttributeData.SetLastError), out TypedConstant setLastErrorValue))
61+
if (namedArguments.TryGetValue(nameof(InteropAttributeCompilationData.SetLastError), out TypedConstant setLastErrorValue))
4862
{
4963
userDefinedValues |= InteropAttributeMember.SetLastError;
5064
if (setLastErrorValue.Value is not bool)
@@ -53,7 +67,7 @@ public static T WithValuesFromNamedArguments<T>(this T t, ImmutableDictionary<st
5367
}
5468
setLastError = (bool)setLastErrorValue.Value!;
5569
}
56-
if (namedArguments.TryGetValue(nameof(InteropAttributeData.StringMarshalling), out TypedConstant stringMarshallingValue))
70+
if (namedArguments.TryGetValue(nameof(InteropAttributeCompilationData.StringMarshalling), out TypedConstant stringMarshallingValue))
5771
{
5872
userDefinedValues |= InteropAttributeMember.StringMarshalling;
5973
// TypedConstant's Value property only contains primitive values.
@@ -64,7 +78,7 @@ public static T WithValuesFromNamedArguments<T>(this T t, ImmutableDictionary<st
6478
// A boxed primitive can be unboxed to an enum with the same underlying type.
6579
stringMarshalling = (StringMarshalling)stringMarshallingValue.Value!;
6680
}
67-
if (namedArguments.TryGetValue(nameof(InteropAttributeData.StringMarshallingCustomType), out TypedConstant stringMarshallingCustomTypeValue))
81+
if (namedArguments.TryGetValue(nameof(InteropAttributeCompilationData.StringMarshallingCustomType), out TypedConstant stringMarshallingCustomTypeValue))
6882
{
6983
userDefinedValues |= InteropAttributeMember.StringMarshallingCustomType;
7084
if (stringMarshallingCustomTypeValue.Value is not INamedTypeSymbol)

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManagedTypeInfo.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
using Microsoft.CodeAnalysis;
55
using Microsoft.CodeAnalysis.CSharp;
66
using Microsoft.CodeAnalysis.CSharp.Syntax;
7-
using System;
8-
using System.Collections.Generic;
9-
using System.Text;
107

118
namespace Microsoft.Interop
129
{
@@ -18,6 +15,19 @@ public abstract record ManagedTypeInfo(string FullTypeName, string DiagnosticFor
1815
private TypeSyntax? _syntax;
1916
public TypeSyntax Syntax => _syntax ??= SyntaxFactory.ParseTypeName(FullTypeName);
2017

18+
public virtual bool Equals(ManagedTypeInfo? other)
19+
{
20+
return other is not null
21+
&& Syntax.IsEquivalentTo(other.Syntax)
22+
&& FullTypeName == other.FullTypeName
23+
&& DiagnosticFormattedName == other.DiagnosticFormattedName;
24+
}
25+
26+
public override int GetHashCode()
27+
{
28+
return FullTypeName.GetHashCode() ^ DiagnosticFormattedName.GetHashCode();
29+
}
30+
2131
protected ManagedTypeInfo(ManagedTypeInfo original)
2232
{
2333
FullTypeName = original.FullTypeName;

src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,24 @@ public readonly record struct CustomTypeMarshallerData(
2424
public readonly record struct CustomTypeMarshallers(
2525
ImmutableDictionary<MarshalMode, CustomTypeMarshallerData> Modes)
2626
{
27+
public bool Equals(CustomTypeMarshallers other)
28+
{
29+
// Check for equal count, then check if any KeyValuePairs exist in one 'Modes'
30+
// but not the other (i.e. set equality on the set of items in the dictionary)
31+
return Modes.Count == other.Modes.Count
32+
&& !Modes.Except(other.Modes).Any();
33+
}
34+
35+
public override int GetHashCode()
36+
{
37+
int hash = 0;
38+
foreach (KeyValuePair<MarshalMode, CustomTypeMarshallerData> mode in Modes)
39+
{
40+
hash ^= mode.Key.GetHashCode() ^ mode.Value.GetHashCode();
41+
}
42+
return hash;
43+
}
44+
2745
public CustomTypeMarshallerData GetModeOrDefault(MarshalMode mode)
2846
{
2947
CustomTypeMarshallerData data;

src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ public static partial void Method(
964964
+ CustomCollectionMarshallingCodeSnippets<CodeSnippets>.Stateless.In
965965
+ CustomCollectionMarshallingCodeSnippets<CodeSnippets>.CustomIntMarshaller;
966966

967-
public static string RecursiveCountElementNameOnReturnValue => $@"
967+
public static string RecursiveCountElementNameOnReturnValue => $@"
968968
using System.Runtime.InteropServices;
969969
using System.Runtime.InteropServices.Marshalling;
970970
{DisableRuntimeMarshalling}

src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/IncrementalGenerationTests.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
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 Microsoft.CodeAnalysis;
5-
using Microsoft.CodeAnalysis.CSharp;
6-
using Microsoft.CodeAnalysis.Text;
7-
using Microsoft.Interop.UnitTests;
84
using System;
95
using System.Collections.Generic;
106
using System.Linq;
117
using System.Runtime.CompilerServices;
128
using System.Runtime.InteropServices;
13-
using System.Text;
149
using System.Threading.Tasks;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.Interop.UnitTests;
1513
using Xunit;
1614
using static Microsoft.Interop.LibraryImportGenerator;
1715

@@ -217,8 +215,7 @@ public static IEnumerable<object[]> CompilationObjectLivenessSources()
217215
// Basic stub
218216
yield return new[] { CodeSnippets.BasicParametersAndModifiers<int>() };
219217
// Stub with custom string marshaller
220-
// TODO: Compilation is held alive by the CustomStringMarshallingType property in LibraryImportData
221-
// yield return new[] { CodeSnippets.CustomStringMarshallingParametersAndModifiers<string>() };
218+
yield return new[] { CodeSnippets.CustomStringMarshallingParametersAndModifiers<string>() };
222219
}
223220

224221
// This test requires precise GC to ensure that we're accurately testing that we aren't

0 commit comments

Comments
 (0)