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
106 changes: 72 additions & 34 deletions sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,78 @@ private void ResolveAnonymousCursorName(NamedDecl namedDecl, ReadOnlySpan<char>
}
}

private static string GetNamespaceQualifiedNativeTypeName(Type type, string nativeTypeName)
{
// Peel pointer/reference/array layers so a stripped namespace is restored even for
// `Ns::Point *` and similar, where clang only ever drops the leading `Namespace::`.

var namedType = type;

while (true)
{
if (namedType is PointerType pointerType)
{
namedType = pointerType.PointeeType;
}
else if (namedType is ReferenceType referenceType)
{
namedType = referenceType.PointeeType;
}
else if (namedType is ArrayType arrayType)
{
namedType = arrayType.ElementType;
}
else if (namedType is AttributedType attributedType)
{
namedType = attributedType.ModifiedType;
}
else
{
break;
}
}

Decl? decl = namedType switch {
TagType tagType => tagType.Decl,
TypedefType typedefType => typedefType.Decl,
_ => null,
};

// A synthesized name for an anonymous decl carries no source namespace to restore.
if (decl is null || decl.Handle.IsAnonymous)
{
return nativeTypeName;
}

return GetNamespaceQualifiedNativeTypeName(decl, nativeTypeName);
}

private static string GetNamespaceQualifiedNativeTypeName(Decl decl, string nativeTypeName)
{
// clang 22's type printer omits the enclosing C++ namespace from a reference when a
// matching `using namespace` is in scope, where-as older releases always spelled it.
// Rebuild the dropped `Namespace::` prefix from the decl so the NativeTypeName keeps the
// fully qualified source spelling (e.g. `Gdiplus::Status`) and stays stable across versions.

var qualifierBuilder = new StringBuilder();

for (var declContext = decl.DeclContext; declContext is Decl parentDecl; declContext = parentDecl.DeclContext)
{
if (parentDecl is NamespaceDecl namespaceDecl && !string.IsNullOrEmpty(namespaceDecl.Name))
{
_ = qualifierBuilder.Insert(0, "::").Insert(0, namespaceDecl.Name);
}
}

if (qualifierBuilder.Length == 0)
{
return nativeTypeName;
}

var qualifier = qualifierBuilder.ToString();
return nativeTypeName.StartsWith(qualifier, StringComparison.Ordinal) ? nativeTypeName : qualifier + nativeTypeName;
}

private string GetCursorQualifiedName(NamedDecl namedDecl, bool truncateParameters = false)
{
if (!_cursorQualifiedNames.TryGetValue((namedDecl, truncateParameters), out var qualifiedName))
Expand Down Expand Up @@ -563,40 +635,6 @@ private string GetRemappedCursorName(NamedDecl namedDecl, out string nativeTypeN
return remappedName;
}

private string ApplyTagTypeNameOverrides(TagType tagType, string leafName)
{
// Keep a `--remap-type` override or a de-clashed nested record name (see
// GetRemappedCursorName) consistent between the type declaration and any reference to it,
// so both resolve to the same C# type.

if (_config._remappedTypeNames.Count != 0)
{
var remappedTypeNamesLookup = _config._remappedTypeNames.GetAlternateLookup<ReadOnlySpan<char>>();

if (remappedTypeNamesLookup.TryGetValue(leafName, out var remappedTypeName))
{
return remappedTypeName;
}
}

// A `--remap-type` takes precedence over prefix stripping, matching the declaration side.
leafName = StripTypePrefix(leafName);

if ((tagType.Decl is RecordDecl recordDecl) && TryDeclashRecordName(recordDecl, leafName, out var declashedName))
{
leafName = declashedName;
}

// Mirror the declaration side (see GetRemappedCursorName): a lowercase-only type name is
// `@`-escaped so the reference resolves to the same escaped C# type and compiles without CS8981.
if (IsLowercaseAsciiOnly(leafName))
{
leafName = $"@{leafName}";
}

return leafName;
}

private bool TryDeclashRecordName(RecordDecl recordDecl, string name, out string declashedName)
{
declashedName = name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,12 @@ private static bool IsType<T>(Cursor? cursor, Type type, [MaybeNullWhen(false)]
return IsType(cursor, usingType.Desugar, out value);
}
}
else if (type.IsSugared && (type.Desugar != type))
{
// A sugar type class we don't specifically handle (e.g. clang 22's
// PredefinedSugarType for `size_t`) still desugars to its underlying type.
return IsType(cursor, type.Desugar, out value);
}

value = default;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,33 +367,22 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type
result.typeName = GetAnonymousName(tagType.Decl, tagType.KindSpelling);
result.nativeTypeName = result.typeName;
}
else if (tagType.Handle.IsConstQualified)
else if (tagType.UnqualifiedType != tagType)
{
// The type carries a local qualifier (const, volatile, or an MS extension
// such as `__unaligned`) that clang folds into the spelling. Resolve the
// decl's own type so the qualifier keyword doesn't leak into the name.
result.typeName = GetTypeName(cursor, context, rootType, tagType.Decl.TypeForDecl, ignoreTransparentStructsWhereRequired, isTemplate, out _);
}
else
{
// The default name should be correct for C++, but C may have a prefix we need to strip
// Resolve the name through the decl so a reference matches its declaration exactly,
// including a `--remap` keyed by the fully qualified name (e.g. `Gdiplus.PointF`).
// clang's type printer may or may not spell the enclosing namespace depending on a
// `using namespace`, so keying off the decl's qualified name rather than the printed
// spelling keeps declarations and references in agreement.

if (result.typeName.StartsWith("enum ", StringComparison.Ordinal))
{
result.typeName = result.typeName[5..];
}
else if (result.typeName.StartsWith("struct ", StringComparison.Ordinal))
{
result.typeName = result.typeName[7..];
}
else if (result.typeName.StartsWith("union ", StringComparison.Ordinal))
{
result.typeName = result.typeName[6..];
}
}

