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
46 changes: 45 additions & 1 deletion sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -682,10 +682,54 @@ private string GetRemappedNameForAnonymousRecord(RecordDecl recordDecl)
}
else
{
return $"_Anonymous{AnonymousTypeKindTag}{(recordDecl.IsUnion ? "Union" : "Struct")}";
return GetRemappedNameForFunctionLocalAnonymousRecord(recordDecl);
}
}

private string GetRemappedNameForFunctionLocalAnonymousRecord(RecordDecl recordDecl)
{
var kind = recordDecl.IsUnion ? "Union" : "Struct";

// A record declared in a function body is hoisted out to the enclosing type, so qualify it
// with the enclosing function's name; otherwise two functions that each declare an anonymous
// record would both hoist to `_Anonymous_e__Union` and clash.
var functionDecl = null as FunctionDecl;

for (var declContext = recordDecl.DeclContext; declContext is Decl decl; declContext = decl.DeclContext)
{
if (decl is FunctionDecl candidate)
{
functionDecl = candidate;
break;
}
}

if (functionDecl is null)
{
return $"_Anonymous{AnonymousTypeKindTag}{kind}";
}

var remappedNameBuilder = new StringBuilder();
_ = remappedNameBuilder.Append('_');
_ = remappedNameBuilder.Append(GetRemappedCursorName(functionDecl));

// Multiple anonymous records of the same kind in one function would still collide on the
// function-qualified name, so disambiguate them by their order within the function.
var sameKindRecords = functionDecl.Decls
.OfType<RecordDecl>()
.Where((other) => (other.IsUnion == recordDecl.IsUnion) && IsAnonymousRecord(GetCursorName(other)))
.ToList();

if (sameKindRecords.Count > 1)
{
_ = remappedNameBuilder.Append(sameKindRecords.IndexOf(recordDecl) + 1);
}

_ = remappedNameBuilder.Append(AnonymousTypeKindTag);
_ = remappedNameBuilder.Append(kind);
return remappedNameBuilder.ToString();
}

private string GetRemappedName(string name, Cursor? cursor, bool tryRemapOperatorName, out bool wasRemapped, bool skipUsing = false)
=> GetRemappedName(name, cursor, tryRemapOperatorName, out wasRemapped, skipUsing, skipUsingIfNotRemapped: skipUsing);

