Skip to content

Commit 1087766

Browse files
authored
Add Diagnostics for ComClassGenerator (#87436)
Adds diagnostics to ComClassGenerator for invalid uses of GeneratedComClass
1 parent ac2d3fb commit 1087766

File tree

22 files changed

+478
-169
lines changed

22 files changed

+478
-169
lines changed

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComClassGenerator.cs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
using System.IO;
77
using System.Linq;
88
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp;
910
using Microsoft.CodeAnalysis.CSharp.Syntax;
1011
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
11-
using Microsoft.CodeAnalysis.CSharp;
1212

1313
namespace Microsoft.Interop
1414
{
@@ -18,15 +18,34 @@ public class ComClassGenerator : IIncrementalGenerator
1818
private sealed record ComClassInfo(string ClassName, ContainingSyntaxContext ContainingSyntaxContext, ContainingSyntax ClassSyntax, SequenceEqualImmutableArray<string> ImplementedInterfacesNames);
1919
public void Initialize(IncrementalGeneratorInitializationContext context)
2020
{
21+
var unsafeCodeIsEnabled = context.CompilationProvider.Select((comp, ct) => comp.Options is CSharpCompilationOptions { AllowUnsafe: true }); // Unsafe code enabled
2122
// Get all types with the [GeneratedComClassAttribute] attribute.
22-
var attributedClasses = context.SyntaxProvider
23+
var attributedClassesOrDiagnostics = context.SyntaxProvider
2324
.ForAttributeWithMetadataName(
2425
TypeNames.GeneratedComClassAttribute,
2526
static (node, ct) => node is ClassDeclarationSyntax,
26-
static (context, ct) =>
27+
static (context, ct) => context)
28+
.Combine(unsafeCodeIsEnabled)
29+
.Select((data, ct) =>
2730
{
31+
var context = data.Left;
32+
var unsafeCodeIsEnabled = data.Right;
2833
var type = (INamedTypeSymbol)context.TargetSymbol;
2934
var syntax = (ClassDeclarationSyntax)context.TargetNode;
35+
if (!unsafeCodeIsEnabled)
36+
{
37+
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.RequiresAllowUnsafeBlocks, syntax.Identifier.GetLocation()));
38+
}
39+
40+
if (!syntax.IsInPartialContext(out _))
41+
{
42+
return DiagnosticOr<ComClassInfo>.From(
43+
DiagnosticInfo.Create(
44+
GeneratorDiagnostics.InvalidAttributedClassMissingPartialModifier,
45+
syntax.Identifier.GetLocation(),
46+
type.ToDisplayString()));
47+
}
48+
3049
ImmutableArray<string>.Builder names = ImmutableArray.CreateBuilder<string>();
3150
foreach (INamedTypeSymbol iface in type.AllInterfaces)
3251
{
@@ -35,13 +54,25 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
3554
names.Add(iface.ToDisplayString());
3655
}
3756
}
38-
return new ComClassInfo(
39-
type.ToDisplayString(),
40-
new ContainingSyntaxContext(syntax),
41-
new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList),
42-
new(names.ToImmutable()));
57+
58+
if (names.Count == 0)
59+
{
60+
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.ClassDoesNotImplementAnyGeneratedComInterface,
61+
syntax.Identifier.GetLocation(),
62+
type.ToDisplayString()));
63+
}
64+
65+
66+
return DiagnosticOr<ComClassInfo>.From(
67+
new ComClassInfo(
68+
type.ToDisplayString(),
69+
new ContainingSyntaxContext(syntax),
70+
new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList),
71+
new(names.ToImmutable())));
4372
});
4473

74+
var attributedClasses = context.FilterAndReportDiagnostics(attributedClassesOrDiagnostics);
75+
4576
var className = attributedClasses.Select(static (info, ct) => info.ClassName);
4677

