Skip to content

Commit

Permalink
Merge pull request #272 from microsoft/fix221
Browse files Browse the repository at this point in the history
Print helpful errors/warnings on generation failures
  • Loading branch information
AArnott committed May 21, 2021
2 parents 9137808 + cd6de1a commit 66f4221
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 30 deletions.
4 changes: 3 additions & 1 deletion src/Microsoft.Windows.CsWin32/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
PInvoke004 | Functionality | Warning | SourceGenerator
PInvoke005 | Functionality | Warning | SourceGenerator
28 changes: 15 additions & 13 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,21 @@ internal static bool TryStripCommonNamespace(string fullNamespace, [NotNullWhen(
return false;
}

/// <summary>
/// Checks whether an exception was originally thrown because of a target platform incompatibility.
/// </summary>
/// <param name="ex">An exception that may be or contain a <see cref="PlatformIncompatibleException"/>.</param>
/// <returns><see langword="true"/> if <paramref name="ex"/> or an inner exception is a <see cref="PlatformIncompatibleException"/>; otherwise <see langword="false" />.</returns>
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)
Expand Down Expand Up @@ -1779,19 +1794,6 @@ protected virtual void Dispose(bool disposing)
}
}

/// <summary>
/// Checks whether an exception was originally thrown because of a target platform incompatibility.
/// </summary>
private static bool IsPlatformCompatibleException(Exception? ex)
{
if (ex is null)
{
return false;
}

return ex is PlatformIncompatibleException || IsPlatformCompatibleException(ex?.InnerException);
}

private static T AddApiDocumentation<T>(string api, T memberDeclaration)
where T : MemberDeclarationSyntax
{
Expand Down
84 changes: 68 additions & 16 deletions src/Microsoft.Windows.CsWin32/SourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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);

/// <inheritdoc/>
public void Initialize(GeneratorInitializationContext context)
{
Expand Down Expand Up @@ -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)));
}
}
}
Expand Down Expand Up @@ -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();
}
}
}

0 comments on commit 66f4221

Please sign in to comment.