diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs index f5e4d03c..0ec17839 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs @@ -388,6 +388,20 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type { result.typeName = result.typeName.Split(s_doubleColonSeparator, StringSplitOptions.RemoveEmptyEntries).Last(); result.typeName = GetRemappedName(result.typeName, cursor, tryRemapOperatorName: false, out _, skipUsing: true); + + // A nested type needs to be qualified by its containing type(s) so it resolves + // when referenced from another scope (e.g. `A::Inner` -> `A.Inner`). Namespaces + // are flattened away, so only walk the enclosing record decls. + + var qualificationBuilder = new StringBuilder(); + + for (var declContext = tagType.Decl.DeclContext; declContext is RecordDecl parentRecordDecl; declContext = parentRecordDecl.DeclContext) + { + var parentRecordDeclName = GetRemappedCursorName(parentRecordDecl, out _, skipUsing: true); + _ = qualificationBuilder.Insert(0, '.').Insert(0, EscapeName(parentRecordDeclName)); + } + + result.typeName = qualificationBuilder.Append(result.typeName).ToString(); } } else if (type is TemplateSpecializationType templateSpecializationType) diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/NestedTypeReferenceTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/NestedTypeReferenceTest.cs new file mode 100644 index 00000000..10688ab5 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/NestedTypeReferenceTest.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ClangSharp.UnitTests; + +/// +/// Regression test for https://github.com/dotnet/ClangSharp/issues/579. +/// A nested type referenced from another scope must be qualified by its containing type(s) +/// (e.g. A::Inner -> A.Inner) so it resolves in the generated C#. +/// +[Platform("win")] +public sealed class NestedTypeReferenceTest : PInvokeGeneratorTest +{ + [Test] + public Task NestedTypeIsQualified() + { + var inputContents = @"struct A +{ + struct Inner + { + int value; + }; +}; + +struct B +{ + A::Inner inner; +}; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public partial struct A + { + + public partial struct Inner + { + public int value; + } + } + + public partial struct B + { + [NativeTypeName(""A::Inner"")] + public A.Inner inner; + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents); + } +}