Skip to content

Commit cbb7968

Browse files
Merge pull request #587 from dpaoliello/deeplynested
Fixes name generation for nested anonymous types
2 parents c245e16 + ad49dcb commit cbb7968

37 files changed

Lines changed: 2455 additions & 502 deletions

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ private void VisitIndirectFieldDecl(IndirectFieldDecl indirectFieldDecl)
865865
return;
866866
}
867867

868-
if (IsPrevContextDecl<RecordDecl>(out var prevContext, out _) && prevContext.IsAnonymousStructOrUnion)
868+
if (IsPrevContextDecl<RecordDecl>(out var prevContext, out _) && prevContext.IsAnonymous)
869869
{
870870
// We shouldn't process indirect fields where the prev context is an anonymous record decl
871871
return;
@@ -880,28 +880,15 @@ private void VisitIndirectFieldDecl(IndirectFieldDecl indirectFieldDecl)
880880
var contextNameParts = new Stack<string>();
881881
var contextTypeParts = new Stack<string>();
882882

883-
while (rootRecordDecl.IsAnonymousStructOrUnion && (rootRecordDecl.Parent is RecordDecl parentRecordDecl))
883+
while (rootRecordDecl.IsAnonymous && (rootRecordDecl.Parent is RecordDecl parentRecordDecl))
884884
{
885+
// The name of a field of an anonymous type should be same as the type's name minus the
886+
// type kind tag at the end and the leading `_`.
885887
var contextNamePart = GetRemappedCursorName(rootRecordDecl);
886-
887-
if (contextNamePart.StartsWith('_'))
888-
{
889-
var suffixLength = 0;
890-
891-
if (contextNamePart.EndsWith("_e__Union", StringComparison.Ordinal))
892-
{
893-
suffixLength = 10;
894-
}
895-
else if (contextNamePart.EndsWith("_e__Struct", StringComparison.Ordinal))
896-
{
897-
suffixLength = 11;
898-
}
899-
900-
if (suffixLength != 0)
901-
{
902-
contextNamePart = contextNamePart.Substring(1, contextNamePart.Length - suffixLength);
903-
}
904-
}
888+
var tagIndex = contextNamePart.LastIndexOf("_e__", StringComparison.Ordinal);
889+
Debug.Assert(contextNamePart[0] == '_');
890+
Debug.Assert(tagIndex >= 0);
891+
contextNamePart = contextNamePart.Substring(1, tagIndex - 1);
905892

906893
contextNameParts.Push(EscapeName(contextNamePart));
907894

@@ -1397,7 +1384,7 @@ private void VisitRecordDecl(RecordDecl recordDecl)
13971384
var maxAlignm = recordDecl.Fields.Any() ? recordDecl.Fields.Max((fieldDecl) => Math.Max(fieldDecl.Type.Handle.AlignOf, 1)) : alignment;
13981385

13991386
var isTopLevelStruct = _config.WithTypes.TryGetValue(name, out var withType) && withType.Equals("struct", StringComparison.Ordinal);
1400-
var generateTestsClass = !recordDecl.IsAnonymousStructOrUnion && recordDecl.DeclContext is not RecordDecl;
1387+
var generateTestsClass = !recordDecl.IsAnonymous && recordDecl.DeclContext is not RecordDecl;
14011388
var testOutputStarted = false;
14021389

14031390
var nullableUuid = (Guid?)null;

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs

Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3166,9 +3166,19 @@ private string GetRemappedCursorName(NamedDecl namedDecl, out string nativeTypeN
31663166
{
31673167
remappedName = "Dispose";
31683168
}
3169-
else if (namedDecl is FieldDecl fieldDecl)
3169+
else if ((namedDecl is FieldDecl fieldDecl) && name.StartsWith("__AnonymousFieldDecl_", StringComparison.Ordinal))
31703170
{
3171-
if (name.StartsWith("__AnonymousFieldDecl_", StringComparison.Ordinal))
3171+
if (fieldDecl.Type.AsCXXRecordDecl?.IsAnonymous == true)
3172+
{
3173+
// For fields of anonymous types, use the name of the type but clean off the type
3174+
// kind tag at the end.
3175+
var typeName = GetRemappedNameForAnonymousRecord(fieldDecl.Type.AsCXXRecordDecl);
3176+
var tagIndex = typeName.LastIndexOf("_e__", StringComparison.Ordinal);
3177+
Debug.Assert(typeName[0] == '_');
3178+
Debug.Assert(tagIndex >= 0);
3179+
remappedName = typeName.Substring(1, tagIndex - 1);
3180+
}
3181+
else
31723182
{
31733183
remappedName = "Anonymous";
31743184

@@ -3184,28 +3194,62 @@ private string GetRemappedCursorName(NamedDecl namedDecl, out string nativeTypeN
31843194
}
31853195
else if ((namedDecl is RecordDecl recordDecl) && name.StartsWith("__AnonymousRecord_", StringComparison.Ordinal))
31863196
{
3187-
if (recordDecl.Parent is RecordDecl parentRecordDecl)
3188-
{
3189-
remappedName = "_Anonymous";
3197+
remappedName = GetRemappedNameForAnonymousRecord(recordDecl);
3198+
}
31903199

3191-
var matchingField = parentRecordDecl.Fields.Where((fieldDecl) => fieldDecl.Type.CanonicalType == recordDecl.TypeForDecl.CanonicalType).FirstOrDefault();
3200+
return remappedName;
3201+
}
31923202

3193-
if (matchingField is not null)
3194-
{
3195-
remappedName = "_";
3196-
remappedName += GetRemappedCursorName(matchingField);
3197-
}
3198-
else if (parentRecordDecl.AnonymousRecords.Count > 1)
3203+
private string GetRemappedNameForAnonymousRecord(RecordDecl recordDecl)
3204+
{
3205+
if (recordDecl.Parent is RecordDecl parentRecordDecl)
3206+
{
3207+
var remappedNameBuilder = new StringBuilder();
3208+
var matchingField = parentRecordDecl.Fields.Where((fieldDecl) => fieldDecl.Type.CanonicalType == recordDecl.TypeForDecl.CanonicalType).FirstOrDefault();
3209+
3210+
if ((matchingField is not null) && !matchingField.IsAnonymousField)
3211+
{
3212+
_ = remappedNameBuilder.Append('_');
3213+
_ = remappedNameBuilder.Append(GetRemappedCursorName(matchingField));
3214+
}
3215+
else
3216+
{
3217+
_ = remappedNameBuilder.Append("_Anonymous");
3218+
3219+
// If there is more than one anonymous type, then add a numeral to differentiate.
3220+
if (parentRecordDecl.AnonymousRecords.Count > 1)
31993221
{
32003222
var index = parentRecordDecl.AnonymousRecords.IndexOf(recordDecl) + 1;
3201-
remappedName += index.ToString(CultureInfo.InvariantCulture);
3223+
Debug.Assert(index > 0);
3224+
_ = remappedNameBuilder.Append(index);
32023225
}
32033226

3204-
remappedName += $"_e__{(recordDecl.IsUnion ? "Union" : "Struct")}";
3227+
// C# doesn't allow a nested type to have the same name as the parent, so if the
3228+
// parent is also anonymous, add the nesting depth as a way to avoid conflicts with
3229+
// the parent's name.
3230+
if (parentRecordDecl.IsAnonymous)
3231+
{
3232+
var depth = 1;
3233+
var currentParent = parentRecordDecl.Parent;
3234+
while ((currentParent is RecordDecl currentParentRecordDecl) && currentParentRecordDecl.IsAnonymous)
3235+
{
3236+
depth++;
3237+
currentParent = currentParentRecordDecl.Parent;
3238+
}
3239+
_ = remappedNameBuilder.Append('_');
3240+
_ = remappedNameBuilder.Append(depth);
3241+
}
32053242
}
3206-
}
32073243

3208-
return remappedName;
3244+
// Add the type kind tag.
3245+
_ = remappedNameBuilder.Append("_e__");
3246+
_ = remappedNameBuilder.Append(recordDecl.IsUnion ? "Union" : "Struct");
3247+
return remappedNameBuilder.ToString();
3248+
}
3249+
else
3250+
{
3251+
return $"_Anonymous_e__{(recordDecl.IsUnion ? "Union" : "Struct")}";
3252+
}
32093253
}
32103254

32113255
private string GetRemappedName(string name, Cursor? cursor, bool tryRemapOperatorName, out bool wasRemapped, bool skipUsing = false)
@@ -3304,48 +3348,7 @@ private string GetRemappedTypeName(Cursor? cursor, Cursor? context, Type type, o
33043348
if (IsType<RecordType>(cursor, type, out var recordType) && remappedName.StartsWith("__AnonymousRecord_", StringComparison.Ordinal))
33053349
{
33063350
var recordDecl = recordType.Decl;
3307-
remappedName = "_Anonymous";
3308-
3309-
if (recordDecl.Parent is RecordDecl parentRecordDecl)
3310-
{
3311-
var matchingField = parentRecordDecl.Fields.Where((fieldDecl) => fieldDecl.Type.CanonicalType == recordType).FirstOrDefault();
3312-
3313-
if (matchingField is not null)
3314-
{
3315-
remappedName = "_";
3316-
remappedName += GetRemappedCursorName(matchingField);
3317-
}
3318-
else
3319-
{
3320-
var index = 0;
3321-
3322-
if (parentRecordDecl.AnonymousRecords.Count > 1)
3323-
{
3324-
index = parentRecordDecl.AnonymousRecords.IndexOf(cursor) + 1;
3325-
}
3326-
3327-
while (parentRecordDecl.IsAnonymousStructOrUnion && (parentRecordDecl.IsUnion == recordType.Decl.IsUnion))
3328-
{
3329-
index += 1;
3330-
3331-
if (parentRecordDecl.Parent is RecordDecl parentRecordDeclParent)
3332-
{
3333-
if (parentRecordDeclParent.AnonymousRecords.Count > 0)
3334-
{
3335-
index += parentRecordDeclParent.AnonymousRecords.Count - 1;
3336-
}
3337-
parentRecordDecl = parentRecordDeclParent;
3338-
}
3339-
}
3340-
3341-
if (index != 0)
3342-
{
3343-
remappedName += index.ToString(CultureInfo.InvariantCulture);
3344-
}
3345-
}
3346-
}
3347-
3348-
remappedName += $"_e__{(recordDecl.IsUnion ? "Union" : "Struct")}";
3351+
remappedName = GetRemappedNameForAnonymousRecord(recordDecl);
33493352
}
33503353
else if (IsType<EnumType>(cursor, type, out var enumType) && remappedName.StartsWith("__AnonymousEnum_", StringComparison.Ordinal))
33513354
{
@@ -4572,7 +4575,7 @@ private bool HasBaseField(CXXRecordDecl cxxRecordDecl)
45724575

45734576
private bool HasField(RecordDecl recordDecl)
45744577
{
4575-
var hasField = recordDecl.Fields.Any() || recordDecl.Decls.Any((decl) => (decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && HasField(nestedRecordDecl));
4578+
var hasField = recordDecl.Fields.Any() || recordDecl.Decls.Any((decl) => (decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymous && HasField(nestedRecordDecl));
45764579

45774580
if (!hasField && (recordDecl is CXXRecordDecl cxxRecordDecl))
45784581
{
@@ -5123,7 +5126,7 @@ bool IsEmptyRecord(RecordDecl recordDecl)
51235126

51245127
foreach (var decl in recordDecl.Decls)
51255128
{
5126-
if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && !IsEmptyRecord(nestedRecordDecl))
5129+
if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymous && !IsEmptyRecord(nestedRecordDecl))
51275130
{
51285131
return false;
51295132
}
@@ -6150,7 +6153,7 @@ private bool IsUnsafe(RecordDecl recordDecl)
61506153
{
61516154
return true;
61526155
}
6153-
else if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && (IsUnsafe(nestedRecordDecl) || Config.GenerateCompatibleCode))
6156+
else if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymous && (IsUnsafe(nestedRecordDecl) || Config.GenerateCompatibleCode))
61546157
{
61556158
return true;
61566159
}

sources/ClangSharp/Cursors/Decls/RecordDecl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ private protected RecordDecl(CXCursor handle, CXCursorKind expectedCursorKind, C
4848
});
4949

5050
_anonymousFields = new Lazy<IReadOnlyList<FieldDecl>>(() => Decls.OfType<FieldDecl>().Where(decl => decl.IsAnonymousField).ToList());
51-
_anonymousRecords = new Lazy<IReadOnlyList<RecordDecl>>(() => Decls.OfType<RecordDecl>().Where(decl => decl.IsAnonymousStructOrUnion && !decl.IsInjectedClassName).ToList());
51+
_anonymousRecords = new Lazy<IReadOnlyList<RecordDecl>>(() => Decls.OfType<RecordDecl>().Where(decl => decl.IsAnonymous && !decl.IsInjectedClassName).ToList());
5252
_indirectFields = new Lazy<IReadOnlyList<IndirectFieldDecl>>(() => Decls.OfType<IndirectFieldDecl>().ToList());
5353
_injectedClassName = new Lazy<RecordDecl?>(() => Decls.OfType<RecordDecl>().Where(decl => decl.IsInjectedClassName).SingleOrDefault());
5454
}
5555

56-
public bool IsAnonymousStructOrUnion => Handle.IsAnonymousStructOrUnion;
56+
public bool IsAnonymous => Handle.IsAnonymous;
5757

5858
public IReadOnlyList<FieldDecl> AnonymousFields => _anonymousFields.Value;
5959

tests/ClangSharp.PInvokeGenerator.UnitTests/Base/StructDeclarationTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,21 @@ public abstract class StructDeclarationTest : PInvokeGeneratorTest
215215
[Test]
216216
public Task SourceLocationAttributeTest() => SourceLocationAttributeTestImpl();
217217

218+
// Regression test: the code that generated the name for anonymous types had a couple of
219+
// bugs:
220+
// * It would only append a numeral if the parent had more than one anonymous records,
221+
// but an anonymous typed array didn't count as a record resulting in multiple structs
222+
// named `_Anonymous_e__Struct`.
223+
// * The code that remapped the name of anonymous types was different between the code that
224+
// generated the type definition and the code that defined fields in a FixedBuffer.
225+
[Test]
226+
public Task AnonStructAndAnonStructArray() => AnonStructAndAnonStructArrayImpl();
227+
228+
// Regression test: nested anonymous structs would result in:
229+
// error CS0542: '_Anonymous_e__Struct': member names cannot be the same as their enclosing type
230+
[Test]
231+
public Task DeeplyNestedAnonStructs() => DeeplyNestedAnonStructsImpl();
232+
218233
protected abstract Task IncompleteArraySizeTestImpl(string nativeType, string expectedManagedType);
219234

220235
protected abstract Task BasicTestImpl(string nativeType, string expectedManagedType);
@@ -290,4 +305,8 @@ public abstract class StructDeclarationTest : PInvokeGeneratorTest
290305
protected abstract Task WithPackingTestImpl();
291306

292307
protected abstract Task SourceLocationAttributeTestImpl();
308+
309+
protected abstract Task AnonStructAndAnonStructArrayImpl();
310+
311+
protected abstract Task DeeplyNestedAnonStructsImpl();
293312
}

0 commit comments

Comments
 (0)