Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.Interop
{
Expand All @@ -18,15 +18,34 @@ public class ComClassGenerator : IIncrementalGenerator
private sealed record ComClassInfo(string ClassName, ContainingSyntaxContext ContainingSyntaxContext, ContainingSyntax ClassSyntax, SequenceEqualImmutableArray<string> ImplementedInterfacesNames);
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var unsafeCodeIsEnabled = context.CompilationProvider.Select((comp, ct) => comp.Options is CSharpCompilationOptions { AllowUnsafe: true }); // Unsafe code enabled
// Get all types with the [GeneratedComClassAttribute] attribute.
var attributedClasses = context.SyntaxProvider
var attributedClassesOrDiagnostics = context.SyntaxProvider
.ForAttributeWithMetadataName(
TypeNames.GeneratedComClassAttribute,
static (node, ct) => node is ClassDeclarationSyntax,
static (context, ct) =>
static (context, ct) => context)
.Combine(unsafeCodeIsEnabled)
.Select((data, ct) =>
{
var context = data.Left;
var unsafeCodeIsEnabled = data.Right;
var type = (INamedTypeSymbol)context.TargetSymbol;
var syntax = (ClassDeclarationSyntax)context.TargetNode;
if (!unsafeCodeIsEnabled)
{
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.RequiresAllowUnsafeBlocks, syntax.Identifier.GetLocation()));
}

if (!syntax.IsInPartialContext(out _))
{
return DiagnosticOr<ComClassInfo>.From(
DiagnosticInfo.Create(
GeneratorDiagnostics.InvalidAttributedClassMissingPartialModifier,
syntax.Identifier.GetLocation(),
type.ToDisplayString()));
}

ImmutableArray<string>.Builder names = ImmutableArray.CreateBuilder<string>();
foreach (INamedTypeSymbol iface in type.AllInterfaces)
{
Expand All @@ -35,13 +54,25 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
names.Add(iface.ToDisplayString());
}
}
return new ComClassInfo(
type.ToDisplayString(),
new ContainingSyntaxContext(syntax),
new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList),
new(names.ToImmutable()));

if (names.Count == 0)
{
return DiagnosticOr<ComClassInfo>.From(DiagnosticInfo.Create(GeneratorDiagnostics.ClassDoesNotImplementAnyGeneratedComInterface,
syntax.Identifier.GetLocation(),
type.ToDisplayString()));
}


return DiagnosticOr<ComClassInfo>.From(
new ComClassInfo(
type.ToDisplayString(),
new ContainingSyntaxContext(syntax),
new ContainingSyntax(syntax.Modifiers, syntax.Kind(), syntax.Identifier, syntax.TypeParameterList),
new(names.ToImmutable())));
});

var attributedClasses = context.FilterAndReportDiagnostics(attributedClassesOrDiagnostics);

var className = attributedClasses.Select(static (info, ct) => info.ClassName);

