Skip to content

Commit

Permalink
Implement support for configurable generator options. (#458)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkoritzinsky authored Jan 4, 2021
1 parent ada198f commit 9649d43
Show file tree
Hide file tree
Showing 13 changed files with 274 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(RepoRoot)DllImportGenerator\DllImportGenerator\Microsoft.Interop.DllImportGenerator.props" />

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
Expand Down
63 changes: 63 additions & 0 deletions DllImportGenerator/DllImportGenerator.UnitTests/Compiles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,68 @@ public async Task ValidateSnippets(string source)
var newCompDiags = newComp.GetDiagnostics();
Assert.Empty(newCompDiags);
}

public static IEnumerable<object[]> CodeSnippetsToCompileWithForwarder()
{
yield return new[] { CodeSnippets.UserDefinedEntryPoint };
yield return new[] { CodeSnippets.AllSupportedDllImportNamedArguments };

// Parameter / return types (supported in DllImportGenerator)
yield return new[] { CodeSnippets.BasicParametersAndModifiers<byte>() };
// Parameter / return types (not supported in DllImportGenerator)
yield return new[] { CodeSnippets.BasicParametersAndModifiers<string>() };
}

[Theory]
[MemberData(nameof(CodeSnippetsToCompileWithForwarder))]
public async Task ValidateSnippetsWithForwarder(string source)
{
Compilation comp = await TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(
comp,
new DllImportGeneratorOptionsProvider(useMarshalType: false, generateForwarders: true),
out var generatorDiags,
new Microsoft.Interop.DllImportGenerator());

Assert.Empty(generatorDiags);

var newCompDiags = newComp.GetDiagnostics();
Assert.Empty(newCompDiags);
}

public static IEnumerable<object[]> CodeSnippetsToCompileWithMarshalType()
{
// SetLastError
yield return new[] { CodeSnippets.AllSupportedDllImportNamedArguments };

// SafeHandle
yield return new[] { CodeSnippets.BasicParametersAndModifiers("Microsoft.Win32.SafeHandles.SafeFileHandle") };
}

[Theory]
[MemberData(nameof(CodeSnippetsToCompileWithMarshalType))]
public async Task ValidateSnippetsWithMarshalType(string source)
{
Compilation comp = await TestUtils.CreateCompilation(source);
TestUtils.AssertPreSourceGeneratorCompilation(comp);

var newComp = TestUtils.RunGenerators(
comp,
new DllImportGeneratorOptionsProvider(useMarshalType: true, generateForwarders: false),
out var generatorDiags,
new Microsoft.Interop.DllImportGenerator());

Assert.Empty(generatorDiags);

var newCompDiags = newComp.GetDiagnostics();

Assert.All(newCompDiags, diag =>
{
Assert.Equal("CS0117", diag.Id);
Assert.StartsWith("'Marshal' does not contain a definition for ", diag.GetMessage());
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Interop;

namespace DllImportGenerator.UnitTests
{
/// <summary>
/// An implementation of <see cref="AnalyzerConfigOptionsProvider"/> that provides configuration in code
/// of the options supported by the DllImportGenerator source generator. Used for testing various configurations.
/// </summary>
internal class DllImportGeneratorOptionsProvider : AnalyzerConfigOptionsProvider
{
public DllImportGeneratorOptionsProvider(bool useMarshalType, bool generateForwarders)
{
GlobalOptions = new GlobalGeneratorOptions(useMarshalType, generateForwarders);
}

public override AnalyzerConfigOptions GlobalOptions { get; }

public override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
{
return EmptyOptions.Instance;
}

public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
{
return EmptyOptions.Instance;
}

private class GlobalGeneratorOptions : AnalyzerConfigOptions
{
private readonly bool _useMarshalType = false;
private readonly bool _generateForwarders = false;
public GlobalGeneratorOptions(bool useMarshalType, bool generateForwarders)
{
_useMarshalType = useMarshalType;
_generateForwarders = generateForwarders;
}

public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
switch (key)
{
case OptionsHelper.UseMarshalTypeOption:
value = _useMarshalType.ToString();
return true;

case OptionsHelper.GenerateForwardersOption:
value = _generateForwarders.ToString();
return true;

default:
value = null;
return false;
}
}
}

private class EmptyOptions : AnalyzerConfigOptions
{
public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
value = null;
return false;
}

public static AnalyzerConfigOptions Instance = new EmptyOptions();
}
}
}
39 changes: 29 additions & 10 deletions DllImportGenerator/DllImportGenerator.UnitTests/TestUtils.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
Expand All @@ -20,15 +22,18 @@ internal static class TestUtils
/// <param name="comp"></param>
public static void AssertPreSourceGeneratorCompilation(Compilation comp)
{
var allowedDiagnostics = new HashSet<string>()
{
"CS8795", // Partial method impl missing
"CS0234", // Missing type or namespace - GeneratedDllImportAttribute
"CS0246", // Missing type or namespace - GeneratedDllImportAttribute
"CS8019", // Unnecessary using
};
var compDiags = comp.GetDiagnostics();
foreach (var diag in compDiags)
Assert.All(compDiags, diag =>
{
Assert.True(
"CS8795".Equals(diag.Id) // Partial method impl missing
|| "CS0234".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute
|| "CS0246".Equals(diag.Id) // Missing type or namespace - GeneratedDllImportAttribute
|| "CS8019".Equals(diag.Id)); // Unnecessary using
}
Assert.Subset(allowedDiagnostics, new HashSet<string> { diag.Id });
});
}

