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
37 changes: 27 additions & 10 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Close.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,33 @@ public void Close()
}
}

// The method class output builders are emitted here, before the helper types, so that in
// single-file mode the helper types are appended at the bottom of the shared namespace
// rather than in the middle of the generated output. In multi-file mode the method classes
// were already written directly above, so both dictionaries are empty and these loops are a
// no-op.

if (leaveStreamOpen)
{
Debug.Assert(stream is not null);

foreach (var entry in methodClassOutputBuilders)
{
CloseOutputBuilder(stream, entry.Value, isMethodClass: true, leaveStreamOpen, emitNamespaceDeclaration);
emitNamespaceDeclaration = false;
}

foreach (var entry in methodClassTestOutputBuilders)
{
CloseOutputBuilder(testStream ?? stream, entry.Value, isMethodClass: true, leaveStreamOpen, emitNamespaceDeclaration);
}
}
else
{
Debug.Assert(methodClassOutputBuilders.Count == 0);
Debug.Assert(methodClassTestOutputBuilders.Count == 0);
}

if (generateHelperTypes)
{
// In single-file mode the helper types are emitted inside the shared namespace and are
Expand Down Expand Up @@ -349,16 +376,6 @@ public void Close()
{
Debug.Assert(stream is not null);

foreach (var entry in methodClassOutputBuilders)
{
CloseOutputBuilder(stream, entry.Value, isMethodClass: true, leaveStreamOpen, emitNamespaceDeclaration);
}

foreach (var entry in methodClassTestOutputBuilders)
{
CloseOutputBuilder(testStream ?? stream, entry.Value, isMethodClass: true, leaveStreamOpen, emitNamespaceDeclaration);
}

using var sw = new StreamWriter(stream, s_defaultStreamWriterEncoding, DefaultStreamWriterBufferSize, leaveStreamOpen);
sw.NewLine = "\n";

Expand Down
154 changes: 152 additions & 2 deletions tests/ClangSharp.PInvokeGenerator.UnitTests/HelperTypesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
namespace ClangSharp.UnitTests;

/// <summary>
/// Regression test for https://github.com/dotnet/ClangSharp/issues/657.
/// Regression tests for https://github.com/dotnet/ClangSharp/issues/657 and
/// https://github.com/dotnet/ClangSharp/issues/429.
/// When <c>generate-helper-types</c> is used with a single output file, the helper types must be
/// emitted inside the shared namespace with their usings hoisted to the top of the file, rather than
/// each helper emitting its own <c>using</c> directives and <c>namespace</c> wrapper (which produced
/// invalid C# with nested namespaces, usings after a namespace, and a stray trailing brace).
/// invalid C# with nested namespaces, usings after a namespace, and a stray trailing brace). The
/// helper types must also be emitted at the bottom of the shared namespace, after the deferred method
/// class, rather than in the middle of the generated output.
/// </summary>
[Platform("win")]
public sealed class HelperTypesTest : PInvokeGeneratorTest
Expand Down Expand Up @@ -122,6 +125,153 @@ public NativeTypeNameAttribute(string name)
public string Name => _name;
}

/// <summary>Defines the annotation found in a native declaration.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
[Conditional(""DEBUG"")]
internal sealed partial class NativeAnnotationAttribute : Attribute
{
private readonly string _annotation;

/// <summary>Initializes a new instance of the <see cref=""NativeAnnotationAttribute"" /> class.</summary>
/// <param name=""annotation"">The annotation that was used in the native declaration.</param>
public NativeAnnotationAttribute(string annotation)
{
_annotation = annotation;
}

/// <summary>Gets the annotation that was used in the native declaration.</summary>
public string Annotation => _annotation;
}
";

return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateHelperTypes | PInvokeGeneratorConfigurationOptions.GenerateFileScopedNamespaces);
}

[Test]
public Task HelperTypesEmittedAfterMethodClass()
{
var inputContents = @"struct SRC_DATA
{
const float *data_in;
long input_frames;
};

extern ""C"" void MyFunction();
";

var expectedOutputContents = @"using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ClangSharp.Test
{
public unsafe partial struct SRC_DATA
{
[NativeTypeName(""const float *"")]
public float* data_in;

[NativeTypeName(""long"")]
public int input_frames;
}

public static partial class Methods
{
[DllImport(""ClangSharpPInvokeGenerator"", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void MyFunction();
}

/// <summary>Defines the type of a member as it was used in the native signature.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)]
[Conditional(""DEBUG"")]
internal sealed partial class NativeTypeNameAttribute : Attribute
{
private readonly string _name;

/// <summary>Initializes a new instance of the <see cref=""NativeTypeNameAttribute"" /> class.</summary>
/// <param name=""name"">The name of the type that was used in the native signature.</param>
public NativeTypeNameAttribute(string name)
{
_name = name;
}

/// <summary>Gets the name of the type that was used in the native signature.</summary>
public string Name => _name;
}

/// <summary>Defines the annotation found in a native declaration.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
[Conditional(""DEBUG"")]
internal sealed partial class NativeAnnotationAttribute : Attribute
{
private readonly string _annotation;

/// <summary>Initializes a new instance of the <see cref=""NativeAnnotationAttribute"" /> class.</summary>
/// <param name=""annotation"">The annotation that was used in the native declaration.</param>
public NativeAnnotationAttribute(string annotation)
{
_annotation = annotation;
}

/// <summary>Gets the annotation that was used in the native declaration.</summary>
public string Annotation => _annotation;
}
}
";

return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, additionalConfigOptions: PInvokeGeneratorConfigurationOptions.GenerateHelperTypes);
}

[Test]
public Task HelperTypesEmittedAfterMethodClassFileScoped()
{
var inputContents = @"struct SRC_DATA
{
const float *data_in;
long input_frames;
};

extern ""C"" void MyFunction();
";

var expectedOutputContents = @"using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ClangSharp.Test;

public unsafe partial struct SRC_DATA
{
[NativeTypeName(""const float *"")]
public float* data_in;

[NativeTypeName(""long"")]
public int input_frames;
}

public static partial class Methods
{
[DllImport(""ClangSharpPInvokeGenerator"", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern void MyFunction();
}

/// <summary>Defines the type of a member as it was used in the native signature.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false, Inherited = true)]
[Conditional(""DEBUG"")]
internal sealed partial class NativeTypeNameAttribute : Attribute
{
private readonly string _name;

/// <summary>Initializes a new instance of the <see cref=""NativeTypeNameAttribute"" /> class.</summary>
/// <param name=""name"">The name of the type that was used in the native signature.</param>
public NativeTypeNameAttribute(string name)
{
_name = name;
}

/// <summary>Gets the name of the type that was used in the native signature.</summary>
public string Name => _name;
}

/// <summary>Defines the annotation found in a native declaration.</summary>
[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
[Conditional(""DEBUG"")]
Expand Down
Loading