var classInfoType = attributedClasses
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -68,17 +67,14 @@ public static DiagnosticOrInterfaceInfo From(INamedTypeSymbol symbol, InterfaceD
private static bool IsInPartialContext(INamedTypeSymbol symbol, InterfaceDeclarationSyntax syntax, [NotNullWhen(false)] out DiagnosticInfo? diagnostic)
{
// Verify that the types the interface is declared in are marked partial.
for (SyntaxNode? parentNode = syntax; parentNode is TypeDeclarationSyntax typeDecl; parentNode = parentNode.Parent)
if (!syntax.IsInPartialContext(out var nonPartialIdentifier))
{
if (!typeDecl.Modifiers.Any(SyntaxKind.PartialKeyword))
{
diagnostic = DiagnosticInfo.Create(
GeneratorDiagnostics.InvalidAttributedInterfaceMissingPartialModifiers,
syntax.Identifier.GetLocation(),
symbol.Name,
typeDecl.Identifier);
return false;
}
diagnostic = DiagnosticInfo.Create(
GeneratorDiagnostics.InvalidAttributedInterfaceMissingPartialModifiers,
syntax.Identifier.GetLocation(),
symbol.Name,
nonPartialIdentifier);
return false;
}
diagnostic = null;
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class Ids

private const string Category = "ComInterfaceGenerator";

/// <inheritdoc cref="SR.RequiresAllowUnsafeBlocksMessage"/>
public static readonly DiagnosticDescriptor RequiresAllowUnsafeBlocks =
new DiagnosticDescriptor(
Ids.RequiresAllowUnsafeBlocks,
Expand Down Expand Up @@ -327,6 +328,18 @@ public class Ids
isEnabledByDefault: true,
description: GetResourceString(nameof(SR.InterfaceTypeNotSupportedMessage)));

/// <inheritdoc cref="SR.ClassDoesNotImplementAnyGeneratedComInterfacesMessage"/>
public static readonly DiagnosticDescriptor ClassDoesNotImplementAnyGeneratedComInterface =
new DiagnosticDescriptor(
Ids.InvalidGeneratedComClassAttributeUsage,
GetResourceString(nameof(SR.InvalidGeneratedComClassAttributeUsageTitle)),
GetResourceString(nameof(SR.ClassDoesNotImplementAnyGeneratedComInterfacesMessage)),
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: GetResourceString(nameof(SR.ClassDoesNotImplementAnyGeneratedComInterfacesDescription)));


private readonly List<DiagnosticInfo> _diagnostics = new List<DiagnosticInfo>();

public IEnumerable<DiagnosticInfo> Diagnostics => _diagnostics;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema

Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes

The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.

Example:

... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
Expand All @@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple

There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the

Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not

The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can

Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.

mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.

mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
Expand Down Expand Up @@ -268,13 +268,13 @@
<value>The configuration of 'StringMarshalling' and 'StringMarshallingCustomType' on interface '{0}' is invalid. {1}</value>
</data>
<data name="RequiresAllowUnsafeBlocksDescription" xml:space="preserve">
<value>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
<value>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
</data>
<data name="RequiresAllowUnsafeBlocksMessage" xml:space="preserve">
<value>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
<value>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</value>
</data>
<data name="RequiresAllowUnsafeBlocksTitle" xml:space="preserve">
<value>GeneratedComInterfaceAttribute requires unsafe code.</value>
<value>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code.</value>
</data>
<data name="InvalidGeneratedComInterfaceUsageMissingPartialModifier" xml:space="preserve">
<value>The interface '{0}' or one of its containing types is missing the 'partial' keyword. Code will not be generated for '{0}'.</value>
Expand All @@ -283,7 +283,7 @@
<value>Classes with 'GeneratedComClassAttribute' must implement one or more interfaces with 'GeneratedComInterfaceAttribute', be marked partial, and be non-generic.</value>
</data>
<data name="InvalidGeneratedComClassAttributeUsageMissingPartialModifier" xml:space="preserve">
<value>Class '{0}' or one of its containing types is not marked 'partial'.</value>
<value>Class '{0}' with 'GeneratedComClassAttribute' or one of its containing types is not marked 'partial'.</value>
</data>
<data name="InvalidGeneratedComClassAttributeUsageTitle" xml:space="preserve">
<value>Invalid 'GeneratedComClassAttribute' usage</value>
Expand Down Expand Up @@ -336,6 +336,12 @@
<data name="ConvertComInterfaceMayProduceInvalidCode" xml:space="preserve">
<value>Converting this interface to use 'GeneratedComInterfaceAttribute' may produce invalid code and may require additional work</value>
</data>
<data name="ClassDoesNotImplementAnyGeneratedComInterfacesDescription" xml:space="preserve">
<value>A class with 'GeneratedComClassAttribute' must implement at least one interface with 'GeneratedComInterfaceAttribute' or else the generated code with not have an effect.</value>
</data>
<data name="ClassDoesNotImplementAnyGeneratedComInterfacesMessage" xml:space="preserve">
<value>Class '{0}' with 'GeneratedComClassAttribute' does not implement any interfaces with 'GeneratedComInterfaceAttribute'. Source will not be generated for '{0}'.</value>
</data>
<data name="CastsBetweenRuntimeComAndSourceGeneratedComNotSupportedDescription" xml:space="preserve">
<value>Casting between a 'ComImport' type and a source-generated COM type is not supported and will fail at runtime</value>
</data>
Expand All @@ -345,4 +351,4 @@
<data name="CastsBetweenRuntimeComAndSourceGeneratedComNotSupportedTitle" xml:space="preserve">
<value>Casting between a 'ComImport' type and a source-generated COM type is not supported</value>
</data>
</root>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@
<target state="new">Casting between a 'ComImport' type and a source-generated COM type is not supported</target>
<note />
</trans-unit>
<trans-unit id="ClassDoesNotImplementAnyGeneratedComInterfacesDescription">
<source>A class with 'GeneratedComClassAttribute' must implement at least one interface with 'GeneratedComInterfaceAttribute' or else the generated code with not have an effect.</source>
<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>
<note />
</trans-unit>
<trans-unit id="ClassDoesNotImplementAnyGeneratedComInterfacesMessage">
<source>Class '{0}' with 'GeneratedComClassAttribute' does not implement any interfaces with 'GeneratedComInterfaceAttribute'. Source will not be generated for '{0}'.</source>
<target state="new">Class '{0}' with 'GeneratedComClassAttribute' does not implement any interfaces with 'GeneratedComInterfaceAttribute'. Source will not be generated for '{0}'.</target>
<note />
</trans-unit>
<trans-unit id="ComHostingDoesNotSupportGeneratedComInterfaceDescription">
<source>.NET COM hosting with 'EnableComHosting' only supports built-in COM interop. It does not support source-generated COM interop with 'GeneratedComInterfaceAttribute'.</source>
<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>
Expand Down Expand Up @@ -218,8 +228,8 @@
<note />
</trans-unit>
<trans-unit id="InvalidGeneratedComClassAttributeUsageMissingPartialModifier">
<source>Class '{0}' or one of its containing types is not marked 'partial'.</source>
<target state="new">Class '{0}' or one of its containing types is not marked 'partial'.</target>
<source>Class '{0}' with 'GeneratedComClassAttribute' or one of its containing types is not marked 'partial'.</source>
<target state="new">Class '{0}' with 'GeneratedComClassAttribute' or one of its containing types is not marked 'partial'.</target>
<note />
</trans-unit>
<trans-unit id="InvalidGeneratedComClassAttributeUsageTitle">
Expand Down Expand Up @@ -313,18 +323,18 @@
<note />
</trans-unit>
<trans-unit id="RequiresAllowUnsafeBlocksDescription">
<source>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
<target state="new">GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
<source>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
<target state="new">'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
<note />
</trans-unit>
<trans-unit id="RequiresAllowUnsafeBlocksMessage">
<source>GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
<target state="new">GeneratedComInterfaceAttribute requires unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
<source>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</source>
<target state="new">'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code. Project must be updated with '&lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt;'.</target>
<note />
</trans-unit>
<trans-unit id="RequiresAllowUnsafeBlocksTitle">
<source>GeneratedComInterfaceAttribute requires unsafe code.</source>
<target state="new">GeneratedComInterfaceAttribute requires unsafe code.</target>
<source>'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code.</source>
<target state="new">'GeneratedComInterfaceAttribute' and 'GeneratedComClassAttribute' require unsafe code.</target>
<note />
</trans-unit>
<trans-unit id="RuntimeComApisDoNotSupportSourceGeneratedComDescription">
Expand Down
Loading