/// <summary>
Expand Down Expand Up @@ -85,13 +90,27 @@ public static (ReferenceAssemblies, MetadataReference) GetReferenceAssemblies()
/// <returns>The resulting compilation</returns>
public static Compilation RunGenerators(Compilation comp, out ImmutableArray<Diagnostic> diagnostics, params ISourceGenerator[] generators)
{
CreateDriver(comp, generators).RunGeneratorsAndUpdateCompilation(comp, out var d, out diagnostics);
CreateDriver(comp, null, generators).RunGeneratorsAndUpdateCompilation(comp, out var d, out diagnostics);
return d;
}

/// <summary>
/// Run the supplied generators on the compilation.
/// </summary>
/// <param name="comp">Compilation target</param>
/// <param name="diagnostics">Resulting diagnostics</param>
/// <param name="generators">Source generator instances</param>
/// <returns>The resulting compilation</returns>
public static Compilation RunGenerators(Compilation comp, AnalyzerConfigOptionsProvider options, out ImmutableArray<Diagnostic> diagnostics, params ISourceGenerator[] generators)
{
CreateDriver(comp, options, generators).RunGeneratorsAndUpdateCompilation(comp, out var d, out diagnostics);
return d;
}

private static GeneratorDriver CreateDriver(Compilation c, params ISourceGenerator[] generators)
private static GeneratorDriver CreateDriver(Compilation c, AnalyzerConfigOptionsProvider? options, ISourceGenerator[] generators)
=> CSharpGeneratorDriver.Create(
ImmutableArray.Create(generators),
parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options);
parseOptions: (CSharpParseOptions)c.SyntaxTrees.First().Options,
optionsProvider: options);
}
}
13 changes: 6 additions & 7 deletions DllImportGenerator/DllImportGenerator/DllImportGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void Execute(GeneratorExecutionContext context)
generatorDiagnostics.ReportTargetFrameworkNotSupported(MinimumSupportedFrameworkVersion);
}

