diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs index 018e9798..3f74cd2b 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs @@ -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() + .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); diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs index 474fe01f..6a164458 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs @@ -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; } } diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs index f5bc631a..c1cf254b 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitVarDecl.cs @@ -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 _); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclCollisionTest.CSharp.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclCollisionTest.CSharp.cs new file mode 100644 index 00000000..39a1f8b5 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclCollisionTest.CSharp.cs @@ -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; + } + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclCollisionTest.Xml.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclCollisionTest.Xml.xml new file mode 100644 index 00000000..abd81f19 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclCollisionTest.Xml.xml @@ -0,0 +1,37 @@ + + + + + + int + _MyFunction1_e__Union u = new _MyFunction1_e__Union(); + + u.a = 0; + return u.a; + + + + int + + + float + + + + int + _MyFunction2_e__Union u = new _MyFunction2_e__Union(); + + u.a = 0; + return u.a; + + + + int + + + float + + + + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclTest.CSharp.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclTest.CSharp.cs new file mode 100644 index 00000000..0d18add2 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclTest.CSharp.cs @@ -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; + } + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclTest.Xml.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclTest.Xml.xml new file mode 100644 index 00000000..3f096b0d --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/FunctionDeclarationBodyImport/NestedRecordDeclTest.Xml.xml @@ -0,0 +1,27 @@ + + + + + + int + + + + + int + _MyFunction_e__Union u = new _MyFunction_e__Union(); + + u.i = 0; + return u.s.x; + + + + MyStruct + + + int + + + + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/FunctionDeclarationBodyImportTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/FunctionDeclarationBodyImportTest.cs index ab2ffd93..88081875 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/FunctionDeclarationBodyImportTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/FunctionDeclarationBodyImportTest.cs @@ -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() { @@ -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() {