Expand Down
25 changes: 21 additions & 4 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1150,16 +1150,33 @@ private void VisitDeclStmt(DeclStmt declStmt)
{
var singleDecl = declStmt.SingleDecl;
Debug.Assert(singleDecl is not null);
Visit(singleDecl);

// A type declared inline (e.g. a struct/union used as a variable's type) has no C#
// equivalent inside a method body; it is hoisted to the enclosing type scope by
// VisitFunctionDecl, so it must not be emitted here.
if (singleDecl is not TypeDecl)
{
Visit(singleDecl);
}
}
else
{
Visit(declStmt.Decls[0]);
var isFirst = true;

foreach (var decl in declStmt.Decls.Skip(1))
foreach (var decl in declStmt.Decls)
{
outputBuilder.Write(", ");
if (decl is TypeDecl)
{
continue;
}

if (!isFirst)
{
outputBuilder.Write(", ");
}

Visit(decl);
isFirst = false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,21 @@ void ForDeclStmt(VarDecl varDecl, DeclStmt declStmt)
var name = GetRemappedCursorName(varDecl);
var escapedName = EscapeName(name);

if (varDecl == declStmt.Decls[0])
// The type prefix (`T` in `T a, b`) is written by the first value declarator. Inline type
// declarations (a struct/union defined as the variable's type) are skipped when emitting the
// DeclStmt, so key off the first non-type declaration rather than Decls[0].
var firstValueDecl = null as Decl;

foreach (var decl in declStmt.Decls)
{
if (decl is not TypeDecl)
{
firstValueDecl = decl;
break;
}
}

if (varDecl == firstValueDecl)
{
var type = varDecl.Type;
var typeName = GetRemappedTypeName(varDecl, context: null, type, out _);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Runtime.InteropServices;

namespace ClangSharp.Test
{
public static partial class Methods
{
public static int MyFunction1()
{
_MyFunction1_e__Union u = new _MyFunction1_e__Union();

u.a = 0;
return u.a;
}

[StructLayout(LayoutKind.Explicit)]
public partial struct _MyFunction1_e__Union
{
[FieldOffset(0)]
public int a;

[FieldOffset(0)]
public float b;
}

public static int MyFunction2()
{
_MyFunction2_e__Union u = new _MyFunction2_e__Union();

u.a = 0;
return u.a;
}

[StructLayout(LayoutKind.Explicit)]
public partial struct _MyFunction2_e__Union
{
[FieldOffset(0)]
public int a;

[FieldOffset(0)]
public float b;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<bindings>
<namespace name="ClangSharp.Test">
<class name="Methods" access="public" static="true">
<function name="MyFunction1" access="public" static="true">
<type>int</type>
<code>_MyFunction1_e__Union u = new _MyFunction1_e__Union();

u.a = 0;
return u.a;</code>
</function>
<struct name="_MyFunction1_e__Union" access="public" layout="Explicit">
<field name="a" access="public" offset="0">
<type>int</type>
</field>
<field name="b" access="public" offset="0">
<type>float</type>
</field>
</struct>
<function name="MyFunction2" access="public" static="true">
<type>int</type>
<code>_MyFunction2_e__Union u = new _MyFunction2_e__Union();

u.a = 0;
return u.a;</code>
</function>
<struct name="_MyFunction2_e__Union" access="public" layout="Explicit">
<field name="a" access="public" offset="0">
<type>int</type>
</field>
<field name="b" access="public" offset="0">
<type>float</type>
</field>
</struct>
</class>
</namespace>
</bindings>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Runtime.InteropServices;

namespace ClangSharp.Test
{
public partial struct MyStruct
{
public int x;
}

public static partial class Methods
{
public static int MyFunction()
{
_MyFunction_e__Union u = new _MyFunction_e__Union();

u.i = 0;
return u.s.x;
}

[StructLayout(LayoutKind.Explicit)]
public partial struct _MyFunction_e__Union
{
[FieldOffset(0)]
[NativeTypeName("struct MyStruct")]
public MyStruct s;

[FieldOffset(0)]
public int i;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<bindings>
<namespace name="ClangSharp.Test">
<struct name="MyStruct" access="public">
<field name="x" access="public">
<type>int</type>
</field>
</struct>
<class name="Methods" access="public" static="true">
<function name="MyFunction" access="public" static="true">
<type>int</type>
<code>_MyFunction_e__Union u = new _MyFunction_e__Union();

u.i = 0;
return u.s.x;</code>
</function>
<struct name="_MyFunction_e__Union" access="public" layout="Explicit">
<field name="s" access="public" offset="0">
<type native="struct MyStruct">MyStruct</type>
</field>
<field name="i" access="public" offset="0">
<type>int</type>
</field>
</struct>
</class>
</namespace>
</bindings>
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,25 @@ public Task UnaryOperatorPrefixTest(string opcode)
return ValidateAsync(nameof(UnaryOperatorPrefixTest), inputContents, discriminator: $"{opcode}");
}

[Test]
public Task NestedRecordDeclTest()
{
var inputContents = @"struct MyStruct
{
int x;
};

int MyFunction()
{
union { struct MyStruct s; int i; } u;
u.i = 0;
return u.s.x;
}
";

return ValidateAsync(nameof(NestedRecordDeclTest), inputContents);
}

[Test]
public Task ThisAsPointerTest()
{
Expand All @@ -899,6 +918,27 @@ int TestMethod() {
return ValidateAsync(nameof(ThisAsPointerTest), inputContents);
}

[Test]
public Task NestedRecordDeclCollisionTest()
{
var inputContents = @"int MyFunction1()
{
union { int a; float b; } u;
u.a = 0;
return u.a;
}

int MyFunction2()
{
union { int a; float b; } u;
u.a = 0;
return u.a;
}
";

return ValidateAsync(nameof(NestedRecordDeclCollisionTest), inputContents);
}

[Test]
public Task WhileTest()
{
Expand Down
Loading