Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Print helpful errors/warnings on generation failures #272

Merged
merged 1 commit into from
May 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
}
}
}