From 9fad04596674b4e73b15f65b22cab30fc2140584 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 13 Jul 2026 01:13:07 -0700 Subject: [PATCH 1/2] Fix incorrect expectedCursorKind for UsingEnumDecl libClang surfaces a `using enum` declaration with `cursor.kind == CXCursor_EnumDecl`, so passing `CXCursor_UnexposedDecl` to the base `Cursor` ctor caused an `ArgumentOutOfRangeException` when walking any decl tree that contained a `using enum`. The deeper `EnumDecl` accessor issue (`Handle.Definition` surfaces as an empty `CXCursor_OverloadedDeclRef`) is not resolvable against the prebuilt native lib and needs a native `libClangSharp` shim wrapping `UsingEnumDecl::getEnumDecl()`; that is left for a follow-up and documented in the code. Fixes #633 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../ClangSharp/Cursors/Decls/UsingEnumDecl.cs | 7 ++++- .../CursorTests/DeclTest.cs | 27 +++++++++++++++++++ .../TranslationUnitTest.cs | 4 +-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs b/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs index dba535cca..75d849c5f 100644 --- a/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs +++ b/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs @@ -10,7 +10,9 @@ public sealed class UsingEnumDecl : BaseUsingDecl, IMergeable { private ValueLazy _enumDecl; - internal unsafe UsingEnumDecl(CXCursor handle) : base(handle, CXCursor_UnexposedDecl, CX_DeclKind_UsingEnum) + // libClang surfaces a `using enum` declaration with `cursor.kind == CXCursor_EnumDecl`, even + // though its `DeclKind` is `CX_DeclKind_UsingEnum`, so that is the expected cursor kind here. + internal unsafe UsingEnumDecl(CXCursor handle) : base(handle, CXCursor_EnumDecl, CX_DeclKind_UsingEnum) { _enumDecl = new ValueLazy(&EnumDeclFactory); } @@ -19,5 +21,8 @@ internal unsafe UsingEnumDecl(CXCursor handle) : base(handle, CXCursor_Unexposed public EnumDecl EnumDecl => _enumDecl.GetValue(this); + // TODO: `Handle.Definition` does not resolve to the referenced `EnumDecl` for a `using enum` + // (it surfaces as an empty `CXCursor_OverloadedDeclRef`); correctly resolving it requires a + // native libClangSharp shim wrapping `UsingEnumDecl::getEnumDecl()`. See dotnet/clangsharp #633. private static unsafe EnumDecl EnumDeclFactory(UsingEnumDecl self) => self.TranslationUnit.GetOrCreate(self.Handle.Definition); } diff --git a/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs b/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs index b9f259592..42407f35a 100644 --- a/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs +++ b/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs @@ -144,6 +144,33 @@ public void VarTemplateSpecializationArgsTest() Assert.That(varTemplateSpecializationDecl.TemplateArgs[1].AsType.AsString, Is.EqualTo("float")); } + [Test] + public void UsingEnumDeclTest() + { + // `using enum` requires C++20 and previously threw because UsingEnumDecl passed the wrong + // expectedCursorKind to the base Cursor ctor (libClang surfaces it as CXCursor_EnumDecl). + var inputContents = """ +enum class E { A, B }; + +struct S { + using enum E; +}; +"""; + + string[] commandLineArgs = ["-std=c++20", "-Wno-pragma-once-outside-header"]; + using var translationUnit = CreateTranslationUnit(inputContents, commandLineArgs: commandLineArgs); + + var structDecl = translationUnit.TranslationUnitDecl.Decls.OfType().Single((recordDecl) => recordDecl.Name.Equals("S", StringComparison.Ordinal)); + var usingEnumDecl = structDecl.Decls.OfType().Single(); + + Assert.That(usingEnumDecl.Handle.kind, Is.EqualTo(CXCursorKind.CXCursor_EnumDecl)); + Assert.That(usingEnumDecl.Handle.DeclKind, Is.EqualTo(CX_DeclKind.CX_DeclKind_UsingEnum)); + + // NOTE: usingEnumDecl.EnumDecl is not asserted here. Handle.Definition does not resolve to + // the referenced EnumDecl in the current native lib; a correct fix needs a native + // libClangSharp shim wrapping UsingEnumDecl::getEnumDecl(). See dotnet/clangsharp #633. + } + [Test] public void IsPodTest() { diff --git a/tests/ClangSharp.UnitTests/TranslationUnitTest.cs b/tests/ClangSharp.UnitTests/TranslationUnitTest.cs index 806d4999f..61d0f9bb9 100644 --- a/tests/ClangSharp.UnitTests/TranslationUnitTest.cs +++ b/tests/ClangSharp.UnitTests/TranslationUnitTest.cs @@ -24,7 +24,7 @@ public abstract class TranslationUnitTest "-Wno-pragma-once-outside-header" // We are processing files which may be header files ]; - protected static TranslationUnit CreateTranslationUnit(string inputContents, string language = "c++" /* input files are C++ by default */) + protected static TranslationUnit CreateTranslationUnit(string inputContents, string language = "c++" /* input files are C++ by default */, string[]? commandLineArgs = null) { Assert.That(DefaultInputFileName, Does.Exist); @@ -32,7 +32,7 @@ public abstract class TranslationUnitTest var unsavedFiles = new CXUnsavedFile[] { unsavedFile }; var index = CXIndex.Create(); - var arguments = new List(DefaultClangCommandLineArgs) + var arguments = new List(commandLineArgs ?? DefaultClangCommandLineArgs) { "-x", language, From 0bfb581821e8845d163530878cd1632d86db8155 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Mon, 13 Jul 2026 07:56:51 -0700 Subject: [PATCH 2/2] Resolve UsingEnumDecl.EnumDecl via a native getEnumDecl shim `Handle.Definition` surfaces as an empty `CXCursor_OverloadedDeclRef` for a `using enum`, so the `EnumDecl` accessor could not resolve the referenced enum. Add a native `clangsharp_Cursor_getUsingEnumDeclEnumDecl` shim wrapping clang's `UsingEnumDecl::getEnumDecl()`, expose the matching interop, and route `EnumDeclFactory` through it instead of `Handle.Definition`. The native shim cannot be built or tested in this environment; the pinned 21.1 prebuilt `libClangSharp` package lacks the new entry point, so the added `UsingEnumDeclEnumDeclTest` is guarded by `SkipUntilNativeRebuild` and stays skipped until the native lib is rebuilt for a newer libClang. Validating this change requires that native rebuild. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../ClangSharp.Interop/Extensions/CXCursor.cs | 2 ++ .../clangsharp/clangsharp.cs | 3 ++ .../ClangSharp/Cursors/Decls/UsingEnumDecl.cs | 5 +-- sources/libClangSharp/ClangSharp.cpp | 12 +++++++ sources/libClangSharp/ClangSharp.h | 2 ++ .../CursorTests/DeclTest.cs | 35 +++++++++++++++---- 6 files changed, 49 insertions(+), 10 deletions(-) diff --git a/sources/ClangSharp.Interop/Extensions/CXCursor.cs b/sources/ClangSharp.Interop/Extensions/CXCursor.cs index fac983ddb..c786e29aa 100644 --- a/sources/ClangSharp.Interop/Extensions/CXCursor.cs +++ b/sources/ClangSharp.Interop/Extensions/CXCursor.cs @@ -1785,6 +1785,8 @@ public readonly CXString UnaryOperatorKindSpelling public readonly CXCursor UsedContext => clangsharp.Cursor_getUsedContext(this); + public readonly CXCursor UsingEnumDeclEnumDecl => clangsharp.Cursor_getUsingEnumDeclEnumDecl(this); + public readonly CXString Usr => clang.getCursorUSR(this); public readonly CXVisibilityKind Visibility => clang.getCursorVisibility(this); diff --git a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs index 6c61f4003..909e8b637 100644 --- a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs +++ b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs @@ -931,6 +931,9 @@ public static unsafe partial class @clangsharp [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getUsedContext", ExactSpelling = true)] public static extern CXCursor Cursor_getUsedContext(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getUsingEnumDeclEnumDecl", ExactSpelling = true)] + public static extern CXCursor Cursor_getUsingEnumDeclEnumDecl(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getTypeParamHasExplicitBound", ExactSpelling = true)] [return: NativeTypeName("unsigned int")] public static extern uint Cursor_getTypeParamHasExplicitBound(CXCursor C); diff --git a/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs b/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs index 75d849c5f..b021d64c3 100644 --- a/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs +++ b/sources/ClangSharp/Cursors/Decls/UsingEnumDecl.cs @@ -21,8 +21,5 @@ internal unsafe UsingEnumDecl(CXCursor handle) : base(handle, CXCursor_EnumDecl, public EnumDecl EnumDecl => _enumDecl.GetValue(this); - // TODO: `Handle.Definition` does not resolve to the referenced `EnumDecl` for a `using enum` - // (it surfaces as an empty `CXCursor_OverloadedDeclRef`); correctly resolving it requires a - // native libClangSharp shim wrapping `UsingEnumDecl::getEnumDecl()`. See dotnet/clangsharp #633. - private static unsafe EnumDecl EnumDeclFactory(UsingEnumDecl self) => self.TranslationUnit.GetOrCreate(self.Handle.Definition); + private static unsafe EnumDecl EnumDeclFactory(UsingEnumDecl self) => self.TranslationUnit.GetOrCreate(self.Handle.UsingEnumDeclEnumDecl); } diff --git a/sources/libClangSharp/ClangSharp.cpp b/sources/libClangSharp/ClangSharp.cpp index 3576f584a..d64bdc236 100644 --- a/sources/libClangSharp/ClangSharp.cpp +++ b/sources/libClangSharp/ClangSharp.cpp @@ -5049,6 +5049,18 @@ CXCursor clangsharp_Cursor_getUsedContext(CXCursor C) { return clang_getNullCursor(); } +CXCursor clangsharp_Cursor_getUsingEnumDeclEnumDecl(CXCursor C) { + if (isDeclOrTU(C.kind)) { + const Decl* D = getCursorDecl(C); + + if (const UsingEnumDecl* UED = dyn_cast(D)) { + return MakeCXCursor(UED->getEnumDecl(), getCursorTU(C)); + } + } + + return clang_getNullCursor(); +} + CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getTypeParamHasExplicitBound(CXCursor C) { if (isDeclOrTU(C.kind)) { diff --git a/sources/libClangSharp/ClangSharp.h b/sources/libClangSharp/ClangSharp.h index 51feab3d9..8d1484084 100644 --- a/sources/libClangSharp/ClangSharp.h +++ b/sources/libClangSharp/ClangSharp.h @@ -773,6 +773,8 @@ CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getUninstantiatedDefaultArg(CXCurs CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getUsedContext(CXCursor C); +CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getUsingEnumDeclEnumDecl(CXCursor C); + CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getTypeParamHasExplicitBound(CXCursor C); CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getTypeParamVariance(CXCursor C); diff --git a/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs b/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs index 42407f35a..324a6c11a 100644 --- a/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs +++ b/tests/ClangSharp.UnitTests/CursorTests/DeclTest.cs @@ -165,10 +165,32 @@ struct S { Assert.That(usingEnumDecl.Handle.kind, Is.EqualTo(CXCursorKind.CXCursor_EnumDecl)); Assert.That(usingEnumDecl.Handle.DeclKind, Is.EqualTo(CX_DeclKind.CX_DeclKind_UsingEnum)); + } + + [Test] + public void UsingEnumDeclEnumDeclTest() + { + // Resolving UsingEnumDecl.EnumDecl relies on the native clangsharp_Cursor_getUsingEnumDeclEnumDecl + // shim, which the pinned 21.1 prebuilt package predates; skip until the native lib is rebuilt. + SkipUntilNativeRebuild(); + + var inputContents = """ +enum class E { A, B }; + +struct S { + using enum E; +}; +"""; + + string[] commandLineArgs = ["-std=c++20", "-Wno-pragma-once-outside-header"]; + using var translationUnit = CreateTranslationUnit(inputContents, commandLineArgs: commandLineArgs); + + var structDecl = translationUnit.TranslationUnitDecl.Decls.OfType().Single((recordDecl) => recordDecl.Name.Equals("S", StringComparison.Ordinal)); + var usingEnumDecl = structDecl.Decls.OfType().Single(); - // NOTE: usingEnumDecl.EnumDecl is not asserted here. Handle.Definition does not resolve to - // the referenced EnumDecl in the current native lib; a correct fix needs a native - // libClangSharp shim wrapping UsingEnumDecl::getEnumDecl(). See dotnet/clangsharp #633. + var enumDecl = usingEnumDecl.EnumDecl; + Assert.That(enumDecl, Is.Not.Null); + Assert.That(enumDecl.Name, Is.EqualTo("E")); } [Test] @@ -295,9 +317,10 @@ enum E { }); } - // The fix for these lives in the native libClangSharp shim (clangsharp_Cursor_getNumTemplateArguments - // and clangsharp_Cursor_getTemplateArgument). The pinned 21.1 prebuilt native package predates it, so - // skip until the native lib is rebuilt for a newer libClang. Rebuilding off 21.1 auto-unskips these. + // Some tests depend on native libClangSharp shims that the pinned 21.1 prebuilt package predates + // (e.g. clangsharp_Cursor_getNumTemplateArguments / getTemplateArgument and + // clangsharp_Cursor_getUsingEnumDeclEnumDecl). Skip those until the native lib is rebuilt for a + // newer libClang. Rebuilding off 21.1 auto-unskips them. private static void SkipUntilNativeRebuild() { using var versionString = clang.getClangVersion();