From d11a4f9bffa17e2b90d73a86540af2d32d836cc0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 25 May 2021 15:26:06 -0700 Subject: [PATCH 01/23] First pass converting to V2 API. --- .../DllImportGenerator.UnitTests/TestUtils.cs | 15 +- .../DllImportGenerator/DllImportGenerator.cs | 329 ++++++++++-------- .../GeneratorDiagnostics.cs | 25 +- NuGet.config | 1 + eng/Versions.props | 2 +- 5 files changed, 207 insertions(+), 165 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index 4664b48d903b..c0dd17afaeb3 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -112,7 +112,7 @@ public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies() /// Resulting diagnostics /// Source generator instances /// The resulting compilation - public static Compilation RunGenerators(Compilation comp, out ImmutableArray diagnostics, params ISourceGenerator[] generators) + public static Compilation RunGenerators(Compilation comp, out ImmutableArray diagnostics, params IIncrementalGenerator[] generators) { CreateDriver(comp, null, generators).RunGeneratorsAndUpdateCompilation(comp, out var d, out diagnostics); return d; @@ -125,16 +125,17 @@ public static Compilation RunGenerators(Compilation comp, out ImmutableArrayResulting diagnostics /// Source generator instances /// The resulting compilation - public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsProvider options, out ImmutableArray diagnostics, params ISourceGenerator[] generators) + public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsProvider options, out ImmutableArray diagnostics, params IIncrementalGenerator[] generators) { CreateDriver(comp, options, generators).RunGeneratorsAndUpdateCompilation(comp, out var d, out diagnostics); return d; } - private static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, ISourceGenerator[] generators) - => CSharpGeneratorDriver.Create( - ImmutableArray.Create(generators), - parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options, - optionsProvider: options); + private static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, IIncrementalGenerator[] generators) + => CSharpGeneratorDriver.Create(generators); + // => CSharpGeneratorDriver.Create( + // ImmutableArray.Create(generators), + // parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options, + // optionsProvider: options); } } diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index de2038f7a097..6c354de138ca 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -1,127 +1,27 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; -using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Diagnostics; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.Interop { [Generator] - public class DllImportGenerator : ISourceGenerator + public class DllImportGenerator : IIncrementalGenerator { private const string GeneratedDllImport = nameof(GeneratedDllImport); private const string GeneratedDllImportAttribute = nameof(GeneratedDllImportAttribute); private static readonly Version MinimumSupportedFrameworkVersion = new Version(5, 0); - - public void Execute(GeneratorExecutionContext context) - { - if (context.SyntaxContextReceiver is not SyntaxContextReceiver synRec - || !synRec.Methods.Any()) - { - return; - } - - INamedTypeSymbol? lcidConversionAttrType = context.Compilation.GetTypeByMetadataName(TypeNames.LCIDConversionAttribute); - - // Fire the start/stop pair for source generation - using var _ = Diagnostics.Events.SourceGenerationStartStop(synRec.Methods.Count); - - // Store a mapping between SyntaxTree and SemanticModel. - // SemanticModels cache results and since we could be looking at - // method declarations in the same SyntaxTree we want to benefit from - // this caching. - var syntaxToModel = new Dictionary(); - - var generatorDiagnostics = new GeneratorDiagnostics(context); - - bool isSupported = IsSupportedTargetFramework(context.Compilation, out Version targetFrameworkVersion); - if (!isSupported) - { - // We don't return early here, letting the source generation continue. - // This allows a user to copy generated source and use it as a starting point - // for manual marshalling if desired. - generatorDiagnostics.ReportTargetFrameworkNotSupported(MinimumSupportedFrameworkVersion); - } - - var env = new StubEnvironment(context.Compilation, isSupported, targetFrameworkVersion, context.AnalyzerConfigOptions.GlobalOptions); - var generatedDllImports = new StringBuilder(); - foreach (SyntaxReference synRef in synRec.Methods) - { - var methodSyntax = (MethodDeclarationSyntax)synRef.GetSyntax(context.CancellationToken); - - // Get the model for the method. - if (!syntaxToModel.TryGetValue(methodSyntax.SyntaxTree, out SemanticModel sm)) - { - sm = context.Compilation.GetSemanticModel(methodSyntax.SyntaxTree, ignoreAccessibility: true); - syntaxToModel.Add(methodSyntax.SyntaxTree, sm); - } - - // Process the method syntax and get its SymbolInfo. - var methodSymbolInfo = sm.GetDeclaredSymbol(methodSyntax, context.CancellationToken)!; - - // Get any attributes of interest on the method - AttributeData? generatedDllImportAttr = null; - AttributeData? lcidConversionAttr = null; - foreach (var attr in methodSymbolInfo.GetAttributes()) - { - if (attr.AttributeClass is not null - && attr.AttributeClass.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) - { - generatedDllImportAttr = attr; - } - else if (lcidConversionAttrType != null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, lcidConversionAttrType)) - { - lcidConversionAttr = attr; - } - } - - if (generatedDllImportAttr == null) - continue; - - // Process the GeneratedDllImport attribute - DllImportStub.GeneratedDllImportData stubDllImportData = this.ProcessGeneratedDllImportAttribute(generatedDllImportAttr); - Debug.Assert(stubDllImportData is not null); - - if (stubDllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.BestFitMapping)) - { - generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr, nameof(DllImportStub.GeneratedDllImportData.BestFitMapping)); - } - - if (stubDllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.ThrowOnUnmappableChar)) - { - generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr, nameof(DllImportStub.GeneratedDllImportData.ThrowOnUnmappableChar)); - } - - if (lcidConversionAttr != null) - { - // Using LCIDConversion with GeneratedDllImport is not supported - generatorDiagnostics.ReportConfigurationNotSupported(lcidConversionAttr, nameof(TypeNames.LCIDConversionAttribute)); - } - - // Create the stub. - var dllImportStub = DllImportStub.Create(methodSymbolInfo, stubDllImportData!, env, generatorDiagnostics, context.CancellationToken); - - PrintGeneratedSource(generatedDllImports, methodSyntax, dllImportStub); - } - - Debug.WriteLine(generatedDllImports.ToString()); // [TODO] Find some way to emit this for debugging - logs? - context.AddSource("DllImportGenerator.g.cs", SourceText.From(generatedDllImports.ToString(), Encoding.UTF8)); - } - - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForSyntaxNotifications(() => new SyntaxContextReceiver()); - } - private SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenList) { SyntaxToken[] strippedTokens = new SyntaxToken[tokenList.Count]; @@ -141,8 +41,7 @@ private TypeDeclarationSyntax CreateTypeDeclarationWithoutTrivia(TypeDeclaration .WithModifiers(typeDeclaration.Modifiers); } - private void PrintGeneratedSource( - StringBuilder builder, + private MemberDeclarationSyntax PrintGeneratedSource( MethodDeclarationSyntax userDeclaredMethod, DllImportStub stub) { @@ -176,7 +75,7 @@ private void PrintGeneratedSource( .AddMembers(toPrint); } - builder.AppendLine(toPrint.NormalizeWhitespace().ToString()); + return toPrint; } private static bool IsSupportedTargetFramework(Compilation compilation, out Version version) @@ -257,58 +156,202 @@ private DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute( return stubDllImportData; } - - private class SyntaxContextReceiver : ISyntaxContextReceiver + private sealed record SyntaxSymbolPair(MethodDeclarationSyntax Syntax, IMethodSymbol Symbol) { - public ICollection Methods { get; } = new List(); + public bool Equals(SyntaxSymbolPair other) + { + return Syntax.IsEquivalentTo(other.Syntax) + && SymbolEqualityComparer.Default.Equals(Symbol, other.Symbol); + } - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + public override int GetHashCode() { - SyntaxNode syntaxNode = context.Node; + return (Syntax.ToFullString().GetHashCode(), SymbolEqualityComparer.Default.GetHashCode(Symbol)).GetHashCode(); + } + } - // We only support C# method declarations. - if (syntaxNode.Language != LanguageNames.CSharp - || !syntaxNode.IsKind(SyntaxKind.MethodDeclaration)) + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterExecutionPipeline( + context => { - return; + var methodsToGenerate = context.Sources.Syntax + .Transform( + static node => ShouldVisitNode(node), + static context => + new SyntaxSymbolPair( + (MethodDeclarationSyntax)context.Node, + (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)) + .Filter( + static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( + static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) + ); + + var compilationAndTargetFramework = context.Sources.Compilation + .Transform(compilation => + { + bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); + return (compilation, isSupported, targetFrameworkVersion); + }); + + compilationAndTargetFramework.GenerateSource( + static (context, data) => + { + if (!data.isSupported) + { + // We don't block source generation when the TFM is unsupported. + // 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, + data.targetFrameworkVersion.ToString(2))); + } + }); + + var stubEnvironment = compilationAndTargetFramework + .Join(context.Sources.AnalyzerConfigOptions) + .Transform( + data => + new StubEnvironment( + data.Item1.compilation, + data.Item1.isSupported, + data.Item1.targetFrameworkVersion, + data.Item2.Single().GlobalOptions) + ); + + methodsToGenerate + .Join(stubEnvironment) + .Transform(data => new + { + Syntax = data.Item1.Syntax, + Symbol = data.Item1.Symbol, + Environment = data.Item2.Single() + }) + .Transform( + data => GenerateSource(data.Syntax, data.Symbol, data.Environment) + ) + .WithComparer(new GeneratedSourceComparer()) + .BatchTransform(static generatedSources => + { + StringBuilder source = new StringBuilder(); + ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); + foreach (var generated in generatedSources) + { + source.AppendLine(generated.Item1.NormalizeWhitespace().ToFullString()); + diagnostics.AddRange(generated.Item2); + } + return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); + }) + .GenerateSource( + static (context, data) => + { + foreach (var diagnostic in data.diagnostics) + { + context.ReportDiagnostic(diagnostic); + } + + context.AddSource("GeneratedDllImports.g.cs", data.source); + } + ); } + ); + } + + private class GeneratedSourceComparer : IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> + { + public bool Equals((MemberDeclarationSyntax, ImmutableArray) x, (MemberDeclarationSyntax, ImmutableArray) y) + { + return x.Item1.IsEquivalentTo(y.Item1) + && x.Item2.SequenceEqual(y.Item2); + } - var methodSyntax = (MethodDeclarationSyntax)syntaxNode; + public int GetHashCode((MemberDeclarationSyntax, ImmutableArray) obj) + { + return (obj.Item1.ToFullString(), obj.Item2.Aggregate(0, (hash, diagnostic) => (hash, diagnostic).GetHashCode())).GetHashCode(); + } + } - // Verify the method has no generic types or defined implementation - // and is marked static and partial. - if (!(methodSyntax.TypeParameterList is null) - || !(methodSyntax.Body is null) - || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) - || !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + private (MemberDeclarationSyntax, ImmutableArray) GenerateSource(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment) + { + INamedTypeSymbol? lcidConversionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.LCIDConversionAttribute); + // Get any attributes of interest on the method + AttributeData? generatedDllImportAttr = null; + AttributeData? lcidConversionAttr = null; + foreach (var attr in symbol.GetAttributes()) + { + if (attr.AttributeClass is not null + && attr.AttributeClass.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) { - return; + generatedDllImportAttr = attr; } - - // Verify that the types the method is declared in are marked partial. - for (SyntaxNode? parentNode = methodSyntax.Parent; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent) + else if (lcidConversionAttrType != null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, lcidConversionAttrType)) { - if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) - { - return; - } + lcidConversionAttr = attr; } + } + + Debug.Assert(generatedDllImportAttr is not null); + + var generatorDiagnostics = new GeneratorDiagnostics(); + + // Process the GeneratedDllImport attribute + DllImportStub.GeneratedDllImportData stubDllImportData = this.ProcessGeneratedDllImportAttribute(generatedDllImportAttr!); + Debug.Assert(stubDllImportData is not null); - // Check if the method is marked with the GeneratedDllImport attribute. - foreach (AttributeListSyntax listSyntax in methodSyntax.AttributeLists) + if (stubDllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.BestFitMapping)) + { + generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(DllImportStub.GeneratedDllImportData.BestFitMapping)); + } + + if (stubDllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.ThrowOnUnmappableChar)) + { + generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(DllImportStub.GeneratedDllImportData.ThrowOnUnmappableChar)); + } + + if (lcidConversionAttr != null) + { + // Using LCIDConversion with GeneratedDllImport is not supported + generatorDiagnostics.ReportConfigurationNotSupported(lcidConversionAttr, nameof(TypeNames.LCIDConversionAttribute)); + } + + // Create the stub. + var dllImportStub = DllImportStub.Create(symbol, stubDllImportData!, environment, generatorDiagnostics); + + return (PrintGeneratedSource(syntax, dllImportStub), generatorDiagnostics.Diagnostics.ToImmutableArray()); + } + + private static bool ShouldVisitNode(SyntaxNode syntaxNode) + { + // We only support C# method declarations. + if (syntaxNode.Language != LanguageNames.CSharp + || !syntaxNode.IsKind(SyntaxKind.MethodDeclaration)) + { + return false; + } + + var methodSyntax = (MethodDeclarationSyntax)syntaxNode; + + // Verify the method has no generic types or defined implementation + // and is marked static and partial. + if (!(methodSyntax.TypeParameterList is null) + || !(methodSyntax.Body is null) + || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) + || !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) + { + return false; + } + + // Verify that the types the method is declared in are marked partial. + for (SyntaxNode? parentNode = methodSyntax.Parent; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent) + { + if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) { - foreach (AttributeSyntax attrSyntax in listSyntax.Attributes) - { - SymbolInfo info = context.SemanticModel.GetSymbolInfo(attrSyntax); - if (info.Symbol is IMethodSymbol attrConstructor - && attrConstructor.ContainingType.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) - { - this.Methods.Add(syntaxNode.GetReference()); - return; - } - } + return false; } } + return true; } } } diff --git a/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs b/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs index 9c5d7952fa6b..e17c8ad9057c 100644 --- a/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs +++ b/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs @@ -147,12 +147,9 @@ public class Ids isEnabledByDefault: true, description: GetResourceString(nameof(Resources.TargetFrameworkNotSupportedDescription))); - private readonly GeneratorExecutionContext context; + private readonly List diagnostics = new List(); - public GeneratorDiagnostics(GeneratorExecutionContext context) - { - this.context = context; - } + public IReadOnlyList Diagnostics => diagnostics; /// /// Report diagnostic for configuration that is not supported by the DLL import source generator @@ -167,14 +164,14 @@ public void ReportConfigurationNotSupported( { if (unsupportedValue == null) { - this.context.ReportDiagnostic( + diagnostics.Add( attributeData.CreateDiagnostic( GeneratorDiagnostics.ConfigurationNotSupported, configurationName)); } else { - this.context.ReportDiagnostic( + diagnostics.Add( attributeData.CreateDiagnostic( GeneratorDiagnostics.ConfigurationValueNotSupported, unsupportedValue, @@ -198,7 +195,7 @@ internal void ReportMarshallingNotSupported( // Report the specific not-supported reason. if (info.IsManagedReturnPosition) { - this.context.ReportDiagnostic( + diagnostics.Add( method.CreateDiagnostic( GeneratorDiagnostics.ReturnTypeNotSupportedWithDetails, notSupportedDetails!, @@ -208,7 +205,7 @@ internal void ReportMarshallingNotSupported( { Debug.Assert(info.ManagedIndex <= method.Parameters.Length); IParameterSymbol paramSymbol = method.Parameters[info.ManagedIndex]; - this.context.ReportDiagnostic( + diagnostics.Add( paramSymbol.CreateDiagnostic( GeneratorDiagnostics.ParameterTypeNotSupportedWithDetails, notSupportedDetails!, @@ -222,7 +219,7 @@ internal void ReportMarshallingNotSupported( // than when there is no attribute and the type itself is not supported. if (info.IsManagedReturnPosition) { - this.context.ReportDiagnostic( + diagnostics.Add( method.CreateDiagnostic( GeneratorDiagnostics.ReturnConfigurationNotSupported, nameof(System.Runtime.InteropServices.MarshalAsAttribute), @@ -232,7 +229,7 @@ internal void ReportMarshallingNotSupported( { Debug.Assert(info.ManagedIndex <= method.Parameters.Length); IParameterSymbol paramSymbol = method.Parameters[info.ManagedIndex]; - this.context.ReportDiagnostic( + diagnostics.Add( paramSymbol.CreateDiagnostic( GeneratorDiagnostics.ParameterConfigurationNotSupported, nameof(System.Runtime.InteropServices.MarshalAsAttribute), @@ -244,7 +241,7 @@ internal void ReportMarshallingNotSupported( // Report that the type is not supported if (info.IsManagedReturnPosition) { - this.context.ReportDiagnostic( + diagnostics.Add( method.CreateDiagnostic( GeneratorDiagnostics.ReturnTypeNotSupported, method.ReturnType.ToDisplayString(), @@ -254,7 +251,7 @@ internal void ReportMarshallingNotSupported( { Debug.Assert(info.ManagedIndex <= method.Parameters.Length); IParameterSymbol paramSymbol = method.Parameters[info.ManagedIndex]; - this.context.ReportDiagnostic( + diagnostics.Add( paramSymbol.CreateDiagnostic( GeneratorDiagnostics.ParameterTypeNotSupported, paramSymbol.Type.ToDisplayString(), @@ -269,7 +266,7 @@ internal void ReportMarshallingNotSupported( /// Minimum supported version of .NET public void ReportTargetFrameworkNotSupported(Version minimumSupportedVersion) { - this.context.ReportDiagnostic( + diagnostics.Add( Diagnostic.Create( TargetFrameworkNotSupported, Location.None, diff --git a/NuGet.config b/NuGet.config index bd75cae005e3..66b0085249d8 100644 --- a/NuGet.config +++ b/NuGet.config @@ -11,6 +11,7 @@ + diff --git a/eng/Versions.props b/eng/Versions.props index a6cc7f1a1ec1..29e5dc4f0011 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -19,7 +19,7 @@ 2.4.1 2.4.3 - 3.10.0-3.21229.26 + 3.10.0-dev.21275.2 1.0.1-beta1.20478.1 3.3.3-beta1.21268.3 From 7be13c35b3697a5f811b09cb0665b6e56d9cf504 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 25 May 2021 15:36:55 -0700 Subject: [PATCH 02/23] Use the WrapGenerator API to enable more scenarios for testing. --- .../DllImportGenerator.UnitTests/TestUtils.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index c0dd17afaeb3..7dd5c29ebfbb 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -132,10 +132,9 @@ public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsP } private static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, IIncrementalGenerator[] generators) - => CSharpGeneratorDriver.Create(generators); - // => CSharpGeneratorDriver.Create( - // ImmutableArray.Create(generators), - // parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options, - // optionsProvider: options); + => CSharpGeneratorDriver.Create( + ImmutableArray.Create(generators.Select(gen => GeneratorDriver.WrapGenerator(gen)).ToArray()), + parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options, + optionsProvider: options); } } From 781eea1d0dbb96a05b5e0af142f7296df720329b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 25 May 2021 16:34:56 -0700 Subject: [PATCH 03/23] Add one more early filter for now. --- .../DllImportGenerator/DllImportGenerator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 6c354de138ca..e4c63699ec28 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -351,6 +351,13 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode) return false; } } + + // Filter out methods with no attributes early. + if (methodSyntax.AttributeLists.Count == 0) + { + return false; + } + return true; } } From 0ea99339c6ffd334df6131160a7847f6f588aa02 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 18 Jun 2021 10:24:05 -0700 Subject: [PATCH 04/23] Update incremental generators to approved API. --- .../DllImportGenerator/DllImportGenerator.cs | 71 ++++++++++--------- eng/Versions.props | 2 +- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 4f042636647b..efaa34729ec9 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -176,26 +176,26 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterExecutionPipeline( context => { - var methodsToGenerate = context.Sources.Syntax - .Transform( - static node => ShouldVisitNode(node), - static context => + var methodsToGenerate = context.SyntaxProvider + .CreateSyntaxProvider( + static (node, ct) => ShouldVisitNode(node), + static (context, ct) => new SyntaxSymbolPair( (MethodDeclarationSyntax)context.Node, - (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!)) - .Filter( + (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)!)) + .Where( static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) ); - var compilationAndTargetFramework = context.Sources.Compilation - .Transform(compilation => + var compilationAndTargetFramework = context.CompilationProvider + .Select((compilation, ct) => { bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); return (compilation, isSupported, targetFrameworkVersion); }); - compilationAndTargetFramework.GenerateSource( + context.RegisterSourceOutput(compilationAndTargetFramework, static (context, data) => { if (!data.isSupported) @@ -212,29 +212,30 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }); var stubEnvironment = compilationAndTargetFramework - .Join(context.Sources.AnalyzerConfigOptions) - .Transform( - data => + .Combine(context.AnalyzerConfigOptionsProvider) + .Select( + (data, ct) => new StubEnvironment( - data.Item1.compilation, - data.Item1.isSupported, - data.Item1.targetFrameworkVersion, - data.Item2.Single().GlobalOptions) + data.Left.compilation, + data.Left.isSupported, + data.Left.targetFrameworkVersion, + data.Right.GlobalOptions) ); - methodsToGenerate - .Join(stubEnvironment) - .Transform(data => new + var methodSourceAndDiagnostics = methodsToGenerate + .Combine(stubEnvironment) + .Select((data, ct) => new { - Syntax = data.Item1.Syntax, - Symbol = data.Item1.Symbol, - Environment = data.Item2.Single() + data.Left.Syntax, + data.Left.Symbol, + Environment = data.Right }) - .Transform( - data => GenerateSource(data.Syntax, data.Symbol, data.Environment) + .Select( + (data, ct) => GenerateSource(data.Syntax, data.Symbol, data.Environment) ) .WithComparer(new GeneratedSourceComparer()) - .BatchTransform(static generatedSources => + .Collect() + .Select(static (generatedSources, ct) => { StringBuilder source = new StringBuilder(); // Mark in source that the file is auto-generated. @@ -246,18 +247,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context) diagnostics.AddRange(generated.Item2); } return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); - }) - .GenerateSource( - static (context, data) => - { - foreach (var diagnostic in data.diagnostics) - { - context.ReportDiagnostic(diagnostic); - } + }); - context.AddSource("GeneratedDllImports.g.cs", data.source); + context.RegisterSourceOutput(methodSourceAndDiagnostics, + static (context, data) => + { + foreach (var diagnostic in data.diagnostics) + { + context.ReportDiagnostic(diagnostic); } - ); + + context.AddSource("GeneratedDllImports.g.cs", data.source); + }); } ); } diff --git a/eng/Versions.props b/eng/Versions.props index 29e5dc4f0011..800e44d973b8 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -19,7 +19,7 @@ 2.4.1 2.4.3 - 3.10.0-dev.21275.2 + 4.0.0-dev.21318.1 1.0.1-beta1.20478.1 3.3.3-beta1.21268.3 From 4d3581af7c47ad13ebb71d00ff683b7f3585ee35 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 18 Jun 2021 10:49:44 -0700 Subject: [PATCH 05/23] Fix unsupported TFM diagnostics. --- .../DllImportGenerator/DllImportGenerator.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index efaa34729ec9..f6adc7953637 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -195,10 +195,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) return (compilation, isSupported, targetFrameworkVersion); }); - context.RegisterSourceOutput(compilationAndTargetFramework, + context.RegisterSourceOutput( + compilationAndTargetFramework + .Combine(methodsToGenerate.Collect()), static (context, data) => { - if (!data.isSupported) + if (!data.Left.isSupported && data.Right.Any()) { // We don't block source generation when the TFM is unsupported. // This allows a user to copy generated source and use it as a starting point @@ -207,7 +209,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) Diagnostic.Create( GeneratorDiagnostics.TargetFrameworkNotSupported, Location.None, - data.targetFrameworkVersion.ToString(2))); + MinimumSupportedFrameworkVersion.ToString(2))); } }); From 44fc62106558707f17a75f934896f62c7f6a7c25 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 28 Jun 2021 11:27:45 -0700 Subject: [PATCH 06/23] Use our own custom objects for representing types at the TypePositionInfo level instead of using symbols. This way, we can use the default comparer between TypePositionInfo instances to know if they're identical, even across compilations. --- .../DllImportGenerator/DllImportGenerator.cs | 5 +- .../DllImportGenerator/DllImportStub.cs | 4 +- .../DllImportGenerator/ManagedTypeInfo.cs | 59 ++++++++++++ .../Marshalling/BlittableMarshaller.cs | 2 +- .../Marshalling/BoolMarshaller.cs | 2 +- .../Marshalling/CharMarshaller.cs | 2 +- .../Marshalling/DelegateMarshaller.cs | 2 +- .../Marshalling/Forwarder.cs | 4 +- .../Marshalling/HResultExceptionMarshaller.cs | 2 +- .../Marshalling/MarshallingGenerator.cs | 95 ++++++++++--------- .../Marshalling/SafeHandleMarshaller.cs | 8 +- .../MarshallingAttributeInfo.cs | 90 ++++++++++-------- .../DllImportGenerator/StubCodeGenerator.cs | 4 +- .../DllImportGenerator/TypePositionInfo.cs | 18 +++- .../TypeSymbolExtensions.cs | 4 +- 15 files changed, 193 insertions(+), 108 deletions(-) create mode 100644 DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index f6adc7953637..a2b018bcd203 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -236,6 +236,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) (data, ct) => GenerateSource(data.Syntax, data.Symbol, data.Environment) ) .WithComparer(new GeneratedSourceComparer()) + // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. + .Select( + (data, ct) => (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2)) .Collect() .Select(static (generatedSources, ct) => { @@ -245,7 +248,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); foreach (var generated in generatedSources) { - source.AppendLine(generated.Item1.NormalizeWhitespace().ToFullString()); + source.AppendLine(generated.Item1); diagnostics.AddRange(generated.Item2); } return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); diff --git a/DllImportGenerator/DllImportGenerator/DllImportStub.cs b/DllImportGenerator/DllImportGenerator/DllImportStub.cs index f74f9b2a3cff..86d0c4efa1a2 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportStub.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportStub.cs @@ -35,7 +35,7 @@ private DllImportStub() public IEnumerable StubContainingTypes { get; init; } - public TypeSyntax StubReturnType { get => this.returnTypeInfo.ManagedType.AsTypeSyntax(); } + public TypeSyntax StubReturnType { get => this.returnTypeInfo.ManagedType.Syntax; } public IEnumerable StubParameters { @@ -47,7 +47,7 @@ public IEnumerable StubParameters && typeinfo.ManagedIndex != TypePositionInfo.ReturnIndex) { yield return Parameter(Identifier(typeinfo.InstanceIdentifier)) - .WithType(typeinfo.ManagedType.AsTypeSyntax()) + .WithType(typeinfo.ManagedType.Syntax) .WithModifiers(TokenList(Token(typeinfo.RefKindSyntax))); } } diff --git a/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs b/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs new file mode 100644 index 000000000000..d0f018d6fbe4 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs @@ -0,0 +1,59 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Interop +{ + /// + /// A discriminated union that contains enough info about a managed type to determine a marshalling generator and generate code. + /// + internal abstract record ManagedTypeInfo(string FullTypeName) + { + public TypeSyntax Syntax { get; } = SyntaxFactory.ParseTypeName(FullTypeName); + + public static ManagedTypeInfo CreateTypeInfoForTypeSymbol(ITypeSymbol type) + { + string typeName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + if (type.SpecialType != SpecialType.None) + { + return new SpecialTypeInfo(typeName, type.SpecialType); + } + if (type.TypeKind == TypeKind.Enum) + { + return new EnumTypeInfo(typeName, ((INamedTypeSymbol)type).EnumUnderlyingType!.SpecialType); + } + if (type.TypeKind == TypeKind.Pointer) + { + return new PointerTypeInfo(typeName, IsFunctionPointer: false); + } + if (type.TypeKind == TypeKind.FunctionPointer) + { + return new PointerTypeInfo(typeName, IsFunctionPointer: true); + } + if (type.TypeKind == TypeKind.Array && type is IArrayTypeSymbol { IsSZArray: true } arraySymbol) + { + return new SzArrayType(CreateTypeInfoForTypeSymbol(arraySymbol.ElementType)); + } + if (type.TypeKind == TypeKind.Delegate) + { + return new DelegateTypeInfo(typeName); + } + return new SimpleManagedTypeInfo(typeName); + } + } + + internal sealed record SpecialTypeInfo(string FullTypeName, SpecialType SpecialType) : ManagedTypeInfo(FullTypeName); + + internal sealed record EnumTypeInfo(string FullTypeName, SpecialType UnderlyingType) : ManagedTypeInfo(FullTypeName); + + internal sealed record PointerTypeInfo(string FullTypeName, bool IsFunctionPointer) : ManagedTypeInfo(FullTypeName); + + internal sealed record SzArrayType(ManagedTypeInfo ElementTypeInfo) : ManagedTypeInfo($"{ElementTypeInfo.FullTypeName}[]"); + + internal sealed record DelegateTypeInfo(string FullTypeName) : ManagedTypeInfo(FullTypeName); + + internal sealed record SimpleManagedTypeInfo(string FullTypeName) : ManagedTypeInfo(FullTypeName); +} diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs index 0f158706cd03..15fa7774a996 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BlittableMarshaller.cs @@ -11,7 +11,7 @@ internal class BlittableMarshaller : IMarshallingGenerator { public TypeSyntax AsNativeType(TypePositionInfo info) { - return info.ManagedType.AsTypeSyntax(); + return info.ManagedType.Syntax; } public ParameterSyntax AsParameter(TypePositionInfo info) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs index b659ccbda4b7..8ca19164a5b6 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/BoolMarshaller.cs @@ -25,7 +25,7 @@ protected BoolMarshallerBase(PredefinedTypeSyntax nativeType, int trueValue, int public TypeSyntax AsNativeType(TypePositionInfo info) { - Debug.Assert(info.ManagedType.SpecialType == SpecialType.System_Boolean); + Debug.Assert(info.ManagedType is SpecialTypeInfo(_, SpecialType.System_Boolean)); return _nativeType; } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs index b3279390da77..128e5245aa35 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/CharMarshaller.cs @@ -33,7 +33,7 @@ public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context) public TypeSyntax AsNativeType(TypePositionInfo info) { - Debug.Assert(info.ManagedType.SpecialType == SpecialType.System_Char); + Debug.Assert(info.ManagedType is SpecialTypeInfo(_, SpecialType.System_Char)); return NativeType; } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs index 21a5c80d4cca..b5aca0f44207 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/DelegateMarshaller.cs @@ -89,7 +89,7 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont .WithTypeArgumentList( TypeArgumentList( SingletonSeparatedList( - info.ManagedType.AsTypeSyntax())))), + info.ManagedType.Syntax)))), ArgumentList(SingletonSeparatedList(Argument(IdentifierName(nativeIdentifier))))), LiteralExpression(SyntaxKind.NullLiteralExpression)))); } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs b/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs index 4921ae92ec64..e6ebf2c13109 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/Forwarder.cs @@ -10,14 +10,14 @@ internal class Forwarder : IMarshallingGenerator { public TypeSyntax AsNativeType(TypePositionInfo info) { - return info.ManagedType.AsTypeSyntax(); + return info.ManagedType.Syntax; } public ParameterSyntax AsParameter(TypePositionInfo info) { return Parameter(Identifier(info.InstanceIdentifier)) .WithModifiers(TokenList(Token(info.RefKindSyntax))) - .WithType(info.ManagedType.AsTypeSyntax()); + .WithType(info.ManagedType.Syntax); } public ArgumentSyntax AsArgument(TypePositionInfo info, StubCodeContext context) diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs index 4b56c8a31072..38e09302fe93 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/HResultExceptionMarshaller.cs @@ -15,7 +15,7 @@ internal sealed class HResultExceptionMarshaller : IMarshallingGenerator public TypeSyntax AsNativeType(TypePositionInfo info) { - Debug.Assert(info.ManagedType.SpecialType == SpecialType.System_Int32); + Debug.Assert(info.ManagedType is SpecialTypeInfo(_, SpecialType.System_Int32)); return NativeType; } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index 8194fc099929..ec6ef3bc92f6 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -181,31 +181,31 @@ private static IMarshallingGenerator CreateCore( if (info.IsNativeReturnPosition && !info.IsManagedReturnPosition) { // Use marshaller for native HRESULT return / exception throwing - System.Diagnostics.Debug.Assert(info.ManagedType.SpecialType == SpecialType.System_Int32); + System.Diagnostics.Debug.Assert(info.ManagedType is SpecialTypeInfo { SpecialType: SpecialType.System_Int32 }); return HResultException; } switch (info) { // Blittable primitives with no marshalling info or with a compatible [MarshalAs] attribute. - case { ManagedType: { SpecialType: SpecialType.System_SByte }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I1, _) } - or { ManagedType: { SpecialType: SpecialType.System_Byte }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U1, _) } - or { ManagedType: { SpecialType: SpecialType.System_Int16 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I2, _) } - or { ManagedType: { SpecialType: SpecialType.System_UInt16 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U2, _) } - or { ManagedType: { SpecialType: SpecialType.System_Int32 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I4, _) } - or { ManagedType: { SpecialType: SpecialType.System_UInt32 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U4, _) } - or { ManagedType: { SpecialType: SpecialType.System_Int64 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I8, _) } - or { ManagedType: { SpecialType: SpecialType.System_UInt64 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U8, _) } - or { ManagedType: { SpecialType: SpecialType.System_IntPtr }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.SysInt, _) } - or { ManagedType: { SpecialType: SpecialType.System_UIntPtr }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.SysUInt, _) } - or { ManagedType: { SpecialType: SpecialType.System_Single }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.R4, _) } - or { ManagedType: { SpecialType: SpecialType.System_Double }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.R8, _) }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_SByte }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I1, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Byte }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U1, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Int16 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I2, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_UInt16 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U2, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Int32 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I4, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_UInt32 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U4, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Int64 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.I8, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_UInt64 }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.U8, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_IntPtr }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.SysInt, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_UIntPtr }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.SysUInt, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Single }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.R4, _) } + or { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Double }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.R8, _) }: return Blittable; // Enum with no marshalling info - case { ManagedType: { TypeKind: TypeKind.Enum }, MarshallingAttributeInfo: NoMarshallingInfo }: + case { ManagedType: EnumTypeInfo enumType, MarshallingAttributeInfo: NoMarshallingInfo }: // Check that the underlying type is not bool or char. C# does not allow this, but ECMA-335 does. - var underlyingSpecialType = ((INamedTypeSymbol)info.ManagedType).EnumUnderlyingType!.SpecialType; + var underlyingSpecialType = enumType.UnderlyingType; if (underlyingSpecialType == SpecialType.System_Boolean || underlyingSpecialType == SpecialType.System_Char) { throw new MarshallingNotSupportedException(info, context); @@ -213,31 +213,31 @@ private static IMarshallingGenerator CreateCore( return Blittable; // Pointer with no marshalling info - case { ManagedType: { TypeKind: TypeKind.Pointer }, MarshallingAttributeInfo: NoMarshallingInfo }: + case { ManagedType: PointerTypeInfo(_, IsFunctionPointer:false), MarshallingAttributeInfo: NoMarshallingInfo }: return Blittable; // Function pointer with no marshalling info - case { ManagedType: { TypeKind: TypeKind.FunctionPointer }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.FunctionPtr, _) }: + case { ManagedType: PointerTypeInfo(_, IsFunctionPointer: true), MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.FunctionPtr, _) }: return Blittable; - case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: NoMarshallingInfo }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: NoMarshallingInfo }: return WinBool; // [Compat] Matching the default for the built-in runtime marshallers. - case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo(UnmanagedType.I1 or UnmanagedType.U1, _) }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo(UnmanagedType.I1 or UnmanagedType.U1, _) }: return ByteBool; - case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo(UnmanagedType.I4 or UnmanagedType.U4 or UnmanagedType.Bool, _) }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo(UnmanagedType.I4 or UnmanagedType.U4 or UnmanagedType.Bool, _) }: return WinBool; - case { ManagedType: { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo(UnmanagedType.VariantBool, _) }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Boolean }, MarshallingAttributeInfo: MarshalAsInfo(UnmanagedType.VariantBool, _) }: return VariantBool; - case { ManagedType: { TypeKind: TypeKind.Delegate }, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.FunctionPtr, _) }: + case { ManagedType: DelegateTypeInfo, MarshallingAttributeInfo: NoMarshallingInfo or MarshalAsInfo(UnmanagedType.FunctionPtr, _) }: return Delegate; - case { MarshallingAttributeInfo: SafeHandleMarshallingInfo }: + case { MarshallingAttributeInfo: SafeHandleMarshallingInfo(_, bool isAbstract) }: if (!context.AdditionalTemporaryStateLivesAcrossStages) { throw new MarshallingNotSupportedException(info, context); } - if (info.IsByRef && info.ManagedType.IsAbstract) + if (info.IsByRef && isAbstract) { throw new MarshallingNotSupportedException(info, context) { @@ -261,13 +261,13 @@ private static IMarshallingGenerator CreateCore( // Cases that just match on type must come after the checks that match only on marshalling attribute info. // The checks below do not account for generic marshalling overrides like [MarshalUsing], so those checks must come first. - case { ManagedType: { SpecialType: SpecialType.System_Char } }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Char } }: return CreateCharMarshaller(info, context); - case { ManagedType: { SpecialType: SpecialType.System_String } }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_String } }: return CreateStringMarshaller(info, context); - case { ManagedType: { SpecialType: SpecialType.System_Void } }: + case { ManagedType: SpecialTypeInfo { SpecialType: SpecialType.System_Void } }: return Forwarder; default: @@ -392,7 +392,7 @@ ExpressionSyntax GetExpressionForParam(TypePositionInfo? paramInfo) NotSupportedDetails = Resources.ArraySizeParamIndexOutOfRange }; } - else if (!paramInfo.ManagedType.IsIntegralType()) + else if (paramInfo.ManagedType is not SpecialTypeInfo specialTypeInfo || !specialTypeInfo.SpecialType.IsIntegralType()) { throw new MarshallingNotSupportedException(info, context) { @@ -414,14 +414,14 @@ private static IMarshallingGenerator CreateCustomNativeTypeMarshaller(TypePositi { ValidateCustomNativeTypeMarshallingSupported(info, context, marshalInfo); - ICustomNativeTypeMarshallingStrategy marshallingStrategy = new SimpleCustomNativeTypeMarshalling(marshalInfo.NativeMarshallingType.AsTypeSyntax()); + ICustomNativeTypeMarshallingStrategy marshallingStrategy = new SimpleCustomNativeTypeMarshalling(marshalInfo.NativeMarshallingType.Syntax); - if ((marshalInfo.MarshallingMethods & SupportedMarshallingMethods.ManagedToNativeStackalloc) != 0) + if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNativeStackalloc) != 0) { marshallingStrategy = new StackallocOptimizationMarshalling(marshallingStrategy); } - if (ManualTypeMarshallingHelper.HasFreeNativeMethod(marshalInfo.NativeMarshallingType)) + if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.FreeNativeResources) != 0) { marshallingStrategy = new FreeNativeCleanupStrategy(marshallingStrategy); } @@ -439,7 +439,7 @@ private static IMarshallingGenerator CreateCustomNativeTypeMarshaller(TypePositi IMarshallingGenerator marshallingGenerator = new CustomNativeTypeMarshallingGenerator(marshallingStrategy, enableByValueContentsMarshalling: false); - if ((marshalInfo.MarshallingMethods & SupportedMarshallingMethods.Pinning) != 0) + if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedTypePinning) != 0) { return new PinnableManagedValueMarshaller(marshallingGenerator); } @@ -452,53 +452,54 @@ private static void ValidateCustomNativeTypeMarshallingSupported(TypePositionInf // The marshalling method for this type doesn't support marshalling from native to managed, // but our scenario requires marshalling from native to managed. if ((info.RefKind == RefKind.Ref || info.RefKind == RefKind.Out || info.IsManagedReturnPosition) - && (marshalInfo.MarshallingMethods & SupportedMarshallingMethods.NativeToManaged) == 0) + && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.NativeToManaged) == 0) { throw new MarshallingNotSupportedException(info, context) { - NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingNativeToManagedUnsupported, marshalInfo.NativeMarshallingType.ToDisplayString()) + NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingNativeToManagedUnsupported, marshalInfo.NativeMarshallingType.FullTypeName) }; } // The marshalling method for this type doesn't support marshalling from managed to native by value, // but our scenario requires marshalling from managed to native by value. else if (!info.IsByRef - && (marshalInfo.MarshallingMethods & SupportedMarshallingMethods.ManagedToNative) == 0 - && (context.SingleFrameSpansNativeContext && (marshalInfo.MarshallingMethods & (SupportedMarshallingMethods.Pinning | SupportedMarshallingMethods.ManagedToNativeStackalloc)) == 0)) + && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNative) == 0 + && (context.SingleFrameSpansNativeContext && (marshalInfo.MarshallingFeatures & (CustomMarshallingFeatures.ManagedTypePinning | CustomMarshallingFeatures.ManagedToNativeStackalloc)) == 0)) { throw new MarshallingNotSupportedException(info, context) { - NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingManagedToNativeUnsupported, marshalInfo.NativeMarshallingType.ToDisplayString()) + NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingManagedToNativeUnsupported, marshalInfo.NativeMarshallingType.FullTypeName) }; } // The marshalling method for this type doesn't support marshalling from managed to native by reference, // but our scenario requires marshalling from managed to native by reference. // "in" byref supports stack marshalling. else if (info.RefKind == RefKind.In - && (marshalInfo.MarshallingMethods & SupportedMarshallingMethods.ManagedToNative) == 0 - && !(context.SingleFrameSpansNativeContext && (marshalInfo.MarshallingMethods & SupportedMarshallingMethods.ManagedToNativeStackalloc) != 0)) + && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNative) == 0 + && !(context.SingleFrameSpansNativeContext && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNativeStackalloc) != 0)) { throw new MarshallingNotSupportedException(info, context) { - NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingManagedToNativeUnsupported, marshalInfo.NativeMarshallingType.ToDisplayString()) + NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingManagedToNativeUnsupported, marshalInfo.NativeMarshallingType.FullTypeName) }; } // The marshalling method for this type doesn't support marshalling from managed to native by reference, // but our scenario requires marshalling from managed to native by reference. // "ref" byref marshalling doesn't support stack marshalling else if (info.RefKind == RefKind.Ref - && (marshalInfo.MarshallingMethods & SupportedMarshallingMethods.ManagedToNative) == 0) + && (marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedToNative) == 0) { throw new MarshallingNotSupportedException(info, context) { - NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingManagedToNativeUnsupported, marshalInfo.NativeMarshallingType.ToDisplayString()) + NotSupportedDetails = string.Format(Resources.CustomTypeMarshallingManagedToNativeUnsupported, marshalInfo.NativeMarshallingType.FullTypeName) }; } } private static ICustomNativeTypeMarshallingStrategy DecorateWithValuePropertyStrategy(NativeMarshallingAttributeInfo marshalInfo, ICustomNativeTypeMarshallingStrategy nativeTypeMarshaller) { - TypeSyntax valuePropertyTypeSyntax = marshalInfo.ValuePropertyType!.AsTypeSyntax(); - if (ManualTypeMarshallingHelper.FindGetPinnableReference(marshalInfo.NativeMarshallingType) is not null) + TypeSyntax valuePropertyTypeSyntax = marshalInfo.ValuePropertyType!.Syntax; + + if ((marshalInfo.MarshallingFeatures & CustomMarshallingFeatures.NativeTypePinning) != 0) { return new PinnableMarshallerTypeMarshalling(nativeTypeMarshaller, valuePropertyTypeSyntax); } @@ -524,7 +525,7 @@ private static IMarshallingGenerator CreateNativeCollectionMarshaller( if (isBlittable) { - marshallingStrategy = new ContiguousBlittableElementCollectionMarshalling(marshallingStrategy, collectionInfo.ElementType.AsTypeSyntax()); + marshallingStrategy = new ContiguousBlittableElementCollectionMarshalling(marshallingStrategy, collectionInfo.ElementType.Syntax); } else { @@ -549,7 +550,7 @@ private static IMarshallingGenerator CreateNativeCollectionMarshaller( numElementsExpression, SizeOfExpression(elementType)); - if (collectionInfo.UseDefaultMarshalling && info.ManagedType is IArrayTypeSymbol { IsSZArray: true }) + if (collectionInfo.UseDefaultMarshalling && info.ManagedType is SzArrayType) { return new ArrayMarshaller( new CustomNativeTypeMarshallingGenerator(marshallingStrategy, enableByValueContentsMarshalling: true), @@ -560,7 +561,7 @@ private static IMarshallingGenerator CreateNativeCollectionMarshaller( IMarshallingGenerator marshallingGenerator = new CustomNativeTypeMarshallingGenerator(marshallingStrategy, enableByValueContentsMarshalling: false); - if ((collectionInfo.MarshallingMethods & SupportedMarshallingMethods.Pinning) != 0) + if ((collectionInfo.MarshallingFeatures & CustomMarshallingFeatures.ManagedTypePinning) != 0) { return new PinnableManagedValueMarshaller(marshallingGenerator); } diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs b/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs index 441d2ce758b7..e3b32f94a5c6 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/SafeHandleMarshaller.cs @@ -83,9 +83,9 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont } var safeHandleCreationExpression = ((SafeHandleMarshallingInfo)info.MarshallingAttributeInfo).AccessibleDefaultConstructor - ? (ExpressionSyntax)ObjectCreationExpression(info.ManagedType.AsTypeSyntax(), ArgumentList(), initializer: null) + ? (ExpressionSyntax)ObjectCreationExpression(info.ManagedType.Syntax, ArgumentList(), initializer: null) : CastExpression( - info.ManagedType.AsTypeSyntax(), + info.ManagedType.Syntax, InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, @@ -97,7 +97,7 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont new []{ Argument( TypeOfExpression( - info.ManagedType.AsTypeSyntax())), + info.ManagedType.Syntax)), Argument( LiteralExpression( SyntaxKind.TrueLiteralExpression)) @@ -121,7 +121,7 @@ public IEnumerable Generate(TypePositionInfo info, StubCodeCont // leak the handle if we failed to create the handle. yield return LocalDeclarationStatement( VariableDeclaration( - info.ManagedType.AsTypeSyntax(), + info.ManagedType.Syntax, SingletonSeparatedList( VariableDeclarator(newHandleObjectIdentifier) .WithInitializer(EqualsValueClause(safeHandleCreationExpression))))); diff --git a/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs b/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs index 1590fc49f696..371e0db6c586 100644 --- a/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs +++ b/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs @@ -67,14 +67,15 @@ internal sealed record MarshalAsInfo( internal sealed record BlittableTypeAttributeInfo : MarshallingInfo; [Flags] - internal enum SupportedMarshallingMethods + internal enum CustomMarshallingFeatures { None = 0, ManagedToNative = 0x1, NativeToManaged = 0x2, ManagedToNativeStackalloc = 0x4, - Pinning = 0x8, - All = -1 + ManagedTypePinning = 0x8, + NativeTypePinning = 0x10, + FreeNativeResources = 0x20, } internal abstract record CountInfo; @@ -104,10 +105,9 @@ internal sealed record SizeAndParamIndexInfo(int ConstSize, int ParamIndex) : Co /// User-applied System.Runtime.InteropServices.NativeMarshallingAttribute /// internal record NativeMarshallingAttributeInfo( - ITypeSymbol NativeMarshallingType, - ITypeSymbol? ValuePropertyType, - SupportedMarshallingMethods MarshallingMethods, - bool NativeTypePinnable, + ManagedTypeInfo NativeMarshallingType, + ManagedTypeInfo? ValuePropertyType, + CustomMarshallingFeatures MarshallingFeatures, bool UseDefaultMarshalling) : MarshallingInfo; /// @@ -120,24 +120,22 @@ internal sealed record GeneratedNativeMarshallingAttributeInfo( /// /// The type of the element is a SafeHandle-derived type with no marshalling attributes. /// - internal sealed record SafeHandleMarshallingInfo(bool AccessibleDefaultConstructor) : MarshallingInfo; + internal sealed record SafeHandleMarshallingInfo(bool AccessibleDefaultConstructor, bool IsAbstract) : MarshallingInfo; /// /// User-applied System.Runtime.InteropServices.NativeMarshallingAttribute /// with a contiguous collection marshaller internal sealed record NativeContiguousCollectionMarshallingInfo( - ITypeSymbol NativeMarshallingType, - ITypeSymbol? ValuePropertyType, - SupportedMarshallingMethods MarshallingMethods, - bool NativeTypePinnable, + ManagedTypeInfo NativeMarshallingType, + ManagedTypeInfo? ValuePropertyType, + CustomMarshallingFeatures MarshallingFeatures, bool UseDefaultMarshalling, CountInfo ElementCountInfo, - ITypeSymbol ElementType, + ManagedTypeInfo ElementType, MarshallingInfo ElementMarshallingInfo) : NativeMarshallingAttributeInfo( NativeMarshallingType, ValuePropertyType, - MarshallingMethods, - NativeTypePinnable, + MarshallingFeatures, UseDefaultMarshalling ); @@ -487,14 +485,15 @@ MarshallingInfo CreateInfoFromMarshalAs( return NoMarshallingInfo.Instance; } + ITypeSymbol? valuePropertyType = ManualTypeMarshallingHelper.FindValueProperty(arrayMarshaller)?.Type; + return new NativeContiguousCollectionMarshallingInfo( - NativeMarshallingType: arrayMarshaller, - ValuePropertyType: ManualTypeMarshallingHelper.FindValueProperty(arrayMarshaller)?.Type, - MarshallingMethods: ~SupportedMarshallingMethods.Pinning, - NativeTypePinnable: true, + NativeMarshallingType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(arrayMarshaller), + ValuePropertyType: valuePropertyType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valuePropertyType) : null, + MarshallingFeatures: ~CustomMarshallingFeatures.ManagedTypePinning, UseDefaultMarshalling: true, ElementCountInfo: arraySizeInfo, - ElementType: elementType, + ElementType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(elementType), ElementMarshallingInfo: elementMarshallingInfo); } @@ -508,11 +507,11 @@ MarshallingInfo CreateNativeMarshallingInfo( ImmutableHashSet inspectedElements, ref int maxIndirectionLevelUsed) { - SupportedMarshallingMethods methods = SupportedMarshallingMethods.None; + CustomMarshallingFeatures features = CustomMarshallingFeatures.None; if (!isMarshalUsingAttribute && ManualTypeMarshallingHelper.FindGetPinnableReference(type) is not null) { - methods |= SupportedMarshallingMethods.Pinning; + features |= CustomMarshallingFeatures.ManagedTypePinning; } ITypeSymbol spanOfByte = compilation.GetTypeByMetadataName(TypeNames.System_Span_Metadata)!.Construct(compilation.GetSpecialType(SpecialType.System_Byte)); @@ -559,12 +558,12 @@ MarshallingInfo CreateNativeMarshallingInfo( { if (ManualTypeMarshallingHelper.IsManagedToNativeConstructor(ctor, type, marshallingVariant) && (valueProperty is null or { GetMethod: not null })) { - methods |= SupportedMarshallingMethods.ManagedToNative; + features |= CustomMarshallingFeatures.ManagedToNative; } else if (ManualTypeMarshallingHelper.IsStackallocConstructor(ctor, type, spanOfByte, marshallingVariant) && (valueProperty is null or { GetMethod: not null })) { - methods |= SupportedMarshallingMethods.ManagedToNativeStackalloc; + features |= CustomMarshallingFeatures.ManagedToNativeStackalloc; } else if (ctor.Parameters.Length == 1 && ctor.Parameters[0].Type.SpecialType == SpecialType.System_Int32) { @@ -579,15 +578,25 @@ MarshallingInfo CreateNativeMarshallingInfo( && ManualTypeMarshallingHelper.HasToManagedMethod(nativeType, type) && (valueProperty is null or { SetMethod: not null })) { - methods |= SupportedMarshallingMethods.NativeToManaged; + features |= CustomMarshallingFeatures.NativeToManaged; } - if (methods == SupportedMarshallingMethods.None) + if (features == CustomMarshallingFeatures.None) { diagnostics.ReportConfigurationNotSupported(attrData, "Native Type", nativeType.ToDisplayString()); return NoMarshallingInfo.Instance; } + if (ManualTypeMarshallingHelper.HasFreeNativeMethod(nativeType)) + { + features |= CustomMarshallingFeatures.FreeNativeResources; + } + + if (ManualTypeMarshallingHelper.FindGetPinnableReference(nativeType) is not null) + { + features |= CustomMarshallingFeatures.NativeTypePinning; + } + if (isContiguousCollectionMarshaller) { if (!ManualTypeMarshallingHelper.HasNativeValueStorageProperty(nativeType, spanOfByte)) @@ -603,21 +612,19 @@ MarshallingInfo CreateNativeMarshallingInfo( } return new NativeContiguousCollectionMarshallingInfo( - nativeType, - valueProperty?.Type, - methods, - NativeTypePinnable: ManualTypeMarshallingHelper.FindGetPinnableReference(nativeType) is not null, + ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType), + valueProperty is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valueProperty.Type) : null, + features, UseDefaultMarshalling: !isMarshalUsingAttribute, parsedCountInfo, - elementType, + ManagedTypeInfo.CreateTypeInfoForTypeSymbol(elementType), GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionLevelUsed)); } return new NativeMarshallingAttributeInfo( - nativeType, - valueProperty?.Type, - methods, - NativeTypePinnable: ManualTypeMarshallingHelper.FindGetPinnableReference(nativeType) is not null, + ManagedTypeInfo.CreateTypeInfoForTypeSymbol(nativeType), + valueProperty is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valueProperty.Type) : null, + features, UseDefaultMarshalling: !isMarshalUsingAttribute); } @@ -648,7 +655,7 @@ bool TryCreateTypeBasedMarshallingInfo( } } } - marshallingInfo = new SafeHandleMarshallingInfo(hasAccessibleDefaultConstructor); + marshallingInfo = new SafeHandleMarshallingInfo(hasAccessibleDefaultConstructor, type.IsAbstract); return true; } @@ -672,14 +679,15 @@ bool TryCreateTypeBasedMarshallingInfo( return false; } + ITypeSymbol? valuePropertyType = ManualTypeMarshallingHelper.FindValueProperty(arrayMarshaller)?.Type; + marshallingInfo = new NativeContiguousCollectionMarshallingInfo( - NativeMarshallingType: arrayMarshaller, - ValuePropertyType: ManualTypeMarshallingHelper.FindValueProperty(arrayMarshaller)?.Type, - MarshallingMethods: ~SupportedMarshallingMethods.Pinning, - NativeTypePinnable: true, + NativeMarshallingType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(arrayMarshaller), + ValuePropertyType: valuePropertyType is not null ? ManagedTypeInfo.CreateTypeInfoForTypeSymbol(valuePropertyType) : null, + MarshallingFeatures: ~CustomMarshallingFeatures.ManagedTypePinning, UseDefaultMarshalling: true, ElementCountInfo: parsedCountInfo, - ElementType: elementType, + ElementType: ManagedTypeInfo.CreateTypeInfoForTypeSymbol(elementType), ElementMarshallingInfo: GetMarshallingInfo(elementType, useSiteAttributes, indirectionLevel + 1, inspectedElements, ref maxIndirectionLevelUsed)); return true; } diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index bd7388856616..7ddf26bf22d2 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -153,7 +153,7 @@ public BlockSyntax GenerateSyntax() AppendVariableDeclations(setupStatements, info, marshaller.Generator); } - bool invokeReturnsVoid = retMarshaller.TypeInfo.ManagedType.SpecialType == SpecialType.System_Void; + bool invokeReturnsVoid = retMarshaller.TypeInfo.ManagedType is SpecialTypeInfo(_, SpecialType.System_Void); bool stubReturnsVoid = stubMethod.ReturnsVoid; // Stub return is not the same as invoke return @@ -394,7 +394,7 @@ private void AppendVariableDeclations(List statementsToUpdate, if (info.IsManagedReturnPosition || info.IsNativeReturnPosition) { statementsToUpdate.Add(MarshallerHelpers.DeclareWithDefault( - info.ManagedType.AsTypeSyntax(), + info.ManagedType.Syntax, managed)); } diff --git a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs index 0f011d9f0846..5ebce011f8ec 100644 --- a/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs +++ b/DllImportGenerator/DllImportGenerator/TypePositionInfo.cs @@ -57,7 +57,7 @@ private TypePositionInfo() #pragma warning restore public string InstanceIdentifier { get; init; } - public ITypeSymbol ManagedType { get; init; } + public ManagedTypeInfo ManagedType { get; init; } public RefKind RefKind { get; init; } public SyntaxKind RefKindSyntax { get; init; } @@ -78,7 +78,7 @@ public static TypePositionInfo CreateForParameter(IParameterSymbol paramSymbol, { var typeInfo = new TypePositionInfo() { - ManagedType = paramSymbol.Type, + ManagedType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(paramSymbol.Type), InstanceIdentifier = ParseToken(paramSymbol.Name).IsReservedKeyword() ? $"@{paramSymbol.Name}" : paramSymbol.Name, RefKind = paramSymbol.RefKind, RefKindSyntax = RefKindToSyntax(paramSymbol.RefKind), @@ -90,6 +90,20 @@ public static TypePositionInfo CreateForParameter(IParameterSymbol paramSymbol, } public static TypePositionInfo CreateForType(ITypeSymbol type, MarshallingInfo marshallingInfo, string identifier = "") + { + var typeInfo = new TypePositionInfo() + { + ManagedType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(type), + InstanceIdentifier = identifier, + RefKind = RefKind.None, + RefKindSyntax = SyntaxKind.None, + MarshallingAttributeInfo = marshallingInfo + }; + + return typeInfo; + } + + public static TypePositionInfo CreateForType(ManagedTypeInfo type, MarshallingInfo marshallingInfo, string identifier = "") { var typeInfo = new TypePositionInfo() { diff --git a/DllImportGenerator/DllImportGenerator/TypeSymbolExtensions.cs b/DllImportGenerator/DllImportGenerator/TypeSymbolExtensions.cs index 2cf28efc9434..cdb6f23538c7 100644 --- a/DllImportGenerator/DllImportGenerator/TypeSymbolExtensions.cs +++ b/DllImportGenerator/DllImportGenerator/TypeSymbolExtensions.cs @@ -163,9 +163,9 @@ public static TypeSyntax AsTypeSyntax(this ITypeSymbol type) return SyntaxFactory.ParseTypeName(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } - public static bool IsIntegralType(this ITypeSymbol type) + public static bool IsIntegralType(this SpecialType type) { - return type.SpecialType switch + return type switch { SpecialType.System_SByte or SpecialType.System_Byte From e1226d27d953095212d8e68d0d5a51ef0bdd5112 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 28 Jun 2021 13:44:38 -0700 Subject: [PATCH 07/23] Add TypePositionInfo record constructor and simplify the API. --- .../DllImportGenerator/DllImportStub.cs | 4 +- .../DllImportGenerator/ManagedTypeInfo.cs | 15 ++++- .../Marshalling/MarshallingGenerator.cs | 2 +- .../MarshallingAttributeInfo.cs | 4 +- .../DllImportGenerator/TypePositionInfo.cs | 58 +++---------------- 5 files changed, 26 insertions(+), 57 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/DllImportStub.cs b/DllImportGenerator/DllImportGenerator/DllImportStub.cs index 86d0c4efa1a2..9ae0319cda3e 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportStub.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportStub.cs @@ -174,7 +174,7 @@ public static DllImportStub Create( paramsTypeInfo.Add(typeInfo); } - TypePositionInfo retTypeInfo = TypePositionInfo.CreateForType(method.ReturnType, marshallingAttributeParser.ParseMarshallingInfo(method.ReturnType, method.GetReturnTypeAttributes())); + TypePositionInfo retTypeInfo = new(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(method.ReturnType), marshallingAttributeParser.ParseMarshallingInfo(method.ReturnType, method.GetReturnTypeAttributes())); retTypeInfo = retTypeInfo with { ManagedIndex = TypePositionInfo.ReturnIndex, @@ -187,7 +187,7 @@ public static DllImportStub Create( if (!dllImportData.PreserveSig && !env.Options.GenerateForwarders()) { // Create type info for native HRESULT return - retTypeInfo = TypePositionInfo.CreateForType(env.Compilation.GetSpecialType(SpecialType.System_Int32), NoMarshallingInfo.Instance); + retTypeInfo = new TypePositionInfo(SpecialTypeInfo.Int32, NoMarshallingInfo.Instance); retTypeInfo = retTypeInfo with { NativeIndex = TypePositionInfo.ReturnIndex diff --git a/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs b/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs index d0f018d6fbe4..866700b42585 100644 --- a/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs +++ b/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs @@ -45,7 +45,20 @@ public static ManagedTypeInfo CreateTypeInfoForTypeSymbol(ITypeSymbol type) } } - internal sealed record SpecialTypeInfo(string FullTypeName, SpecialType SpecialType) : ManagedTypeInfo(FullTypeName); + internal sealed record SpecialTypeInfo(string FullTypeName, SpecialType SpecialType) : ManagedTypeInfo(FullTypeName) + { + public static readonly SpecialTypeInfo Int32 = new("int", SpecialType.System_Int32); + + public bool Equals(SpecialTypeInfo? other) + { + return other is not null && SpecialType == other.SpecialType; + } + + public override int GetHashCode() + { + return (int)SpecialType; + } + } internal sealed record EnumTypeInfo(string FullTypeName, SpecialType UnderlyingType) : ManagedTypeInfo(FullTypeName); diff --git a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs index ec6ef3bc92f6..8106af474800 100644 --- a/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/Marshalling/MarshallingGenerator.cs @@ -514,7 +514,7 @@ private static IMarshallingGenerator CreateNativeCollectionMarshaller( AnalyzerConfigOptions options, ICustomNativeTypeMarshallingStrategy marshallingStrategy) { - var elementInfo = TypePositionInfo.CreateForType(collectionInfo.ElementType, collectionInfo.ElementMarshallingInfo); + var elementInfo = new TypePositionInfo(collectionInfo.ElementType, collectionInfo.ElementMarshallingInfo); var elementMarshaller = Create( elementInfo, new ContiguousCollectionElementMarshallingCodeContext(StubCodeContext.Stage.Setup, string.Empty, string.Empty, context), diff --git a/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs b/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs index 371e0db6c586..f943e9ce931d 100644 --- a/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs +++ b/DllImportGenerator/DllImportGenerator/MarshallingAttributeInfo.cs @@ -361,8 +361,8 @@ CountInfo CreateCountInfo(AttributeData marshalUsingData, ImmutableHashSet /// Positional type information involved in unmanaged/managed scenarios. /// - internal sealed record TypePositionInfo + internal sealed record TypePositionInfo(ManagedTypeInfo ManagedType, MarshallingInfo MarshallingAttributeInfo) { public const int UnsetIndex = int.MinValue; public const int ReturnIndex = UnsetIndex + 1; -// We don't need the warnings around not setting the various -// non-nullable fields/properties on this type in the constructor -// since we always use a property initializer. -#pragma warning disable 8618 - private TypePositionInfo() - { - this.ManagedIndex = UnsetIndex; - this.NativeIndex = UnsetIndex; - } -#pragma warning restore - - public string InstanceIdentifier { get; init; } - public ManagedTypeInfo ManagedType { get; init; } + public string InstanceIdentifier { get; init; } = string.Empty; - public RefKind RefKind { get; init; } - public SyntaxKind RefKindSyntax { get; init; } + public RefKind RefKind { get; init; } = RefKind.None; + public SyntaxKind RefKindSyntax { get; init; } = SyntaxKind.None; public bool IsByRef => RefKind != RefKind.None; @@ -69,54 +57,22 @@ private TypePositionInfo() public bool IsManagedReturnPosition { get => this.ManagedIndex == ReturnIndex; } public bool IsNativeReturnPosition { get => this.NativeIndex == ReturnIndex; } - public int ManagedIndex { get; init; } - public int NativeIndex { get; init; } - - public MarshallingInfo MarshallingAttributeInfo { get; init; } + public int ManagedIndex { get; init; } = UnsetIndex; + public int NativeIndex { get; init; } = UnsetIndex; public static TypePositionInfo CreateForParameter(IParameterSymbol paramSymbol, MarshallingInfo marshallingInfo, Compilation compilation) { - var typeInfo = new TypePositionInfo() + var typeInfo = new TypePositionInfo(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(paramSymbol.Type), marshallingInfo) { - ManagedType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(paramSymbol.Type), InstanceIdentifier = ParseToken(paramSymbol.Name).IsReservedKeyword() ? $"@{paramSymbol.Name}" : paramSymbol.Name, RefKind = paramSymbol.RefKind, RefKindSyntax = RefKindToSyntax(paramSymbol.RefKind), - MarshallingAttributeInfo = marshallingInfo, ByValueContentsMarshalKind = GetByValueContentsMarshalKind(paramSymbol.GetAttributes(), compilation) }; return typeInfo; } - public static TypePositionInfo CreateForType(ITypeSymbol type, MarshallingInfo marshallingInfo, string identifier = "") - { - var typeInfo = new TypePositionInfo() - { - ManagedType = ManagedTypeInfo.CreateTypeInfoForTypeSymbol(type), - InstanceIdentifier = identifier, - RefKind = RefKind.None, - RefKindSyntax = SyntaxKind.None, - MarshallingAttributeInfo = marshallingInfo - }; - - return typeInfo; - } - - public static TypePositionInfo CreateForType(ManagedTypeInfo type, MarshallingInfo marshallingInfo, string identifier = "") - { - var typeInfo = new TypePositionInfo() - { - ManagedType = type, - InstanceIdentifier = identifier, - RefKind = RefKind.None, - RefKindSyntax = SyntaxKind.None, - MarshallingAttributeInfo = marshallingInfo - }; - - return typeInfo; - } - private static ByValueContentsMarshalKind GetByValueContentsMarshalKind(IEnumerable attributes, Compilation compilation) { INamedTypeSymbol outAttributeType = compilation.GetTypeByMetadataName(TypeNames.System_Runtime_InteropServices_OutAttribute)!; From ee8f2eae5b918d64941fca3dba65a9acc5c0aae4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 28 Jun 2021 14:53:39 -0700 Subject: [PATCH 08/23] Build out some infrastructure for testing incrementality of the source generator and write one test. --- .../IncrementalGenerationTests.cs | 38 ++++++++ .../DllImportGenerator.UnitTests/TestUtils.cs | 2 +- .../DllImportGenerator/DllImportGenerator.cs | 94 ++++++++++++++++--- 3 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs new file mode 100644 index 000000000000..5d27106b23dd --- /dev/null +++ b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs @@ -0,0 +1,38 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static Microsoft.Interop.DllImportGenerator; + +namespace DllImportGenerator.UnitTests +{ + public class IncrementalGenerationTests + { + [Fact] + public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() + { + string source = CodeSnippets.BasicParametersAndModifiers(); + + Compilation comp1 = await TestUtils.CreateCompilation(source); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + Compilation comp2 = comp1.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct Foo {}", new CSharpParseOptions(LanguageVersion.Preview))); + driver.RunGenerators(comp2); + + Assert.All(generator.IncrementalTracker.ExecutedSteps, step => + { + Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step); + }); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index 7dd5c29ebfbb..3c9674da985d 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -131,7 +131,7 @@ public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsP return d; } - private static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, IIncrementalGenerator[] generators) + public static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, IIncrementalGenerator[] generators) => CSharpGeneratorDriver.Create( ImmutableArray.Create(generators.Select(gen => GeneratorDriver.WrapGenerator(gen)).ToArray()), parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options, diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index a2b018bcd203..446234a3ed3d 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -171,6 +171,26 @@ public override int GetHashCode() } } + public class IncrementalityTracker + { + public enum StepName + { + GenerateSingleStub, + NormalizeWhitespace, + ConcatenateStubs, + OutputSourceFile + } + + public record ExecutedStepInfo(object Input, StepName Step); + + private List executedSteps = new(); + public IEnumerable ExecutedSteps => executedSteps; + + internal void RecordExecutedStep(ExecutedStepInfo step) => executedSteps.Add(step); + } + + public IncrementalityTracker? IncrementalTracker { get; set; } + public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterExecutionPipeline( @@ -233,15 +253,25 @@ public void Initialize(IncrementalGeneratorInitializationContext context) Environment = data.Right }) .Select( - (data, ct) => GenerateSource(data.Syntax, data.Symbol, data.Environment) + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.GenerateSingleStub)); + return GenerateSource(data.Syntax, data.Symbol, data.Environment); + } ) - .WithComparer(new GeneratedSourceComparer()) + .WithComparer(new GeneratedSyntaxComparer()) // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. .Select( - (data, ct) => (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2)) + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.NormalizeWhitespace)); + return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); + }) .Collect() - .Select(static (generatedSources, ct) => + .WithComparer(new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new GeneratedSourceComparer())) + .Select((generatedSources, ct) => { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(generatedSources, IncrementalityTracker.StepName.ConcatenateStubs)); StringBuilder source = new StringBuilder(); // Mark in source that the file is auto-generated. source.AppendLine("// "); @@ -252,37 +282,77 @@ public void Initialize(IncrementalGeneratorInitializationContext context) diagnostics.AddRange(generated.Item2); } return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); - }); + }) + .WithComparer(new GeneratedSourceComparer()); context.RegisterSourceOutput(methodSourceAndDiagnostics, - static (context, data) => + (context, data) => { - foreach (var diagnostic in data.diagnostics) + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.OutputSourceFile)); + foreach (var diagnostic in data.Item2) { context.ReportDiagnostic(diagnostic); } - context.AddSource("GeneratedDllImports.g.cs", data.source); + context.AddSource("GeneratedDllImports.g.cs", data.Item1); }); } ); } - private class GeneratedSourceComparer : IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> + private class ImmutableArraySequenceEqualComparer : IEqualityComparer> { + private readonly IEqualityComparer elementComparer; + + public ImmutableArraySequenceEqualComparer(IEqualityComparer elementComparer) + { + this.elementComparer = elementComparer; + } + + public bool Equals(ImmutableArray x, ImmutableArray y) + { + return x.SequenceEqual(y, elementComparer); + } + + public int GetHashCode(ImmutableArray obj) + { + return obj.Aggregate(0, (hash, elem) => (hash, elementComparer.GetHashCode(elem)).GetHashCode()); + } + } + + private class GeneratedSyntaxComparer : IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> + { + private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); public bool Equals((MemberDeclarationSyntax, ImmutableArray) x, (MemberDeclarationSyntax, ImmutableArray) y) { return x.Item1.IsEquivalentTo(y.Item1) - && x.Item2.SequenceEqual(y.Item2); + && diagnosticComparer.Equals(x.Item2, y.Item2); } public int GetHashCode((MemberDeclarationSyntax, ImmutableArray) obj) { - return (obj.Item1.ToFullString(), obj.Item2.Aggregate(0, (hash, diagnostic) => (hash, diagnostic).GetHashCode())).GetHashCode(); + return (obj.Item1.ToFullString(), diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); + } + } + + + private class GeneratedSourceComparer : IEqualityComparer<(string, ImmutableArray)> + { + private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); + + public bool Equals((string, ImmutableArray) x, (string, ImmutableArray) y) + { + return x.Item1 == y.Item1 + && diagnosticComparer.Equals(x.Item2, y.Item2); + } + + public int GetHashCode((string, ImmutableArray) obj) + { + return (obj.Item1, diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); } } - private (MemberDeclarationSyntax, ImmutableArray) GenerateSource(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment) + private (MemberDeclarationSyntax, ImmutableArray) GenerateSource(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment) { INamedTypeSymbol? lcidConversionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.LCIDConversionAttribute); // Get any attributes of interest on the method From 02ba8aefa615e690517f86784d73cc9e688d2ea5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 28 Jun 2021 20:14:36 -0700 Subject: [PATCH 09/23] Refactor TypePositionInfo/MarshallingGenerator creation into a separate step so syntax generation only happens incrementally --- .../IncrementalGenerationTests.cs | 9 +- .../DllImportGenerator/BoundGenerator.cs | 41 ++++ .../DllImportGenerator/Comparers.cs | 77 +++++++ .../DllImportGenerator/DllImportGenerator.cs | 176 ++++++++-------- ...lImportStub.cs => DllImportStubContext.cs} | 183 ++++++++--------- .../GeneratedDllImportData.cs | 55 +++++ .../ManagedToNativeCodeContext.cs | 72 +++++++ .../DllImportGenerator/ManagedTypeInfo.cs | 1 + .../DllImportGenerator/StubCodeContext.cs | 4 +- .../DllImportGenerator/StubCodeGenerator.cs | 193 +++++++----------- 10 files changed, 509 insertions(+), 302 deletions(-) create mode 100644 DllImportGenerator/DllImportGenerator/BoundGenerator.cs create mode 100644 DllImportGenerator/DllImportGenerator/Comparers.cs rename DllImportGenerator/DllImportGenerator/{DllImportStub.cs => DllImportStubContext.cs} (66%) create mode 100644 DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs create mode 100644 DllImportGenerator/DllImportGenerator/ManagedToNativeCodeContext.cs diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs index 5d27106b23dd..07c4920091cd 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs @@ -29,10 +29,11 @@ public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() Compilation comp2 = comp1.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct Foo {}", new CSharpParseOptions(LanguageVersion.Preview))); driver.RunGenerators(comp2); - Assert.All(generator.IncrementalTracker.ExecutedSteps, step => - { - Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step); - }); + Assert.Collection(generator.IncrementalTracker.ExecutedSteps, + step => + { + Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step); + }); } } } diff --git a/DllImportGenerator/DllImportGenerator/BoundGenerator.cs b/DllImportGenerator/DllImportGenerator/BoundGenerator.cs new file mode 100644 index 000000000000..4a7ed49462a3 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/BoundGenerator.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Interop +{ + struct BoundGenerator : IEquatable + { + public BoundGenerator(TypePositionInfo typeInfo, IMarshallingGenerator marshallingGenerator) + { + TypeInfo = typeInfo; + Generator = marshallingGenerator; + } + + public TypePositionInfo TypeInfo { get; } + public IMarshallingGenerator Generator { get; } + + public void Deconstruct(out TypePositionInfo typeInfo, out IMarshallingGenerator generator) + { + typeInfo = TypeInfo; + generator = Generator; + } + + public override bool Equals(object obj) + { + return obj is BoundGenerator other && Equals(other); + } + + public bool Equals(BoundGenerator other) + { + // Only compare the type info as the selected generator is deterministically + // determined based on the overall scenario (P/Invoke) and the TypeInfo exclusively. + return TypeInfo.Equals(other.TypeInfo); + } + + public override int GetHashCode() + { + return TypeInfo.GetHashCode(); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator/Comparers.cs b/DllImportGenerator/DllImportGenerator/Comparers.cs new file mode 100644 index 000000000000..d4f24bf75223 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/Comparers.cs @@ -0,0 +1,77 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; + +namespace Microsoft.Interop +{ + + internal class ImmutableArraySequenceEqualComparer : IEqualityComparer> + { + private readonly IEqualityComparer elementComparer; + + public ImmutableArraySequenceEqualComparer(IEqualityComparer elementComparer) + { + this.elementComparer = elementComparer; + } + + public bool Equals(ImmutableArray x, ImmutableArray y) + { + return x.SequenceEqual(y, elementComparer); + } + + public int GetHashCode(ImmutableArray obj) + { + return obj.Aggregate(0, (hash, elem) => (hash, elementComparer.GetHashCode(elem)).GetHashCode()); + } + } + + internal class GeneratedSyntaxComparer : IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> + { + private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); + public bool Equals((MemberDeclarationSyntax, ImmutableArray) x, (MemberDeclarationSyntax, ImmutableArray) y) + { + return x.Item1.IsEquivalentTo(y.Item1) + && diagnosticComparer.Equals(x.Item2, y.Item2); + } + + public int GetHashCode((MemberDeclarationSyntax, ImmutableArray) obj) + { + return (obj.Item1.ToFullString(), diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); + } + } + + internal class SyntaxEquivalentComparer : IEqualityComparer + { + private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); + public bool Equals(SyntaxNode x, SyntaxNode y) + { + return x.IsEquivalentTo(y); + } + + public int GetHashCode(SyntaxNode obj) + { + return obj.ToFullString().GetHashCode(); + } + } + + + internal class GeneratedSourceComparer : IEqualityComparer<(string, ImmutableArray)> + { + private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); + + public bool Equals((string, ImmutableArray) x, (string, ImmutableArray) y) + { + return x.Item1 == y.Item1 + && diagnosticComparer.Equals(x.Item2, y.Item2); + } + + public int GetHashCode((string, ImmutableArray) obj) + { + return (obj.Item1, diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 08fdeee69476..c2a64b3ec90e 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -82,14 +82,15 @@ private TypeDeclarationSyntax CreateTypeDeclarationWithoutTrivia(TypeDeclaration private MemberDeclarationSyntax PrintGeneratedSource( MethodDeclarationSyntax userDeclaredMethod, - DllImportStub stub) + DllImportStubContext stub, + BlockSyntax stubCode) { // Create stub function var stubMethod = MethodDeclaration(stub.StubReturnType, userDeclaredMethod.Identifier) .AddAttributeLists(stub.AdditionalAttributes) .WithModifiers(StripTriviaFromModifiers(userDeclaredMethod.Modifiers)) .WithParameterList(ParameterList(SeparatedList(stub.StubParameters))) - .WithBody(stub.StubCode); + .WithBody(stubCode); // Stub should have at least one containing type Debug.Assert(stub.StubContainingTypes.Any()); @@ -134,9 +135,9 @@ private static bool IsSupportedTargetFramework(Compilation compilation, out Vers }; } - private DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute(AttributeData attrData) + private GeneratedDllImportData ProcessGeneratedDllImportAttribute(AttributeData attrData) { - var stubDllImportData = new DllImportStub.GeneratedDllImportData(); + var stubDllImportData = new GeneratedDllImportData(); // Found the GeneratedDllImport, but it has an error so report the error. // This is most likely an issue with targeting an incorrect TFM. @@ -157,37 +158,61 @@ private DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute( default: Debug.Fail($"An unknown member was found on {GeneratedDllImport}"); continue; - case nameof(DllImportStub.GeneratedDllImportData.BestFitMapping): - stubDllImportData.BestFitMapping = (bool)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.BestFitMapping; + case nameof(GeneratedDllImportData.BestFitMapping): + stubDllImportData = stubDllImportData with + { + BestFitMapping = (bool)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.BestFitMapping, + }; break; - case nameof(DllImportStub.GeneratedDllImportData.CallingConvention): - stubDllImportData.CallingConvention = (CallingConvention)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.CallingConvention; + case nameof(GeneratedDllImportData.CallingConvention): + stubDllImportData = stubDllImportData with + { + CallingConvention = (CallingConvention)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.CallingConvention, + }; break; - case nameof(DllImportStub.GeneratedDllImportData.CharSet): - stubDllImportData.CharSet = (CharSet)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.CharSet; + case nameof(GeneratedDllImportData.CharSet): + stubDllImportData = stubDllImportData with + { + CharSet = (CharSet)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.CharSet, + }; break; - case nameof(DllImportStub.GeneratedDllImportData.EntryPoint): - stubDllImportData.EntryPoint = (string)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.EntryPoint; + case nameof(GeneratedDllImportData.EntryPoint): + stubDllImportData = stubDllImportData with + { + EntryPoint = (string)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.EntryPoint, + }; break; - case nameof(DllImportStub.GeneratedDllImportData.ExactSpelling): - stubDllImportData.ExactSpelling = (bool)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.ExactSpelling; + case nameof(GeneratedDllImportData.ExactSpelling): + stubDllImportData = stubDllImportData with + { + ExactSpelling = (bool)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.ExactSpelling, + }; break; - case nameof(DllImportStub.GeneratedDllImportData.PreserveSig): - stubDllImportData.PreserveSig = (bool)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.PreserveSig; + case nameof(GeneratedDllImportData.PreserveSig): + stubDllImportData = stubDllImportData with + { + PreserveSig = (bool)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.PreserveSig, + }; break; - case nameof(DllImportStub.GeneratedDllImportData.SetLastError): - stubDllImportData.SetLastError = (bool)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.SetLastError; + case nameof(GeneratedDllImportData.SetLastError): + stubDllImportData = stubDllImportData with + { + SetLastError = (bool)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.SetLastError, + }; break; - case nameof(DllImportStub.GeneratedDllImportData.ThrowOnUnmappableChar): - stubDllImportData.ThrowOnUnmappableChar = (bool)namedArg.Value.Value!; - stubDllImportData.IsUserDefined |= DllImportStub.DllImportMember.ThrowOnUnmappableChar; + case nameof(GeneratedDllImportData.ThrowOnUnmappableChar): + stubDllImportData = stubDllImportData with + { + ThrowOnUnmappableChar = (bool)namedArg.Value.Value!, + IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.ThrowOnUnmappableChar, + }; break; } } @@ -213,6 +238,7 @@ public class IncrementalityTracker { public enum StepName { + CalculateStubInformation, GenerateSingleStub, NormalizeWhitespace, ConcatenateStubs, @@ -293,10 +319,17 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select( (data, ct) => { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.GenerateSingleStub)); - return GenerateSource(data.Syntax, data.Symbol, data.Environment, ct); + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.CalculateStubInformation)); + return (data.Syntax, Context: ComputeStubContext(data.Syntax, data.Symbol, data.Environment, ct)); } ) + .Combine(context.AnalyzerConfigOptionsProvider) + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.GenerateSingleStub)); + return (GenerateSource(data.Left.Context.StubContext, data.Left.Context.DllImportData, data.Left.Syntax, data.Left.Context.ForwardedAttributes, data.Right.GlobalOptions), data.Left.Context.Diagnostics); + }) .WithComparer(new GeneratedSyntaxComparer()) // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. .Select( @@ -338,59 +371,24 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ); } - private class ImmutableArraySequenceEqualComparer : IEqualityComparer> - { - private readonly IEqualityComparer elementComparer; - - public ImmutableArraySequenceEqualComparer(IEqualityComparer elementComparer) - { - this.elementComparer = elementComparer; - } - - public bool Equals(ImmutableArray x, ImmutableArray y) - { - return x.SequenceEqual(y, elementComparer); - } - - public int GetHashCode(ImmutableArray obj) - { - return obj.Aggregate(0, (hash, elem) => (hash, elementComparer.GetHashCode(elem)).GetHashCode()); - } - } - - private class GeneratedSyntaxComparer : IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> + internal sealed record IncrementalStubGenerationContext(DllImportStubContext StubContext, ImmutableArray ForwardedAttributes, GeneratedDllImportData DllImportData, ImmutableArray Diagnostics) { - private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); - public bool Equals((MemberDeclarationSyntax, ImmutableArray) x, (MemberDeclarationSyntax, ImmutableArray) y) + public bool Equals(IncrementalStubGenerationContext? other) { - return x.Item1.IsEquivalentTo(y.Item1) - && diagnosticComparer.Equals(x.Item2, y.Item2); + return other is not null + && StubContext.Equals(other.StubContext) + && DllImportData.Equals(other.DllImportData) + && ForwardedAttributes.SequenceEqual(other.ForwardedAttributes, (IEqualityComparer)new SyntaxEquivalentComparer()) + && Diagnostics.SequenceEqual(other.Diagnostics); } - public int GetHashCode((MemberDeclarationSyntax, ImmutableArray) obj) - { - return (obj.Item1.ToFullString(), diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); - } - } - - - private class GeneratedSourceComparer : IEqualityComparer<(string, ImmutableArray)> - { - private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); - - public bool Equals((string, ImmutableArray) x, (string, ImmutableArray) y) - { - return x.Item1 == y.Item1 - && diagnosticComparer.Equals(x.Item2, y.Item2); - } - - public int GetHashCode((string, ImmutableArray) obj) + public override int GetHashCode() { - return (obj.Item1, diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); + return (StubContext, DllImportData, ForwardedAttributes.Length, Diagnostics.Length).GetHashCode(); } } - private (MemberDeclarationSyntax, ImmutableArray) GenerateSource(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment, CancellationToken ct) + private IncrementalStubGenerationContext ComputeStubContext(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment, CancellationToken ct) { INamedTypeSymbol? lcidConversionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.LCIDConversionAttribute); INamedTypeSymbol? suppressGCTransitionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.SuppressGCTransitionAttribute); @@ -426,17 +424,17 @@ public int GetHashCode((string, ImmutableArray) obj) var generatorDiagnostics = new GeneratorDiagnostics(); // Process the GeneratedDllImport attribute - DllImportStub.GeneratedDllImportData stubDllImportData = this.ProcessGeneratedDllImportAttribute(generatedDllImportAttr!); + GeneratedDllImportData stubDllImportData = this.ProcessGeneratedDllImportAttribute(generatedDllImportAttr!); Debug.Assert(stubDllImportData is not null); - if (stubDllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.BestFitMapping)) + if (stubDllImportData!.IsUserDefined.HasFlag(DllImportMember.BestFitMapping)) { - generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(DllImportStub.GeneratedDllImportData.BestFitMapping)); + generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(GeneratedDllImportData.BestFitMapping)); } - if (stubDllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.ThrowOnUnmappableChar)) + if (stubDllImportData!.IsUserDefined.HasFlag(DllImportMember.ThrowOnUnmappableChar)) { - generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(DllImportStub.GeneratedDllImportData.ThrowOnUnmappableChar)); + generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(GeneratedDllImportData.ThrowOnUnmappableChar)); } if (lcidConversionAttr != null) @@ -447,9 +445,23 @@ public int GetHashCode((string, ImmutableArray) obj) List additionalAttributes = GenerateSyntaxForForwardedAttributes(suppressGCTransitionAttribute, unmanagedCallConvAttribute); // Create the stub. - var dllImportStub = DllImportStub.Create(symbol, stubDllImportData!, environment, generatorDiagnostics, additionalAttributes, ct); + var dllImportStub = DllImportStubContext.Create(symbol, stubDllImportData!, environment, generatorDiagnostics, ct); + + return new IncrementalStubGenerationContext(dllImportStub, additionalAttributes.ToImmutableArray(), stubDllImportData, generatorDiagnostics.Diagnostics.ToImmutableArray()); + } + + private MemberDeclarationSyntax GenerateSource( + DllImportStubContext dllImportStub, + GeneratedDllImportData dllImportData, + MethodDeclarationSyntax originalSyntax, + ImmutableArray forwardedAttributes, + AnalyzerConfigOptions options) + { + // Generate stub code + var stubGenerator = new StubCodeGenerator(dllImportData, dllImportStub.BoundGenerators, dllImportStub.CodeContext, options); + var code = stubGenerator.GenerateSyntax(originalSyntax.Identifier.Text, forwardedAttributes: forwardedAttributes.Length != 0 ? AttributeList(SeparatedList(forwardedAttributes)) : null); - return (PrintGeneratedSource(syntax, dllImportStub), generatorDiagnostics.Diagnostics.ToImmutableArray()); + return PrintGeneratedSource(originalSyntax, dllImportStub, code); } private static bool ShouldVisitNode(SyntaxNode syntaxNode) diff --git a/DllImportGenerator/DllImportGenerator/DllImportStub.cs b/DllImportGenerator/DllImportGenerator/DllImportStubContext.cs similarity index 66% rename from DllImportGenerator/DllImportGenerator/DllImportStub.cs rename to DllImportGenerator/DllImportGenerator/DllImportStubContext.cs index 6c3279974200..be8165e07d9a 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportStub.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportStubContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; @@ -17,100 +18,52 @@ internal record StubEnvironment( Version TargetFrameworkVersion, AnalyzerConfigOptions Options); - internal class DllImportStub + internal sealed class DllImportStubContext : IEquatable { - private TypePositionInfo returnTypeInfo; - private IEnumerable paramsTypeInfo; - // We don't need the warnings around not setting the various // non-nullable fields/properties on this type in the constructor // since we always use a property initializer. #pragma warning disable 8618 - private DllImportStub() + private DllImportStubContext() { } #pragma warning restore + public IEnumerable BoundGenerators { get; init; } + public string? StubTypeNamespace { get; init; } public IEnumerable StubContainingTypes { get; init; } - public TypeSyntax StubReturnType { get => this.returnTypeInfo.ManagedType.Syntax; } + public TypeSyntax StubReturnType { get; init; } + + public ManagedToNativeCodeContext CodeContext { get; init; } public IEnumerable StubParameters { get { - foreach (var typeinfo in paramsTypeInfo) + foreach (var generator in BoundGenerators) { - if (typeinfo.ManagedIndex != TypePositionInfo.UnsetIndex - && typeinfo.ManagedIndex != TypePositionInfo.ReturnIndex) + TypePositionInfo typeInfo = generator.TypeInfo; + if (typeInfo.ManagedIndex != TypePositionInfo.UnsetIndex + && typeInfo.ManagedIndex != TypePositionInfo.ReturnIndex) { - yield return Parameter(Identifier(typeinfo.InstanceIdentifier)) - .WithType(typeinfo.ManagedType.Syntax) - .WithModifiers(TokenList(Token(typeinfo.RefKindSyntax))); + yield return Parameter(Identifier(typeInfo.InstanceIdentifier)) + .WithType(typeInfo.ManagedType.Syntax) + .WithModifiers(TokenList(Token(typeInfo.RefKindSyntax))); } } } } - public BlockSyntax StubCode { get; init; } - public AttributeListSyntax[] AdditionalAttributes { get; init; } - /// - /// Flags used to indicate members on GeneratedDllImport attribute. - /// - [Flags] - public enum DllImportMember - { - None = 0, - BestFitMapping = 1 << 0, - CallingConvention = 1 << 1, - CharSet = 1 << 2, - EntryPoint = 1 << 3, - ExactSpelling = 1 << 4, - PreserveSig = 1 << 5, - SetLastError = 1 << 6, - ThrowOnUnmappableChar = 1 << 7, - All = ~None - } - - /// - /// GeneratedDllImportAttribute data - /// - /// - /// The names of these members map directly to those on the - /// DllImportAttribute and should not be changed. - /// - public class GeneratedDllImportData - { - public string ModuleName { get; set; } = null!; - - /// - /// Value set by the user on the original declaration. - /// - public DllImportMember IsUserDefined = DllImportMember.None; - - // Default values for the below fields are based on the - // documented semanatics of DllImportAttribute: - // - https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute - public bool BestFitMapping { get; set; } = true; - public CallingConvention CallingConvention { get; set; } = CallingConvention.Winapi; - public CharSet CharSet { get; set; } = CharSet.Ansi; - public string EntryPoint { get; set; } = null!; - public bool ExactSpelling { get; set; } = false; // VB has different and unusual default behavior here. - public bool PreserveSig { get; set; } = true; - public bool SetLastError { get; set; } = false; - public bool ThrowOnUnmappableChar { get; set; } = false; - } - - public static DllImportStub Create( + public static DllImportStubContext Create( IMethodSymbol method, GeneratedDllImportData dllImportData, StubEnvironment env, GeneratorDiagnostics diagnostics, - List forwardedAttributes, CancellationToken token) { // Cancel early if requested @@ -143,6 +96,37 @@ public static DllImportStub Create( currType = currType.ContainingType; } + var (context, boundGenerators) = GenerateTypeInformation(method, dllImportData, diagnostics, env); + + var additionalAttrs = new List(); + + // Define additional attributes for the stub definition. + if (env.TargetFrameworkVersion >= new Version(5, 0)) + { + additionalAttrs.Add( + AttributeList( + SeparatedList(new[] + { + // Adding the skip locals init indiscriminately since the source generator is + // targeted at non-blittable method signatures which typically will contain locals + // in the generated code. + Attribute(ParseName(TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute)) + }))); + } + + return new DllImportStubContext() + { + StubReturnType = method.ReturnType.AsTypeSyntax(), + BoundGenerators = boundGenerators, + CodeContext = context, + StubTypeNamespace = stubTypeNamespace, + StubContainingTypes = containingTypes, + AdditionalAttributes = additionalAttrs.ToArray(), + }; + } + + private static (ManagedToNativeCodeContext, IEnumerable) GenerateTypeInformation(IMethodSymbol method, GeneratedDllImportData dllImportData, GeneratorDiagnostics diagnostics, StubEnvironment env) + { // Compute the current default string encoding value. var defaultEncoding = CharEncoding.Undefined; if (dllImportData.IsUserDefined.HasFlag(DllImportMember.CharSet)) @@ -161,18 +145,19 @@ public static DllImportStub Create( var marshallingAttributeParser = new MarshallingAttributeInfoParser(env.Compilation, diagnostics, defaultInfo, method); // Determine parameter and return types - var paramsTypeInfo = new List(); + var typeInfos = new List(); for (int i = 0; i < method.Parameters.Length; i++) { var param = method.Parameters[i]; MarshallingInfo marshallingInfo = marshallingAttributeParser.ParseMarshallingInfo(param.Type, param.GetAttributes()); var typeInfo = TypePositionInfo.CreateForParameter(param, marshallingInfo, env.Compilation); - typeInfo = typeInfo with + typeInfo = typeInfo with { ManagedIndex = i, - NativeIndex = paramsTypeInfo.Count + NativeIndex = typeInfos.Count }; - paramsTypeInfo.Add(typeInfo); + typeInfos.Add(typeInfo); + } TypePositionInfo retTypeInfo = new(ManagedTypeInfo.CreateTypeInfoForTypeSymbol(method.ReturnType), marshallingAttributeParser.ParseMarshallingInfo(method.ReturnType, method.GetReturnTypeAttributes())); @@ -204,41 +189,53 @@ public static DllImportStub Create( RefKind = RefKind.Out, RefKindSyntax = SyntaxKind.OutKeyword, ManagedIndex = TypePositionInfo.ReturnIndex, - NativeIndex = paramsTypeInfo.Count + NativeIndex = typeInfos.Count }; - paramsTypeInfo.Add(nativeOutInfo); + typeInfos.Add(nativeOutInfo); } } + typeInfos.Add(retTypeInfo); - // Generate stub code - var stubGenerator = new StubCodeGenerator(method, dllImportData, paramsTypeInfo, retTypeInfo, diagnostics, env.Options); - var code = stubGenerator.GenerateSyntax(forwardedAttributes: forwardedAttributes.Count != 0 ? AttributeList(SeparatedList(forwardedAttributes)) : null); + var context = new ManagedToNativeCodeContext(typeInfos); + var boundGenerators = new List(); + foreach (var typeInfo in typeInfos) + { + boundGenerators.Add(CreateGenerator(typeInfo)); + } - var additionalAttrs = new List(); + return (context, boundGenerators); - // Define additional attributes for the stub definition. - if (env.TargetFrameworkVersion >= new Version(5, 0)) + BoundGenerator CreateGenerator(TypePositionInfo p) { - additionalAttrs.Add( - AttributeList( - SeparatedList(new [] - { - // Adding the skip locals init indiscriminately since the source generator is - // targeted at non-blittable method signatures which typically will contain locals - // in the generated code. - Attribute(ParseName(TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute)) - }))); + try + { + return new BoundGenerator(p, MarshallingGenerators.Create(p, context, env.Options)); + } + catch (MarshallingNotSupportedException e) + { + diagnostics.ReportMarshallingNotSupported(method, p, e.NotSupportedDetails); + return new BoundGenerator(p, MarshallingGenerators.Forwarder); + } } + } - return new DllImportStub() - { - returnTypeInfo = managedRetTypeInfo, - paramsTypeInfo = paramsTypeInfo, - StubTypeNamespace = stubTypeNamespace, - StubContainingTypes = containingTypes, - StubCode = code, - AdditionalAttributes = additionalAttrs.ToArray(), - }; + public override bool Equals(object obj) + { + return obj is DllImportStubContext other && Equals(other); + } + + public bool Equals(DllImportStubContext other) + { + return other is not null + && StubTypeNamespace == other.StubTypeNamespace + && BoundGenerators.SequenceEqual(other.BoundGenerators) + && StubContainingTypes.SequenceEqual(other.StubContainingTypes, new SyntaxEquivalentComparer()) + && StubReturnType.IsEquivalentTo(other.StubReturnType); + } + + public override int GetHashCode() + { + return StubTypeNamespace?.GetHashCode() ?? 0; } } } diff --git a/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs new file mode 100644 index 000000000000..d2fe6517eb97 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Interop +{ + + /// + /// Flags used to indicate members on GeneratedDllImport attribute. + /// + [Flags] + public enum DllImportMember + { + None = 0, + BestFitMapping = 1 << 0, + CallingConvention = 1 << 1, + CharSet = 1 << 2, + EntryPoint = 1 << 3, + ExactSpelling = 1 << 4, + PreserveSig = 1 << 5, + SetLastError = 1 << 6, + ThrowOnUnmappableChar = 1 << 7, + All = ~None + } + + /// + /// GeneratedDllImportAttribute data + /// + /// + /// The names of these members map directly to those on the + /// DllImportAttribute and should not be changed. + /// + public sealed record GeneratedDllImportData + { + public string ModuleName { get; set; } = null!; + + /// + /// Value set by the user on the original declaration. + /// + public DllImportMember IsUserDefined { get; init; } = DllImportMember.None; + + // Default values for the below fields are based on the + // documented semanatics of DllImportAttribute: + // - https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute + public bool BestFitMapping { get; init; } = true; + public CallingConvention CallingConvention { get; init; } = CallingConvention.Winapi; + public CharSet CharSet { get; init; } = CharSet.Ansi; + public string EntryPoint { get; init; } = null!; + public bool ExactSpelling { get; init; } = false; // VB has different and unusual default behavior here. + public bool PreserveSig { get; init; } = true; + public bool SetLastError { get; init; } = false; + public bool ThrowOnUnmappableChar { get; init; } = false; + } +} diff --git a/DllImportGenerator/DllImportGenerator/ManagedToNativeCodeContext.cs b/DllImportGenerator/DllImportGenerator/ManagedToNativeCodeContext.cs new file mode 100644 index 000000000000..70b75f24c3c0 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/ManagedToNativeCodeContext.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.Interop +{ + internal sealed class ManagedToNativeCodeContext : StubCodeContext + { + public override bool SingleFrameSpansNativeContext => true; + + public override bool AdditionalTemporaryStateLivesAcrossStages => true; + + /// + /// Identifier for managed return value + /// + public const string ReturnIdentifier = "__retVal"; + private const string InvokeReturnIdentifier = "__invokeRetVal"; + private readonly IEnumerable typeInfos; + + /// + /// Identifier for native return value + /// + /// Same as the managed identifier by default + public string ReturnNativeIdentifier { get; set; } = ReturnIdentifier; + + public ManagedToNativeCodeContext(IEnumerable typeInfos) + { + this.typeInfos = typeInfos; + } + + public override (string managed, string native) GetIdentifiers(TypePositionInfo info) + { + // If the info is in the managed return position, then we need to generate a name to use + // for both the managed and native values since there is no name in the signature for the return value. + if (info.IsManagedReturnPosition) + { + return (ReturnIdentifier, ReturnNativeIdentifier); + } + // If the info is in the native return position but is not in the managed return position, + // then that means that the stub is introducing an additional info for the return position. + // This means that there is no name in source for this info, so we must provide one here. + // We can't use ReturnIdentifier or ReturnNativeIdentifier since that will be used by the managed return value. + // Additionally, since all use cases today of a TypePositionInfo in the native position but not the managed + // are for infos that aren't in the managed signature at all (PreserveSig scenario), we don't have a name + // that we can use from source. As a result, we generate another name for the native return value + // and use the same name for native and managed. + else if (info.IsNativeReturnPosition) + { + Debug.Assert(info.ManagedIndex == TypePositionInfo.UnsetIndex); + return (InvokeReturnIdentifier, InvokeReturnIdentifier); + } + else + { + // If the info isn't in either the managed or native return position, + // then we can use the base implementation since we have an identifier name provided + // in the original metadata. + return base.GetIdentifiers(info); + } + } + + public override TypePositionInfo? GetTypePositionInfoForManagedIndex(int index) + { + foreach (var info in typeInfos) + { + if (info.ManagedIndex == index) + { + return info; + } + } + return null; + } + } +} diff --git a/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs b/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs index 866700b42585..379562ac8359 100644 --- a/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs +++ b/DllImportGenerator/DllImportGenerator/ManagedTypeInfo.cs @@ -48,6 +48,7 @@ public static ManagedTypeInfo CreateTypeInfoForTypeSymbol(ITypeSymbol type) internal sealed record SpecialTypeInfo(string FullTypeName, SpecialType SpecialType) : ManagedTypeInfo(FullTypeName) { public static readonly SpecialTypeInfo Int32 = new("int", SpecialType.System_Int32); + public static readonly SpecialTypeInfo Void = new("void", SpecialType.System_Void); public bool Equals(SpecialTypeInfo? other) { diff --git a/DllImportGenerator/DllImportGenerator/StubCodeContext.cs b/DllImportGenerator/DllImportGenerator/StubCodeContext.cs index 1ea0d17553e6..b6e3d439b744 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeContext.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeContext.cs @@ -61,7 +61,7 @@ public enum Stage GuaranteedUnmarshal } - public Stage CurrentStage { get; protected set; } = Stage.Invalid; + public Stage CurrentStage { get; set; } = Stage.Invalid; /// /// The stub emits code that runs in a single stack frame and the frame spans over the native context. @@ -88,7 +88,7 @@ public enum Stage /// public StubCodeContext? ParentContext { get; protected set; } - protected const string GeneratedNativeIdentifierSuffix = "_gen_native"; + public const string GeneratedNativeIdentifierSuffix = "_gen_native"; /// /// Get managed and native instance identifiers for the diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index 7f53b05eb93b..56476a7a7b06 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; @@ -8,15 +9,12 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using static Microsoft.Interop.StubCodeContext; namespace Microsoft.Interop { - internal sealed class StubCodeGenerator : StubCodeContext + internal sealed class StubCodeGenerator { - public override bool SingleFrameSpansNativeContext => true; - - public override bool AdditionalTemporaryStateLivesAcrossStages => true; - /// /// Identifier for managed return value /// @@ -46,40 +44,54 @@ internal sealed class StubCodeGenerator : StubCodeContext Stage.Cleanup }; - private readonly GeneratorDiagnostics diagnostics; private readonly AnalyzerConfigOptions options; - private readonly IMethodSymbol stubMethod; - private readonly DllImportStub.GeneratedDllImportData dllImportData; - private readonly IEnumerable paramsTypeInfo; - private readonly List<(TypePositionInfo TypeInfo, IMarshallingGenerator Generator)> paramMarshallers; - private readonly (TypePositionInfo TypeInfo, IMarshallingGenerator Generator) retMarshaller; - private readonly List<(TypePositionInfo TypeInfo, IMarshallingGenerator Generator)> sortedMarshallers; + private readonly GeneratedDllImportData dllImportData; + private readonly StubCodeContext context; + private readonly List paramMarshallers; + private readonly BoundGenerator retMarshaller; + private readonly List sortedMarshallers; + private readonly bool stubReturnsVoid; public StubCodeGenerator( - IMethodSymbol stubMethod, - DllImportStub.GeneratedDllImportData dllImportData, - IEnumerable paramsTypeInfo, - TypePositionInfo retTypeInfo, - GeneratorDiagnostics generatorDiagnostics, + GeneratedDllImportData dllImportData, + IEnumerable elements, + ManagedToNativeCodeContext context, AnalyzerConfigOptions options) { - Debug.Assert(retTypeInfo.IsNativeReturnPosition); - - this.stubMethod = stubMethod; this.dllImportData = dllImportData; - this.paramsTypeInfo = paramsTypeInfo.ToList(); - this.diagnostics = generatorDiagnostics; + this.context = context; this.options = options; - // Get marshallers for parameters - this.paramMarshallers = paramsTypeInfo.Select(p => CreateGenerator(p)).ToList(); - - // Get marshaller for return - this.retMarshaller = CreateGenerator(retTypeInfo); + List allMarshallers = new(); + List paramMarshallers = new(); + bool foundNativeRetMarshaller = false, foundManagedRetMarshaller = false; + BoundGenerator nativeRetMarshaller = new(new TypePositionInfo(SpecialTypeInfo.Void, NoMarshallingInfo.Instance), new Forwarder()); + BoundGenerator managedRetMarshaller = new(new TypePositionInfo(SpecialTypeInfo.Void, NoMarshallingInfo.Instance), new Forwarder()); + foreach (var element in elements) + { + allMarshallers.Add(element); + if (element.TypeInfo.IsManagedReturnPosition) + { + Debug.Assert(!foundManagedRetMarshaller); + managedRetMarshaller = element; + foundManagedRetMarshaller = true; + } + if (element.TypeInfo.IsNativeReturnPosition) + { + Debug.Assert(!foundNativeRetMarshaller); + nativeRetMarshaller = element; + foundNativeRetMarshaller = true; + } + if (!element.TypeInfo.IsManagedReturnPosition && !element.TypeInfo.IsNativeReturnPosition) + { + paramMarshallers.Add(element); + } + } - List<(TypePositionInfo TypeInfo, IMarshallingGenerator Generator)> allMarshallers = new(this.paramMarshallers); - allMarshallers.Add(retMarshaller); + this.retMarshaller = nativeRetMarshaller; + this.paramMarshallers = paramMarshallers; + this.stubReturnsVoid = managedRetMarshaller.TypeInfo.ManagedType == SpecialTypeInfo.Void; // We are doing a topological sort of our marshallers to ensure that each parameter/return value's // dependencies are unmarshalled before their dependents. This comes up in the case of contiguous @@ -109,17 +121,10 @@ public StubCodeGenerator( static m => GetInfoDependencies(m.TypeInfo)) .ToList(); - (TypePositionInfo info, IMarshallingGenerator gen) CreateGenerator(TypePositionInfo p) + if (managedRetMarshaller.Generator.UsesNativeIdentifier(managedRetMarshaller.TypeInfo, context)) { - try - { - return (p, MarshallingGenerators.Create(p, this, options)); - } - catch (MarshallingNotSupportedException e) - { - this.diagnostics.ReportMarshallingNotSupported(this.stubMethod, p, e.NotSupportedDetails); - return (p, MarshallingGenerators.Forwarder); - } + // Update the native identifier for the return value + context.ReturnNativeIdentifier = $"{ReturnIdentifier}{GeneratedNativeIdentifierSuffix}"; } static IEnumerable GetInfoDependencies(TypePositionInfo info) @@ -145,47 +150,11 @@ static int GetInfoIndex(TypePositionInfo info) } } - public override (string managed, string native) GetIdentifiers(TypePositionInfo info) + public BlockSyntax GenerateSyntax(string methodName, AttributeListSyntax? forwardedAttributes) { - // If the info is in the managed return position, then we need to generate a name to use - // for both the managed and native values since there is no name in the signature for the return value. - if (info.IsManagedReturnPosition) - { - return (ReturnIdentifier, ReturnNativeIdentifier); - } - // If the info is in the native return position but is not in the managed return position, - // then that means that the stub is introducing an additional info for the return position. - // This means that there is no name in source for this info, so we must provide one here. - // We can't use ReturnIdentifier or ReturnNativeIdentifier since that will be used by the managed return value. - // Additionally, since all use cases today of a TypePositionInfo in the native position but not the managed - // are for infos that aren't in the managed signature at all (PreserveSig scenario), we don't have a name - // that we can use from source. As a result, we generate another name for the native return value - // and use the same name for native and managed. - else if (info.IsNativeReturnPosition) - { - Debug.Assert(info.ManagedIndex == TypePositionInfo.UnsetIndex); - return (InvokeReturnIdentifier, InvokeReturnIdentifier); - } - else - { - // If the info isn't in either the managed or native return position, - // then we can use the base implementation since we have an identifier name provided - // in the original metadata. - return base.GetIdentifiers(info); - } - } - - public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) - { - string dllImportName = stubMethod.Name + "__PInvoke__"; + string dllImportName = methodName + "__PInvoke__"; var setupStatements = new List(); - if (retMarshaller.Generator.UsesNativeIdentifier(retMarshaller.TypeInfo, this)) - { - // Update the native identifier for the return value - ReturnNativeIdentifier = $"{ReturnIdentifier}{GeneratedNativeIdentifierSuffix}"; - } - foreach (var marshaller in paramMarshallers) { TypePositionInfo info = marshaller.TypeInfo; @@ -208,8 +177,7 @@ public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) AppendVariableDeclations(setupStatements, info, marshaller.Generator); } - bool invokeReturnsVoid = retMarshaller.TypeInfo.ManagedType is SpecialTypeInfo(_, SpecialType.System_Void); - bool stubReturnsVoid = stubMethod.ReturnsVoid; + bool invokeReturnsVoid = retMarshaller.TypeInfo.ManagedType == SpecialTypeInfo.Void; // Stub return is not the same as invoke return if (!stubReturnsVoid && !retMarshaller.TypeInfo.IsManagedReturnPosition) @@ -221,11 +189,6 @@ public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) Debug.Assert(paramMarshallers.Any() && paramMarshallers.Last().TypeInfo.IsManagedReturnPosition, "Expected stub return to be the last parameter for the invoke"); (TypePositionInfo stubRetTypeInfo, IMarshallingGenerator stubRetGenerator) = paramMarshallers.Last(); - if (stubRetGenerator.UsesNativeIdentifier(stubRetTypeInfo, this)) - { - // Update the native identifier for the return value - ReturnNativeIdentifier = $"{ReturnIdentifier}{GeneratedNativeIdentifierSuffix}"; - } // Declare variables for stub return value AppendVariableDeclations(setupStatements, stubRetTypeInfo, stubRetGenerator); @@ -255,12 +218,12 @@ public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) { var statements = GetStatements(stage); int initialCount = statements.Count; - this.CurrentStage = stage; + context.CurrentStage = stage; if (!invokeReturnsVoid && (stage is Stage.Setup or Stage.Cleanup)) { // Handle setup and unmarshalling for return - var retStatements = retMarshaller.Generator.Generate(retMarshaller.TypeInfo, this); + var retStatements = retMarshaller.Generator.Generate(retMarshaller.TypeInfo, context); statements.AddRange(retStatements); } @@ -271,7 +234,7 @@ public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) foreach (var marshaller in sortedMarshallers) { - statements.AddRange(marshaller.Generator.Generate(marshaller.TypeInfo, this)); + statements.AddRange(marshaller.Generator.Generate(marshaller.TypeInfo, context)); } } else @@ -282,12 +245,12 @@ public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) if (stage == Stage.Invoke) { // Get arguments for invocation - ArgumentSyntax argSyntax = marshaller.Generator.AsArgument(marshaller.TypeInfo, this); + ArgumentSyntax argSyntax = marshaller.Generator.AsArgument(marshaller.TypeInfo, context); invoke = invoke.AddArgumentListArguments(argSyntax); } else { - var generatedStatements = marshaller.Generator.Generate(marshaller.TypeInfo, this); + var generatedStatements = marshaller.Generator.Generate(marshaller.TypeInfo, context); if (stage == Stage.Pin) { // Collect all the fixed statements. These will be used in the Invoke stage. @@ -321,7 +284,7 @@ public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) invokeStatement = ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, - IdentifierName(this.GetIdentifiers(retMarshaller.TypeInfo).native), + IdentifierName(context.GetIdentifiers(retMarshaller.TypeInfo).native), invoke)); } @@ -421,7 +384,7 @@ public BlockSyntax GenerateSyntax(AttributeListSyntax? forwardedAttributes) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) .WithAttributeLists( SingletonList(AttributeList( - SingletonSeparatedList(CreateDllImportAttributeForTarget(GetTargetDllImportDataFromStubData()))))); + SingletonSeparatedList(CreateDllImportAttributeForTarget(GetTargetDllImportDataFromStubData(methodName)))))); if (retMarshaller.Generator is IAttributedReturnTypeMarshallingGenerator retGenerator) { @@ -457,21 +420,9 @@ List GetStatements(Stage stage) } } - public override TypePositionInfo? GetTypePositionInfoForManagedIndex(int index) - { - foreach (var info in paramsTypeInfo) - { - if (info.ManagedIndex == index) - { - return info; - } - } - return null; - } - private void AppendVariableDeclations(List statementsToUpdate, TypePositionInfo info, IMarshallingGenerator generator) { - var (managed, native) = GetIdentifiers(info); + var (managed, native) = context.GetIdentifiers(info); // Declare variable for return value if (info.IsManagedReturnPosition || info.IsNativeReturnPosition) @@ -482,7 +433,7 @@ private void AppendVariableDeclations(List statementsToUpdate, } // Declare variable with native type for parameter or return value - if (generator.UsesNativeIdentifier(info, this)) + if (generator.UsesNativeIdentifier(info, context)) { statementsToUpdate.Add(MarshallerHelpers.DeclareWithDefault( generator.AsNativeType(info), @@ -490,7 +441,7 @@ private void AppendVariableDeclations(List statementsToUpdate, } } - private static AttributeSyntax CreateDllImportAttributeForTarget(DllImportStub.GeneratedDllImportData targetDllImportData) + private static AttributeSyntax CreateDllImportAttributeForTarget(GeneratedDllImportData targetDllImportData) { var newAttributeArgs = new List { @@ -503,43 +454,43 @@ private static AttributeSyntax CreateDllImportAttributeForTarget(DllImportStub.G CreateStringExpressionSyntax(targetDllImportData.EntryPoint)) }; - if (targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.BestFitMapping)) + if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.BestFitMapping)) { var name = NameEquals(nameof(DllImportAttribute.BestFitMapping)); var value = CreateBoolExpressionSyntax(targetDllImportData.BestFitMapping); newAttributeArgs.Add(AttributeArgument(name, null, value)); } - if (targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.CallingConvention)) + if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.CallingConvention)) { var name = NameEquals(nameof(DllImportAttribute.CallingConvention)); var value = CreateEnumExpressionSyntax(targetDllImportData.CallingConvention); newAttributeArgs.Add(AttributeArgument(name, null, value)); } - if (targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.CharSet)) + if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.CharSet)) { var name = NameEquals(nameof(DllImportAttribute.CharSet)); var value = CreateEnumExpressionSyntax(targetDllImportData.CharSet); newAttributeArgs.Add(AttributeArgument(name, null, value)); } - if (targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.ExactSpelling)) + if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.ExactSpelling)) { var name = NameEquals(nameof(DllImportAttribute.ExactSpelling)); var value = CreateBoolExpressionSyntax(targetDllImportData.ExactSpelling); newAttributeArgs.Add(AttributeArgument(name, null, value)); } - if (targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.PreserveSig)) + if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.PreserveSig)) { var name = NameEquals(nameof(DllImportAttribute.PreserveSig)); var value = CreateBoolExpressionSyntax(targetDllImportData.PreserveSig); newAttributeArgs.Add(AttributeArgument(name, null, value)); } - if (targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.SetLastError)) + if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.SetLastError)) { var name = NameEquals(nameof(DllImportAttribute.SetLastError)); var value = CreateBoolExpressionSyntax(targetDllImportData.SetLastError); newAttributeArgs.Add(AttributeArgument(name, null, value)); } - if (targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.ThrowOnUnmappableChar)) + if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.ThrowOnUnmappableChar)) { var name = NameEquals(nameof(DllImportAttribute.ThrowOnUnmappableChar)); var value = CreateBoolExpressionSyntax(targetDllImportData.ThrowOnUnmappableChar); @@ -575,21 +526,21 @@ static ExpressionSyntax CreateEnumExpressionSyntax(T value) where T : Enum } } - DllImportStub.GeneratedDllImportData GetTargetDllImportDataFromStubData() + GeneratedDllImportData GetTargetDllImportDataFromStubData(string methodName) { - DllImportStub.DllImportMember membersToForward = DllImportStub.DllImportMember.All + DllImportMember membersToForward = DllImportMember.All // https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.preservesig // If PreserveSig=false (default is true), the P/Invoke stub checks/converts a returned HRESULT to an exception. - & ~DllImportStub.DllImportMember.PreserveSig + & ~DllImportMember.PreserveSig // https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute.setlasterror // If SetLastError=true (default is false), the P/Invoke stub gets/caches the last error after invoking the native function. - & ~DllImportStub.DllImportMember.SetLastError; + & ~DllImportMember.SetLastError; if (options.GenerateForwarders()) { - membersToForward = DllImportStub.DllImportMember.All; + membersToForward = DllImportMember.All; } - var targetDllImportData = new DllImportStub.GeneratedDllImportData + var targetDllImportData = new GeneratedDllImportData { CharSet = dllImportData.CharSet, BestFitMapping = dllImportData.BestFitMapping, @@ -608,9 +559,9 @@ DllImportStub.GeneratedDllImportData GetTargetDllImportDataFromStubData() // // N.B. The export discovery logic is identical regardless of where // the name is defined (i.e. method name vs EntryPoint property). - if (!targetDllImportData.IsUserDefined.HasFlag(DllImportStub.DllImportMember.EntryPoint)) + if (!targetDllImportData.IsUserDefined.HasFlag(DllImportMember.EntryPoint)) { - targetDllImportData.EntryPoint = stubMethod.Name; + targetDllImportData = targetDllImportData with { EntryPoint = methodName }; } return targetDllImportData; From a38ced3c8c60b56988c3c6b473f9a47715b6e7ed Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Jun 2021 10:42:37 -0700 Subject: [PATCH 10/23] Refactor comparers. Update to public Roslyn build + API changes. Add more incremental tests and stub out some that should theoretially work, but we can't actually test since GeneratorDriver doesn't support incrementality in the right way. --- .../IncrementalGenerationTests.cs | 166 ++++++++++++- .../DllImportGenerator.UnitTests/TestUtils.cs | 48 +++- .../DllImportGenerator/Comparers.cs | 46 ++-- .../DllImportGenerator/DllImportGenerator.cs | 220 +++++++++--------- NuGet.config | 1 - eng/Versions.props | 2 +- 6 files changed, 331 insertions(+), 152 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs index 07c4920091cd..e37e6fdd7ab4 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Generic; using System.Linq; @@ -12,6 +13,9 @@ namespace DllImportGenerator.UnitTests { public class IncrementalGenerationTests { + public const string RequiresIncrementalSyntaxTreeModifySupport = "The GeneratorDriver treats all SyntaxTree replace operations on a Compilation as an Add/Remove operation instead of a Modify operation" + + ", so all cached results based on that input are thrown out. As a result, we cannot validate that unrelated changes within the same SyntaxTree do not cause regeneration."; + [Fact] public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() { @@ -20,7 +24,7 @@ public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() Compilation comp1 = await TestUtils.CreateCompilation(source); Microsoft.Interop.DllImportGenerator generator = new(); - GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new IIncrementalGenerator[] { generator }); driver = driver.RunGenerators(comp1); @@ -35,5 +39,165 @@ public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step); }); } + + [Fact(Skip = RequiresIncrementalSyntaxTreeModifySupport)] + public async Task AppendingUnrelatedSource_DoesNotRegenerateSource() + { + string source = $"namespace NS{{{CodeSnippets.BasicParametersAndModifiers()}}}"; + + SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)); + + Compilation comp1 = await TestUtils.CreateCompilation(new[] { syntaxTree }); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + SyntaxTree newTree = syntaxTree.WithRootAndOptions(syntaxTree.GetCompilationUnitRoot().AddMembers(SyntaxFactory.ParseMemberDeclaration("struct Foo {}")!), syntaxTree.Options); + + Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), newTree); + driver.RunGenerators(comp2); + + Assert.Collection(generator.IncrementalTracker.ExecutedSteps, + step => + { + Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step); + }); + } + + [Fact] + public async Task AddingFileWithNewGeneratedDllImport_DoesNotRegenerateOriginalMethod() + { + string source = CodeSnippets.BasicParametersAndModifiers(); + + Compilation comp1 = await TestUtils.CreateCompilation(source); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + Compilation comp2 = comp1.AddSyntaxTrees(CSharpSyntaxTree.ParseText(CodeSnippets.BasicParametersAndModifiers(), new CSharpParseOptions(LanguageVersion.Preview))); + driver.RunGenerators(comp2); + + Assert.Equal(2, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.CalculateStubInformation)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.GenerateSingleStub)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.NormalizeWhitespace)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.ConcatenateStubs)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.OutputSourceFile)); + } + + [Fact] + public async Task ReplacingFileWithNewGeneratedDllImport_DoesNotRegenerateStubsInOtherFiles() + { + string source = CodeSnippets.BasicParametersAndModifiers(); + + Compilation comp1 = await TestUtils.CreateCompilation(new string[] { CodeSnippets.BasicParametersAndModifiers(), CodeSnippets.BasicParametersAndModifiers() }); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), CSharpSyntaxTree.ParseText(CodeSnippets.BasicParametersAndModifiers(), new CSharpParseOptions(LanguageVersion.Preview))); + driver.RunGenerators(comp2); + + Assert.Equal(2, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.CalculateStubInformation)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.GenerateSingleStub)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.NormalizeWhitespace)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.ConcatenateStubs)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.OutputSourceFile)); + } + + [Fact] + public async Task ChangingMarshallingStrategy_RegeneratesStub() + { + string stubSource = CodeSnippets.BasicParametersAndModifiers("CustomType"); + + string customTypeImpl1 = "struct CustomType { System.IntPtr handle; }"; + + string customTypeImpl2 = "class CustomType : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { public CustomType():base(true){} protected override bool ReleaseHandle(){return true;} }"; + + + Compilation comp1 = await TestUtils.CreateCompilation(stubSource); + + SyntaxTree customTypeImpl1Tree = CSharpSyntaxTree.ParseText(customTypeImpl1, new CSharpParseOptions(LanguageVersion.Preview)); + comp1 = comp1.AddSyntaxTrees(customTypeImpl1Tree); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + Compilation comp2 = comp1.ReplaceSyntaxTree(customTypeImpl1Tree, CSharpSyntaxTree.ParseText(customTypeImpl2, new CSharpParseOptions(LanguageVersion.Preview))); + driver.RunGenerators(comp2); + + Assert.Collection(generator.IncrementalTracker.ExecutedSteps, + step => + { + Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.NormalizeWhitespace, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.ConcatenateStubs, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.OutputSourceFile, step.Step); + }); + } + + [Fact(Skip = RequiresIncrementalSyntaxTreeModifySupport)] + public async Task ChangingMarshallingAttributes_SameStrategy_DoesNotRegenerate() + { + string source = CodeSnippets.BasicParametersAndModifiers(); + + SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)); + + Compilation comp1 = await TestUtils.CreateCompilation(new[] { syntaxTree }); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + SyntaxTree newTree = syntaxTree.WithRootAndOptions( + syntaxTree.GetCompilationUnitRoot().AddMembers( + SyntaxFactory.ParseMemberDeclaration( + CodeSnippets.MarshalAsParametersAndModifiers(System.Runtime.InteropServices.UnmanagedType.Bool))!), + syntaxTree.Options); + + Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), newTree); + driver.RunGenerators(comp2); + + Assert.Collection(generator.IncrementalTracker.ExecutedSteps, + step => + { + Assert.Equal(IncrementalityTracker.StepName.CalculateStubInformation, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step); + }); + } } } diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index 144b3cd8c7e3..b113f15dec4b 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -44,14 +44,26 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp) /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static async Task CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) + public static Task CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) { - var (mdRefs, ancillary) = GetReferenceAssemblies(); + return CreateCompilation(new[] { source }, outputKind, allowUnsafe, preprocessorSymbols); + } - return CSharpCompilation.Create("compilation", - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols)) }, - (await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary), - new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); + /// + /// Create a compilation given sources + /// + /// Sources to compile + /// 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) + { + return CreateCompilation( + sources.Select(source => + CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols))).ToArray(), + outputKind, + allowUnsafe, + preprocessorSymbols); } /// @@ -61,13 +73,12 @@ public static async Task CreateCompilation(string source, OutputKin /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static async Task CreateCompilation(string[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) + public static async Task CreateCompilation(SyntaxTree[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) { var (mdRefs, ancillary) = GetReferenceAssemblies(); return CSharpCompilation.Create("compilation", - sources.Select(source => - CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols))).ToArray(), + sources, (await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary), new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); } @@ -80,10 +91,23 @@ public static async Task CreateCompilation(string[] sources, Output /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static async Task CreateCompilationWithReferenceAssemblies(string source, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true) + public static Task CreateCompilationWithReferenceAssemblies(string source, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true) + { + 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", - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, + sources, (await referenceAssemblies.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)), new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); } @@ -133,7 +157,7 @@ public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsP public static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, IIncrementalGenerator[] generators) => CSharpGeneratorDriver.Create( - ImmutableArray.Create(generators.Select(gen => GeneratorDriver.WrapGenerator(gen)).ToArray()), + ImmutableArray.Create(generators.Select(gen => gen.AsSourceGenerator()).ToArray()), parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options, optionsProvider: options); } diff --git a/DllImportGenerator/DllImportGenerator/Comparers.cs b/DllImportGenerator/DllImportGenerator/Comparers.cs index d4f24bf75223..1e71c7b57ed9 100644 --- a/DllImportGenerator/DllImportGenerator/Comparers.cs +++ b/DllImportGenerator/DllImportGenerator/Comparers.cs @@ -4,10 +4,17 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; namespace Microsoft.Interop { + internal static class Comparers + { + public static IEqualityComparer)>> GeneratedSourceSet = new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default))); + public static IEqualityComparer<(string, ImmutableArray)> GeneratedSource = new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); + public static IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> GeneratedSyntax = new CustomValueTupleElementComparer>(new SyntaxEquivalentComparer(), new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); + + public static IEqualityComparer<(MethodDeclarationSyntax, DllImportGenerator.IncrementalStubGenerationContext)> CalculatedContextWithSyntax = new CustomValueTupleElementComparer(new SyntaxEquivalentComparer(), EqualityComparer.Default); + } internal class ImmutableArraySequenceEqualComparer : IEqualityComparer> { @@ -29,24 +36,8 @@ public int GetHashCode(ImmutableArray obj) } } - internal class GeneratedSyntaxComparer : IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> - { - private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); - public bool Equals((MemberDeclarationSyntax, ImmutableArray) x, (MemberDeclarationSyntax, ImmutableArray) y) - { - return x.Item1.IsEquivalentTo(y.Item1) - && diagnosticComparer.Equals(x.Item2, y.Item2); - } - - public int GetHashCode((MemberDeclarationSyntax, ImmutableArray) obj) - { - return (obj.Item1.ToFullString(), diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); - } - } - internal class SyntaxEquivalentComparer : IEqualityComparer { - private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); public bool Equals(SyntaxNode x, SyntaxNode y) { return x.IsEquivalentTo(y); @@ -58,20 +49,25 @@ public int GetHashCode(SyntaxNode obj) } } - - internal class GeneratedSourceComparer : IEqualityComparer<(string, ImmutableArray)> + internal class CustomValueTupleElementComparer : IEqualityComparer<(T, U)> { - private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); + private readonly IEqualityComparer item1Comparer; + private readonly IEqualityComparer item2Comparer; + + public CustomValueTupleElementComparer(IEqualityComparer item1Comparer, IEqualityComparer item2Comparer) + { + this.item1Comparer = item1Comparer; + this.item2Comparer = item2Comparer; + } - public bool Equals((string, ImmutableArray) x, (string, ImmutableArray) y) + public bool Equals((T, U) x, (T, U) y) { - return x.Item1 == y.Item1 - && diagnosticComparer.Equals(x.Item2, y.Item2); + return item1Comparer.Equals(x.Item1, y.Item1) && item2Comparer.Equals(x.Item2, y.Item2); } - public int GetHashCode((string, ImmutableArray) obj) + public int GetHashCode((T, U) obj) { - return (obj.Item1, diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); + return (item1Comparer.GetHashCode(obj.Item1), item2Comparer.GetHashCode(obj.Item2)).GetHashCode(); } } } diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index c2a64b3ec90e..97e5d16e264b 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -245,7 +245,7 @@ public enum StepName OutputSourceFile } - public record ExecutedStepInfo(object Input, StepName Step); + public record ExecutedStepInfo(StepName Step, object Input); private List executedSteps = new(); public IEnumerable ExecutedSteps => executedSteps; @@ -257,118 +257,114 @@ public record ExecutedStepInfo(object Input, StepName Step); public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterExecutionPipeline( - context => + var methodsToGenerate = context.SyntaxProvider + .CreateSyntaxProvider( + static (node, ct) => ShouldVisitNode(node), + static (context, ct) => + new SyntaxSymbolPair( + (MethodDeclarationSyntax)context.Node, + (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)!)) + .Where( + static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( + static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) + ); + + var compilationAndTargetFramework = context.CompilationProvider + .Select((compilation, ct) => { - var methodsToGenerate = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, ct) => ShouldVisitNode(node), - static (context, ct) => - new SyntaxSymbolPair( - (MethodDeclarationSyntax)context.Node, - (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)!)) - .Where( - static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( - static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) - ); - - var compilationAndTargetFramework = context.CompilationProvider - .Select((compilation, ct) => - { - bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); - return (compilation, isSupported, targetFrameworkVersion); - }); - - context.RegisterSourceOutput( - compilationAndTargetFramework - .Combine(methodsToGenerate.Collect()), - static (context, data) => - { - if (!data.Left.isSupported && data.Right.Any()) - { - // We don't block source generation when the TFM is unsupported. - // 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, - MinimumSupportedFrameworkVersion.ToString(2))); - } - }); - - var stubEnvironment = compilationAndTargetFramework - .Combine(context.AnalyzerConfigOptionsProvider) - .Select( - (data, ct) => - new StubEnvironment( - data.Left.compilation, - data.Left.isSupported, - data.Left.targetFrameworkVersion, - data.Right.GlobalOptions) - ); - - var methodSourceAndDiagnostics = methodsToGenerate - .Combine(stubEnvironment) - .Select((data, ct) => new - { - data.Left.Syntax, - data.Left.Symbol, - Environment = data.Right - }) - .Select( - (data, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.CalculateStubInformation)); - return (data.Syntax, Context: ComputeStubContext(data.Syntax, data.Symbol, data.Environment, ct)); - } - ) - .Combine(context.AnalyzerConfigOptionsProvider) - .Select( - (data, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.GenerateSingleStub)); - return (GenerateSource(data.Left.Context.StubContext, data.Left.Context.DllImportData, data.Left.Syntax, data.Left.Context.ForwardedAttributes, data.Right.GlobalOptions), data.Left.Context.Diagnostics); - }) - .WithComparer(new GeneratedSyntaxComparer()) - // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. - .Select( - (data, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.NormalizeWhitespace)); - return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); - }) - .Collect() - .WithComparer(new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new GeneratedSourceComparer())) - .Select((generatedSources, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(generatedSources, IncrementalityTracker.StepName.ConcatenateStubs)); - StringBuilder source = new StringBuilder(); - // Mark in source that the file is auto-generated. - source.AppendLine("// "); - ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); - foreach (var generated in generatedSources) - { - source.AppendLine(generated.Item1); - diagnostics.AddRange(generated.Item2); - } - return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); - }) - .WithComparer(new GeneratedSourceComparer()); - - context.RegisterSourceOutput(methodSourceAndDiagnostics, - (context, data) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.OutputSourceFile)); - foreach (var diagnostic in data.Item2) - { - context.ReportDiagnostic(diagnostic); - } - - context.AddSource("GeneratedDllImports.g.cs", data.Item1); - }); - } - ); + bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); + return (compilation, isSupported, targetFrameworkVersion); + }); + + context.RegisterSourceOutput( + compilationAndTargetFramework + .Combine(methodsToGenerate.Collect()), + static (context, data) => + { + if (!data.Left.isSupported && data.Right.Any()) + { + // We don't block source generation when the TFM is unsupported. + // 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, + MinimumSupportedFrameworkVersion.ToString(2))); + } + }); + + var stubEnvironment = compilationAndTargetFramework + .Combine(context.AnalyzerConfigOptionsProvider) + .Select( + (data, ct) => + new StubEnvironment( + data.Left.compilation, + data.Left.isSupported, + data.Left.targetFrameworkVersion, + data.Right.GlobalOptions) + ); + + var methodSourceAndDiagnostics = methodsToGenerate + .Combine(stubEnvironment) + .Select((data, ct) => new + { + data.Left.Syntax, + data.Left.Symbol, + Environment = data.Right + }) + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.CalculateStubInformation, data)); + return (data.Syntax, ComputeStubContext(data.Syntax, data.Symbol, data.Environment, ct)); + } + ) + .WithComparer(Comparers.CalculatedContextWithSyntax) + .Combine(context.AnalyzerConfigOptionsProvider) + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.GenerateSingleStub, data)); + return (GenerateSource(data.Left.Item2.StubContext, data.Left.Item2.DllImportData, data.Left.Item1, data.Left.Item2.ForwardedAttributes, data.Right.GlobalOptions), data.Left.Item2.Diagnostics); + }) + .WithComparer(Comparers.GeneratedSyntax) + // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.NormalizeWhitespace, data)); + return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); + }) + .Collect() + .WithComparer(Comparers.GeneratedSourceSet) + .Select((generatedSources, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.ConcatenateStubs, generatedSources)); + StringBuilder source = new StringBuilder(); + // Mark in source that the file is auto-generated. + source.AppendLine("// "); + ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); + foreach (var generated in generatedSources) + { + source.AppendLine(generated.Item1); + diagnostics.AddRange(generated.Item2); + } + return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); + }) + .WithComparer(Comparers.GeneratedSource); + + context.RegisterSourceOutput(methodSourceAndDiagnostics, + (context, data) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.OutputSourceFile, data)); + foreach (var diagnostic in data.Item2) + { + context.ReportDiagnostic(diagnostic); + } + + context.AddSource("GeneratedDllImports.g.cs", data.Item1); + }); } internal sealed record IncrementalStubGenerationContext(DllImportStubContext StubContext, ImmutableArray ForwardedAttributes, GeneratedDllImportData DllImportData, ImmutableArray Diagnostics) diff --git a/NuGet.config b/NuGet.config index 66b0085249d8..bd75cae005e3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -11,7 +11,6 @@ - diff --git a/eng/Versions.props b/eng/Versions.props index a6b0c61e2fa6..ffdf3f09f8d7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -19,7 +19,7 @@ 2.4.1 2.4.3 - 4.0.0-dev.21318.1 + 4.0.0-2.21329.25 1.0.1-beta1.20478.1 3.3.3-beta1.21268.3 From e11e1afd4c5590b94d8c1995d420d0cab404471a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Jun 2021 10:42:37 -0700 Subject: [PATCH 11/23] Refactor comparers. Update to public Roslyn build + API changes. Add more incremental tests and stub out some that should theoretially work, but we can't actually test since GeneratorDriver doesn't support incrementality in the right way. --- .../IncrementalGenerationTests.cs | 124 +++++++++++++- .../DllImportGenerator.UnitTests/TestUtils.cs | 48 ++++-- .../DllImportGenerator/Comparers.cs | 71 ++++++++ .../DllImportGenerator/DllImportGenerator.cs | 162 +++++++----------- NuGet.config | 1 - eng/Versions.props | 2 +- 6 files changed, 292 insertions(+), 116 deletions(-) create mode 100644 DllImportGenerator/DllImportGenerator/Comparers.cs diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs index 5d27106b23dd..583c8b84af0b 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/IncrementalGenerationTests.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; using System; using System.Collections.Generic; using System.Linq; @@ -12,6 +13,9 @@ namespace DllImportGenerator.UnitTests { public class IncrementalGenerationTests { + public const string RequiresIncrementalSyntaxTreeModifySupport = "The GeneratorDriver treats all SyntaxTree replace operations on a Compilation as an Add/Remove operation instead of a Modify operation" + + ", so all cached results based on that input are thrown out. As a result, we cannot validate that unrelated changes within the same SyntaxTree do not cause regeneration."; + [Fact] public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() { @@ -20,7 +24,7 @@ public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() Compilation comp1 = await TestUtils.CreateCompilation(source); Microsoft.Interop.DllImportGenerator generator = new(); - GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new IIncrementalGenerator[] { generator }); driver = driver.RunGenerators(comp1); @@ -34,5 +38,123 @@ public async Task AddingNewUnrelatedType_DoesNotRegenerateSource() Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step); }); } + + [Fact(Skip = RequiresIncrementalSyntaxTreeModifySupport)] + public async Task AppendingUnrelatedSource_DoesNotRegenerateSource() + { + string source = $"namespace NS{{{CodeSnippets.BasicParametersAndModifiers()}}}"; + + SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)); + + Compilation comp1 = await TestUtils.CreateCompilation(new[] { syntaxTree }); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + SyntaxTree newTree = syntaxTree.WithRootAndOptions(syntaxTree.GetCompilationUnitRoot().AddMembers(SyntaxFactory.ParseMemberDeclaration("struct Foo {}")!), syntaxTree.Options); + + Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), newTree); + driver.RunGenerators(comp2); + + Assert.Collection(generator.IncrementalTracker.ExecutedSteps, + step => + { + Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step); + }); + } + + [Fact] + public async Task AddingFileWithNewGeneratedDllImport_DoesNotRegenerateOriginalMethod() + { + string source = CodeSnippets.BasicParametersAndModifiers(); + + Compilation comp1 = await TestUtils.CreateCompilation(source); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + Compilation comp2 = comp1.AddSyntaxTrees(CSharpSyntaxTree.ParseText(CodeSnippets.BasicParametersAndModifiers(), new CSharpParseOptions(LanguageVersion.Preview))); + driver.RunGenerators(comp2); + + Assert.Equal(2, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.GenerateSingleStub)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.NormalizeWhitespace)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.ConcatenateStubs)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.OutputSourceFile)); + } + + [Fact] + public async Task ReplacingFileWithNewGeneratedDllImport_DoesNotRegenerateStubsInOtherFiles() + { + string source = CodeSnippets.BasicParametersAndModifiers(); + + Compilation comp1 = await TestUtils.CreateCompilation(new string[] { CodeSnippets.BasicParametersAndModifiers(), CodeSnippets.BasicParametersAndModifiers() }); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + Compilation comp2 = comp1.ReplaceSyntaxTree(comp1.SyntaxTrees.First(), CSharpSyntaxTree.ParseText(CodeSnippets.BasicParametersAndModifiers(), new CSharpParseOptions(LanguageVersion.Preview))); + driver.RunGenerators(comp2); + + Assert.Equal(2, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.GenerateSingleStub)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.NormalizeWhitespace)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.ConcatenateStubs)); + Assert.Equal(1, generator.IncrementalTracker.ExecutedSteps.Count(s => s.Step == IncrementalityTracker.StepName.OutputSourceFile)); + } + + [Fact] + public async Task ChangingMarshallingStrategy_RegeneratesStub() + { + string stubSource = CodeSnippets.BasicParametersAndModifiers("CustomType"); + + string customTypeImpl1 = "struct CustomType { System.IntPtr handle; }"; + + string customTypeImpl2 = "class CustomType : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { public CustomType():base(true){} protected override bool ReleaseHandle(){return true;} }"; + + + Compilation comp1 = await TestUtils.CreateCompilation(stubSource); + + SyntaxTree customTypeImpl1Tree = CSharpSyntaxTree.ParseText(customTypeImpl1, new CSharpParseOptions(LanguageVersion.Preview)); + comp1 = comp1.AddSyntaxTrees(customTypeImpl1Tree); + + Microsoft.Interop.DllImportGenerator generator = new(); + GeneratorDriver driver = TestUtils.CreateDriver(comp1, null, new[] { generator }); + + driver = driver.RunGenerators(comp1); + + generator.IncrementalTracker = new IncrementalityTracker(); + + Compilation comp2 = comp1.ReplaceSyntaxTree(customTypeImpl1Tree, CSharpSyntaxTree.ParseText(customTypeImpl2, new CSharpParseOptions(LanguageVersion.Preview))); + driver.RunGenerators(comp2); + + Assert.Collection(generator.IncrementalTracker.ExecutedSteps, + step => + { + Assert.Equal(IncrementalityTracker.StepName.GenerateSingleStub, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.NormalizeWhitespace, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.ConcatenateStubs, step.Step); + }, + step => + { + Assert.Equal(IncrementalityTracker.StepName.OutputSourceFile, step.Step); + }); + } } } diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index 144b3cd8c7e3..b113f15dec4b 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -44,14 +44,26 @@ public static void AssertPreSourceGeneratorCompilation(Compilation comp) /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static async Task CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) + public static Task CreateCompilation(string source, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) { - var (mdRefs, ancillary) = GetReferenceAssemblies(); + return CreateCompilation(new[] { source }, outputKind, allowUnsafe, preprocessorSymbols); + } - return CSharpCompilation.Create("compilation", - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols)) }, - (await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary), - new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); + /// + /// Create a compilation given sources + /// + /// Sources to compile + /// 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) + { + return CreateCompilation( + sources.Select(source => + CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols))).ToArray(), + outputKind, + allowUnsafe, + preprocessorSymbols); } /// @@ -61,13 +73,12 @@ public static async Task CreateCompilation(string source, OutputKin /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static async Task CreateCompilation(string[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) + public static async Task CreateCompilation(SyntaxTree[] sources, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true, IEnumerable? preprocessorSymbols = null) { var (mdRefs, ancillary) = GetReferenceAssemblies(); return CSharpCompilation.Create("compilation", - sources.Select(source => - CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview, preprocessorSymbols: preprocessorSymbols))).ToArray(), + sources, (await mdRefs.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)).Add(ancillary), new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); } @@ -80,10 +91,23 @@ public static async Task CreateCompilation(string[] sources, Output /// Output type /// Whether or not use of the unsafe keyword should be allowed /// The resulting compilation - public static async Task CreateCompilationWithReferenceAssemblies(string source, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true) + public static Task CreateCompilationWithReferenceAssemblies(string source, ReferenceAssemblies referenceAssemblies, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool allowUnsafe = true) + { + 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", - new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview)) }, + sources, (await referenceAssemblies.ResolveAsync(LanguageNames.CSharp, CancellationToken.None)), new CSharpCompilationOptions(outputKind, allowUnsafe: allowUnsafe)); } @@ -133,7 +157,7 @@ public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsP public static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, IIncrementalGenerator[] generators) => CSharpGeneratorDriver.Create( - ImmutableArray.Create(generators.Select(gen => GeneratorDriver.WrapGenerator(gen)).ToArray()), + ImmutableArray.Create(generators.Select(gen => gen.AsSourceGenerator()).ToArray()), parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options, optionsProvider: options); } diff --git a/DllImportGenerator/DllImportGenerator/Comparers.cs b/DllImportGenerator/DllImportGenerator/Comparers.cs new file mode 100644 index 000000000000..b93fbb15eb54 --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/Comparers.cs @@ -0,0 +1,71 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace Microsoft.Interop +{ + internal static class Comparers + { + public static IEqualityComparer)>> GeneratedSourceSet = new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default))); + public static IEqualityComparer<(string, ImmutableArray)> GeneratedSource = new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); + public static IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> GeneratedSyntax = new CustomValueTupleElementComparer>(new SyntaxEquivalentComparer(), new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); + } + + internal class ImmutableArraySequenceEqualComparer : IEqualityComparer> + { + private readonly IEqualityComparer elementComparer; + + public ImmutableArraySequenceEqualComparer(IEqualityComparer elementComparer) + { + this.elementComparer = elementComparer; + } + + public bool Equals(ImmutableArray x, ImmutableArray y) + { + return x.SequenceEqual(y, elementComparer); + } + + public int GetHashCode(ImmutableArray obj) + { + return obj.Aggregate(0, (hash, elem) => (hash, elementComparer.GetHashCode(elem)).GetHashCode()); + } + } + + internal class SyntaxEquivalentComparer : IEqualityComparer + { + public bool Equals(SyntaxNode x, SyntaxNode y) + { + return x.IsEquivalentTo(y); + } + + public int GetHashCode(SyntaxNode obj) + { + return obj.ToFullString().GetHashCode(); + } + } + + internal class CustomValueTupleElementComparer : IEqualityComparer<(T, U)> + { + private readonly IEqualityComparer item1Comparer; + private readonly IEqualityComparer item2Comparer; + + public CustomValueTupleElementComparer(IEqualityComparer item1Comparer, IEqualityComparer item2Comparer) + { + this.item1Comparer = item1Comparer; + this.item2Comparer = item2Comparer; + } + + public bool Equals((T, U) x, (T, U) y) + { + return item1Comparer.Equals(x.Item1, y.Item1) && item2Comparer.Equals(x.Item2, y.Item2); + } + + public int GetHashCode((T, U) obj) + { + return (item1Comparer.GetHashCode(obj.Item1), item2Comparer.GetHashCode(obj.Item2)).GetHashCode(); + } + } +} diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 08fdeee69476..5dea8e279780 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -219,7 +219,7 @@ public enum StepName OutputSourceFile } - public record ExecutedStepInfo(object Input, StepName Step); + public record ExecutedStepInfo(StepName Step, object Input); private List executedSteps = new(); public IEnumerable ExecutedSteps => executedSteps; @@ -231,56 +231,53 @@ public record ExecutedStepInfo(object Input, StepName Step); public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterExecutionPipeline( - context => + var methodsToGenerate = context.SyntaxProvider + .CreateSyntaxProvider( + static (node, ct) => ShouldVisitNode(node), + static (context, ct) => + new SyntaxSymbolPair( + (MethodDeclarationSyntax)context.Node, + (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)!)) + .Where( + static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( + static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) + ); + + var compilationAndTargetFramework = context.CompilationProvider + .Select((compilation, ct) => { - var methodsToGenerate = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, ct) => ShouldVisitNode(node), - static (context, ct) => - new SyntaxSymbolPair( - (MethodDeclarationSyntax)context.Node, - (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)!)) - .Where( - static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( - static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) - ); - - var compilationAndTargetFramework = context.CompilationProvider - .Select((compilation, ct) => - { - bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); - return (compilation, isSupported, targetFrameworkVersion); - }); - - context.RegisterSourceOutput( - compilationAndTargetFramework - .Combine(methodsToGenerate.Collect()), - static (context, data) => - { - if (!data.Left.isSupported && data.Right.Any()) - { - // We don't block source generation when the TFM is unsupported. - // 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, - MinimumSupportedFrameworkVersion.ToString(2))); - } - }); - - var stubEnvironment = compilationAndTargetFramework - .Combine(context.AnalyzerConfigOptionsProvider) - .Select( - (data, ct) => - new StubEnvironment( - data.Left.compilation, - data.Left.isSupported, - data.Left.targetFrameworkVersion, - data.Right.GlobalOptions) - ); + bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); + return (compilation, isSupported, targetFrameworkVersion); + }); + + context.RegisterSourceOutput( + compilationAndTargetFramework + .Combine(methodsToGenerate.Collect()), + static (context, data) => + { + if (!data.Left.isSupported && data.Right.Any()) + { + // We don't block source generation when the TFM is unsupported. + // 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, + MinimumSupportedFrameworkVersion.ToString(2))); + } + }); + + var stubEnvironment = compilationAndTargetFramework + .Combine(context.AnalyzerConfigOptionsProvider) + .Select( + (data, ct) => + new StubEnvironment( + data.Left.compilation, + data.Left.isSupported, + data.Left.targetFrameworkVersion, + data.Right.GlobalOptions) + ); var methodSourceAndDiagnostics = methodsToGenerate .Combine(stubEnvironment) @@ -293,23 +290,23 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select( (data, ct) => { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.GenerateSingleStub)); + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.GenerateSingleStub, data)); return GenerateSource(data.Syntax, data.Symbol, data.Environment, ct); } ) - .WithComparer(new GeneratedSyntaxComparer()) + .WithComparer(Comparers.GeneratedSyntax) // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. .Select( (data, ct) => { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.NormalizeWhitespace)); + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.NormalizeWhitespace, data)); return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); }) .Collect() .WithComparer(new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new GeneratedSourceComparer())) .Select((generatedSources, ct) => { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(generatedSources, IncrementalityTracker.StepName.ConcatenateStubs)); + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.ConcatenateStubs, generatedSources)); StringBuilder source = new StringBuilder(); // Mark in source that the file is auto-generated. source.AppendLine("// "); @@ -323,54 +320,17 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }) .WithComparer(new GeneratedSourceComparer()); - context.RegisterSourceOutput(methodSourceAndDiagnostics, - (context, data) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(data, IncrementalityTracker.StepName.OutputSourceFile)); - foreach (var diagnostic in data.Item2) - { - context.ReportDiagnostic(diagnostic); - } - - context.AddSource("GeneratedDllImports.g.cs", data.Item1); - }); - } - ); - } - - private class ImmutableArraySequenceEqualComparer : IEqualityComparer> - { - private readonly IEqualityComparer elementComparer; - - public ImmutableArraySequenceEqualComparer(IEqualityComparer elementComparer) - { - this.elementComparer = elementComparer; - } - - public bool Equals(ImmutableArray x, ImmutableArray y) - { - return x.SequenceEqual(y, elementComparer); - } - - public int GetHashCode(ImmutableArray obj) - { - return obj.Aggregate(0, (hash, elem) => (hash, elementComparer.GetHashCode(elem)).GetHashCode()); - } - } - - private class GeneratedSyntaxComparer : IEqualityComparer<(MemberDeclarationSyntax, ImmutableArray)> - { - private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); - public bool Equals((MemberDeclarationSyntax, ImmutableArray) x, (MemberDeclarationSyntax, ImmutableArray) y) - { - return x.Item1.IsEquivalentTo(y.Item1) - && diagnosticComparer.Equals(x.Item2, y.Item2); - } + context.RegisterSourceOutput(methodSourceAndDiagnostics, + (context, data) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.OutputSourceFile, data)); + foreach (var diagnostic in data.Item2) + { + context.ReportDiagnostic(diagnostic); + } - public int GetHashCode((MemberDeclarationSyntax, ImmutableArray) obj) - { - return (obj.Item1.ToFullString(), diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); - } + context.AddSource("GeneratedDllImports.g.cs", data.Item1); + }); } diff --git a/NuGet.config b/NuGet.config index 66b0085249d8..bd75cae005e3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -11,7 +11,6 @@ - diff --git a/eng/Versions.props b/eng/Versions.props index a6b0c61e2fa6..ffdf3f09f8d7 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -19,7 +19,7 @@ 2.4.1 2.4.3 - 4.0.0-dev.21318.1 + 4.0.0-2.21329.25 1.0.1-beta1.20478.1 3.3.3-beta1.21268.3 From 21ba952d970cd82715445726040b0180e5b0edaa Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 27 Jul 2021 13:36:14 -0700 Subject: [PATCH 12/23] Fix PreserveSig handling. --- .../DllImportGenerator/StubCodeGenerator.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index 37f97088fc26..57cc1013d927 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -78,9 +78,17 @@ public StubCodeGenerator( } } + this.stubReturnsVoid = managedRetMarshaller.TypeInfo.ManagedType == SpecialTypeInfo.Void; + + if (!managedRetMarshaller.TypeInfo.IsNativeReturnPosition && !this.stubReturnsVoid) + { + // If the managed ret marshaller isn't the native ret marshaller, then the managed ret marshaller + // is a parameter. + paramMarshallers.Add(managedRetMarshaller); + } + this.retMarshaller = nativeRetMarshaller; this.paramMarshallers = paramMarshallers; - this.stubReturnsVoid = managedRetMarshaller.TypeInfo.ManagedType == SpecialTypeInfo.Void; // We are doing a topological sort of our marshallers to ensure that each parameter/return value's // dependencies are unmarshalled before their dependents. This comes up in the case of contiguous From 231e32371c4269b63202dccfc245399c7fc9d8af Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 27 Jul 2021 13:37:00 -0700 Subject: [PATCH 13/23] Upgrade tooling to be able to build from the CLI and VS2022 with the incremental generator. --- Directory.Build.props | 2 +- .../Ancillary.Interop/Ancillary.Interop.csproj | 1 - DllImportGenerator/Benchmarks/Benchmarks.csproj | 5 +++-- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- global.json | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index c2a6fec87fd1..c22450ed10bd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,7 +7,7 @@ True embedded true - 9 + 10 true - + https://github.com/dotnet/runtime - 01188c75f06412dc86508e7b653deaeace9623ba + ae003344a51bdfad153c4c995851f5652d28836a diff --git a/eng/Versions.props b/eng/Versions.props index ffdf3f09f8d7..a450d4eb5033 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,7 +15,7 @@ false 16.7.1 - 6.0.0-preview.6.21317.4 + 6.0.0-preview.7.21376.23 2.4.1 2.4.3 diff --git a/global.json b/global.json index 2495b5a8245d..26e4cbd3d6e5 100644 --- a/global.json +++ b/global.json @@ -1,11 +1,11 @@ { "sdk": { - "version": "6.0.100-preview.6.21316.11", + "version": "6.0.100-preview.7.21377.7", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "6.0.100-preview.6.21316.11", + "dotnet": "6.0.100-preview.7.21377.7", "runtimes": { "dotnet": [ "$(MicrosoftNETCoreAppVersion)" From af42e1bea14405da1275a3939e9a63d817213430 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Aug 2021 09:02:02 -0700 Subject: [PATCH 14/23] Reorganize file and remove unneeded record (will always compare as different, so no reason to keep around). --- .../DllImportGenerator/DllImportGenerator.cs | 283 ++++++++---------- 1 file changed, 127 insertions(+), 156 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index c45c739707e8..5f58e3aba6d2 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -23,6 +23,133 @@ public class DllImportGenerator : IIncrementalGenerator private static readonly Version MinimumSupportedFrameworkVersion = new Version(5, 0); + public class IncrementalityTracker + { + public enum StepName + { + GenerateSingleStub, + NormalizeWhitespace, + ConcatenateStubs, + OutputSourceFile + } + + public record ExecutedStepInfo(StepName Step, object Input); + + private List executedSteps = new(); + public IEnumerable ExecutedSteps => executedSteps; + + internal void RecordExecutedStep(ExecutedStepInfo step) => executedSteps.Add(step); + } + + public IncrementalityTracker? IncrementalTracker { get; set; } + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var methodsToGenerate = context.SyntaxProvider + .CreateSyntaxProvider( + static (node, ct) => ShouldVisitNode(node), + static (context, ct) => + new + { + Syntax = (MethodDeclarationSyntax)context.Node, + Symbol = (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)! + }) + .Where( + static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( + static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) + ); + + var compilationAndTargetFramework = context.CompilationProvider + .Select((compilation, ct) => + { + bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); + return (compilation, isSupported, targetFrameworkVersion); + }); + + context.RegisterSourceOutput( + compilationAndTargetFramework + .Combine(methodsToGenerate.Collect()), + static (context, data) => + { + if (!data.Left.isSupported && data.Right.Any()) + { + // We don't block source generation when the TFM is unsupported. + // 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, + MinimumSupportedFrameworkVersion.ToString(2))); + } + }); + + var stubEnvironment = compilationAndTargetFramework + .Combine(context.AnalyzerConfigOptionsProvider) + .Select( + (data, ct) => + new StubEnvironment( + data.Left.compilation, + data.Left.isSupported, + data.Left.targetFrameworkVersion, + data.Right.GlobalOptions, + data.Left.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute)) + ); + + var methodSourceAndDiagnostics = methodsToGenerate + .Combine(stubEnvironment) + .Select((data, ct) => new + { + data.Left.Syntax, + data.Left.Symbol, + Environment = data.Right + }) + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.GenerateSingleStub, data)); + return GenerateSource(data.Syntax, data.Symbol, data.Environment, ct); + } + ) + .WithComparer(Comparers.GeneratedSyntax) + // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.NormalizeWhitespace, data)); + return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); + }) + .Collect() + .WithComparer(Comparers.GeneratedSourceSet) + .Select((generatedSources, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.ConcatenateStubs, generatedSources)); + StringBuilder source = new StringBuilder(); + // Mark in source that the file is auto-generated. + source.AppendLine("// "); + ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); + foreach (var generated in generatedSources) + { + source.AppendLine(generated.Item1); + diagnostics.AddRange(generated.Item2); + } + return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); + }) + .WithComparer(Comparers.GeneratedSource); + + context.RegisterSourceOutput(methodSourceAndDiagnostics, + (context, data) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.OutputSourceFile, data)); + foreach (var diagnostic in data.Item2) + { + context.ReportDiagnostic(diagnostic); + } + + context.AddSource("GeneratedDllImports.g.cs", data.Item1); + }); + } + private List GenerateSyntaxForForwardedAttributes(AttributeData? suppressGCTransitionAttribute, AttributeData? unmanagedCallConvAttribute) { const string CallConvsField = "CallConvs"; @@ -195,162 +322,6 @@ private DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute( return stubDllImportData; } - private sealed record SyntaxSymbolPair(MethodDeclarationSyntax Syntax, IMethodSymbol Symbol) - { - public bool Equals(SyntaxSymbolPair other) - { - return Syntax.IsEquivalentTo(other.Syntax) - && SymbolEqualityComparer.Default.Equals(Symbol, other.Symbol); - } - - public override int GetHashCode() - { - return (Syntax.ToFullString().GetHashCode(), SymbolEqualityComparer.Default.GetHashCode(Symbol)).GetHashCode(); - } - } - - public class IncrementalityTracker - { - public enum StepName - { - GenerateSingleStub, - NormalizeWhitespace, - ConcatenateStubs, - OutputSourceFile - } - - public record ExecutedStepInfo(StepName Step, object Input); - - private List executedSteps = new(); - public IEnumerable ExecutedSteps => executedSteps; - - internal void RecordExecutedStep(ExecutedStepInfo step) => executedSteps.Add(step); - } - - public IncrementalityTracker? IncrementalTracker { get; set; } - - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var methodsToGenerate = context.SyntaxProvider - .CreateSyntaxProvider( - static (node, ct) => ShouldVisitNode(node), - static (context, ct) => - new SyntaxSymbolPair( - (MethodDeclarationSyntax)context.Node, - (IMethodSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node, ct)!)) - .Where( - static modelData => modelData.Symbol.IsStatic && modelData.Symbol.GetAttributes().Any( - static attribute => attribute.AttributeClass?.ToDisplayString() == TypeNames.GeneratedDllImportAttribute) - ); - - var compilationAndTargetFramework = context.CompilationProvider - .Select((compilation, ct) => - { - bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); - return (compilation, isSupported, targetFrameworkVersion); - }); - - context.RegisterSourceOutput( - compilationAndTargetFramework - .Combine(methodsToGenerate.Collect()), - static (context, data) => - { - if (!data.Left.isSupported && data.Right.Any()) - { - // We don't block source generation when the TFM is unsupported. - // 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, - MinimumSupportedFrameworkVersion.ToString(2))); - } - }); - - var stubEnvironment = compilationAndTargetFramework - .Combine(context.AnalyzerConfigOptionsProvider) - .Select( - (data, ct) => - new StubEnvironment( - data.Left.compilation, - data.Left.isSupported, - data.Left.targetFrameworkVersion, - data.Right.GlobalOptions, - data.Left.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute)) - ); - - var methodSourceAndDiagnostics = methodsToGenerate - .Combine(stubEnvironment) - .Select((data, ct) => new - { - data.Left.Syntax, - data.Left.Symbol, - Environment = data.Right - }) - .Select( - (data, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.GenerateSingleStub, data)); - return GenerateSource(data.Syntax, data.Symbol, data.Environment, ct); - } - ) - .WithComparer(Comparers.GeneratedSyntax) - // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. - .Select( - (data, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.NormalizeWhitespace, data)); - return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); - }) - .Collect() - .WithComparer(new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new GeneratedSourceComparer())) - .Select((generatedSources, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.ConcatenateStubs, generatedSources)); - StringBuilder source = new StringBuilder(); - // Mark in source that the file is auto-generated. - source.AppendLine("// "); - ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); - foreach (var generated in generatedSources) - { - source.AppendLine(generated.Item1); - diagnostics.AddRange(generated.Item2); - } - return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); - }) - .WithComparer(new GeneratedSourceComparer()); - - context.RegisterSourceOutput(methodSourceAndDiagnostics, - (context, data) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.OutputSourceFile, data)); - foreach (var diagnostic in data.Item2) - { - context.ReportDiagnostic(diagnostic); - } - - context.AddSource("GeneratedDllImports.g.cs", data.Item1); - }); - } - - - private class GeneratedSourceComparer : IEqualityComparer<(string, ImmutableArray)> - { - private static readonly IEqualityComparer> diagnosticComparer = new ImmutableArraySequenceEqualComparer(EqualityComparer.Default); - - public bool Equals((string, ImmutableArray) x, (string, ImmutableArray) y) - { - return x.Item1 == y.Item1 - && diagnosticComparer.Equals(x.Item2, y.Item2); - } - - public int GetHashCode((string, ImmutableArray) obj) - { - return (obj.Item1, diagnosticComparer.GetHashCode(obj.Item2)).GetHashCode(); - } - } - private (MemberDeclarationSyntax, ImmutableArray) GenerateSource(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment, CancellationToken ct) { INamedTypeSymbol? lcidConversionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.LCIDConversionAttribute); From 4931e3a08f1cf9c4ccb659cacb5408e8629038c6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Aug 2021 09:05:33 -0700 Subject: [PATCH 15/23] Make more methods static --- .../DllImportGenerator/DllImportGenerator.cs | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 5f58e3aba6d2..a00c44a54f5b 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -60,7 +60,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ); var compilationAndTargetFramework = context.CompilationProvider - .Select((compilation, ct) => + .Select(static (compilation, ct) => { bool isSupported = IsSupportedTargetFramework(compilation, out Version targetFrameworkVersion); return (compilation, isSupported, targetFrameworkVersion); @@ -87,7 +87,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var stubEnvironment = compilationAndTargetFramework .Combine(context.AnalyzerConfigOptionsProvider) .Select( - (data, ct) => + static (data, ct) => new StubEnvironment( data.Left.compilation, data.Left.isSupported, @@ -96,46 +96,46 @@ public void Initialize(IncrementalGeneratorInitializationContext context) data.Left.compilation.SourceModule.GetAttributes().Any(attr => attr.AttributeClass?.ToDisplayString() == TypeNames.System_Runtime_CompilerServices_SkipLocalsInitAttribute)) ); - var methodSourceAndDiagnostics = methodsToGenerate - .Combine(stubEnvironment) - .Select((data, ct) => new - { - data.Left.Syntax, - data.Left.Symbol, - Environment = data.Right - }) - .Select( - (data, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.GenerateSingleStub, data)); - return GenerateSource(data.Syntax, data.Symbol, data.Environment, ct); - } - ) - .WithComparer(Comparers.GeneratedSyntax) - // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. - .Select( - (data, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.NormalizeWhitespace, data)); - return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); - }) - .Collect() - .WithComparer(Comparers.GeneratedSourceSet) - .Select((generatedSources, ct) => - { - IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.ConcatenateStubs, generatedSources)); - StringBuilder source = new StringBuilder(); - // Mark in source that the file is auto-generated. - source.AppendLine("// "); - ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); - foreach (var generated in generatedSources) - { - source.AppendLine(generated.Item1); - diagnostics.AddRange(generated.Item2); - } - return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); - }) - .WithComparer(Comparers.GeneratedSource); + var methodSourceAndDiagnostics = methodsToGenerate + .Combine(stubEnvironment) + .Select(static (data, ct) => new + { + data.Left.Syntax, + data.Left.Symbol, + Environment = data.Right + }) + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.GenerateSingleStub, data)); + return GenerateSource(data.Syntax, data.Symbol, data.Environment, ct); + } + ) + .WithComparer(Comparers.GeneratedSyntax) + // Handle NormalizeWhitespace as a separate stage for incremental runs since it is an expensive operation. + .Select( + (data, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.NormalizeWhitespace, data)); + return (data.Item1.NormalizeWhitespace().ToFullString(), data.Item2); + }) + .Collect() + .WithComparer(Comparers.GeneratedSourceSet) + .Select((generatedSources, ct) => + { + IncrementalTracker?.RecordExecutedStep(new IncrementalityTracker.ExecutedStepInfo(IncrementalityTracker.StepName.ConcatenateStubs, generatedSources)); + StringBuilder source = new StringBuilder(); + // Mark in source that the file is auto-generated. + source.AppendLine("// "); + ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder(); + foreach (var generated in generatedSources) + { + source.AppendLine(generated.Item1); + diagnostics.AddRange(generated.Item2); + } + return (source: source.ToString(), diagnostics: diagnostics.ToImmutable()); + }) + .WithComparer(Comparers.GeneratedSource); context.RegisterSourceOutput(methodSourceAndDiagnostics, (context, data) => @@ -150,7 +150,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) }); } - private List GenerateSyntaxForForwardedAttributes(AttributeData? suppressGCTransitionAttribute, AttributeData? unmanagedCallConvAttribute) + private static List GenerateSyntaxForForwardedAttributes(AttributeData? suppressGCTransitionAttribute, AttributeData? unmanagedCallConvAttribute) { const string CallConvsField = "CallConvs"; // Manually rehydrate the forwarded attributes with fully qualified types so we don't have to worry about any using directives. @@ -188,7 +188,7 @@ private List GenerateSyntaxForForwardedAttributes(AttributeData return attributes; } - private SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenList) + private static SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenList) { SyntaxToken[] strippedTokens = new SyntaxToken[tokenList.Count]; for (int i = 0; i < tokenList.Count; i++) @@ -198,7 +198,7 @@ private SyntaxTokenList StripTriviaFromModifiers(SyntaxTokenList tokenList) return new SyntaxTokenList(strippedTokens); } - private TypeDeclarationSyntax CreateTypeDeclarationWithoutTrivia(TypeDeclarationSyntax typeDeclaration) + private static TypeDeclarationSyntax CreateTypeDeclarationWithoutTrivia(TypeDeclarationSyntax typeDeclaration) { return TypeDeclaration( typeDeclaration.Kind(), @@ -207,7 +207,7 @@ private TypeDeclarationSyntax CreateTypeDeclarationWithoutTrivia(TypeDeclaration .WithModifiers(typeDeclaration.Modifiers); } - private MemberDeclarationSyntax PrintGeneratedSource( + private static MemberDeclarationSyntax PrintGeneratedSource( MethodDeclarationSyntax userDeclaredMethod, DllImportStub stub) { @@ -261,7 +261,7 @@ private static bool IsSupportedTargetFramework(Compilation compilation, out Vers }; } - private DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute(AttributeData attrData) + private static DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute(AttributeData attrData) { var stubDllImportData = new DllImportStub.GeneratedDllImportData(); @@ -322,7 +322,7 @@ private DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute( return stubDllImportData; } - private (MemberDeclarationSyntax, ImmutableArray) GenerateSource(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment, CancellationToken ct) + private static (MemberDeclarationSyntax, ImmutableArray) GenerateSource(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment, CancellationToken ct) { INamedTypeSymbol? lcidConversionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.LCIDConversionAttribute); INamedTypeSymbol? suppressGCTransitionAttrType = environment.Compilation.GetTypeByMetadataName(TypeNames.SuppressGCTransitionAttribute); @@ -358,7 +358,7 @@ private DllImportStub.GeneratedDllImportData ProcessGeneratedDllImportAttribute( var generatorDiagnostics = new GeneratorDiagnostics(); // Process the GeneratedDllImport attribute - DllImportStub.GeneratedDllImportData stubDllImportData = this.ProcessGeneratedDllImportAttribute(generatedDllImportAttr!); + DllImportStub.GeneratedDllImportData stubDllImportData = ProcessGeneratedDllImportAttribute(generatedDllImportAttr!); Debug.Assert(stubDllImportData is not null); if (stubDllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.BestFitMapping)) From 5e173a0e64ab029db8b342b068aa7ba4b8d51e4f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Aug 2021 11:02:09 -0700 Subject: [PATCH 16/23] Make ReturnNativeIdentifier get-only. --- DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index bd1ae8a2574b..3ff504ec02ff 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -28,7 +28,7 @@ internal sealed class StubCodeGenerator : StubCodeContext /// Identifier for native return value /// /// Same as the managed identifier by default - public string ReturnNativeIdentifier { get; set; } = ReturnIdentifier; + public string ReturnNativeIdentifier { get; } = ReturnIdentifier; private const string InvokeReturnIdentifier = "__invokeRetVal"; private const string LastErrorIdentifier = "__lastError"; From 3d725b905cbd8cbe0a306f6f39cb1fe8af7b9a15 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Aug 2021 11:05:06 -0700 Subject: [PATCH 17/23] Move BoundGenerator to be a nested implementation detail of StubCodeGenerator. --- .../DllImportGenerator/BoundGenerator.cs | 41 ------------------- .../DllImportGenerator/DllImportGenerator.cs | 1 - .../DllImportGenerator/StubCodeGenerator.cs | 2 + 3 files changed, 2 insertions(+), 42 deletions(-) delete mode 100644 DllImportGenerator/DllImportGenerator/BoundGenerator.cs diff --git a/DllImportGenerator/DllImportGenerator/BoundGenerator.cs b/DllImportGenerator/DllImportGenerator/BoundGenerator.cs deleted file mode 100644 index 4a7ed49462a3..000000000000 --- a/DllImportGenerator/DllImportGenerator/BoundGenerator.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.Interop -{ - struct BoundGenerator : IEquatable - { - public BoundGenerator(TypePositionInfo typeInfo, IMarshallingGenerator marshallingGenerator) - { - TypeInfo = typeInfo; - Generator = marshallingGenerator; - } - - public TypePositionInfo TypeInfo { get; } - public IMarshallingGenerator Generator { get; } - - public void Deconstruct(out TypePositionInfo typeInfo, out IMarshallingGenerator generator) - { - typeInfo = TypeInfo; - generator = Generator; - } - - public override bool Equals(object obj) - { - return obj is BoundGenerator other && Equals(other); - } - - public bool Equals(BoundGenerator other) - { - // Only compare the type info as the selected generator is deterministically - // determined based on the overall scenario (P/Invoke) and the TypeInfo exclusively. - return TypeInfo.Equals(other.TypeInfo); - } - - public override int GetHashCode() - { - return TypeInfo.GetHashCode(); - } - } -} diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 4a36716cbb41..363fd4cd54ed 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -446,7 +446,6 @@ private static IncrementalStubGenerationContext CalculateStubInformation(MethodD MethodDeclarationSyntax originalSyntax, AnalyzerConfigOptions options) { - List generators = new(); var diagnostics = new GeneratorDiagnostics(); // Generate stub code diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index 3ff504ec02ff..d87e4f036f77 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -15,6 +15,8 @@ namespace Microsoft.Interop { internal sealed class StubCodeGenerator : StubCodeContext { + record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGenerator Generator); + public override bool SingleFrameSpansNativeContext => true; public override bool AdditionalTemporaryStateLivesAcrossStages => true; From 82b02e8df45ce950a9f786c593c7d13be7252d07 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Mon, 2 Aug 2021 11:09:54 -0700 Subject: [PATCH 18/23] Cleanup whitespace. --- DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs index d2fe6517eb97..9759f24ab694 100644 --- a/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs +++ b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs @@ -5,7 +5,6 @@ namespace Microsoft.Interop { - /// /// Flags used to indicate members on GeneratedDllImport attribute. /// From d8933f25863e62d7f158af22ad0564ee2a7da306 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 3 Aug 2021 10:25:54 -0700 Subject: [PATCH 19/23] PR feedback. --- .../Benchmarks/Benchmarks.csproj | 2 +- .../DllImportGenerator/Comparers.cs | 36 +++++++++++++++---- .../DllImportGenerator/DllImportGenerator.cs | 24 ++++++------- .../DllImportStubContext.cs | 2 +- .../GeneratedDllImportData.cs | 4 +-- .../GeneratorDiagnostics.cs | 2 +- .../DllImportGenerator/StubCodeGenerator.cs | 17 ++++----- .../UnreachableException.cs | 13 +++++++ 8 files changed, 67 insertions(+), 33 deletions(-) create mode 100644 DllImportGenerator/DllImportGenerator/UnreachableException.cs diff --git a/DllImportGenerator/Benchmarks/Benchmarks.csproj b/DllImportGenerator/Benchmarks/Benchmarks.csproj index 99c79040545f..02ec9f973bfd 100644 --- a/DllImportGenerator/Benchmarks/Benchmarks.csproj +++ b/DllImportGenerator/Benchmarks/Benchmarks.csproj @@ -17,7 +17,7 @@ - + diff --git a/DllImportGenerator/DllImportGenerator/Comparers.cs b/DllImportGenerator/DllImportGenerator/Comparers.cs index 64b95b0595a9..e1ded7b4ab18 100644 --- a/DllImportGenerator/DllImportGenerator/Comparers.cs +++ b/DllImportGenerator/DllImportGenerator/Comparers.cs @@ -9,17 +9,39 @@ namespace Microsoft.Interop { internal static class Comparers { - public static IEqualityComparer)>> GeneratedSourceSet = new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default))); - public static IEqualityComparer<(string, ImmutableArray)> GeneratedSource = new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); - public static IEqualityComparer<(MemberDeclarationSyntax Syntax, ImmutableArray Diagnostics)> GeneratedSyntax = new CustomValueTupleElementComparer>(new SyntaxEquivalentComparer(), new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); + /// + /// Comparer for the set of all of the generated stubs and diagnostics generated for each of them. + /// + public static readonly IEqualityComparer)>> GeneratedSourceSet = new ImmutableArraySequenceEqualComparer<(string, ImmutableArray)>(new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default))); + + /// + /// Comparer for an individual generated stub source as a string and the generated diagnostics for the stub. + /// + public static readonly IEqualityComparer<(string, ImmutableArray)> GeneratedSource = new CustomValueTupleElementComparer>(EqualityComparer.Default, new ImmutableArraySequenceEqualComparer(EqualityComparer.Default)); + + /// + /// 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 IEqualityComparer<(MethodDeclarationSyntax Syntax, DllImportGenerator.IncrementalStubGenerationContext StubContext)> CalculatedContextWithSyntax = new CustomValueTupleElementComparer(new SyntaxEquivalentComparer(), 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); } + /// + /// Generic comparer to compare two instances element by element. + /// + /// The type of immutable array element. internal class ImmutableArraySequenceEqualComparer : IEqualityComparer> { private readonly IEqualityComparer elementComparer; + /// + /// Creates an with a custom comparer for the elements of the collection. + /// + /// The comparer instance for the collection elements. public ImmutableArraySequenceEqualComparer(IEqualityComparer elementComparer) { this.elementComparer = elementComparer; @@ -32,7 +54,7 @@ public bool Equals(ImmutableArray x, ImmutableArray y) public int GetHashCode(ImmutableArray obj) { - return obj.Aggregate(0, (hash, elem) => (hash, elementComparer.GetHashCode(elem)).GetHashCode()); + throw new UnreachableException(); } } @@ -45,7 +67,7 @@ public bool Equals(SyntaxNode x, SyntaxNode y) public int GetHashCode(SyntaxNode obj) { - return obj.ToFullString().GetHashCode(); + throw new UnreachableException(); } } @@ -67,7 +89,7 @@ public bool Equals((T, U) x, (T, U) y) public int GetHashCode((T, U) obj) { - return (item1Comparer.GetHashCode(obj.Item1), item2Comparer.GetHashCode(obj.Item2)).GetHashCode(); + throw new UnreachableException(); } } } diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 363fd4cd54ed..9eca3d4140de 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -36,7 +36,7 @@ public bool Equals(IncrementalStubGenerationContext? other) public override int GetHashCode() { - return (StubContext, DllImportData, ForwardedAttributes.Length, Diagnostics.Length).GetHashCode(); + throw new UnreachableException(); } } @@ -59,6 +59,10 @@ public record ExecutedStepInfo(StepName Step, object Input); internal void RecordExecutedStep(ExecutedStepInfo step) => executedSteps.Add(step); } + /// + /// This property provides a test-only hook to enable testing the incrementality of the source generator. + /// This will be removed when https://github.com/dotnet/roslyn/issues/54832 is implemented and can be consumed. + /// public IncrementalityTracker? IncrementalTracker { get; set; } public void Initialize(IncrementalGeneratorInitializationContext context) @@ -291,8 +295,6 @@ private static bool IsSupportedTargetFramework(Compilation compilation, out Vers private static GeneratedDllImportData ProcessGeneratedDllImportAttribute(AttributeData attrData) { - var stubDllImportData = new GeneratedDllImportData(); - // Found the GeneratedDllImport, but it has an error so report the error. // This is most likely an issue with targeting an incorrect TFM. if (attrData.AttributeClass?.TypeKind is null or TypeKind.Error) @@ -301,8 +303,7 @@ private static GeneratedDllImportData ProcessGeneratedDllImportAttribute(Attribu throw new InvalidProgramException(); } - // Populate the DllImport data from the GeneratedDllImportAttribute attribute. - stubDllImportData.ModuleName = attrData.ConstructorArguments[0].Value!.ToString(); + var stubDllImportData = new GeneratedDllImportData(attrData.ConstructorArguments[0].Value!.ToString()); // All other data on attribute is defined as NamedArguments. foreach (var namedArg in attrData.NamedArguments) @@ -411,19 +412,18 @@ private static IncrementalStubGenerationContext CalculateStubInformation(MethodD // Process the GeneratedDllImport attribute GeneratedDllImportData stubDllImportData = ProcessGeneratedDllImportAttribute(generatedDllImportAttr!); - Debug.Assert(stubDllImportData is not null); - if (stubDllImportData!.IsUserDefined.HasFlag(DllImportMember.BestFitMapping)) + if (stubDllImportData.IsUserDefined.HasFlag(DllImportMember.BestFitMapping)) { generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(GeneratedDllImportData.BestFitMapping)); } - if (stubDllImportData!.IsUserDefined.HasFlag(DllImportMember.ThrowOnUnmappableChar)) + if (stubDllImportData.IsUserDefined.HasFlag(DllImportMember.ThrowOnUnmappableChar)) { generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(GeneratedDllImportData.ThrowOnUnmappableChar)); } - if (stubDllImportData!.IsUserDefined.HasFlag(DllImportMember.CallingConvention)) + if (stubDllImportData.IsUserDefined.HasFlag(DllImportMember.CallingConvention)) { generatorDiagnostics.ReportConfigurationNotSupported(generatedDllImportAttr!, nameof(GeneratedDllImportData.CallingConvention)); } @@ -436,7 +436,7 @@ private static IncrementalStubGenerationContext CalculateStubInformation(MethodD List additionalAttributes = GenerateSyntaxForForwardedAttributes(suppressGCTransitionAttribute, unmanagedCallConvAttribute); // Create the stub. - var dllImportStub = DllImportStubContext.Create(symbol, stubDllImportData!, environment, generatorDiagnostics, ct); + var dllImportStub = DllImportStubContext.Create(symbol, stubDllImportData, environment, generatorDiagnostics, ct); return new IncrementalStubGenerationContext(dllImportStub, additionalAttributes.ToImmutableArray(), stubDllImportData, generatorDiagnostics.Diagnostics.ToImmutableArray()); } @@ -475,8 +475,8 @@ private static bool ShouldVisitNode(SyntaxNode syntaxNode) // Verify the method has no generic types or defined implementation // and is marked static and partial. - if (!(methodSyntax.TypeParameterList is null) - || !(methodSyntax.Body is null) + if (methodSyntax.TypeParameterList is not null + || methodSyntax.Body is not null || !methodSyntax.Modifiers.Any(SyntaxKind.StaticKeyword) || !methodSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) { diff --git a/DllImportGenerator/DllImportGenerator/DllImportStubContext.cs b/DllImportGenerator/DllImportGenerator/DllImportStubContext.cs index c63623efe032..e86b8fdef826 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportStubContext.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportStubContext.cs @@ -214,7 +214,7 @@ public bool Equals(DllImportStubContext other) public override int GetHashCode() { - return StubTypeNamespace?.GetHashCode() ?? 0; + throw new UnreachableException(); } private static bool MethodIsSkipLocalsInit(StubEnvironment env, IMethodSymbol method) diff --git a/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs index 9759f24ab694..009ba26c1fd8 100644 --- a/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs +++ b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs @@ -30,10 +30,8 @@ public enum DllImportMember /// The names of these members map directly to those on the /// DllImportAttribute and should not be changed. /// - public sealed record GeneratedDllImportData + public record struct GeneratedDllImportData(string ModuleName) { - public string ModuleName { get; set; } = null!; - /// /// Value set by the user on the original declaration. /// diff --git a/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs b/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs index e938c882129c..74abfb3c7149 100644 --- a/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs +++ b/DllImportGenerator/DllImportGenerator/GeneratorDiagnostics.cs @@ -177,7 +177,7 @@ public class Ids private readonly List diagnostics = new List(); - public IReadOnlyList Diagnostics => diagnostics; + public IEnumerable Diagnostics => diagnostics; /// /// Report diagnostic for configuration that is not supported by the DLL import source generator diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index d87e4f036f77..e421e8b69b86 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -15,7 +15,7 @@ namespace Microsoft.Interop { internal sealed class StubCodeGenerator : StubCodeContext { - record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGenerator Generator); + private record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGenerator Generator); public override bool SingleFrameSpansNativeContext => true; @@ -48,7 +48,7 @@ record struct BoundGenerator(TypePositionInfo TypeInfo, IMarshallingGenerator Ge public StubCodeGenerator( GeneratedDllImportData dllImportData, - IEnumerable elements, + IEnumerable argTypes, AnalyzerConfigOptions options, Action marshallingNotSupportedCallback) { @@ -57,27 +57,28 @@ public StubCodeGenerator( List allMarshallers = new(); List paramMarshallers = new(); - bool foundNativeRetMarshaller = false, foundManagedRetMarshaller = false; + bool foundNativeRetMarshaller = false; + bool foundManagedRetMarshaller = false; BoundGenerator nativeRetMarshaller = new(new TypePositionInfo(SpecialTypeInfo.Void, NoMarshallingInfo.Instance), new Forwarder()); BoundGenerator managedRetMarshaller = new(new TypePositionInfo(SpecialTypeInfo.Void, NoMarshallingInfo.Instance), new Forwarder()); - foreach (var element in elements) + foreach (var argType in argTypes) { - BoundGenerator generator = CreateGenerator(element); + BoundGenerator generator = CreateGenerator(argType); allMarshallers.Add(generator); - if (element.IsManagedReturnPosition) + if (argType.IsManagedReturnPosition) { Debug.Assert(!foundManagedRetMarshaller); managedRetMarshaller = generator; foundManagedRetMarshaller = true; } - if (element.IsNativeReturnPosition) + if (argType.IsNativeReturnPosition) { Debug.Assert(!foundNativeRetMarshaller); nativeRetMarshaller = generator; foundNativeRetMarshaller = true; } - if (!element.IsManagedReturnPosition && !element.IsNativeReturnPosition) + if (!argType.IsManagedReturnPosition && !argType.IsNativeReturnPosition) { paramMarshallers.Add(generator); } diff --git a/DllImportGenerator/DllImportGenerator/UnreachableException.cs b/DllImportGenerator/DllImportGenerator/UnreachableException.cs new file mode 100644 index 000000000000..f33ae8b9564f --- /dev/null +++ b/DllImportGenerator/DllImportGenerator/UnreachableException.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Interop +{ + /// + /// An exception that should be thrown on code-paths that are unreachable. + /// + internal class UnreachableException : Exception + { + } +} From a29b161c8ae925f4c337aa0c53f606a7013abff8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 3 Aug 2021 10:50:41 -0700 Subject: [PATCH 20/23] Update ref assemblies to match the SDK we're using. --- DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index b113f15dec4b..b847fdf604b9 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -119,7 +119,7 @@ public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies() "net6.0", new PackageIdentity( "Microsoft.NETCore.App.Ref", - "6.0.0-preview.6.21317.4"), + "6.0.0-preview.7.21376.23"), Path.Combine("ref", "net6.0")); // Include the assembly containing the new attribute and all of its references. From 2b370030b10112e41f4d0d5de2f258a80e31fba0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 3 Aug 2021 13:41:25 -0700 Subject: [PATCH 21/23] Construct GeneratedDllImportData from locals instead of using `with` expressions during initial parsing. --- .../DllImportGenerator/DllImportGenerator.cs | 92 +++++++++---------- .../GeneratedDllImportData.cs | 24 ++--- .../DllImportGenerator/StubCodeGenerator.cs | 14 +-- 3 files changed, 59 insertions(+), 71 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs index 9eca3d4140de..92668a646d46 100644 --- a/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/DllImportGenerator.cs @@ -1,5 +1,8 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -7,10 +10,6 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Microsoft.Interop @@ -303,6 +302,20 @@ 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 + DllImportMember userDefinedValues = DllImportMember.None; + bool bestFitMapping = false; + CallingConvention callingConvention = CallingConvention.Winapi; + CharSet charSet = CharSet.Ansi; + string? entryPoint = null; + bool exactSpelling = false; // VB has different and unusual default behavior here. + bool preserveSig = true; + bool setLastError = false; + bool throwOnUnmappableChar = false; + var stubDllImportData = new GeneratedDllImportData(attrData.ConstructorArguments[0].Value!.ToString()); // All other data on attribute is defined as NamedArguments. @@ -314,65 +327,52 @@ private static GeneratedDllImportData ProcessGeneratedDllImportAttribute(Attribu Debug.Fail($"An unknown member was found on {GeneratedDllImport}"); continue; case nameof(GeneratedDllImportData.BestFitMapping): - stubDllImportData = stubDllImportData with - { - BestFitMapping = (bool)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.BestFitMapping, - }; + userDefinedValues |= DllImportMember.BestFitMapping; + bestFitMapping = (bool)namedArg.Value.Value!; break; case nameof(GeneratedDllImportData.CallingConvention): - stubDllImportData = stubDllImportData with - { - CallingConvention = (CallingConvention)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.CallingConvention, - }; + userDefinedValues |= DllImportMember.CallingConvention; + callingConvention = (CallingConvention)namedArg.Value.Value!; break; case nameof(GeneratedDllImportData.CharSet): - stubDllImportData = stubDllImportData with - { - CharSet = (CharSet)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.CharSet, - }; + userDefinedValues |= DllImportMember.CharSet; + charSet = (CharSet)namedArg.Value.Value!; break; case nameof(GeneratedDllImportData.EntryPoint): - stubDllImportData = stubDllImportData with - { - EntryPoint = (string)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.EntryPoint, - }; + userDefinedValues |= DllImportMember.EntryPoint; + entryPoint = (string)namedArg.Value.Value!; break; case nameof(GeneratedDllImportData.ExactSpelling): - stubDllImportData = stubDllImportData with - { - ExactSpelling = (bool)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.ExactSpelling, - }; + userDefinedValues |= DllImportMember.ExactSpelling; + exactSpelling = (bool)namedArg.Value.Value!; break; case nameof(GeneratedDllImportData.PreserveSig): - stubDllImportData = stubDllImportData with - { - PreserveSig = (bool)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.PreserveSig, - }; + userDefinedValues |= DllImportMember.PreserveSig; + preserveSig = (bool)namedArg.Value.Value!; break; case nameof(GeneratedDllImportData.SetLastError): - stubDllImportData = stubDllImportData with - { - SetLastError = (bool)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.SetLastError, - }; + userDefinedValues |= DllImportMember.SetLastError; + setLastError = (bool)namedArg.Value.Value!; break; case nameof(GeneratedDllImportData.ThrowOnUnmappableChar): - stubDllImportData = stubDllImportData with - { - ThrowOnUnmappableChar = (bool)namedArg.Value.Value!, - IsUserDefined = stubDllImportData.IsUserDefined | DllImportMember.ThrowOnUnmappableChar, - }; + userDefinedValues |= DllImportMember.ThrowOnUnmappableChar; + throwOnUnmappableChar = (bool)namedArg.Value.Value!; break; } } - return stubDllImportData; + return new GeneratedDllImportData(attrData.ConstructorArguments[0].Value!.ToString()) + { + IsUserDefined = userDefinedValues, + BestFitMapping = bestFitMapping, + CallingConvention = callingConvention, + CharSet = charSet, + EntryPoint = entryPoint, + ExactSpelling = exactSpelling, + PreserveSig = preserveSig, + SetLastError = setLastError, + ThrowOnUnmappableChar = throwOnUnmappableChar + }; } private static IncrementalStubGenerationContext CalculateStubInformation(MethodDeclarationSyntax syntax, IMethodSymbol symbol, StubEnvironment environment, CancellationToken ct) diff --git a/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs index 009ba26c1fd8..9a4fc90a125b 100644 --- a/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs +++ b/DllImportGenerator/DllImportGenerator/GeneratedDllImportData.cs @@ -30,23 +30,19 @@ public enum DllImportMember /// The names of these members map directly to those on the /// DllImportAttribute and should not be changed. /// - public record struct GeneratedDllImportData(string ModuleName) + public sealed record GeneratedDllImportData(string ModuleName) { /// /// Value set by the user on the original declaration. /// - public DllImportMember IsUserDefined { get; init; } = DllImportMember.None; - - // Default values for the below fields are based on the - // documented semanatics of DllImportAttribute: - // - https://docs.microsoft.com/dotnet/api/system.runtime.interopservices.dllimportattribute - public bool BestFitMapping { get; init; } = true; - public CallingConvention CallingConvention { get; init; } = CallingConvention.Winapi; - public CharSet CharSet { get; init; } = CharSet.Ansi; - public string EntryPoint { get; init; } = null!; - public bool ExactSpelling { get; init; } = false; // VB has different and unusual default behavior here. - public bool PreserveSig { get; init; } = true; - public bool SetLastError { get; init; } = false; - public bool ThrowOnUnmappableChar { get; init; } = false; + public DllImportMember IsUserDefined { get; init; } + public bool BestFitMapping { get; init; } + public CallingConvention CallingConvention { get; init; } + public CharSet CharSet { get; init; } + public string? EntryPoint { get; init; } + public bool ExactSpelling { get; init; } + public bool PreserveSig { get; init; } + public bool SetLastError { get; init; } + public bool ThrowOnUnmappableChar { get; init; } } } diff --git a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs index e421e8b69b86..2732414adc42 100644 --- a/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs +++ b/DllImportGenerator/DllImportGenerator/StubCodeGenerator.cs @@ -505,6 +505,7 @@ private void AppendVariableDeclations(List statementsToUpdate, private static AttributeSyntax CreateDllImportAttributeForTarget(GeneratedDllImportData targetDllImportData) { + Debug.Assert(targetDllImportData.EntryPoint is not null); var newAttributeArgs = new List { AttributeArgument(LiteralExpression( @@ -513,7 +514,7 @@ private static AttributeSyntax CreateDllImportAttributeForTarget(GeneratedDllImp AttributeArgument( NameEquals(nameof(DllImportAttribute.EntryPoint)), null, - CreateStringExpressionSyntax(targetDllImportData.EntryPoint)) + CreateStringExpressionSyntax(targetDllImportData.EntryPoint!)) }; if (targetDllImportData.IsUserDefined.HasFlag(DllImportMember.BestFitMapping)) @@ -602,17 +603,8 @@ GeneratedDllImportData GetTargetDllImportDataFromStubData(string methodName) membersToForward = DllImportMember.All; } - var targetDllImportData = new GeneratedDllImportData + var targetDllImportData = dllImportData with { - CharSet = dllImportData.CharSet, - BestFitMapping = dllImportData.BestFitMapping, - CallingConvention = dllImportData.CallingConvention, - EntryPoint = dllImportData.EntryPoint, - ModuleName = dllImportData.ModuleName, - ExactSpelling = dllImportData.ExactSpelling, - SetLastError = dllImportData.SetLastError, - PreserveSig = dllImportData.PreserveSig, - ThrowOnUnmappableChar = dllImportData.ThrowOnUnmappableChar, IsUserDefined = dllImportData.IsUserDefined & membersToForward }; From 3c89ab48046837419f9ac581190bfdd5d694deb0 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Aug 2021 15:52:38 -0700 Subject: [PATCH 22/23] Update to released Preview 7 version. --- DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs | 2 +- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index db8c033a6167..e6e3fd2f4eb2 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -120,7 +120,7 @@ public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies() "net6.0", new PackageIdentity( "Microsoft.NETCore.App.Ref", - "6.0.0-preview.7.21376.23"), + "6.0.100-preview.7.21379.14"), Path.Combine("ref", "net6.0")) .WithNuGetConfigFilePath(Path.Combine(Path.GetDirectoryName(typeof(TestUtils).Assembly.Location)!, "NuGet.config")); diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2523c4e8baa1..c3ef4380f809 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,9 +3,9 @@ - + https://github.com/dotnet/runtime - ae003344a51bdfad153c4c995851f5652d28836a + 91ba01788d4d83475fec3aea7c830376e08585da diff --git a/eng/Versions.props b/eng/Versions.props index 553f06852d63..e5d9069f5a88 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,7 +15,7 @@ false 16.7.1 - 6.0.0-preview.7.21376.23 + 6.0.100-preview.7.21379.14 2.4.1 2.4.3 From 20b9ac793d48615d19931e1f49fe53a1bf262ca9 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 27 Aug 2021 17:07:06 -0700 Subject: [PATCH 23/23] Use runtime version when appropriate. Update global.json --- DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs | 2 +- eng/Version.Details.xml | 2 +- eng/Versions.props | 2 +- global.json | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs index e6e3fd2f4eb2..db7b0f3feccf 100644 --- a/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs +++ b/DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs @@ -120,7 +120,7 @@ public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies() "net6.0", new PackageIdentity( "Microsoft.NETCore.App.Ref", - "6.0.100-preview.7.21379.14"), + "6.0.0-preview.7.21377.19"), Path.Combine("ref", "net6.0")) .WithNuGetConfigFilePath(Path.Combine(Path.GetDirectoryName(typeof(TestUtils).Assembly.Location)!, "NuGet.config")); diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c3ef4380f809..0c72b2c00bf3 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,7 +3,7 @@ - + https://github.com/dotnet/runtime 91ba01788d4d83475fec3aea7c830376e08585da diff --git a/eng/Versions.props b/eng/Versions.props index e5d9069f5a88..fed00ea970b2 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -15,7 +15,7 @@ false 16.7.1 - 6.0.100-preview.7.21379.14 + 6.0.0-preview.7.21377.19 2.4.1 2.4.3 diff --git a/global.json b/global.json index 26e4cbd3d6e5..a0a4f3664b71 100644 --- a/global.json +++ b/global.json @@ -1,11 +1,11 @@ { "sdk": { - "version": "6.0.100-preview.7.21377.7", + "version": "6.0.100-preview.7.21379.14", "allowPrerelease": true, "rollForward": "major" }, "tools": { - "dotnet": "6.0.100-preview.7.21377.7", + "dotnet": "6.0.100-preview.7.21379.14", "runtimes": { "dotnet": [ "$(MicrosoftNETCoreAppVersion)"