diff --git a/docs/design/libraries/DllImportGenerator/Compatibility.md b/docs/design/libraries/DllImportGenerator/Compatibility.md index 2790eff6e8943..5f5997a36edaa 100644 --- a/docs/design/libraries/DllImportGenerator/Compatibility.md +++ b/docs/design/libraries/DllImportGenerator/Compatibility.md @@ -4,7 +4,11 @@ Documentation on compatibility guidance and the current state. The version headi ## Version 1 -The focus of version 1 is to support `NetCoreApp`. This implies that anything not needed by `NetCoreApp` is subject to change. Only .NET 6+ is supported. +The focus of version 1 is to support `NetCoreApp`. This implies that anything not needed by `NetCoreApp` is subject to change. + +### Fallback mechanism + +In the event a marshaller would generate code that has a specific target framework or version requirement that is not satisfied, the generator will instead produce a normal `DllImportAttribute` declaration. This fallback mechanism enables the use of `GeneratedDllImportAttribute` in most circumstances and permits the conversion from `DllImportAttribute` to `GeneratedDllImportAttribute` to be across most code bases. There are instances where the generator will not be able to handle signatures or configuration. For example, uses of `StringBuilder` are not supported in any form and consumers should retain uses of `DllImportAttribute`. Additionally, `GeneratedDllImportAttribute` cannot represent all settings available on `DllImportAttribute`—see below for details. ### Semantic changes compared to `DllImportAttribute` @@ -92,4 +96,4 @@ Unlike the built-in system, the source generator does not support marshalling fo ## Version 0 -This version is the built-in IL Stub generation system that is triggered whenever a method marked with `DllImport` is invoked. +This version is the built-in IL Stub generation system that is triggered whenever a method marked with `DllImportAttribute` is invoked. diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Comparers.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Comparers.cs index 71ecae6a88860..39166e8c76748 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Comparers.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Comparers.cs @@ -25,12 +25,12 @@ internal static class Comparers /// /// Comparer for an individual generated stub source as a syntax tree and the generated diagnostics for the stub. /// - public static readonly IEqualityComparer<(MemberDeclarationSyntax Syntax, ImmutableArray Diagnostics)> GeneratedSyntax = new CustomValueTupleElementComparer>(new SyntaxEquivalentComparer(), new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); + public static readonly IEqualityComparer<(MemberDeclarationSyntax Syntax, ImmutableArray Diagnostics)> GeneratedSyntax = new CustomValueTupleElementComparer>(SyntaxEquivalentComparer.Instance, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); /// /// Comparer for the context used to generate a stub and the original user-provided syntax that triggered stub creation. /// - public static readonly IEqualityComparer<(MethodDeclarationSyntax Syntax, DllImportGenerator.IncrementalStubGenerationContext StubContext)> CalculatedContextWithSyntax = new CustomValueTupleElementComparer(new SyntaxEquivalentComparer(), EqualityComparer.Default); + public static readonly IEqualityComparer<(MethodDeclarationSyntax Syntax, DllImportGenerator.IncrementalStubGenerationContext StubContext)> CalculatedContextWithSyntax = new CustomValueTupleElementComparer(SyntaxEquivalentComparer.Instance, EqualityComparer.Default); } /// @@ -63,6 +63,10 @@ public int GetHashCode(ImmutableArray obj) internal class SyntaxEquivalentComparer : IEqualityComparer { + public static readonly SyntaxEquivalentComparer Instance = new(); + + private SyntaxEquivalentComparer() { } + public bool Equals(SyntaxNode x, SyntaxNode y) { return x.IsEquivalentTo(y); diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs index 69ee5175042ba..f69393e6e1589 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportGenerator.cs @@ -24,16 +24,20 @@ public sealed class DllImportGenerator : IIncrementalGenerator private const string GeneratedDllImport = nameof(GeneratedDllImport); private const string GeneratedDllImportAttribute = nameof(GeneratedDllImportAttribute); - private static readonly Version s_minimumSupportedFrameworkVersion = new Version(5, 0); - - internal sealed record IncrementalStubGenerationContext(DllImportStubContext StubContext, ImmutableArray ForwardedAttributes, GeneratedDllImportData DllImportData, ImmutableArray Diagnostics) + internal sealed record IncrementalStubGenerationContext( + StubEnvironment Environment, + DllImportStubContext StubContext, + ImmutableArray ForwardedAttributes, + GeneratedDllImportData DllImportData, + ImmutableArray Diagnostics) { public bool Equals(IncrementalStubGenerationContext? other) { return other is not null + && StubEnvironment.AreCompilationSettingsEqual(Environment, other.Environment) && StubContext.Equals(other.StubContext) && DllImportData.Equals(other.DllImportData) - && ForwardedAttributes.SequenceEqual(other.ForwardedAttributes, (IEqualityComparer)new SyntaxEquivalentComparer()) + && ForwardedAttributes.SequenceEqual(other.ForwardedAttributes, (IEqualityComparer)SyntaxEquivalentComparer.Instance) && Diagnostics.SequenceEqual(other.Diagnostics); } @@ -93,11 +97,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.ReportDiagnostic(Diagnostic.Create(GeneratorDiagnostics.ReturnConfigurationNotSupported, refReturnMethod.Syntax.GetLocation(), "ref return", refReturnMethod.Symbol.ToDisplayString())); }); - IncrementalValueProvider<(Compilation compilation, bool isSupported, Version targetFrameworkVersion)> compilationAndTargetFramework = context.CompilationProvider + IncrementalValueProvider<(Compilation compilation, TargetFramework targetFramework, Version targetFrameworkVersion)> compilationAndTargetFramework = context.CompilationProvider .Select(static (compilation, ct) => { - bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); - return (compilation, isSupported, targetFrameworkVersion); + TargetFramework fmk = DetermineTargetFramework(compilation, out Version targetFrameworkVersion); + return (compilation, fmk, targetFrameworkVersion); }); context.RegisterSourceOutput( @@ -105,16 +109,16 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Combine(methodsToGenerate.Collect()), static (context, data) => { - if (!data.Left.isSupported && data.Right.Any()) + if (data.Left.targetFramework is TargetFramework.Unknown && data.Right.Any()) { - // We don't block source generation when the TFM is unsupported. + // We don't block source generation when the TFM is unknown. // This allows a user to copy generated source and use it as a starting point // for manual marshalling if desired. context.ReportDiagnostic( Diagnostic.Create( GeneratorDiagnostics.TargetFrameworkNotSupported, Location.None, - s_minimumSupportedFrameworkVersion.ToString(2))); + data.Left.targetFrameworkVersion)); } }); @@ -127,7 +131,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (data, ct) => new StubEnvironment( data.Left.compilation, - data.Left.isSupported, + data.Left.targetFramework, data.Left.targetFrameworkVersion, data.Left.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute), data.Right) @@ -312,7 +316,7 @@ private static MemberDeclarationSyntax WrapMethodInContainingScopes(DllImportStu return toPrint; } - private static bool IsSupportedTargetFramework(Compilation compilation, out Version version) + private static TargetFramework DetermineTargetFramework(Compilation compilation, out Version version) { IAssemblySymbol systemAssembly = compilation.GetSpecialType(SpecialType.System_Object).ContainingAssembly; version = systemAssembly.Identity.Version; @@ -320,12 +324,13 @@ private static bool IsSupportedTargetFramework(Compilation compilation, out Vers return systemAssembly.Identity.Name switch { // .NET Framework - "mscorlib" => false, + "mscorlib" => TargetFramework.Framework, // .NET Standard - "netstandard" => false, + "netstandard" => TargetFramework.Standard, // .NET Core (when version < 5.0) or .NET - "System.Runtime" or "System.Private.CoreLib" => version >= s_minimumSupportedFrameworkVersion, - _ => false, + "System.Runtime" or "System.Private.CoreLib" => + (version.Major < 5) ? TargetFramework.Core : TargetFramework.Net, + _ => TargetFramework.Unknown, }; } @@ -339,7 +344,6 @@ private static GeneratedDllImportData ProcessGeneratedDllImportAttribute(Attribu throw new InvalidProgramException(); } - // Default values for these properties are based on the // documented semanatics of DllImportAttribute: // - https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute @@ -473,7 +477,7 @@ private static IncrementalStubGenerationContext CalculateStubInformation(IMethod // Create the stub. var dllImportStub = DllImportStubContext.Create(symbol, stubDllImportData, environment, generatorDiagnostics, ct); - return new IncrementalStubGenerationContext(dllImportStub, additionalAttributes.ToImmutableArray(), stubDllImportData, generatorDiagnostics.Diagnostics.ToImmutableArray()); + return new IncrementalStubGenerationContext(environment, dllImportStub, additionalAttributes.ToImmutableArray(), stubDllImportData, generatorDiagnostics.Diagnostics.ToImmutableArray()); } private (MemberDeclarationSyntax, ImmutableArray) GenerateSource( @@ -490,12 +494,16 @@ private static IncrementalStubGenerationContext CalculateStubInformation(IMethod // Generate stub code var stubGenerator = new PInvokeStubCodeGenerator( + dllImportStub.Environment, dllImportStub.StubContext.ElementTypeInformation, dllImportStub.DllImportData.SetLastError && !options.GenerateForwarders, (elementInfo, ex) => diagnostics.ReportMarshallingNotSupported(originalSyntax, elementInfo, ex.NotSupportedDetails), dllImportStub.StubContext.GeneratorFactory); - if (stubGenerator.StubIsBasicForwarder) + // Check if the generator should produce a forwarder stub - regular DllImport. + // This is done if the signature is blittable or the target framework is not supported. + if (stubGenerator.StubIsBasicForwarder + || !stubGenerator.SupportsTargetFramework) { return (PrintForwarderStub(originalSyntax, dllImportStub), dllImportStub.Diagnostics.AddRange(diagnostics.Diagnostics)); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportStubContext.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportStubContext.cs index 853422ab5a1ff..420e2b6f88743 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportStubContext.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/DllImportStubContext.cs @@ -18,10 +18,27 @@ namespace Microsoft.Interop { internal record StubEnvironment( Compilation Compilation, - bool SupportedTargetFramework, + TargetFramework TargetFramework, Version TargetFrameworkVersion, bool ModuleSkipLocalsInit, - DllImportGeneratorOptions Options); + DllImportGeneratorOptions Options) + { + /// + /// Override for determining if two StubEnvironment instances are + /// equal. This intentionally excludes the Compilation instance + /// since that represents the actual compilation and not just the settings. + /// + /// The first StubEnvironment + /// The second StubEnvironment + /// True if the settings are equal, otherwise false. + public static bool AreCompilationSettingsEqual(StubEnvironment env1, StubEnvironment env2) + { + return env1.TargetFramework == env2.TargetFramework + && env1.TargetFrameworkVersion == env2.TargetFrameworkVersion + && env1.ModuleSkipLocalsInit == env2.ModuleSkipLocalsInit + && env1.Options.Equals(env2.Options); + } + } internal sealed class DllImportStubContext : IEquatable { @@ -164,7 +181,6 @@ private static (ImmutableArray, IMarshallingGeneratorFactory) NativeIndex = typeInfos.Count }; typeInfos.Add(typeInfo); - } TypePositionInfo retTypeInfo = new(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(method.ReturnType), marshallingAttributeParser.ParseMarshallingInfo(method.ReturnType, method.GetReturnTypeAttributes())); @@ -234,9 +250,9 @@ public bool Equals(DllImportStubContext other) return other is not null && StubTypeNamespace == other.StubTypeNamespace && ElementTypeInformation.SequenceEqual(other.ElementTypeInformation) - && StubContainingTypes.SequenceEqual(other.StubContainingTypes, (IEqualityComparer)new SyntaxEquivalentComparer()) + && StubContainingTypes.SequenceEqual(other.StubContainingTypes, (IEqualityComparer)SyntaxEquivalentComparer.Instance) && StubReturnType.IsEquivalentTo(other.StubReturnType) - && AdditionalAttributes.SequenceEqual(other.AdditionalAttributes, (IEqualityComparer)new SyntaxEquivalentComparer()) + && AdditionalAttributes.SequenceEqual(other.AdditionalAttributes, (IEqualityComparer)SyntaxEquivalentComparer.Instance) && Options.Equals(other.Options); } diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/PInvokeStubCodeGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/PInvokeStubCodeGenerator.cs index 6915a3c996601..4c9bbd0b1e392 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/PInvokeStubCodeGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/PInvokeStubCodeGenerator.cs @@ -34,6 +34,10 @@ private record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGene public override bool AdditionalTemporaryStateLivesAcrossStages => true; + public bool SupportsTargetFramework { get; init; } + + public bool StubIsBasicForwarder { get; init; } + /// /// Identifier for managed return value /// @@ -58,9 +62,8 @@ private record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGene private readonly List _sortedMarshallers; private readonly bool _stubReturnsVoid; - public bool StubIsBasicForwarder { get; } - public PInvokeStubCodeGenerator( + StubEnvironment environment, IEnumerable argTypes, bool setLastError, Action marshallingNotSupportedCallback, @@ -68,6 +71,19 @@ public PInvokeStubCodeGenerator( { _setLastError = setLastError; + // Support for SetLastError logic requires .NET 6+. Initialize the + // supports target framework value with this value. + if (_setLastError) + { + SupportsTargetFramework = environment.TargetFramework == TargetFramework.Net + && environment.TargetFrameworkVersion.Major >= 6; + } + else + { + SupportsTargetFramework = true; + } + + bool noMarshallingNeeded = true; List allMarshallers = new(); List paramMarshallers = new(); bool foundNativeRetMarshaller = false; @@ -78,6 +94,14 @@ public PInvokeStubCodeGenerator( foreach (TypePositionInfo argType in argTypes) { BoundGenerator generator = CreateGenerator(argType); + + // Check each marshaler if the current target framework is supported or not. + SupportsTargetFramework &= generator.Generator.IsSupported(environment.TargetFramework, environment.TargetFrameworkVersion); + + // Check if generator is either blittable or just a forwarder. + noMarshallingNeeded &= generator is { Generator: BlittableMarshaller, TypeInfo: { IsByRef: false } } + or { Generator: Forwarder }; + allMarshallers.Add(generator); if (argType.IsManagedReturnPosition) { @@ -139,9 +163,7 @@ public PInvokeStubCodeGenerator( StubIsBasicForwarder = !setLastError && managedRetMarshaller.TypeInfo.IsNativeReturnPosition // If the managed return has native return position, then it's the return for both. - && _sortedMarshallers.All( - m => m is { Generator: BlittableMarshaller, TypeInfo: { IsByRef: false } } - or { Generator: Forwarder }); + && noMarshallingNeeded; if (managedRetMarshaller.Generator.UsesNativeIdentifier(managedRetMarshaller.TypeInfo, this)) { diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs index eb6bfa6e52e08..30ad6508d4f17 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.Designer.cs @@ -511,7 +511,7 @@ internal static string StackallocMarshallingShouldSupportAllocatingMarshallingFa } /// - /// Looks up a localized string similar to P/Invoke source generation is only supported on .NET {0} or above. The generated source will not be compatible with other frameworks.. + /// Looks up a localized string similar to P/Invoke source generation is not supported on unknown target framework v{0}. The generated source will not be compatible with other frameworks.. /// internal static string TargetFrameworkNotSupportedDescription { get { @@ -520,7 +520,7 @@ internal static string TargetFrameworkNotSupportedDescription { } /// - /// Looks up a localized string similar to 'GeneratedDllImportAttribute' cannot be used for source-generated P/Invokes on the current target framework. Source-generated P/Invokes require .NET {0} or above.. + /// Looks up a localized string similar to 'GeneratedDllImportAttribute' cannot be used for source-generated P/Invokes on an unknown target framework v{0}.. /// internal static string TargetFrameworkNotSupportedMessage { get { diff --git a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx index 3124872bc3175..e66ea89a005c8 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx +++ b/src/libraries/System.Runtime.InteropServices/gen/DllImportGenerator/Resources.resx @@ -269,11 +269,11 @@ Native type '{0}' has a stack-allocating constructor does not support marshalling in scenarios where stack allocation is impossible - P/Invoke source generation is only supported on .NET {0} or above. The generated source will not be compatible with other frameworks. + P/Invoke source generation is not supported on unknown target framework v{0}. The generated source will not be compatible with other frameworks. {0} is a version number - 'GeneratedDllImportAttribute' cannot be used for source-generated P/Invokes on the current target framework. Source-generated P/Invokes require .NET {0} or above. + 'GeneratedDllImportAttribute' cannot be used for source-generated P/Invokes on an unknown target framework v{0}. {0} is a version number diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ArrayMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ArrayMarshaller.cs index 82ee55861816f..4e6980a80250f 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ArrayMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ArrayMarshaller.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -23,6 +24,11 @@ public ArrayMarshaller(IMarshallingGenerator manualMarshallingGenerator, TypeSyn _options = options; } + public bool IsSupported(TargetFramework target, Version version) + { + return target is TargetFramework.Net && version.Major >= 6; + } + public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context) { if (IsPinningPathSupported(info, context)) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BlittableMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BlittableMarshaller.cs index c68037306af57..b137316d21aed 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BlittableMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BlittableMarshaller.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using Microsoft.CodeAnalysis; @@ -12,6 +13,8 @@ namespace Microsoft.Interop { public sealed class BlittableMarshaller : IMarshallingGenerator { + public bool IsSupported(TargetFramework target, Version version) => true; + public TypeSyntax AsNativeType(TypePositionInfo info) { return info.ManagedType.Syntax; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BoolMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BoolMarshaller.cs index a11a5243575fa..247d19ff1cf2c 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BoolMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/BoolMarshaller.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using System.Diagnostics; @@ -26,6 +27,8 @@ protected BoolMarshallerBase(PredefinedTypeSyntax nativeType, int trueValue, int _compareToTrue = compareToTrue; } + public bool IsSupported(TargetFramework target, Version version) => true; + public TypeSyntax AsNativeType(TypePositionInfo info) { Debug.Assert(info.ManagedType is SpecialTypeInfo(_, _, SpecialType.System_Boolean)); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CharMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CharMarshaller.cs index fb18cb531f513..4852974aa8053 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CharMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CharMarshaller.cs @@ -20,6 +20,8 @@ public Utf16CharMarshaller() { } + public bool IsSupported(TargetFramework target, Version version) => true; + public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context) { (string managedIdentifier, string nativeIdentifier) = context.GetIdentifiers(info); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs index 2b638538904e0..fb097a53a12f5 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/ConditionalStackallocMarshallingGenerator.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -227,6 +228,12 @@ protected virtual ExpressionSyntax GenerateNullCheckExpression( LiteralExpression(SyntaxKind.NullLiteralExpression)); } + /// + public virtual bool IsSupported(TargetFramework target, Version version) + { + return target is TargetFramework.Net && version.Major >= 6; + } + /// public abstract TypeSyntax AsNativeType(TypePositionInfo info); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CustomNativeTypeMarshallingGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CustomNativeTypeMarshallingGenerator.cs index f01c2741ea7e0..4a27fa3cb4eb5 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CustomNativeTypeMarshallingGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/CustomNativeTypeMarshallingGenerator.cs @@ -23,6 +23,11 @@ public CustomNativeTypeMarshallingGenerator(ICustomNativeTypeMarshallingStrategy _enableByValueContentsMarshalling = enableByValueContentsMarshalling; } + public bool IsSupported(TargetFramework target, Version version) + { + return target is TargetFramework.Net && version.Major >= 6; + } + public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context) { return _nativeTypeMarshaller.AsArgument(info, context); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/DelegateMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/DelegateMarshaller.cs index 32fe10c6ec9c7..4db41933d591a 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/DelegateMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/DelegateMarshaller.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -11,6 +12,8 @@ namespace Microsoft.Interop { public sealed class DelegateMarshaller : IMarshallingGenerator { + public bool IsSupported(TargetFramework target, Version version) => true; + public TypeSyntax AsNativeType(TypePositionInfo info) { return MarshallerHelpers.SystemIntPtrType; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/Forwarder.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/Forwarder.cs index fd4ca6a37ae62..931fccaf1616e 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/Forwarder.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/Forwarder.cs @@ -13,6 +13,8 @@ namespace Microsoft.Interop { public sealed class Forwarder : IMarshallingGenerator, IAttributedReturnTypeMarshallingGenerator { + public bool IsSupported(TargetFramework target, Version version) => true; + public TypeSyntax AsNativeType(TypePositionInfo info) { return info.ManagedType.Syntax; diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/HResultExceptionMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/HResultExceptionMarshaller.cs index 446750e2bc5ca..cb32ee3b33cc7 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/HResultExceptionMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/HResultExceptionMarshaller.cs @@ -16,6 +16,8 @@ public sealed class HResultExceptionMarshaller : IMarshallingGenerator { private static readonly TypeSyntax s_nativeType = PredefinedType(Token(SyntaxKind.IntKeyword)); + public bool IsSupported(TargetFramework target, Version version) => true; + public TypeSyntax AsNativeType(TypePositionInfo info) { Debug.Assert(info.ManagedType is SpecialTypeInfo(_, _, SpecialType.System_Int32)); diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGenerator.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGenerator.cs index 74e7a517a4707..bad094d4ed357 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGenerator.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/MarshallingGenerator.cs @@ -8,11 +8,31 @@ namespace Microsoft.Interop { + /// + /// Target framework identifier + /// + public enum TargetFramework + { + Unknown, + Framework, + Core, + Standard, + Net + } + /// /// Interface for generation of marshalling code for P/Invoke stubs /// public interface IMarshallingGenerator { + /// + /// Determine if the generator is supported for the supplied version of the framework. + /// + /// The framework to target. + /// The version of the framework. + /// True if the marshaller is supported, otherwise false. + bool IsSupported(TargetFramework target, Version version); + /// /// Get the native type syntax for /// diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/PinnableManagedValueMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/PinnableManagedValueMarshaller.cs index aa47a55409cd2..e5890d04e9567 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/PinnableManagedValueMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/PinnableManagedValueMarshaller.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -17,6 +18,9 @@ public PinnableManagedValueMarshaller(IMarshallingGenerator manualMarshallingGen _manualMarshallingGenerator = manualMarshallingGenerator; } + public bool IsSupported(TargetFramework target, Version version) + => _manualMarshallingGenerator.IsSupported(target, version); + public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context) { if (IsPinningPathSupported(info, context)) diff --git a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/SafeHandleMarshaller.cs b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/SafeHandleMarshaller.cs index 4a663542f52c1..59dc0b6001bd2 100644 --- a/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/SafeHandleMarshaller.cs +++ b/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/SafeHandleMarshaller.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; @@ -12,6 +13,11 @@ namespace Microsoft.Interop { public sealed class SafeHandleMarshaller : IMarshallingGenerator { + public bool IsSupported(TargetFramework target, Version version) + { + return target is TargetFramework.Net && version.Major >= 6; + } + public TypeSyntax AsNativeType(TypePositionInfo info) { return MarshallerHelpers.SystemIntPtrType; diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/AdditionalAttributesOnStub.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/AdditionalAttributesOnStub.cs index df7a1c536a8a9..803904a9f9881 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/AdditionalAttributesOnStub.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/AdditionalAttributesOnStub.cs @@ -64,47 +64,45 @@ partial class C public static IEnumerable GetDownlevelTargetFrameworks() { - yield return new object[] { ReferenceAssemblies.Net.Net50, true }; - yield return new object[] { ReferenceAssemblies.NetCore.NetCoreApp31, false }; - yield return new object[] { ReferenceAssemblies.NetStandard.NetStandard20, false }; - yield return new object[] { ReferenceAssemblies.NetFramework.Net48.Default, false }; + yield return new object[] { TestTargetFramework.Net, true }; + yield return new object[] { TestTargetFramework.Net6, true }; + yield return new object[] { TestTargetFramework.Net5, false }; + yield return new object[] { TestTargetFramework.Core, false }; + yield return new object[] { TestTargetFramework.Standard, false }; + yield return new object[] { TestTargetFramework.Framework, false }; } [ConditionalTheory] [MemberData(nameof(GetDownlevelTargetFrameworks))] - public async Task SkipLocalsInitOnDownlevelTargetFrameworks(ReferenceAssemblies referenceAssemblies, bool expectSkipLocalsInit) + public async Task SkipLocalsInitOnDownlevelTargetFrameworks(TestTargetFramework targetFramework, bool expectSkipLocalsInit) { - string source = @" + string source = $@" using System.Runtime.InteropServices; +{CodeSnippets.GeneratedDllImportAttributeDeclaration} namespace System.Runtime.InteropServices -{ - sealed class GeneratedDllImportAttribute : System.Attribute - { - public GeneratedDllImportAttribute(string a) { } - } - +{{ sealed class NativeMarshallingAttribute : System.Attribute - { - public NativeMarshallingAttribute(System.Type nativeType) { } - } -} + {{ + public NativeMarshallingAttribute(System.Type nativeType) {{ }} + }} +}} partial class C -{ +{{ [GeneratedDllImportAttribute(""DoesNotExist"")] public static partial S Method(); -} +}} [NativeMarshalling(typeof(Native))] struct S -{ -} +{{ +}} struct Native -{ - public Native(S s) { } - public S ToManaged() { return default; } -}"; - Compilation comp = await TestUtils.CreateCompilationWithReferenceAssemblies(source, referenceAssemblies); +{{ + public Native(S s) {{ }} + public S ToManaged() {{ return default; }} +}}"; + Compilation comp = await TestUtils.CreateCompilation(source, targetFramework); Compilation newComp = TestUtils.RunGenerators(comp, out _, new Microsoft.Interop.DllImportGenerator()); diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/CodeSnippets.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/CodeSnippets.cs index a4fde3e1b7436..ce3a00f98292e 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/CodeSnippets.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/CodeSnippets.cs @@ -7,6 +7,20 @@ namespace DllImportGenerator.UnitTests { internal static class CodeSnippets { + /// + /// Partially define attribute for pre-.NET 6.0 + /// + public static readonly string GeneratedDllImportAttributeDeclaration = @" +namespace System.Runtime.InteropServices +{ + sealed class GeneratedDllImportAttribute : System.Attribute + { + public GeneratedDllImportAttribute(string a) { } + public CharSet CharSet { get; set; } + } +} +"; + /// /// Trivial declaration of GeneratedDllImport usage /// @@ -336,8 +350,9 @@ partial class Test /// /// Declaration with parameters with set. /// - public static string BasicParametersAndModifiersWithCharSet(string typename, CharSet value) => @$" + public static string BasicParametersAndModifiersWithCharSet(string typename, CharSet value, string preDeclaration = "") => @$" using System.Runtime.InteropServices; +{preDeclaration} partial class Test {{ [GeneratedDllImport(""DoesNotExist"", CharSet = CharSet.{value})] @@ -349,14 +364,15 @@ partial class Test }} "; - public static string BasicParametersAndModifiersWithCharSet(CharSet value) => - BasicParametersAndModifiersWithCharSet(typeof(T).ToString(), value); + public static string BasicParametersAndModifiersWithCharSet(CharSet value, string preDeclaration = "") => + BasicParametersAndModifiersWithCharSet(typeof(T).ToString(), value, preDeclaration); /// /// Declaration with parameters. /// - public static string BasicParametersAndModifiers(string typeName) => @$" + public static string BasicParametersAndModifiers(string typeName, string preDeclaration = "") => @$" using System.Runtime.InteropServices; +{preDeclaration} partial class Test {{ [GeneratedDllImport(""DoesNotExist"")] @@ -396,7 +412,7 @@ partial class Test out {typeName} pOut); }}"; - public static string BasicParametersAndModifiers() => BasicParametersAndModifiers(typeof(T).ToString()); + public static string BasicParametersAndModifiers(string preDeclaration = "") => BasicParametersAndModifiers(typeof(T).ToString(), preDeclaration); /// /// Declaration with [In, Out] style attributes on a by-value parameter. diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Compiles.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Compiles.cs index 4e8509f805679..aee1904683418 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Compiles.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Compiles.cs @@ -310,27 +310,38 @@ public async Task ValidateSnippetsWithPreprocessorDefintions(string source, IEnu Assert.Empty(newCompDiags); } - public static IEnumerable CodeSnippetsToCompileWithForwarder() + public static IEnumerable CodeSnippetsToValidateFallbackForwarder() { - yield return new[] { CodeSnippets.UserDefinedEntryPoint }; - yield return new[] { CodeSnippets.AllSupportedDllImportNamedArguments }; + yield return new object[] { CodeSnippets.UserDefinedEntryPoint, TestTargetFramework.Net, true }; - // Parameter / return types (supported in DllImportGenerator) - yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; - // Parameter / return types (not supported in DllImportGenerator) - yield return new[] { CodeSnippets.BasicParametersAndModifiers() }; + // Confirm that all unsupported target frameworks can be generated. + { + string code = CodeSnippets.BasicParametersAndModifiers(CodeSnippets.GeneratedDllImportAttributeDeclaration); + yield return new object[] { code, TestTargetFramework.Net5, false }; + yield return new object[] { code, TestTargetFramework.Core, false }; + yield return new object[] { code, TestTargetFramework.Standard, false }; + yield return new object[] { code, TestTargetFramework.Framework, false }; + } + + // Confirm that all unsupported target frameworks fallback to a forwarder. + { + string code = CodeSnippets.BasicParametersAndModifiersWithCharSet(CharSet.Unicode, CodeSnippets.GeneratedDllImportAttributeDeclaration); + yield return new object[] { code, TestTargetFramework.Net5, true }; + yield return new object[] { code, TestTargetFramework.Core, true }; + yield return new object[] { code, TestTargetFramework.Standard, true }; + yield return new object[] { code, TestTargetFramework.Framework, true }; + } } [ConditionalTheory] - [MemberData(nameof(CodeSnippetsToCompileWithForwarder))] - public async Task ValidateSnippetsWithForwarder(string source) + [MemberData(nameof(CodeSnippetsToValidateFallbackForwarder))] + public async Task ValidateSnippetsFallbackForwarder(string source, TestTargetFramework targetFramework, bool expectFallbackForwarder) { - Compilation comp = await TestUtils.CreateCompilation(source); + Compilation comp = await TestUtils.CreateCompilation(source, targetFramework); TestUtils.AssertPreSourceGeneratorCompilation(comp); var newComp = TestUtils.RunGenerators( comp, - new DllImportGeneratorOptionsProvider(useMarshalType: false, generateForwarders: true), out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); @@ -349,7 +360,8 @@ public async Task ValidateSnippetsWithForwarder(string source) IMethodSymbol method = model.GetDeclaredSymbol(generatedMethod)!; - Assert.NotNull(method.GetDllImportData()); + // If we expect fallback forwarder, then the DllImportData will not be null. + Assert.Equal(expectFallbackForwarder, method.GetDllImportData() is not null); } public static IEnumerable FullyBlittableSnippetsToCompile() diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs index fdeee50748409..135bfe5224198 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/Diagnostics.cs @@ -16,56 +16,38 @@ namespace DllImportGenerator.UnitTests { public class Diagnostics { - public enum TargetFramework - { - Framework, - Core, - Standard, - Net - } - [ConditionalTheory] - [InlineData(TargetFramework.Framework)] - [InlineData(TargetFramework.Core)] - [InlineData(TargetFramework.Standard)] - public async Task TargetFrameworkNotSupported_ReportsDiagnostic(TargetFramework targetFramework) + [InlineData(TestTargetFramework.Framework)] + [InlineData(TestTargetFramework.Core)] + [InlineData(TestTargetFramework.Standard)] + [InlineData(TestTargetFramework.Net5)] + public async Task TargetFrameworkNotSupported_NoDiagnostic(TestTargetFramework targetFramework) { - string source = @" + string source = $@" using System.Runtime.InteropServices; -namespace System.Runtime.InteropServices -{ - // Define attribute for pre-.NET 5.0 - sealed class GeneratedDllImportAttribute : System.Attribute - { - public GeneratedDllImportAttribute(string a) { } - } -} +{CodeSnippets.GeneratedDllImportAttributeDeclaration} partial class Test -{ +{{ [GeneratedDllImport(""DoesNotExist"")] public static partial void Method(); -} +}} "; - Compilation comp = await TestUtils.CreateCompilationWithReferenceAssemblies(source, GetReferenceAssemblies(targetFramework)); + Compilation comp = await TestUtils.CreateCompilation(source, targetFramework); TestUtils.AssertPreSourceGeneratorCompilation(comp); var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); - DiagnosticResult[] expectedDiags = new DiagnosticResult[] - { - new DiagnosticResult(GeneratorDiagnostics.TargetFrameworkNotSupported) - .WithArguments("5.0") - }; - VerifyDiagnostics(expectedDiags, GetSortedDiagnostics(generatorDiags)); + Assert.Empty(generatorDiags); var newCompDiags = newComp.GetDiagnostics(); Assert.Empty(newCompDiags); } [ConditionalTheory] - [InlineData(TargetFramework.Framework)] - [InlineData(TargetFramework.Core)] - [InlineData(TargetFramework.Standard)] - public async Task TargetFrameworkNotSupported_NoGeneratedDllImport_NoDiagnostic(TargetFramework targetFramework) + [InlineData(TestTargetFramework.Framework)] + [InlineData(TestTargetFramework.Core)] + [InlineData(TestTargetFramework.Standard)] + [InlineData(TestTargetFramework.Net5)] + public async Task TargetFrameworkNotSupported_NoGeneratedDllImport_NoDiagnostic(TestTargetFramework targetFramework) { string source = @" using System.Runtime.InteropServices; @@ -75,7 +57,7 @@ partial class Test public static extern void Method(); } "; - Compilation comp = await TestUtils.CreateCompilationWithReferenceAssemblies(source, GetReferenceAssemblies(targetFramework)); + Compilation comp = await TestUtils.CreateCompilation(source, targetFramework); TestUtils.AssertPreSourceGeneratorCompilation(comp); var newComp = TestUtils.RunGenerators(comp, out var generatorDiags, new Microsoft.Interop.DllImportGenerator()); @@ -398,17 +380,5 @@ private static Diagnostic[] GetSortedDiagnostics(IEnumerable diagnos .ThenBy(d => d.Id) .ToArray(); } - - private static ReferenceAssemblies GetReferenceAssemblies(TargetFramework targetFramework) - { - return targetFramework switch - { - TargetFramework.Framework => ReferenceAssemblies.NetFramework.Net48.Default, - TargetFramework.Standard => ReferenceAssemblies.NetStandard.NetStandard21, - TargetFramework.Core => ReferenceAssemblies.NetCore.NetCoreApp31, - TargetFramework.Net => ReferenceAssemblies.Net.Net50, - _ => ReferenceAssemblies.Default - }; - } } } diff --git a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs index 194b75193e706..c3314ba5d2e72 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs +++ b/src/libraries/System.Runtime.InteropServices/tests/DllImportGenerator.UnitTests/TestUtils.cs @@ -19,7 +19,41 @@ namespace DllImportGenerator.UnitTests { - internal static class TestUtils + /// + /// The target framework to compile against. + /// + /// + /// This enumeration is for testing only and is not to be confused with the product's TargetFramework enum. + /// + public enum TestTargetFramework + { + /// + /// The latest supported .NET Framework version. + /// + Framework, + /// + /// The latest supported .NET Core version. + /// + Core, + /// + /// The latest supported .NET Standard version. + /// + Standard, + /// + /// The latest supported .NET version. + /// + Net, + /// + /// .NET version 5.0. + /// + Net5, + /// + /// .NET version 6.0. + /// + Net6, + } + + public static class TestUtils { /// /// Assert the pre-srouce generator compilation has only @@ -49,9 +83,9 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp) /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static Task CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) + public static Task CreateCompilation(string source, TestTargetFramework targetFramework = TestTargetFramework.Net, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) { - return CreateCompilation(new[] { source }, outputKind, allowUnsafe, preprocessorSymbols); + return CreateCompilation(new[] { source }, targetFramework, outputKind, allowUnsafe, preprocessorSymbols); } /// @@ -61,11 +95,12 @@ public static Task CreateCompilation(string source, OutputKind outp /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static Task CreateCompilation(string[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) + public static Task CreateCompilation(string[] sources, TestTargetFramework targetFramework = TestTargetFramework.Net, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) { return CreateCompilation( sources.Select(source => CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols))).ToArray(), + targetFramework, outputKind, allowUnsafe, preprocessorSymbols); @@ -78,48 +113,45 @@ public static Task CreateCompilation(string[] sources, OutputKind o /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static async Task CreateCompilation(SyntaxTree[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) + public static async Task CreateCompilation(SyntaxTree[] sources, TestTargetFramework targetFramework = TestTargetFramework.Net, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) { - var (mdRefs, ancillary) = GetReferenceAssemblies(); + var (mdRefs, ancillary) = GetReferenceAssemblies(targetFramework); + + var referenceAssemblies = await ResolveReferenceAssemblies(mdRefs); + + // [TODO] Can remove once ancillary logic is removed. + if (targetFramework is TestTargetFramework.Net6 or TestTargetFramework.Net) + { + referenceAssemblies = referenceAssemblies.Add(ancillary); + } return CSharpCompilation.Create("compilation", sources, - (await ResolveReferenceAssemblies(mdRefs)).Add(ancillary), + referenceAssemblies, new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); } /// - /// Create a compilation given source and reference assemblies + /// Get the reference assembly collection for the . /// - /// Source to compile - /// Reference assemblies to include - /// Output type - /// Whether or not use of the unsafe keyword should be allowed - /// The resulting compilation - public static Task CreateCompilationWithReferenceAssemblies(string source, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true) + /// The target framework. + /// The reference assembly collection and metadata references + public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies(TestTargetFramework targetFramework = TestTargetFramework.Net) { - return CreateCompilationWithReferenceAssemblies(new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, referenceAssemblies, outputKind, allowUnsafe); - } - - /// - /// Create a compilation given source and reference assemblies - /// - /// Source to compile - /// Reference assemblies to include - /// Output type - /// Whether or not use of the unsafe keyword should be allowed - /// The resulting compilation - public static async Task CreateCompilationWithReferenceAssemblies(SyntaxTree[] sources, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true) - { - return CSharpCompilation.Create("compilation", - sources, - await ResolveReferenceAssemblies(referenceAssemblies), - new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); - } + // Compute the reference assemblies for the target framework. + var referenceAssembliesSdk = targetFramework switch + { + TestTargetFramework.Framework => ReferenceAssemblies.NetFramework.Net48.Default, + TestTargetFramework.Standard => ReferenceAssemblies.NetStandard.NetStandard21, + TestTargetFramework.Core => ReferenceAssemblies.NetCore.NetCoreApp31, + TestTargetFramework.Net => ReferenceAssemblies.Net.Net60, + TestTargetFramework.Net5 => ReferenceAssemblies.Net.Net50, + TestTargetFramework.Net6 => ReferenceAssemblies.Net.Net60, + _ => ReferenceAssemblies.Default + }; - public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies() - { - var referenceAssemblies = ReferenceAssemblies.Net.Net60 + // Update the reference assemblies to include details from the NuGet.config. + var referenceAssemblies = referenceAssembliesSdk .WithNuGetConfigFilePath(Path.Combine(Path.GetDirectoryName(typeof(TestUtils).Assembly.Location)!, "NuGet.config")); // Include the assembly containing the new attribute and all of its references.