Skip to content

Commit

Permalink
Report diagnostics in generator (dotnet/runtimelab#158)
Browse files Browse the repository at this point in the history
Commit migrated from dotnet/runtimelab@98f55be
  • Loading branch information
elinor-fung authored Oct 10, 2020
1 parent 09e1a9d commit 653aeb3
Show file tree
Hide file tree
Showing 16 changed files with 866 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public void Execute(GeneratorExecutionContext context)
// this caching.
var syntaxToModel = new Dictionary<SyntaxTree, SemanticModel>();

var generatorDiagnostics = new GeneratorDiagnostics(context);

var generatedDllImports = new StringBuilder();
foreach (SyntaxReference synRef in synRec.Methods)
{
Expand All @@ -55,13 +57,7 @@ public void Execute(GeneratorExecutionContext context)
Debug.Assert(!(dllImportAttr is null) && !(dllImportData is null));

// Create the stub.
var dllImportStub = DllImportStub.Create(methodSymbolInfo, dllImportData, context.Compilation, context.CancellationToken);

// Report any diagnostics from the stub generation step.
foreach (var diag in dllImportStub.Diagnostics)
{
context.ReportDiagnostic(diag);
}
var dllImportStub = DllImportStub.Create(methodSymbolInfo, dllImportData, context.Compilation, generatorDiagnostics, context.CancellationToken);

PrintGeneratedSource(generatedDllImports, methodSyntax, dllImportStub, dllImportAttr);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>Preview</LangVersion>
<RootNamespace>Microsoft.Interop</RootNamespace>

<!-- Uncomment to generate stub code that simply forwards parameters to p/invoke (i.e. no marshalling) -->
<!--<DefineConstants>GENERATE_FORWARDER</DefineConstants>-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ public IEnumerable<ParameterSyntax> StubParameters

public MethodDeclarationSyntax DllImportDeclaration { get; private set; }

public IEnumerable<Diagnostic> Diagnostics { get; private set; }

/// <summary>
/// Flags used to indicate members on GeneratedDllImport attribute.
/// </summary>
Expand Down Expand Up @@ -101,6 +99,7 @@ public static DllImportStub Create(
IMethodSymbol method,
GeneratedDllImportData dllImportData,
Compilation compilation,
GeneratorDiagnostics diagnostics,
CancellationToken token = default)
{
// Cancel early if requested
Expand Down Expand Up @@ -137,13 +136,13 @@ public static DllImportStub Create(
for (int i = 0; i < method.Parameters.Length; i++)
{
var param = method.Parameters[i];
var typeInfo = TypePositionInfo.CreateForParameter(param, compilation);
var typeInfo = TypePositionInfo.CreateForParameter(param, compilation, diagnostics);
typeInfo.ManagedIndex = i;
typeInfo.NativeIndex = paramsTypeInfo.Count;
paramsTypeInfo.Add(typeInfo);
}

TypePositionInfo retTypeInfo = TypePositionInfo.CreateForType(method.ReturnType, method.GetReturnTypeAttributes(), compilation);
TypePositionInfo retTypeInfo = TypePositionInfo.CreateForType(method.ReturnType, method.GetReturnTypeAttributes(), compilation, diagnostics);
retTypeInfo.ManagedIndex = TypePositionInfo.ReturnIndex;
retTypeInfo.NativeIndex = TypePositionInfo.ReturnIndex;
if (!dllImportData.PreserveSig)
Expand All @@ -162,7 +161,8 @@ public static DllImportStub Create(
}

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

return new DllImportStub()
{
Expand All @@ -172,7 +172,6 @@ public static DllImportStub Create(
StubContainingTypes = containingTypes,
StubCode = code,
DllImportDeclaration = dllImport,
Diagnostics = Enumerable.Empty<Diagnostic>(),
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using Microsoft.CodeAnalysis;

#nullable enable

namespace Microsoft.Interop
{
internal static class DiagnosticExtensions
{
public static Diagnostic CreateDiagnostic(
this ISymbol symbol,
DiagnosticDescriptor descriptor,
params object[] args)
{
IEnumerable<Location> locationsInSource = symbol.Locations.Where(l => l.IsInSource);
if (!locationsInSource.Any())
return Diagnostic.Create(descriptor, Location.None, args);

return Diagnostic.Create(
descriptor,
location: locationsInSource.First(),
additionalLocations: locationsInSource.Skip(1),
messageArgs: args);
}

public static Diagnostic CreateDiagnostic(
this AttributeData attributeData,
DiagnosticDescriptor descriptor,
params object[] args)
{
SyntaxReference? syntaxReference = attributeData.ApplicationSyntaxReference;
Location location = syntaxReference is not null
? syntaxReference.GetSyntax().GetLocation()
: Location.None;

return Diagnostic.Create(
descriptor,
location: location.IsInSource ? location : Location.None,
messageArgs: args);
}
}

/// <summary>
/// Class for reporting diagnostics in the DLL import generator
/// </summary>
public class GeneratorDiagnostics
{
public class Ids
{
public const string Prefix = "DLLIMPORTGEN";
public const string TypeNotSupported = Prefix + "001";
public const string ConfigurationNotSupported = Prefix + "002";
}

private const string Category = "DllImportGenerator";

public readonly static DiagnosticDescriptor ParameterTypeNotSupported =
new DiagnosticDescriptor(
Ids.TypeNotSupported,
GetResourceString(nameof(Resources.TypeNotSupportedTitle)),
GetResourceString(nameof(Resources.TypeNotSupportedMessageParameter)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(Resources.TypeNotSupportedDescription));

public readonly static DiagnosticDescriptor ReturnTypeNotSupported =
new DiagnosticDescriptor(
Ids.TypeNotSupported,
GetResourceString(nameof(Resources.TypeNotSupportedTitle)),
GetResourceString(nameof(Resources.TypeNotSupportedMessageReturn)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(Resources.TypeNotSupportedDescription));

public readonly static DiagnosticDescriptor ParameterConfigurationNotSupported =
new DiagnosticDescriptor(
Ids.ConfigurationNotSupported,
GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)),
GetResourceString(nameof(Resources.ConfigurationNotSupportedMessageParameter)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(Resources.ConfigurationNotSupportedDescription));

public readonly static DiagnosticDescriptor ReturnConfigurationNotSupported =
new DiagnosticDescriptor(
Ids.ConfigurationNotSupported,
GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)),
GetResourceString(nameof(Resources.ConfigurationNotSupportedMessageReturn)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(Resources.ConfigurationNotSupportedDescription));

public readonly static DiagnosticDescriptor ConfigurationNotSupported =
new DiagnosticDescriptor(
Ids.ConfigurationNotSupported,
GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)),
GetResourceString(nameof(Resources.ConfigurationNotSupportedMessage)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(Resources.ConfigurationNotSupportedDescription));

public readonly static DiagnosticDescriptor ConfigurationValueNotSupported =
new DiagnosticDescriptor(
Ids.ConfigurationNotSupported,
GetResourceString(nameof(Resources.ConfigurationNotSupportedTitle)),
GetResourceString(nameof(Resources.ConfigurationNotSupportedMessageValue)),
Category,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: GetResourceString(Resources.ConfigurationNotSupportedDescription));

private readonly GeneratorExecutionContext context;

public GeneratorDiagnostics(GeneratorExecutionContext context)
{
this.context = context;
}

/// <summary>
/// Report diagnostic for configuration that is not supported by the DLL import source generator
/// </summary>
/// <param name="attributeData">Attribute specifying the unsupported configuration</param>
/// <param name="configurationName">Name of the configuration</param>
/// <param name="unsupportedValue">[Optiona] Unsupported configuration value</param>
public void ReportConfigurationNotSupported(
AttributeData attributeData,
string configurationName,
string? unsupportedValue = null)
{
if (unsupportedValue == null)
{
this.context.ReportDiagnostic(
attributeData.CreateDiagnostic(
GeneratorDiagnostics.ConfigurationNotSupported,
configurationName));
}
else
{
this.context.ReportDiagnostic(
attributeData.CreateDiagnostic(
GeneratorDiagnostics.ConfigurationValueNotSupported,
unsupportedValue,
configurationName));
}
}

/// <summary>
/// Report diagnostic for marshalling of a parameter/return that is not supported
/// </summary>
/// <param name="method">Method with the parameter/return</param>
/// <param name="info">Type info for the parameter/return</param>
internal void ReportMarshallingNotSupported(
IMethodSymbol method,
TypePositionInfo info)
{
if (info.MarshallingAttributeInfo != null && info.MarshallingAttributeInfo is MarshalAsInfo)
{
// Report that the specified marshalling configuration is not supported.
// We don't forward marshalling attributes, so this is reported differently
// than when there is no attribute and the type itself is not supported.
if (info.IsManagedReturnPosition)
{
this.context.ReportDiagnostic(
method.CreateDiagnostic(
GeneratorDiagnostics.ReturnConfigurationNotSupported,
nameof(System.Runtime.InteropServices.MarshalAsAttribute),
method.Name));
}
else
{
Debug.Assert(info.ManagedIndex <= method.Parameters.Length);
IParameterSymbol paramSymbol = method.Parameters[info.ManagedIndex];
this.context.ReportDiagnostic(
paramSymbol.CreateDiagnostic(
GeneratorDiagnostics.ParameterConfigurationNotSupported,
nameof(System.Runtime.InteropServices.MarshalAsAttribute),
paramSymbol.Name));
}
}
else
{
// Report that the type is not supported
if (info.IsManagedReturnPosition)
{
this.context.ReportDiagnostic(
method.CreateDiagnostic(
GeneratorDiagnostics.ReturnTypeNotSupported,
method.ReturnType.ToDisplayString(),
method.Name));
}
else
{
Debug.Assert(info.ManagedIndex <= method.Parameters.Length);
IParameterSymbol paramSymbol = method.Parameters[info.ManagedIndex];
this.context.ReportDiagnostic(
paramSymbol.CreateDiagnostic(
GeneratorDiagnostics.ParameterTypeNotSupported,
paramSymbol.Type.ToDisplayString(),
paramSymbol.Name));
}
}
}

private static LocalizableResourceString GetResourceString(string resourceName)
{
return new LocalizableResourceString(resourceName, Resources.ResourceManager, typeof(Resources));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using DllImportGenerator;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ internal interface IMarshallingGenerator
/// <param name="info">Object to marshal</param>
/// <param name="context">Code generation context</param>
/// <returns>If the marshaller uses an identifier for the native value, true; otherwise, false.</returns>
/// <remarks>
/// <see cref="StubCodeContext.CurrentStage" /> of <paramref name="context"/> may not be valid.
/// </remarks>
bool UsesNativeIdentifier(TypePositionInfo info, StubCodeContext context);
}

Expand Down Expand Up @@ -117,7 +120,7 @@ public static bool TryCreate(TypePositionInfo info, StubCodeContext context, out
generator = Blittable;
return true;

// Marshalling in new model
// Marshalling in new model
case { MarshallingAttributeInfo: NativeMarshallingAttributeInfo marshalInfo }:
generator = Forwarder;
return false;
Expand All @@ -127,10 +130,14 @@ public static bool TryCreate(TypePositionInfo info, StubCodeContext context, out
generator = Forwarder;
return false;

case { MarshallingAttributeInfo: SafeHandleMarshallingInfo _}:
case { MarshallingAttributeInfo: SafeHandleMarshallingInfo _}:
generator = SafeHandle;
return true;

case { ManagedType: { SpecialType: SpecialType.System_Void } }:
generator = Forwarder;
return true;

default:
generator = Forwarder;
return false;
Expand Down
Loading

0 comments on commit 653aeb3

Please sign in to comment.