if (result.typeName.Contains("::", StringComparison.Ordinal))
{
result.typeName = result.typeName.Split(s_doubleColonSeparator, StringSplitOptions.RemoveEmptyEntries).Last();
result.typeName = GetRemappedName(result.typeName, cursor, tryRemapOperatorName: false, out _, skipUsing: true);
result.typeName = ApplyTagTypeNameOverrides(tagType, result.typeName);
result.typeName = GetRemappedCursorName(tagType.Decl, 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
Expand All @@ -409,10 +398,6 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type

result.typeName = qualificationBuilder.Append(result.typeName).ToString();
}
else
{
result.typeName = ApplyTagTypeNameOverrides(tagType, result.typeName);
}
}
else if (type is TemplateSpecializationType templateSpecializationType)
{
Expand Down Expand Up @@ -550,6 +535,12 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type
{
result.typeName = EscapeName(GetRemappedCursorName(objCInterfaceType.Decl));
}
else if (type.IsSugared && (type.Desugar != type))
{
// A sugar type class we don't specifically handle (e.g. clang 22's
// PredefinedSugarType for `size_t`) still desugars to its underlying type.
result.typeName = GetTypeName(cursor, context, rootType, type.Desugar, ignoreTransparentStructsWhereRequired, isTemplate, out _);
}
else
{
AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported type: '{type.TypeClass}'. Falling back '{result.typeName}'.", cursor);
Expand All @@ -558,6 +549,12 @@ private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type
Debug.Assert(!string.IsNullOrWhiteSpace(result.typeName));
Debug.Assert(!string.IsNullOrWhiteSpace(result.nativeTypeName));

// clang 22's type printer omits the enclosing C++ namespace from a reference when the
// namespace is in scope (a `using namespace` or a reference from within the namespace),
// where-as older releases always spelled it. Restore the dropped `Namespace::` prefix from
// the referenced decl so the NativeTypeName keeps the fully qualified, version-stable spelling.
result.nativeTypeName = GetNamespaceQualifiedNativeTypeName(type, result.nativeTypeName);

if (IsNativeTypeNameEquivalent(result.nativeTypeName, result.typeName))
{
result.nativeTypeName = string.Empty;
Expand Down
1 change: 0 additions & 1 deletion sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public sealed partial class PInvokeGenerator : IDisposable

private static readonly Encoding s_defaultStreamWriterEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
private static readonly SearchValues<char> s_lowercaseAsciiLetters = SearchValues.Create("abcdefghijklmnopqrstuvwxyz");
private static readonly string[] s_doubleColonSeparator = ["::"];
private static readonly char[] s_doubleQuoteSeparator = ['"'];
private static readonly SearchValues<char> s_qualifiedNameSeparatorChars = SearchValues.Create(".:");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace ClangSharp.Test
{
public partial struct Point
{
[NativeTypeName("Ns::Real")]
public float X;

[NativeTypeName("Ns::Real")]
public float Y;
}

public enum Status
{
Err = -1,
Ok = 0,
}

public unsafe partial struct Holder
{
[NativeTypeName("Ns::Point")]
public Point point;

[NativeTypeName("Ns::Point *")]
public Point* pointPtr;

[NativeTypeName("Ns::Real")]
public float r;

[NativeTypeName("Ns::Status")]
public Status status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<bindings>
<namespace name="ClangSharp.Test">
<struct name="Point" access="public">
<field name="X" access="public">
<type native="Ns::Real">float</type>
</field>
<field name="Y" access="public">
<type native="Ns::Real">float</type>
</field>
</struct>
<enumeration name="Status" access="public">
<type>int</type>
<enumerator name="Err" access="public">
<type primitive="False">int</type>
<value>
<code>-1</code>
</value>
</enumerator>
<enumerator name="Ok" access="public">
<type primitive="False">int</type>
<value>
<code>0</code>
</value>
</enumerator>
</enumeration>
<struct name="Holder" access="public" unsafe="true">
<field name="point" access="public">
<type native="Ns::Point">Point</type>
</field>
<field name="pointPtr" access="public">
<type native="Ns::Point *">Point*</type>
</field>
<field name="r" access="public">
<type native="Ns::Real">float</type>
</field>
<field name="status" access="public">
<type native="Ns::Status">Status</type>
</field>
</struct>
</namespace>
</bindings>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace ClangSharp.Test
{
public partial struct NsPoint
{
public int X;

public int Y;
}

public enum NsStatus
{
Err = -1,
Ok = 0,
}

public unsafe partial struct Holder
{
[NativeTypeName("Ns::Point")]
public NsPoint point;

[NativeTypeName("Ns::Point *")]
public NsPoint* pointPtr;

[NativeTypeName("Ns::Status")]
public NsStatus status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<bindings>
<namespace name="ClangSharp.Test">
<struct name="NsPoint" access="public">
<field name="X" access="public">
<type>int</type>
</field>
<field name="Y" access="public">
<type>int</type>
</field>
</struct>
<enumeration name="NsStatus" access="public">
<type>int</type>
<enumerator name="Err" access="public">
<type primitive="False">int</type>
<value>
<code>-1</code>
</value>
</enumerator>
<enumerator name="Ok" access="public">
<type primitive="False">int</type>
<value>
<code>0</code>
</value>
</enumerator>
</enumeration>
<struct name="Holder" access="public" unsafe="true">
<field name="point" access="public">
<type native="Ns::Point">NsPoint</type>
</field>
<field name="pointPtr" access="public">
<type native="Ns::Point *">NsPoint*</type>
</field>
<field name="status" access="public">
<type native="Ns::Status">NsStatus</type>
</field>
</struct>
</namespace>
</bindings>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ClangSharp.Test
{
public static partial class Methods
{
[NativeTypeName("#define MY_INT_SIZE sizeof(int)")]
public const ulong MY_INT_SIZE = 4;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ClangSharp.Test
{
public static partial class Methods
{
[NativeTypeName("#define MY_INT_SIZE sizeof(int)")]
public const ulong MY_INT_SIZE = 4;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Runtime.InteropServices;

namespace ClangSharp.Test
{
public partial struct tagMETARECORD
{
public int rdSize;
}

public static unsafe partial class Methods
{
[DllImport("ClangSharpPInvokeGenerator", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?PlayMetaFileRecord@@YAXPEFAUtagMETARECORD@@@Z", ExactSpelling = true)]
public static extern void PlayMetaFileRecord([NativeTypeName("LPMETARECORD")] tagMETARECORD* lpMR);
}
}
Loading
Loading