From 837b3312d39632f305b1d31cc2f1cc8713cc351c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 13 Jul 2026 23:11:30 -0700 Subject: [PATCH] Degrade gracefully on C++ pointer-to-member types Emit an opaque 'void*' with the original spelling preserved via NativeTypeName and a warning, instead of hard-erroring the whole run when a field has a member-pointer type. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PInvokeGenerator.TypeResolution.cs | 13 +++- .../DataMemberPointerTest.CSharp.cs | 12 ++++ .../DataMemberPointerTest.Xml.xml | 11 ++++ .../FunctionMemberPointerTest.CSharp.cs | 12 ++++ .../FunctionMemberPointerTest.Xml.xml | 11 ++++ .../Baseline/MemberPointerDeclarationTest.cs | 61 +++++++++++++++++++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.CSharp.cs create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.Xml.xml create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.CSharp.cs create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.Xml.xml create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/MemberPointerDeclarationTest.cs diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs index 8ba271f8..7e99481f 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs @@ -347,6 +347,17 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type result.nativeTypeName = $"{nativePointeeTypeName} &"; } } + else if (type is MemberPointerType) + { + // C++ pointers-to-members have no C# equivalent. Their size and layout are ABI-specific + // (a data member pointer is pointer-sized on the Itanium ABI, where-as a member function + // pointer is generally larger), so we emit an opaque pointer-sized `void*` and preserve the + // original spelling via the NativeTypeName. The layout may be incorrect for member function + // pointers, which the diagnostic calls out so a consumer can remap the field if needed. + + result.typeName = "void*"; + AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported type: '{type.TypeClass}'. Emitting an opaque 'void*'; the size and layout may be incorrect for member function pointers.", cursor); + } else if (type is SubstTemplateTypeParmType substTemplateTypeParmType) { result.typeName = GetTypeName(cursor, context, rootType, substTemplateTypeParmType.ReplacementType, ignoreTransparentStructsWhereRequired, isTemplate, out _); @@ -900,7 +911,7 @@ private void GetTypeSize(Cursor cursor, Type type, ref long alignment32, ref lon { GetTypeSize(cursor, enumType.Decl.IntegerType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); } - else if (type is FunctionType or PointerType or ReferenceType) + else if (type is FunctionType or PointerType or ReferenceType or MemberPointerType) { size32 = 4; size64 = 8; diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.CSharp.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.CSharp.cs new file mode 100644 index 00000000..fc5d83e8 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.CSharp.cs @@ -0,0 +1,12 @@ +namespace ClangSharp.Test +{ + public partial struct MyClass + { + } + + public unsafe partial struct MyStruct + { + [NativeTypeName("int MyClass::*")] + public void* field; + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.Xml.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.Xml.xml new file mode 100644 index 00000000..4a783953 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/DataMemberPointerTest.Xml.xml @@ -0,0 +1,11 @@ + + + + + + + void* + + + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.CSharp.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.CSharp.cs new file mode 100644 index 00000000..0b8cfb91 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.CSharp.cs @@ -0,0 +1,12 @@ +namespace ClangSharp.Test +{ + public partial struct MyClass + { + } + + public unsafe partial struct MyStruct + { + [NativeTypeName("void (MyClass::*)(int)")] + public void* callback; + } +} diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.Xml.xml b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.Xml.xml new file mode 100644 index 00000000..7f4fdbd2 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/Baselines/MemberPointerDeclaration/FunctionMemberPointerTest.Xml.xml @@ -0,0 +1,11 @@ + + + + + + + void* + + + + diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/MemberPointerDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/MemberPointerDeclarationTest.cs new file mode 100644 index 00000000..8199ff3b --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/MemberPointerDeclarationTest.cs @@ -0,0 +1,61 @@ +// 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.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ClangSharp.UnitTests.Baseline; + +[TestFixtureSource(nameof(Variants))] +public sealed class MemberPointerDeclarationTest : BaselineTest +{ + public MemberPointerDeclarationTest(BaselineVariant variant) : base(variant) + { + } + + protected override string Area => "MemberPointerDeclaration"; + + // C++ pointers-to-members have no C# equivalent, so the generator degrades to an opaque `void*` and a + // warning rather than hard-erroring the whole run (see dotnet/ClangSharp#511). Windows targets use the + // MSVC ABI, which additionally attaches an MSInheritance attribute to the referenced class. + private IReadOnlyList ExpectedDiagnostics(int column) + { + var memberPointer = new Diagnostic(DiagnosticLevel.Warning, "Unsupported type: 'CX_TypeClass_MemberPointer'. Emitting an opaque 'void*'; the size and layout may be incorrect for member function pointers.", $"Line 5, Column {column} in ClangUnsavedFile.h"); + + if (Variant.Os == BaselineOs.Windows) + { + var msInheritance = new Diagnostic(DiagnosticLevel.Warning, "Unsupported attribute: 'MSInheritance'. Generated bindings may be incomplete.", "Line 1, Column 8 in ClangUnsavedFile.h"); + return [msInheritance, memberPointer]; + } + + return [memberPointer]; + } + + [Test] + public Task DataMemberPointerTest() + { + var inputContents = @"struct MyClass; + +struct MyStruct +{ + int MyClass::* field; +}; +"; + + return ValidateAsync(nameof(DataMemberPointerTest), inputContents, expectedDiagnostics: ExpectedDiagnostics(column: 20)); + } + + [Test] + public Task FunctionMemberPointerTest() + { + var inputContents = @"struct MyClass; + +struct MyStruct +{ + void (MyClass::* callback)(int); +}; +"; + + return ValidateAsync(nameof(FunctionMemberPointerTest), inputContents, expectedDiagnostics: ExpectedDiagnostics(column: 22)); + } +}