From cd6de1a8835d80b1e612e3ef935611fba70d17d6 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Thu, 20 May 2021 13:17:00 -0600 Subject: [PATCH] Print helpful errors/warnings on generation failures This prevents the whole generator from failing to generate anything when something goes wrong. Closes #221 --- .../AnalyzerReleases.Unshipped.md | 4 +- src/Microsoft.Windows.CsWin32/Generator.cs | 28 ++++--- .../SourceGenerator.cs | 84 +++++++++++++++---- 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md b/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md index 70bdd122..25203f87 100644 --- a/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md +++ b/src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md @@ -4,7 +4,9 @@ ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- +PInvoke000 | Functionality | Error | SourceGenerator PInvoke001 | Functionality | Warning | SourceGenerator PInvoke002 | Functionality | Warning | SourceGenerator PInvoke003 | Functionality | Warning | SourceGenerator -PInvoke004 | Functionality | Warning | SourceGenerator \ No newline at end of file +PInvoke004 | Functionality | Warning | SourceGenerator +PInvoke005 | Functionality | Warning | SourceGenerator \ No newline at end of file diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 7a444182..ddbda303 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -1118,6 +1118,21 @@ internal static bool TryStripCommonNamespace(string fullNamespace, [NotNullWhen( return false; } + /// + /// Checks whether an exception was originally thrown because of a target platform incompatibility. + /// + /// An exception that may be or contain a . + /// if or an inner exception is a ; otherwise . + internal static bool IsPlatformCompatibleException(Exception? ex) + { + if (ex is null) + { + return false; + } + + return ex is PlatformIncompatibleException || IsPlatformCompatibleException(ex?.InnerException); + } + internal bool IsAttribute(CustomAttribute attribute, string ns, string name) => IsAttribute(this.mr, attribute, ns, name); internal bool TryGetHandleReleaseMethod(EntityHandle handleStructDefHandle, [NotNullWhen(true)] out string? releaseMethod) @@ -1779,19 +1794,6 @@ protected virtual void Dispose(bool disposing) } } - /// - /// Checks whether an exception was originally thrown because of a target platform incompatibility. - /// - private static bool IsPlatformCompatibleException(Exception? ex) - { - if (ex is null) - { - return false; - } - - return ex is PlatformIncompatibleException || IsPlatformCompatibleException(ex?.InnerException); - } - private static T AddApiDocumentation(string api, T memberDeclaration) where T : MemberDeclarationSyntax { diff --git a/src/Microsoft.Windows.CsWin32/SourceGenerator.cs b/src/Microsoft.Windows.CsWin32/SourceGenerator.cs index d2f30364..f6a87053 100644 --- a/src/Microsoft.Windows.CsWin32/SourceGenerator.cs +++ b/src/Microsoft.Windows.CsWin32/SourceGenerator.cs @@ -23,6 +23,15 @@ public class SourceGenerator : ISourceGenerator { private const string NativeMethodsTxtAdditionalFileName = "NativeMethods.txt"; private const string NativeMethodsJsonAdditionalFileName = "NativeMethods.json"; + + private static readonly DiagnosticDescriptor InternalError = new DiagnosticDescriptor( + "PInvoke000", + "CsWin32InternalError", + "An internal error occurred: {0}", + "Functionality", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + private static readonly DiagnosticDescriptor NoMatchingMethodOrType = new DiagnosticDescriptor( "PInvoke001", "No matching method or type found", @@ -73,6 +82,14 @@ public class SourceGenerator : ISourceGenerator isEnabledByDefault: true, description: "Constants that are defined within enums should be generated by requesting the name of their declaring enum instead."); + private static readonly DiagnosticDescriptor CpuArchitectureIncompatibility = new DiagnosticDescriptor( + "PInvoke005", + "TargetSpecificCpuArchitecture", + "This API is only available when targeting a specific CPU architecture. AnyCPU cannot generate this API.", + "Functionality", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + /// public void Initialize(GeneratorInitializationContext context) { @@ -141,31 +158,46 @@ public void Execute(GeneratorExecutionContext context) name = name.Trim(); var location = Location.Create(nativeMethodsTxtFile.Path, line.Span, nativeMethodsTxt.Lines.GetLinePositionSpan(line.Span)); - if (generator.BannedAPIs.TryGetValue(name, out string? reason)) + try { - context.ReportDiagnostic(Diagnostic.Create(BannedApi, location, reason)); - } - else if (name.EndsWith(".*", StringComparison.Ordinal)) - { - var moduleName = name.Substring(0, name.Length - 2); - if (!generator.TryGenerateAllExternMethods(moduleName, context.CancellationToken)) + if (generator.BannedAPIs.TryGetValue(name, out string? reason)) { - context.ReportDiagnostic(Diagnostic.Create(NoMethodsForModule, location, moduleName)); + context.ReportDiagnostic(Diagnostic.Create(BannedApi, location, reason)); } - } - else if (!generator.TryGenerate(name, context.CancellationToken)) - { - if (generator.TryGetEnumName(name, out string? declaringEnum)) + else if (name.EndsWith(".*", StringComparison.Ordinal)) + { + var moduleName = name.Substring(0, name.Length - 2); + if (!generator.TryGenerateAllExternMethods(moduleName, context.CancellationToken)) + { + context.ReportDiagnostic(Diagnostic.Create(NoMethodsForModule, location, moduleName)); + } + } + else if (!generator.TryGenerate(name, context.CancellationToken)) { - context.ReportDiagnostic(Diagnostic.Create(UseEnumValueDeclaringType, location, declaringEnum)); - if (!generator.TryGenerate(declaringEnum, context.CancellationToken)) + if (generator.TryGetEnumName(name, out string? declaringEnum)) { - ReportNoMatch(location, declaringEnum); + context.ReportDiagnostic(Diagnostic.Create(UseEnumValueDeclaringType, location, declaringEnum)); + if (!generator.TryGenerate(declaringEnum, context.CancellationToken)) + { + ReportNoMatch(location, declaringEnum); + } } + else + { + ReportNoMatch(location, name); + } + } + } + catch (GenerationFailedException ex) + { + if (Generator.IsPlatformCompatibleException(ex)) + { + context.ReportDiagnostic(Diagnostic.Create(CpuArchitectureIncompatibility, location)); } else { - ReportNoMatch(location, name); + // Build up a complete error message. + context.ReportDiagnostic(Diagnostic.Create(InternalError, location, AssembleFullExceptionMessage(ex))); } } } @@ -202,5 +234,25 @@ void ReportNoMatch(Location? location, string failedAttempt) } } } + + private static string AssembleFullExceptionMessage(Exception ex) + { + var sb = new StringBuilder(); + + Exception? inner = ex; + while (inner is object) + { + sb.Append(inner.Message); + if (sb.Length > 0 && sb[sb.Length - 1] != '.') + { + sb.Append('.'); + } + + sb.Append(' '); + inner = inner.InnerException; + } + + return sb.ToString(); + } } }