var env = new StubEnvironment(context.Compilation, isSupported, targetFrameworkVersion);
var env = new StubEnvironment(context.Compilation, isSupported, targetFrameworkVersion, context.AnalyzerConfigOptions.GlobalOptions);
var generatedDllImports = new StringBuilder();
foreach (SyntaxReference synRef in synRec.Methods)
{
Expand Down Expand Up @@ -94,7 +94,7 @@ public void Execute(GeneratorExecutionContext context)

// Process the GeneratedDllImport attribute
DllImportStub.GeneratedDllImportData dllImportData;
AttributeSyntax dllImportAttr = this.ProcessGeneratedDllImportAttribute(methodSymbolInfo, generatedDllImportAttr, out dllImportData);
AttributeSyntax dllImportAttr = this.ProcessGeneratedDllImportAttribute(methodSymbolInfo, generatedDllImportAttr, context.AnalyzerConfigOptions.GlobalOptions.GenerateForwarders(), out dllImportData);
Debug.Assert((dllImportAttr is not null) && (dllImportData is not null));

if (dllImportData!.IsUserDefined.HasFlag(DllImportStub.DllImportMember.BestFitMapping))
Expand Down Expand Up @@ -213,6 +213,7 @@ private static bool IsGeneratedDllImportAttribute(AttributeSyntax attrSyntaxMayb
private AttributeSyntax ProcessGeneratedDllImportAttribute(
IMethodSymbol method,
AttributeData attrData,
bool generateForwarders,
out DllImportStub.GeneratedDllImportData dllImportData)
{
dllImportData = new DllImportStub.GeneratedDllImportData();
Expand Down Expand Up @@ -287,7 +288,9 @@ private AttributeSyntax ProcessGeneratedDllImportAttribute(

Debug.Assert(expSyntaxMaybe is not null);

if (PassThroughToDllImportAttribute(namedArg.Key))
// If we're generating a forwarder stub, then all parameters on the GenerateDllImport attribute
// must also be added to the generated DllImport attribute.
if (generateForwarders || PassThroughToDllImportAttribute(namedArg.Key))
{
// Defer the name equals syntax till we know the value means something. If we created
// an expression we know the key value was valid.
Expand Down Expand Up @@ -340,9 +343,6 @@ static ExpressionSyntax CreateEnumExpressionSyntax<T>(T value) where T : Enum

static bool PassThroughToDllImportAttribute(string argName)
{
#if GENERATE_FORWARDER
return true;
#else
// Certain fields on DllImport will prevent inlining. Their functionality should be handled by the
// generated source, so the generated DllImport declaration should not include these fields.
return argName switch
Expand All @@ -355,7 +355,6 @@ static bool PassThroughToDllImportAttribute(string argName)
nameof(DllImportStub.GeneratedDllImportData.SetLastError) => false,
_ => true
};
#endif
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@
<LangVersion>Preview</LangVersion>
<Nullable>enable</Nullable>
<RootNamespace>Microsoft.Interop</RootNamespace>

<!-- Uncomment to generate stub code that simply forwards parameters to p/invoke (i.e. no marshalling) -->
<!--<DefineConstants>GENERATE_FORWARDER</DefineConstants>-->
</PropertyGroup>

<PropertyGroup>
Expand Down Expand Up @@ -42,6 +39,7 @@

<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
<None Include="Microsoft.Interop.DllImportGenerator.props" Pack="true" PackagePath="build" />
</ItemGroup>

<ItemGroup>
Expand Down
10 changes: 7 additions & 3 deletions DllImportGenerator/DllImportGenerator/DllImportStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
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
{
internal record StubEnvironment(
Compilation Compilation,
bool SupportedTargetFramework,
Version TargetFrameworkVersion);
Version TargetFrameworkVersion,
AnalyzerConfigOptions Options);

internal class DllImportStub
{
Expand Down Expand Up @@ -177,7 +179,9 @@ public static DllImportStub Create(
};

var managedRetTypeInfo = retTypeInfo;
if (!dllImportData.PreserveSig)
// Do not manually handle PreserveSig when generating forwarders.
// We want the runtime to handle everything.
if (!dllImportData.PreserveSig && !env.Options.GenerateForwarders())
{
// Create type info for native HRESULT return
retTypeInfo = TypePositionInfo.CreateForType(env.Compilation.GetSpecialType(SpecialType.System_Int32), NoMarshallingInfo.Instance);
Expand All @@ -203,7 +207,7 @@ public static DllImportStub Create(
}

// Generate stub code
var stubGenerator = new StubCodeGenerator(method, dllImportData, paramsTypeInfo, retTypeInfo, diagnostics);
var stubGenerator = new StubCodeGenerator(method, dllImportData, paramsTypeInfo, retTypeInfo, diagnostics, env.Options);
var (code, dllImport) = stubGenerator.GenerateSyntax();

var additionalAttrs = new List<AttributeListSyntax>();
Expand Down
Loading

0 comments on commit 9649d43

Please sign in to comment.