From 00cc8981d8bf37042fc22d61e8c214a7ce1b9805 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sun, 12 Jul 2026 22:23:41 -0700 Subject: [PATCH] Strip enum member type name from sibling references in initializers When strip-enum-member-type-name is enabled, only the member declarations were stripped; references to sibling members inside an initializer expression kept the enum type prefix, producing code that referenced undefined names. Apply the same EscapeAndStripEnumMemberName transform to enum constant references so they match the stripped declarations. Fixes #576 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../PInvokeGenerator.VisitStmt.cs | 10 +++++ .../StripEnumMemberReferenceTest.cs | 45 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/ClangSharp.PInvokeGenerator.UnitTests/StripEnumMemberReferenceTest.cs diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs index 43e4fce9e..3bdeedc8a 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs @@ -1020,6 +1020,16 @@ private void VisitDeclRefExpr(DeclRefExpr declRefExpr) if (declRefExpr.Decl is EnumConstantDecl enumConstantDecl) { + if (enumConstantDecl.DeclContext is NamedDecl enumTypeDecl) + { + var enumTypeName = GetRemappedCursorName(enumTypeDecl, out _, skipUsing: true); + + if (!IsAnonymousEnum(enumTypeName)) + { + escapedName = EscapeAndStripEnumMemberName(name, enumTypeName); + } + } + if ((declRefExpr.DeclContext != enumConstantDecl.DeclContext) && (enumConstantDecl.DeclContext is NamedDecl namedDecl)) { var enumName = GetRemappedCursorName(namedDecl, out _, skipUsing: true); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/StripEnumMemberReferenceTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/StripEnumMemberReferenceTest.cs new file mode 100644 index 000000000..47b87ad85 --- /dev/null +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/StripEnumMemberReferenceTest.cs @@ -0,0 +1,45 @@ +// 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/576. +/// When strip-enum-member-type-name is used, references to sibling enum members inside +/// an initializer expression must be stripped the same way as the member declarations, otherwise +/// the emitted code references undefined names. +/// +[Platform("win")] +public sealed class StripEnumMemberReferenceTest : PInvokeGeneratorTest +{ + [Test] + public Task SiblingReferencesAreStripped() + { + var inputContents = @"typedef enum WGPUInstanceBackend +{ + WGPUInstanceBackend_Vulkan = 1 << 0, + WGPUInstanceBackend_GL = 1 << 1, + WGPUInstanceBackend_Metal = 1 << 2, + WGPUInstanceBackend_Primary = WGPUInstanceBackend_Vulkan | WGPUInstanceBackend_Metal, + WGPUInstanceBackend_Secondary = WGPUInstanceBackend_GL +} WGPUInstanceBackend; +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public enum WGPUInstanceBackend + { + Vulkan = 1 << 0, + GL = 1 << 1, + Metal = 1 << 2, + Primary = Vulkan | Metal, + Secondary = GL, + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, PInvokeGeneratorConfigurationOptions.StripEnumMemberTypeName); + } +}