Repository: shader-slang/slang (2025-07-22T17:10:37Z)

Issue:
Public unscoped enum constants not visible across module boundaries
**Issue Description**
After #7798, the constants in public unscoped enums are no longer visible across module boundaries, regardless of `public`.

**Reproducer Code**
```slang
// test_a.slang
[UnscopedEnum]
public enum TestEnum
{
    CASE_A,
    CASE_B
};
```

```slang
// test_b.slang
import test_a;

[numthreads(1, 1, 1)]
void computeMain(int3 dispatchThreadID: SV_DispatchThreadID)
{
    TestEnum e = CASE_A;
}
```

Compile with:
```
slangc -target spirv test_b.slang -o test.spv
```

Error message:
```
test_b.slang(6): error 30600: 'CASE_A' is not accessible from the current context.
    TestEnum e = CASE_A;
```

`TestEnum.CASE_A` is visible and if the enum is moved into `test_b.slang`, the unscoped `CASE_A` is also visible.

**Expected Behavior**
`CASE_A` is visible, as if it was in the same module.

**Actual Behavior**
Error message is given.

**Environment:**
 - Slang Version: a77d22bf3bfc3098b01e4886c3a63fb751edc704
 - OS: OpenSUSE Tumbleweed

**Additional context**
The PR that changed this behaviour is related to issue #7758, where it was mentioned that "canonical Slang code should always use scoped enums." Since modules are a Slang feature, it can also be entirely reasonable to ban unscoped enum visibility across module boundaries.

I have some code that currently uses this feature, but I'm working on moving it to scoped enums, so this issue is not at all a blocker for me. I was more just surprised that there was a change to the exisitng behaviour.


Discussion:
There is a bug in PR 7798. 

In `CompleteDecl`, we are inserting a `static const EnumType EnumCaseName = EnumCaseValue;` outside the `enum` decl for each enum case. However, we forgot to add the visibility modifier on these synthesized decls. We need to call `getDeclVisibility(enumDecl)` and add a visibility modifier that declares the synthesized `staticConstDecl` to have the same visibility as the enum decl.



Patch:
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index a12f216b17..891db2867c 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -4972,6 +4972,15 @@ static void CompleteDecl(
                     staticConstDecl->nameAndLoc = enumCase->nameAndLoc;
                     addModifier(staticConstDecl, parser->astBuilder->create<HLSLStaticModifier>());
                     addModifier(staticConstDecl, parser->astBuilder->create<ConstModifier>());
+
+                    // Copy visibility modifiers from the enum declaration
+                    if (auto visibilityModifier = modifiers.findModifier<VisibilityModifier>())
+                    {
+                        auto newVisibilityModifier = as<VisibilityModifier>(
+                            parser->astBuilder->createByNodeType(visibilityModifier->astNodeType));
+                        addModifier(staticConstDecl, newVisibilityModifier);
+                    }
+
                     auto valueExpr = parser->astBuilder->create<VarExpr>();
                     valueExpr->declRef = DeclRef<Decl>(enumCase);
                     staticConstDecl->initExpr = valueExpr;


Test patch:
diff --git a/tests/language-feature/modules/unscoped-enum-visibility/unscoped_enum_lib.slang b/tests/language-feature/modules/unscoped-enum-visibility/unscoped_enum_lib.slang
new file mode 100644
index 0000000000..7982d8baab
--- /dev/null
+++ b/tests/language-feature/modules/unscoped-enum-visibility/unscoped_enum_lib.slang
@@ -0,0 +1,8 @@
+// Library module containing a public unscoped enum
+
+[UnscopedEnum]
+public enum TestEnum
+{
+    CASE_A,
+    CASE_B
+};
\ No newline at end of file
diff --git a/tests/language-feature/modules/unscoped-enum-visibility/unscoped_enum_user.slang b/tests/language-feature/modules/unscoped-enum-visibility/unscoped_enum_user.slang
new file mode 100644
index 0000000000..e20f387fb8
--- /dev/null
+++ b/tests/language-feature/modules/unscoped-enum-visibility/unscoped_enum_user.slang
@@ -0,0 +1,29 @@
+//TEST:COMPARE_COMPUTE(filecheck-buffer=CHECK):-cpu -output-using-type
+//
+// Test for public unscoped enum constants visibility across module boundaries
+//
+// This test verifies that unscoped enum constants are visible across module
+// boundaries when the enum is declared as public.
+//
+
+import unscoped_enum_lib;
+
+//TEST_INPUT: set outputBuffer = out ubuffer(data=[0 0 0 0], stride=4)
+RWStructuredBuffer<uint> outputBuffer;
+
+[numthreads(1,1,1)]
+void computeMain()
+{
+    // These should all be visible due to the public unscoped enum
+    TestEnum e1 = CASE_A; // Should work - unscoped access
+    TestEnum e2 = TestEnum.CASE_B; // Should work - scoped access
+    
+    // CHECK: 0
+    outputBuffer[0] = (uint)e1;
+    // CHECK: 1  
+    outputBuffer[1] = (uint)e2;
+    // CHECK: 0
+    outputBuffer[2] = (uint)CASE_A;
+    // CHECK: 1
+    outputBuffer[3] = (uint)CASE_B;
+}
\ No newline at end of file