Repository: shader-slang/slang (2026-04-07T12:07:01Z)

Issue:
Non-copyable type accepted in regular interface impl but rejected in dyn interface
## Issue Description

A struct marked `[__NonCopyableType]` used as a field in a regular interface implementation compiles without error, even though `dyn` interfaces reject non-copyable members with error 33081. AnyValue marshalling copies data at the byte level, bypassing copy restrictions, which may violate the invariants that non-copyable semantics are meant to enforce.

## Reproducer Code

```hlsl
interface IFoo { int get(); }
[__NonCopyableType]
struct NonCopyData { int val; }
export struct Impl : IFoo {
    NonCopyData data;
    int get() { return 1; }
}
export struct GoodImpl : IFoo {
    int v;
    int get() { return v; }
}
RWStructuredBuffer<int> out;
[numthreads(1,1,1)]
void computeMain() {
    IFoo f = createDynamicObject<IFoo>(0, 0);
    out[0] = f.get();
}
```

**Command:**
```bash
slangc -target spirv test.slang
```

## Expected Behavior

Error diagnostic rejecting non-copyable field, consistent with `dyn` interface validation (error 33081).

## Actual Behavior

Compiles successfully (exit 0) with no warnings or errors.

## Test to Enable

When fixed, enable the DISABLED test `tests/language-feature/dynamic-dispatch/diagnose-noncopyable-in-impl.slang`.



Discussion:

Patch:
diff --git a/source/slang/slang-ir-lower-dynamic-dispatch-insts.cpp b/source/slang/slang-ir-lower-dynamic-dispatch-insts.cpp
index 5893cd7b2df..0fdc4b4981d 100644
--- a/source/slang/slang-ir-lower-dynamic-dispatch-insts.cpp
+++ b/source/slang/slang-ir-lower-dynamic-dispatch-insts.cpp
@@ -628,6 +628,9 @@ struct UntaggedUnionLoweringContext : public InstPassBase
     // in emitMarshallingCode or produce invalid output for the current target.
     bool containsUnmarshalableType(IRType* type)
     {
+        if (getResolvedInstForDecorations(type)->findDecoration<IRNonCopyableTypeDecoration>())
+            return true;
+
         switch (type->getOp())
         {
         case kIROp_AtomicType:


Test patch:
diff --git a/tests/language-feature/dynamic-dispatch/diagnose-noncopyable-in-impl.slang b/tests/language-feature/dynamic-dispatch/diagnose-noncopyable-in-impl.slang
index 28ce40eb222..62bbb0f940d 100644
--- a/tests/language-feature/dynamic-dispatch/diagnose-noncopyable-in-impl.slang
+++ b/tests/language-feature/dynamic-dispatch/diagnose-noncopyable-in-impl.slang
@@ -1,16 +1,14 @@
 // Non-copyable types have their copy semantics disabled, but AnyValue
 // marshalling copies data at the byte level, bypassing the type system's
-// copy restrictions. The dyn interface validation rejects non-copyable
-// members (error 33081), but regular interfaces currently allow them.
+// copy restrictions. Both dyn interface validation (error 33081) and
+// regular interface validation (error 41014) reject non-copyable members.
 //
-// This test documents the inconsistency (#10421): the compiler silently
-// accepts non-copyable fields in regular interface implementations, even
-// though AnyValue packing effectively copies the data. Whether this is
-// intentional or a bug depends on whether non-copyable semantics should
-// be enforced at the marshalling level.
+// This test verifies that error 41014 is emitted when a struct implementing
+// a regular interface contains non-copyable fields, either directly or
+// nested inside another struct. See #10421.
 
-//DISABLE_DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target spirv -conformance "Impl:IFoo=0" -conformance "GoodImpl:IFoo=1"
-//DISABLE_DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target hlsl -stage compute -entry computeMain -conformance "Impl:IFoo=0" -conformance "GoodImpl:IFoo=1"
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target spirv -conformance "Impl:IFoo=0" -conformance "ImplNested:IFoo=1" -conformance "GoodImpl:IFoo=2"
+//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK): -target hlsl -stage compute -entry computeMain -conformance "Impl:IFoo=0" -conformance "ImplNested:IFoo=1" -conformance "GoodImpl:IFoo=2"
 
 [__NonCopyableType]
 struct NonCopyData
@@ -31,6 +29,20 @@ export struct Impl : IFoo
     int get() { return 1; }
 }
 
+struct WrapperStruct
+{
+    NonCopyData wrapped;
+    int padding;
+}
+
+export struct ImplNested : IFoo
+//CHECK:      ^^^^^^^^^^ type cannot be packed for dynamic dispatch
+//CHECK:      ^^^^^^^^^^ type 'ImplNested' contains fields that cannot be packed into ordinary bytes for dynamic dispatch.
+{
+    WrapperStruct container;
+    int get() { return 2; }
+}
+
 export struct GoodImpl : IFoo
 {
     int v;