4778
var classInfoType = attributedClasses

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/ComInterfaceInfo.cs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Diagnostics.CodeAnalysis;
6-
using System.Linq;
76
using System.Threading;
87
using Microsoft.CodeAnalysis;
98
using Microsoft.CodeAnalysis.CSharp;
@@ -68,17 +67,14 @@ public static DiagnosticOrInterfaceInfo From(INamedTypeSymbol symbol, InterfaceD
6867
private static bool IsInPartialContext(INamedTypeSymbol symbol, InterfaceDeclarationSyntax syntax, [NotNullWhen(false)] out DiagnosticInfo? diagnostic)
6968
{
7069
// Verify that the types the interface is declared in are marked partial.
71-
for (SyntaxNode? parentNode = syntax; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent)
70+
if (!syntax.IsInPartialContext(out var nonPartialIdentifier))
7271
{
73-
if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
74-
{
75-
diagnostic = DiagnosticInfo.Create(
76-
GeneratorDiagnostics.InvalidAttributedInterfaceMissingPartialModifiers,
77-
syntax.Identifier.GetLocation(),
78-
symbol.Name,
79-
typeDecl.Identifier);
80-
return false;
81-
}
72+
diagnostic = DiagnosticInfo.Create(
73+
GeneratorDiagnostics.InvalidAttributedInterfaceMissingPartialModifiers,
74+
syntax.Identifier.GetLocation(),
75+
symbol.Name,
76+
nonPartialIdentifier);
77+
return false;
8278
}
8379
diagnostic = null;
8480
return true;

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/GeneratorDiagnostics.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public class Ids
3131

3232
private const string Category = "ComInterfaceGenerator";
3333

34+
/// <inheritdoc cref="SR.RequiresAllowUnsafeBlocksMessage"/>
3435
public static readonly DiagnosticDescriptor RequiresAllowUnsafeBlocks =
3536
new DiagnosticDescriptor(
3637
Ids.RequiresAllowUnsafeBlocks,
@@ -327,6 +328,18 @@ public class Ids
327328
isEnabledByDefault: true,
328329
description: GetResourceString(nameof(SR.InterfaceTypeNotSupportedMessage)));
329330

331+
/// <inheritdoc cref="SR.ClassDoesNotImplementAnyGeneratedComInterfacesMessage"/>
332+
public static readonly DiagnosticDescriptor ClassDoesNotImplementAnyGeneratedComInterface =
333+
new DiagnosticDescriptor(
334+
Ids.InvalidGeneratedComClassAttributeUsage,
335+
GetResourceString(nameof(SR.InvalidGeneratedComClassAttributeUsageTitle)),
336+
GetResourceString(nameof(SR.ClassDoesNotImplementAnyGeneratedComInterfacesMessage)),
337+
Category,
338+
DiagnosticSeverity.Warning,
339+
isEnabledByDefault: true,
340+
description: GetResourceString(nameof(SR.ClassDoesNotImplementAnyGeneratedComInterfacesDescription)));
341+
342+
330343
private readonly List<DiagnosticInfo> _diagnostics = new List<DiagnosticInfo>();
331344

332345
public IEnumerable<DiagnosticInfo> Diagnostics => _diagnostics;

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/Strings.resx

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<root>
3-
<!--
4-
Microsoft ResX Schema
5-
3+
<!--
4+
Microsoft ResX Schema
5+
66
Version 2.0
7-
8-
The primary goals of this format is to allow a simple XML format
9-
that is mostly human readable. The generation and parsing of the
10-
various data types are done through the TypeConverter classes
7+
8+
The primary goals of this format is to allow a simple XML format
9+
that is mostly human readable. The generation and parsing of the
10+
various data types are done through the TypeConverter classes
1111
associated with the data types.
12-
12+
1313
Example:
14-
14+
1515
... ado.net/XML headers & schema ...
1616
<resheader name="resmimetype">text/microsoft-resx</resheader>
1717
<resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
2626
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
2727
<comment>This is a comment</comment>
2828
</data>
29-
30-
There are any number of "resheader" rows that contain simple
29+
30+
There are any number of "resheader" rows that contain simple
3131
name/value pairs.
32-
33-
Each data row contains a name, and value. The row also contains a
34-
type or mimetype. Type corresponds to a .NET class that support
35-
text/value conversion through the TypeConverter architecture.
36-
Classes that don't support this are serialized and stored with the
32+
33+
Each data row contains a name, and value. The row also contains a
34+
type or mimetype. Type corresponds to a .NET class that support
35+
text/value conversion through the TypeConverter architecture.
36+
Classes that don't support this are serialized and stored with the
3737
mimetype set.
38-
39-
The mimetype is used for serialized objects, and tells the
40-
ResXResourceReader how to depersist the object. This is currently not
38+
39+
The mimetype is used for serialized objects, and tells the
40+
ResXResourceReader how to depersist the object. This is currently not
4141
extensible. For a given mimetype the value must be set accordingly:
42-
43-
Note - application/x-microsoft.net.object.binary.base64 is the format
44-
that the ResXResourceWriter will generate, however the reader can
42+
43+
Note - application/x-microsoft.net.object.binary.base64 is the format
44+
that the ResXResourceWriter will generate, however the reader can
4545
read any of the formats listed below.
46-
46+
4747
mimetype: application/x-microsoft.net.object.binary.base64
48-
value : The object must be serialized with
48+
value : The object must be serialized with
4949
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
5050
: and then encoded with base64 encoding.
51-
51+
5252
mimetype: application/x-microsoft.net.object.soap.base64
53-
value : The object must be serialized with
53+
value : The object must be serialized with
5454
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
5555
: and then encoded with base64 encoding.
5656
5757
mimetype: application/x-microsoft.net.object.bytearray.base64
58-
value : The object must be serialized into a byte array
58+
value : The object must be serialized into a byte array
5959
: using a System.ComponentModel.TypeConverter
6060
: and then encoded with base64 encoding.
6161
-->
@@ -268,13 +268,13 @@
268268
<value>The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on interface '{0}' is invalid. {1}</value>
269269
</data>
270270
<data name="RequiresAllowUnsafeBlocksDescription" xml:space="preserve">
271-
<value>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
271+
<value>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
272272
</data>
273273
<data name="RequiresAllowUnsafeBlocksMessage" xml:space="preserve">
274-
<value>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
274+
<value>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
275275
</data>
276276
<data name="RequiresAllowUnsafeBlocksTitle" xml:space="preserve">
277-
<value>GeneratedComInterfaceAttribute requires unsafe code.</value>
277+
<value>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code.</value>
278278
</data>
279279
<data name="InvalidGeneratedComInterfaceUsageMissingPartialModifier" xml:space="preserve">
280280
<value>The interface '{0}' or one of its containing types is missing the 'partial' keyword. Code will not be generated for '{0}'.</value>
@@ -283,7 +283,7 @@
283283
<value>Classes with 'GeneratedComClassAttribute' must implement one or more interfaces with 'GeneratedComInterfaceAttribute', be marked partial, and be non-generic.</value>
284284
</data>
285285
<data name="InvalidGeneratedComClassAttributeUsageMissingPartialModifier" xml:space="preserve">
286-
<value>Class '{0}' or one of its containing types is not marked 'partial'.</value>
286+
<value>Class '{0}' with 'GeneratedComClassAttribute' or one of its containing types is not marked 'partial'.</value>
287287
</data>
288288
<data name="InvalidGeneratedComClassAttributeUsageTitle" xml:space="preserve">
289289
<value>Invalid 'GeneratedComClassAttribute' usage</value>
@@ -336,6 +336,12 @@
336336
<data name="ConvertComInterfaceMayProduceInvalidCode" xml:space="preserve">
337337
<value>Converting this interface to use 'GeneratedComInterfaceAttribute' may produce invalid code and may require additional work</value>
338338
</data>
339+
<data name="ClassDoesNotImplementAnyGeneratedComInterfacesDescription" xml:space="preserve">
340+
<value>A class with 'GeneratedComClassAttribute' must implement at least one interface with 'GeneratedComInterfaceAttribute' or else the generated code with not have an effect.</value>
341+
</data>
342+
<data name="ClassDoesNotImplementAnyGeneratedComInterfacesMessage" xml:space="preserve">
343+
<value>Class '{0}' with 'GeneratedComClassAttribute' does not implement any interfaces with 'GeneratedComInterfaceAttribute'. Source will not be generated for '{0}'.</value>
344+
</data>
339345
<data name="CastsBetweenRuntimeComAndSourceGeneratedComNotSupportedDescription" xml:space="preserve">
340346
<value>Casting between a 'ComImport' type and a source-generated COM type is not supported and will fail at runtime</value>
341347
</data>
@@ -345,4 +351,4 @@
345351
<data name="CastsBetweenRuntimeComAndSourceGeneratedComNotSupportedTitle" xml:space="preserve">
346352
<value>Casting between a 'ComImport' type and a source-generated COM type is not supported</value>
347353
</data>
348-
</root>
354+
</root>

src/libraries/System.Runtime.InteropServices/gen/ComInterfaceGenerator/Resources/xlf/Strings.cs.xlf

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@
7272
<target state="new">Casting between a 'ComImport' type and a source-generated COM type is not supported</target>
7373
<note />
7474
</trans-unit>
75+
<trans-unit id="ClassDoesNotImplementAnyGeneratedComInterfacesDescription">
76+
<source>A class with 'GeneratedComClassAttribute' must implement at least one interface with 'GeneratedComInterfaceAttribute' or else the generated code with not have an effect.</source>
77+
<target state="new">A class with 'GeneratedComClassAttribute' must implement at least one interface with 'GeneratedComInterfaceAttribute' or else the generated code with not have an effect.</target>
78+
<note />
79+
</trans-unit>
80+
<trans-unit id="ClassDoesNotImplementAnyGeneratedComInterfacesMessage">
81+
<source>Class '{0}' with 'GeneratedComClassAttribute' does not implement any interfaces with 'GeneratedComInterfaceAttribute'. Source will not be generated for '{0}'.</source>
82+
<target state="new">Class '{0}' with 'GeneratedComClassAttribute' does not implement any interfaces with 'GeneratedComInterfaceAttribute'. Source will not be generated for '{0}'.</target>
83+
<note />
84+
</trans-unit>
7585
<trans-unit id="ComHostingDoesNotSupportGeneratedComInterfaceDescription">
7686
<source>.NET COM hosting with 'EnableComHosting' only supports built-in COM interop. It does not support source-generated COM interop with 'GeneratedComInterfaceAttribute'.</source>
7787
<target state="new">.NET COM hosting with 'EnableComHosting' only supports built-in COM interop. It does not support source-generated COM interop with 'GeneratedComInterfaceAttribute'.</target>
@@ -218,8 +228,8 @@
218228
<note />
219229
</trans-unit>
220230
<trans-unit id="InvalidGeneratedComClassAttributeUsageMissingPartialModifier">
221-
<source>Class '{0}' or one of its containing types is not marked 'partial'.</source>
222-
<target state="new">Class '{0}' or one of its containing types is not marked 'partial'.</target>
231+
<source>Class '{0}' with 'GeneratedComClassAttribute' or one of its containing types is not marked 'partial'.</source>
232+
<target state="new">Class '{0}' with 'GeneratedComClassAttribute' or one of its containing types is not marked 'partial'.</target>
223233
<note />
224234
</trans-unit>
225235
<trans-unit id="InvalidGeneratedComClassAttributeUsageTitle">
@@ -313,18 +323,18 @@
313323
<note />
314324
</trans-unit>
315325
<trans-unit id="RequiresAllowUnsafeBlocksDescription">
316-
<source>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
317-
<target state="new">GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
326+
<source>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
327+
<target state="new">'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
318328
<note />
319329
</trans-unit>
320330
<trans-unit id="RequiresAllowUnsafeBlocksMessage">
321-
<source>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
322-
<target state="new">GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
331+
<source>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
332+
<target state="new">'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
323333
<note />
324334
</trans-unit>
325335
<trans-unit id="RequiresAllowUnsafeBlocksTitle">
326-
<source>GeneratedComInterfaceAttribute requires unsafe code.</source>
327-
<target state="new">GeneratedComInterfaceAttribute requires unsafe code.</target>
336+
<source>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code.</source>
337+
<target state="new">'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code.</target>
328338
<note />
329339
</trans-unit>
330340
<trans-unit id="RuntimeComApisDoNotSupportSourceGeneratedComDescription">

0 commit comments

Comments
 (0)