diff --git a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodIndexData.cs b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodIndexData.cs index 62caf56448648..c5a7b93f492ed 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodIndexData.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/VirtualMethodIndexData.cs @@ -8,7 +8,7 @@ namespace Microsoft.Interop /// /// VirtualMethodIndexAttribute data /// - internal sealed record VirtualMethodIndexData(int Index) : InteropAttributeData + internal sealed record VirtualMethodIndexData(int Index) : InteropAttributeCompilationData { public bool ImplicitThisParameter { get; init; } diff --git a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs index d07cfd88010ae..2150eacf9689c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Common/DefaultMarshallingInfoParser.cs @@ -8,7 +8,7 @@ namespace Microsoft.Interop { internal static class DefaultMarshallingInfoParser { - public static MarshallingInfoParser Create(StubEnvironment env, IGeneratorDiagnostics diagnostics, IMethodSymbol method, InteropAttributeData interopAttributeData, AttributeData unparsedAttributeData) + public static MarshallingInfoParser Create(StubEnvironment env, IGeneratorDiagnostics diagnostics, IMethodSymbol method, InteropAttributeCompilationData interopAttributeData, AttributeData unparsedAttributeData) { // Compute the current default string encoding value. diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs index 0f1ca4252cfeb..0a47ad0a00891 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/Analyzers/ConvertToLibraryImportAnalyzer.cs @@ -146,9 +146,9 @@ private static bool HasUnsupportedMarshalAsInfo(TypePositionInfo info) || unmanagedType == UnmanagedType.SafeArray; } - private static InteropAttributeData CreateInteropAttributeDataFromDllImport(DllImportData dllImportData) + private static InteropAttributeCompilationData CreateInteropAttributeDataFromDllImport(DllImportData dllImportData) { - InteropAttributeData interopData = new(); + InteropAttributeCompilationData interopData = new(); if (dllImportData.SetLastError) { interopData = interopData with { IsUserDefined = interopData.IsUserDefined | InteropAttributeMember.SetLastError, SetLastError = true }; diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportData.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportData.cs index 957d3ce381a12..65691faf8beb3 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportData.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportData.cs @@ -1,16 +1,31 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Runtime.InteropServices; -using Microsoft.CodeAnalysis; - namespace Microsoft.Interop { /// - /// LibraryImportAttribute data + /// Contains the data related to a LibraryImportAttribute, without references to Roslyn symbols. + /// See for a type with a reference to the StringMarshallingCustomType /// internal sealed record LibraryImportData(string ModuleName) : InteropAttributeData + { + public string EntryPoint { get; init; } + + public static LibraryImportData From(LibraryImportCompilationData libraryImport) + => new LibraryImportData(libraryImport.ModuleName) with + { + EntryPoint = libraryImport.EntryPoint, + IsUserDefined = libraryImport.IsUserDefined, + SetLastError = libraryImport.SetLastError, + StringMarshalling = libraryImport.StringMarshalling + }; + } + + /// + /// Contains the data related to a LibraryImportAttribute, with references to Roslyn symbols. + /// Use instead when using for incremental compilation state to avoid keeping a compilation alive + /// + internal sealed record LibraryImportCompilationData(string ModuleName) : InteropAttributeCompilationData { public string EntryPoint { get; init; } } diff --git a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs index 88aaa11d82051..459fff2cb589a 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/LibraryImportGenerator/LibraryImportGenerator.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -181,7 +180,7 @@ private static SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenLis .WithBody(stubCode); } - private static LibraryImportData? ProcessLibraryImportAttribute(AttributeData attrData) + private static LibraryImportCompilationData? ProcessLibraryImportAttribute(AttributeData attrData) { // Found the LibraryImport, but it has an error so report the error. // This is most likely an issue with targeting an incorrect TFM. @@ -198,7 +197,7 @@ private static SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenLis ImmutableDictionary namedArguments = ImmutableDictionary.CreateRange(attrData.NamedArguments); string? entryPoint = null; - if (namedArguments.TryGetValue(nameof(LibraryImportData.EntryPoint), out TypedConstant entryPointValue)) + if (namedArguments.TryGetValue(nameof(LibraryImportCompilationData.EntryPoint), out TypedConstant entryPointValue)) { if (entryPointValue.Value is not string) { @@ -207,7 +206,7 @@ private static SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenLis entryPoint = (string)entryPointValue.Value!; } - return new LibraryImportData(attrData.ConstructorArguments[0].Value!.ToString()) + return new LibraryImportCompilationData(attrData.ConstructorArguments[0].Value!.ToString()) { EntryPoint = entryPoint, }.WithValuesFromNamedArguments(namedArguments); @@ -261,9 +260,9 @@ private static SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenLis var generatorDiagnostics = new GeneratorDiagnostics(); // Process the LibraryImport attribute - LibraryImportData libraryImportData = + LibraryImportCompilationData libraryImportData = ProcessLibraryImportAttribute(generatedDllImportAttr!) ?? - new LibraryImportData("INVALID_CSHARP_SYNTAX"); + new LibraryImportCompilationData("INVALID_CSHARP_SYNTAX"); if (libraryImportData.IsUserDefined.HasFlag(InteropAttributeMember.StringMarshalling)) { @@ -302,7 +301,7 @@ private static SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenLis methodSyntaxTemplate, new MethodSignatureDiagnosticLocations(originalSyntax), new SequenceEqualImmutableArray(additionalAttributes.ToImmutableArray(), SyntaxEquivalentComparer.Instance), - libraryImportData, + LibraryImportData.From(libraryImportData), LibraryImportGeneratorHelpers.CreateGeneratorFactory(environment, options), new SequenceEqualImmutableArray(generatorDiagnostics.Diagnostics.ToImmutableArray()) ); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/InteropAttributeData.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/InteropAttributeData.cs index c49511de23336..ecbb18426791f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/InteropAttributeData.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/InteropAttributeData.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Immutable; -using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; namespace Microsoft.Interop @@ -22,9 +21,24 @@ public enum InteropAttributeMember } /// - /// Common data for all source-generated-interop trigger attributes + /// Common data for all source-generated-interop trigger attributes. + /// This type and derived types should not have any reference that would keep a compilation alive. /// public record InteropAttributeData + { + /// + /// Value set by the user on the original declaration. + /// + public InteropAttributeMember IsUserDefined { get; init; } + public bool SetLastError { get; init; } + public StringMarshalling StringMarshalling { get; init; } + } + + /// + /// Common data for all source-generated-interop trigger attributes that also includes a reference to the Roslyn symbol for StringMarshallingCustomType. + /// See for a type that doesn't keep a compilation alive. + /// + public record InteropAttributeCompilationData { /// /// Value set by the user on the original declaration. @@ -37,14 +51,14 @@ public record InteropAttributeData public static class InteropAttributeDataExtensions { - public static T WithValuesFromNamedArguments(this T t, ImmutableDictionary namedArguments) where T : InteropAttributeData + public static T WithValuesFromNamedArguments(this T t, ImmutableDictionary namedArguments) where T : InteropAttributeCompilationData { InteropAttributeMember userDefinedValues = InteropAttributeMember.None; bool setLastError = false; StringMarshalling stringMarshalling = StringMarshalling.Custom; INamedTypeSymbol? stringMarshallingCustomType = null; - if (namedArguments.TryGetValue(nameof(InteropAttributeData.SetLastError), out TypedConstant setLastErrorValue)) + if (namedArguments.TryGetValue(nameof(InteropAttributeCompilationData.SetLastError), out TypedConstant setLastErrorValue)) { userDefinedValues |= InteropAttributeMember.SetLastError; if (setLastErrorValue.Value is not bool) @@ -53,7 +67,7 @@ public static class InteropAttributeDataExtensions } setLastError = (bool)setLastErrorValue.Value!; } - if (namedArguments.TryGetValue(nameof(InteropAttributeData.StringMarshalling), out TypedConstant stringMarshallingValue)) + if (namedArguments.TryGetValue(nameof(InteropAttributeCompilationData.StringMarshalling), out TypedConstant stringMarshallingValue)) { userDefinedValues |= InteropAttributeMember.StringMarshalling; // TypedConstant's Value property only contains primitive values. @@ -64,7 +78,7 @@ public static class InteropAttributeDataExtensions // A boxed primitive can be unboxed to an enum with the same underlying type. stringMarshalling = (StringMarshalling)stringMarshallingValue.Value!; } - if (namedArguments.TryGetValue(nameof(InteropAttributeData.StringMarshallingCustomType), out TypedConstant stringMarshallingCustomTypeValue)) + if (namedArguments.TryGetValue(nameof(InteropAttributeCompilationData.StringMarshallingCustomType), out TypedConstant stringMarshallingCustomTypeValue)) { userDefinedValues |= InteropAttributeMember.StringMarshallingCustomType; if (stringMarshallingCustomTypeValue.Value is not INamedTypeSymbol) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManagedTypeInfo.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManagedTypeInfo.cs index 647fc427baf48..193dab83f8f76 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManagedTypeInfo.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManagedTypeInfo.cs @@ -4,9 +4,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.Interop { @@ -18,6 +15,19 @@ public abstract record ManagedTypeInfo(string FullTypeName, string DiagnosticFor private TypeSyntax? _syntax; public TypeSyntax Syntax => _syntax ??= SyntaxFactory.ParseTypeName(FullTypeName); + public virtual bool Equals(ManagedTypeInfo? other) + { + return other is not null + && Syntax.IsEquivalentTo(other.Syntax) + && FullTypeName == other.FullTypeName + && DiagnosticFormattedName == other.DiagnosticFormattedName; + } + + public override int GetHashCode() + { + return FullTypeName.GetHashCode() ^ DiagnosticFormattedName.GetHashCode(); + } + protected ManagedTypeInfo(ManagedTypeInfo original) { FullTypeName = original.FullTypeName; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs index 1f343d691ad5b..2e12f600d3736 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/ManualTypeMarshallingHelper.cs @@ -24,6 +24,24 @@ namespace Microsoft.Interop public readonly record struct CustomTypeMarshallers( ImmutableDictionary Modes) { + public bool Equals(CustomTypeMarshallers other) + { + // Check for equal count, then check if any KeyValuePairs exist in one 'Modes' + // but not the other (i.e. set equality on the set of items in the dictionary) + return Modes.Count == other.Modes.Count + && !Modes.Except(other.Modes).Any(); + } + + public override int GetHashCode() + { + int hash = 0; + foreach (KeyValuePair mode in Modes) + { + hash ^= mode.Key.GetHashCode() ^ mode.Value.GetHashCode(); + } + return hash; + } + public CustomTypeMarshallerData GetModeOrDefault(MarshalMode mode) { CustomTypeMarshallerData data; diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs index 8a919c642312c..5aba5d6133608 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/CodeSnippets.cs @@ -964,7 +964,7 @@ partial class Test + CustomCollectionMarshallingCodeSnippets.Stateless.In + CustomCollectionMarshallingCodeSnippets.CustomIntMarshaller; - public static string RecursiveCountElementNameOnReturnValue => $@" + public static string RecursiveCountElementNameOnReturnValue => $@" using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; {DisableRuntimeMarshalling} diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/IncrementalGenerationTests.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/IncrementalGenerationTests.cs index ef240d2810d7a..8b6a8d043e5b9 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/IncrementalGenerationTests.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/IncrementalGenerationTests.cs @@ -1,17 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Interop.UnitTests; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Interop.UnitTests; using Xunit; using static Microsoft.Interop.LibraryImportGenerator; @@ -217,8 +215,7 @@ public static IEnumerable CompilationObjectLivenessSources() // Basic stub yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; // Stub with custom string marshaller - // TODO: Compilation is held alive by the CustomStringMarshallingType property in LibraryImportData - // yield return new[] { CodeSnippets.CustomStringMarshallingParametersAndModifiers() }; + yield return new[] { CodeSnippets.CustomStringMarshallingParametersAndModifiers() }; } // This test requires precise GC to ensure that we're accurately testing that we aren't