Skip to content
Closed
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
59 changes: 42 additions & 17 deletions Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,6 @@
// fitness for a particular purpose. Attribution is appreciated, but not
// required.

// About this version
//
// v1.0.0 of this project aimed to provide dynamically instanciated Delegate
// objects that implemented one of the provided interfaces, but relied on
// classes from System.Reflection.Emit, which is incompatible with some .NET
// platforms (those using AOT compilation).
//
// This version instead aims to create classes that implement the provided
// interfaces at compile-time, using an incremental source generator. This
// version is a work-in-progress that may have bugs and is not feature
// complete. Importantly, the generated class objects are not instances of the
// Delegate or MulticastDelegate types.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
Expand All @@ -30,8 +17,49 @@
namespace NativeGenericDelegatesGenerator
{
[Generator]
public class Generator : IIncrementalGenerator
internal class Generator : IIncrementalGenerator
{
/// <summary>
/// <inheritdoc cref="IIncrementalGenerator.Initialize" path="//summary"/>
/// </summary>
/// <remarks>
/// <para>
/// The translation phases for generating the source for native generic delegates are:
/// </para><para>
/// <list type="bullet">
/// <item>
/// From the syntax tree, select any <see cref="InvocationExpressionSyntax"/> nodes that begin with the text
/// "INativeAction" or "INativeFunc".
/// </item><item>
/// Filter the selected nodes using the <see cref="IMethodSymbol"/> to validate that the method is a native generic
/// delegate interface method call with no open generic type parameters. The validated symbol is then paired with a <see
/// cref="GeneratorSyntaxContext"/> in a <see cref="MethodSymbolWithContext"/>. Invalid symbols are filtered out of this
/// translation.
/// </item><item>
/// Custom marshaling behavior is parsed from the user-supplied arguments (e.g., what the user passed for the
/// <c>marshalParamsAs</c> argument). A <see cref="MethodSymbolWithMarshalAndDiagnosticInfo"/> is created which may
/// contain a <see cref="Diagnostic"/> that will be reported in a later translation phase. If this object does contain
/// diagnostic info, then no further translations are performed on this object except to report the diagnostic.
/// </item><item>
/// Created <see cref="Diagnostic"/>s, if any, will be reported at this translation phase.
/// </item><item>
/// The selected <see cref="MethodSymbolWithMarshalAndDiagnosticInfo"/>s that do not have any <see cref="Diagnostic"/>
/// are filtered one additional time to create a unique set. See <see cref="MethodSymbolWithMarshalInfo"/> for details on
/// how this unique set is formed.
/// </item><item>
/// Each item in the selected set is translated into a <see cref="NativeGenericDelegateInfo"/>.
/// </item><item>
/// Each item is translated into a <see cref="NativeGenericDelegateConcreteClassInfo"/>.
/// </item><item>
/// The data set is translated into a <see cref="PartialImplementations"/> object which will create the final generated
/// source.
/// </item>
/// </list>
/// </para>
/// </remarks>
/// <param name="initContext">
/// <inheritdoc cref="IIncrementalGenerator.Initialize" path="//param[@name='context']"/>
/// </param>
public void Initialize(IncrementalGeneratorInitializationContext initContext)
{
var methodSymbolsWithContext = initContext.SyntaxProvider.CreateSyntaxProvider(static (node, cancellationToken) =>
Expand Down Expand Up @@ -67,9 +95,6 @@ public void Initialize(IncrementalGeneratorInitializationContext initContext)
var methodSymbolsWithMarshalInfo = methodSymbolsWithMarshalAndDiagnosticInfo.Where(static x => x.Diagnostics is null)
.Collect().SelectMany(static (symbolsWithMarshalInfo, cancellationToken) =>
{
// this hash set will ensure each combination of a method symbol and marshaling behavior are unique
// if the method is the generic FromFunctionPointer then the IMethodSymbol is used for comparison, otherwise the
// INamedTypeSymbol (e.g., INativeAction<...>) is used for comparison
HashSet<MethodSymbolWithMarshalInfo> infoSet = new();
foreach (var symbolWithMarshalInfo in symbolsWithMarshalInfo)
{
Expand Down
5 changes: 5 additions & 0 deletions MethodSymbolWithContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@

namespace NativeGenericDelegatesGenerator
{
/// <summary>
/// Represents an <see cref="IMethodSymbol"/> (representing a native generic delegate method, such as
/// <c>INativeAction.FromAction</c>) paired with a <see cref="GeneratorSyntaxContext"/> (representing the method invocation
/// site in user code).
/// </summary>
internal readonly struct MethodSymbolWithContext
{
public readonly GeneratorSyntaxContext Context;
Expand Down
10 changes: 10 additions & 0 deletions MethodSymbolWithMarshalAndDiagnosticInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@

namespace NativeGenericDelegatesGenerator
{
/// <summary>
/// Represents an <see cref="IMethodSymbol"/> (see <see cref="MethodSymbolWithContext"/>) with parsed custom marshaling
/// behavior or a <see cref="Diagnostic"/> reporting the parsing error.
/// </summary>
/// <remarks>
/// <para>
/// If <see cref="Diagnostics"/> is not <see langword="null"/>, the other members of this object should not be considered as
/// valid, and they will not be used during source generation.
/// </para>
/// </remarks>
internal readonly struct MethodSymbolWithMarshalAndDiagnosticInfo
{
public readonly ImmutableArray<Diagnostic>? Diagnostics;
Expand Down
17 changes: 16 additions & 1 deletion MethodSymbolWithMarshalInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,26 @@
// required.

using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace NativeGenericDelegatesGenerator
{
/// <summary>
/// Represents an <see cref="IMethodSymbol"/> (see <see cref="MethodSymbolWithContext"/>) with custom marshaling behavior.
/// </summary>
/// <remarks>
/// <para>
/// This type represents a method symbol and custom marshaling behaviors that may or may not be unique, but is suitable for
/// equality comparisons and hashing operations (e.g., use in a <see cref="HashSet{T}"/>). Equality and hashing combine an
/// <see cref="ISymbol"/> (either <see cref="MethodSymbol"/> or the <see cref="INamedTypeSymbol"/> representing the
/// containing interface) with the custom marshaling behavior. The <see cref="ISymbol"/> is always compared using <see
/// cref="SymbolEqualityComparer.Default"/>. If <see cref="MethodSymbol"/> represents a generic overload of
/// <c>FromFunctionPointer</c> (e.g., <c>INativeAction&lt;string&gt;.FromFunctionPointer&lt;nint&gt;</c>), then equality and
/// hashing is performed using <see cref="MethodSymbol"/>; otherwise, the <see cref="INamedTypeSymbol"/> is used.
/// </para>
/// </remarks>
internal readonly struct MethodSymbolWithMarshalInfo : IEquatable<MethodSymbolWithMarshalInfo>
{
private readonly int Hash;
Expand Down
4 changes: 4 additions & 0 deletions NativeGenericDelegateConcreteClassInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

namespace NativeGenericDelegatesGenerator
{
/// <summary>
/// Represents a set of concrete classes that produce a native generic delegate with a unique signature, including custom
/// marshaling behaviors and calling conventions.
/// </summary>
internal readonly struct NativeGenericDelegateConcreteClassInfo
{
public readonly int ArgumentCount;
Expand Down
3 changes: 3 additions & 0 deletions NativeGenericDelegateInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

namespace NativeGenericDelegatesGenerator
{
/// <summary>
/// Represents basic information about a native generic delegate signature, including custom marshaling behaviors.
/// </summary>
internal readonly struct NativeGenericDelegateInfo
{
public readonly string ClassNamePrefix;
Expand Down
4 changes: 4 additions & 0 deletions PartialImplementations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@

namespace NativeGenericDelegatesGenerator
{
/// <summary>
/// Represents the partial interface implementations for native generic delegates and the concrete class definitions which
/// implement those interfaces.
/// </summary>
internal readonly struct PartialImplementations
{
public readonly string ConcreteClassDefinitions;
Expand Down
3 changes: 3 additions & 0 deletions PostInitialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

namespace NativeGenericDelegatesGenerator
{
/// <summary>
/// Represents the native generic delegate sources that will be added after the source generator has been initialized.
/// </summary>
internal static class PostInitialization
{
private static void BuildNativeAction(StringBuilder sb, string callConv)
Expand Down
Loading