Repository: shader-slang/slang (2026-04-02T18:03:02Z)

Issue:
Conditional<> in entry point causes ICE
# Issue Description
Using a `Conditional<T, hasValue>` as an entrypoint parameter causes an ICE with an assertion failure `structTypeLayout` when T is a struct type and the target is SPIRV.
This is a regression from #9655 which changed Conditional's internal
storage from an array to a vector.

# Reproducer Code
test.slang:
```
struct VSInput { float4 Position : POSITION0; }
struct VSOutput { float4 Position : SV_Position; }
[shader("vertex")]
VSOutput main(in Conditional<VSInput, true> condInput)
{
    if (let input = condInput.get())
    {
        VSOutput output;
        output.Position = input.Position;
        return output;
    }
    else
    {
        VSOutput output;
        output.Position = float4(0, 0, 0, 1);
        return output;
    }
}
```

`slangc test.slang -target spirv -entry main`

# Expected Behavior
Compilation succeeds and produces valid SPIRV with the VSInput struct as an entry point input parameter.

# Actual Behavior
```
error[E99997]: Slang compilation aborted due to an exception of class
Slang::InternalError assert failure: structTypeLayout
```

# Environment
 - Slang v2026.5.2



Discussion:

Patch:
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang
index 320de39bab9..b37aa81c383 100644
--- a/source/slang/core.meta.slang
+++ b/source/slang/core.meta.slang
@@ -1851,20 +1851,25 @@ bool operator!=(__none_t noneVal, Optional<T> val)
 }
 
 __magic_type(ConditionalType)
+__intrinsic_type($(kIROp_ConditionalType))
 struct Conditional<T, bool hasValue>
 {
-    internal vector<T, hasValue ? 1 : 0> storage;
+    internal property T value
+    {
+        __intrinsic_op($(kIROp_GetConditionalValue))
+        get;
+    }
 
     __implicit_conversion($(kConversionCost_ValToOptional))
-    [__unsafeForceInlineEarly]
-    __init(T val) { if (hasValue) storage[0] = val;}
+    __intrinsic_op($(kIROp_MakeConditionalValue))
+    __init(T val);
 
     [__unsafeForceInlineEarly]
     public Optional<T> get()
     {
         if (hasValue)
         {
-            return Optional<T>(storage[0]);
+            return Optional<T>(value);
         }
         else
         {
@@ -1874,10 +1879,10 @@ struct Conditional<T, bool hasValue>
 
     [__unsafeForceInlineEarly]
     [mutating]
-    public void set(T value)
+    public void set(T newValue)
     {
         if (hasValue)
-            storage[0] = value;
+            this = Conditional<T, hasValue>(newValue);
     }
 }
 
@@ -1889,7 +1894,7 @@ extension<T> Optional<T>
     __init(Conditional<T, condHasValue> condVal)
     {
         if (condHasValue)
-            this = Optional<T>(condVal.storage[0]);
+            this = Optional<T>(condVal.value);
         else
             this = none;
     }
diff --git a/source/slang/slang-ast-type.cpp b/source/slang/slang-ast-type.cpp
index d957df72e4b..cbaa86a42bc 100644
--- a/source/slang/slang-ast-type.cpp
+++ b/source/slang/slang-ast-type.cpp
@@ -1114,6 +1114,11 @@ Type* ConditionalType::getValueType()
     return as<Type>(_getGenericTypeArg(this, 0));
 }
 
+IntVal* ConditionalType::getHasValue()
+{
+    return as<IntVal>(_getGenericTypeArg(this, 1));
+}
+
 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! AtomicType !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 Type* AtomicType::getElementType()
 {
diff --git a/source/slang/slang-ast-type.h b/source/slang/slang-ast-type.h
index d005bb38527..e99bd214c5d 100644
--- a/source/slang/slang-ast-type.h
+++ b/source/slang/slang-ast-type.h
@@ -566,6 +566,7 @@ class ConditionalType : public DeclRefType
 {
     FIDDLE(...)
     Type* getValueType();
+    IntVal* getHasValue();
 };
 
 FIDDLE()
diff --git a/source/slang/slang-code-gen.h b/source/slang/slang-code-gen.h
index 2f294ca1ed6..ff62021540a 100644
--- a/source/slang/slang-code-gen.h
+++ b/source/slang/slang-code-gen.h
@@ -54,6 +54,7 @@ struct RequiredLoweringPassSet
     bool debugInfo;
     bool resultType;
     bool optionalType;
+    bool conditionalType;
     bool enumType;
     bool combinedTextureSamplers;
     bool reinterpret;
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index 08627bc20f8..c5cd7869ac3 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -79,6 +79,7 @@
 #include "slang-ir-lower-bit-cast.h"
 #include "slang-ir-lower-buffer-element-type.h"
 #include "slang-ir-lower-combined-texture-sampler.h"
+#include "slang-ir-lower-conditional-type.h"
 #include "slang-ir-lower-coopvec.h"
 #include "slang-ir-lower-dynamic-dispatch-insts.h"
 #include "slang-ir-lower-dynamic-resource-heap.h"
@@ -426,6 +427,9 @@ void calcRequiredLoweringPassSet(
     case kIROp_OptionalType:
         result.optionalType = true;
         break;
+    case kIROp_ConditionalType:
+        result.conditionalType = true;
+        break;
     case kIROp_EnumType:
         result.enumType = true;
         break;
@@ -1185,6 +1189,9 @@ Result linkAndOptimizeIR(
     if (requiredLoweringPassSet.resultType)
         SLANG_PASS(lowerResultType, targetProgram, sink);
 
+    if (requiredLoweringPassSet.conditionalType)
+        SLANG_PASS(lowerConditionalType, sink);
+
     if (requiredLoweringPassSet.optionalType)
         SLANG_PASS(lowerReinterpretOptional, targetProgram, sink);
 
diff --git a/source/slang/slang-ir-autodiff.cpp b/source/slang/slang-ir-autodiff.cpp
index fc855fac871..88b15d77f70 100644
--- a/source/slang/slang-ir-autodiff.cpp
+++ b/source/slang/slang-ir-autodiff.cpp
@@ -930,6 +930,7 @@ bool canTypeBeStored(IRInst* type)
     {
     case kIROp_StructType:
     case kIROp_OptionalType:
+    case kIROp_ConditionalType:
     case kIROp_ArrayType:
     case kIROp_DifferentialPairType:
     case kIROp_InterfaceType:
diff --git a/source/slang/slang-ir-constexpr.cpp b/source/slang/slang-ir-constexpr.cpp
index c69f8303517..901973d2470 100644
--- a/source/slang/slang-ir-constexpr.cpp
+++ b/source/slang/slang-ir-constexpr.cpp
@@ -132,6 +132,7 @@ bool opCanBeConstExpr(IROp op)
     case kIROp_MakeExistentialWithRTTI:
     case kIROp_MakeOptionalNone:
     case kIROp_MakeOptionalValue:
+    case kIROp_MakeConditionalValue:
     case kIROp_MakeResultError:
     case kIROp_MakeResultValue:
     case kIROp_MakeString:
@@ -149,6 +150,7 @@ bool opCanBeConstExpr(IROp op)
     case kIROp_GetResultError:
     case kIROp_GetResultValue:
     case kIROp_GetOptionalValue:
+    case kIROp_GetConditionalValue:
     case kIROp_DifferentialPairGetDifferential:
     case kIROp_DifferentialPairGetPrimal:
     case kIROp_LookupWitnessMethod:
diff --git a/source/slang/slang-ir-insts-stable-names.lua b/source/slang/slang-ir-insts-stable-names.lua
index d687a57eaf1..7a17f788749 100644
--- a/source/slang/slang-ir-insts-stable-names.lua
+++ b/source/slang/slang-ir-insts-stable-names.lua
@@ -825,5 +825,8 @@ return {
 	["Type.TranslatedTypeBase.TrivialBackwardDiffMinimalContextType"] = 845,
 	["Type.TranslatedTypeBase.BackwardMinimalContextFromLegacyBwdDiffFunc"] = 846,
 	["Decoration.ParamsContextDecoration"] = 847,
-	["matrixSwizzleStore"] = 848
+	["matrixSwizzleStore"] = 848,
+	["Type.Conditional"] = 849,
+	["getConditionalValue"] = 850,
+	["makeConditionalValue"] = 851,
 }
diff --git a/source/slang/slang-ir-insts.lua b/source/slang/slang-ir-insts.lua
index c0c84dc233c..82a40b4cb95 100644
--- a/source/slang/slang-ir-insts.lua
+++ b/source/slang/slang-ir-insts.lua
@@ -131,6 +131,8 @@ local insts = {
 			},
 			-- Represents an `Optional<T>`.
 			{ Optional = { struct_name = "OptionalType", operands = { { "valueType", "IRType" } }, hoistable = true } },
+			-- Represents a `Conditional<T, hasValue>`.
+			{ Conditional = { struct_name = "ConditionalType", operands = { { "valueType", "IRType" }, { "hasValue", "IRInst" } }, hoistable = true } },
 			-- Represents an enum type
 			{ Enum = { struct_name = "EnumType", operands = { { "tagType", "IRType" } }, parent = true } },
 			{
@@ -1024,6 +1026,8 @@ local insts = {
 	{ optionalHasValue = { operands = { { "optionalOperand" } } } },
 	{ makeOptionalValue = { operands = { { "value" } } } },
 	{ makeOptionalNone = {} },
+	{ getConditionalValue = { operands = { { "conditionalOperand" } } } },
+	{ makeConditionalValue = { operands = { { "value" } } } },
 	{ CombinedTextureSamplerGetTexture = { operands = { { "sampler" } } } },
 	{ CombinedTextureSamplerGetSampler = { operands = { { "sampler" } } } },
 	{ call = { operands = { { "callee" } } } },
diff --git a/source/slang/slang-ir-lower-conditional-type.cpp b/source/slang/slang-ir-lower-conditional-type.cpp
new file mode 100644
index 00000000000..185654ec2c4
--- /dev/null
+++ b/source/slang/slang-ir-lower-conditional-type.cpp
@@ -0,0 +1,209 @@
+#include "slang-ir-lower-conditional-type.h"
+
+#include "slang-ir-insts.h"
+#include "slang-ir.h"
+
+namespace Slang
+{
+struct ConditionalTypeLoweringContext
+{
+    IRModule* module;
+    DiagnosticSink* sink;
+
+    InstWorkList workList;
+    InstHashSet workListSet;
+
+    struct LoweredConditionalTypeInfo
+    {
+        IRType* loweredType;
+        bool hasValue;
+    };
+    Dictionary<IRConditionalType*, LoweredConditionalTypeInfo> loweredConditionalTypes;
+    IRType* emptyStructType = nullptr;
+
+    ConditionalTypeLoweringContext(IRModule* inModule)
+        : module(inModule), workList(inModule), workListSet(inModule)
+    {
+    }
+
+    IRType* getEmptyStructType()
+    {
+        if (!emptyStructType)
+        {
+            IRBuilder builder(module);
+            builder.setInsertInto(module->getModuleInst());
+            auto emptyStruct = builder.createStructType();
+            builder.addNameHintDecoration(
+                emptyStruct,
+                UnownedStringSlice("_slang_Conditional_empty"));
+            emptyStructType = emptyStruct;
+        }
+        return emptyStructType;
+    }
+
+    void addToWorkList(IRInst* inst)
+    {
+        if (workListSet.contains(inst))
+            return;
+        workList.add(inst);
+        workListSet.add(inst);
+    }
+
+    void processConditionalType(IRConditionalType* condType)
+    {
+        if (loweredConditionalTypes.containsKey(condType))
+            return;
+
+        auto valueType = condType->getValueType();
+        auto hasValueInst = condType->getHasValue();
+
+        bool hasValue = false;
+        bool resolved = false;
+
+        if (auto boolLit = as<IRBoolLit>(hasValueInst))
+        {
+            hasValue = boolLit->getValue();
+            resolved = true;
+        }
+        else if (auto intLit = as<IRIntLit>(hasValueInst))
+        {
+            hasValue = getIntVal(intLit) != 0;
+            resolved = true;
+        }
+
+        if (!resolved)
+            return;
+
+        LoweredConditionalTypeInfo info;
+        info.hasValue = hasValue;
+
+        if (hasValue)
+        {
+            // Lower to the underlying value type.
+            IRType* resolvedType = valueType;
+            while (auto innerCond = as<IRConditionalType>(resolvedType))
+            {
+                if (auto innerInfo = loweredConditionalTypes.tryGetValue(innerCond))
+                    resolvedType = innerInfo->loweredType;
+                else
+                    break;
+            }
+            info.loweredType = resolvedType;
+        }
+        else
+        {
+            // Lower to a shared empty struct.
+            info.loweredType = getEmptyStructType();
+        }
+
+        loweredConditionalTypes[condType] = info;
+    }
+
+    void processMakeConditionalValue(IRMakeConditionalValue* inst)
+    {
+        auto condType = as<IRConditionalType>(inst->getDataType());
+        if (!condType)
+            return;
+        auto info = loweredConditionalTypes.tryGetValue(condType);
+        if (!info)
+            return;
+
+        IRBuilder builder(module);
+        builder.setInsertBefore(inst);
+
+        if (info->hasValue)
+        {
+            inst->replaceUsesWith(inst->getValue());
+        }
+        else
+        {
+            auto emptyVal = builder.emitMakeStruct(info->loweredType, 0, nullptr);
+            inst->replaceUsesWith(emptyVal);
+        }
+        inst->removeAndDeallocate();
+    }
+
+    void processGetConditionalValue(IRGetConditionalValue* inst)
+    {
+        auto condType = as<IRConditionalType>(inst->getConditionalOperand()->getDataType());
+        if (!condType)
+        {
+            // Already lowered.
+            auto operand = inst->getConditionalOperand();
+            IRBuilder builder(module);
+            builder.setInsertBefore(inst);
+            if (operand->getDataType() == inst->getDataType())
+                inst->replaceUsesWith(operand);
+            else
+                inst->replaceUsesWith(builder.emitPoison(inst->getDataType()));
+            inst->removeAndDeallocate();
+            return;
+        }
+        auto info = loweredConditionalTypes.tryGetValue(condType);
+        if (!info)
+            return;
+
+        IRBuilder builder(module);
+        builder.setInsertBefore(inst);
+
+        if (info->hasValue)
+        {
+            inst->replaceUsesWith(inst->getConditionalOperand());
+        }
+        else
+        {
+            auto poisonVal = builder.emitPoison(inst->getDataType());
+            inst->replaceUsesWith(poisonVal);
+        }
+        inst->removeAndDeallocate();
+    }
+
+    void processInst(IRInst* inst)
+    {
+        switch (inst->getOp())
+        {
+        case kIROp_ConditionalType:
+            processConditionalType(as<IRConditionalType>(inst));
+            break;
+        case kIROp_MakeConditionalValue:
+            processMakeConditionalValue(as<IRMakeConditionalValue>(inst));
+            break;
+        case kIROp_GetConditionalValue:
+            processGetConditionalValue(as<IRGetConditionalValue>(inst));
+            break;
+        default:
+            break;
+        }
+    }
+
+    void processModule()
+    {
+        addToWorkList(module->getModuleInst());
+
+        while (workList.getCount() != 0)
+        {
+            IRInst* inst = workList.getLast();
+            workList.removeLast();
+            workListSet.remove(inst);
+
+            processInst(inst);
+
+            for (auto child = inst->getLastChild(); child; child = child->getPrevInst())
+            {
+                addToWorkList(child);
+            }
+        }
+
+        // Replace all conditional types with lowered types.
+        for (const auto& [key, value] : loweredConditionalTypes)
+            key->replaceUsesWith(value.loweredType);
+    }
+};
+
+void lowerConditionalType(IRModule* module, DiagnosticSink* sink)
+{
+    ConditionalTypeLoweringContext context(module);
+    context.sink = sink;
+    context.processModule();
+}
+} // namespace Slang
diff --git a/source/slang/slang-ir-lower-conditional-type.h b/source/slang/slang-ir-lower-conditional-type.h
new file mode 100644
index 00000000000..a4f1b512835
--- /dev/null
+++ b/source/slang/slang-ir-lower-conditional-type.h
@@ -0,0 +1,14 @@
+// slang-ir-lower-conditional-type.h
+#pragma once
+
+#include "slang-ir.h"
+
+namespace Slang
+{
+class DiagnosticSink;
+
+/// Lower `Conditional<T, hasValue>` intrinsic types into concrete representations.
+/// Conditional<T, true> is replaced with T directly.
+/// Conditional<T, false> is replaced with a shared zero-field empty struct.
+void lowerConditionalType(IRModule* module, DiagnosticSink* sink);
+} // namespace Slang
diff --git a/source/slang/slang-ir-peephole.cpp b/source/slang/slang-ir-peephole.cpp
index e77f061c39b..aacdfb4bd29 100644
--- a/source/slang/slang-ir-peephole.cpp
+++ b/source/slang/slang-ir-peephole.cpp
@@ -1250,6 +1250,16 @@ struct PeepholeContext : InstPassBase
                 }
             }
             break;
+        case kIROp_GetConditionalValue:
+            {
+                if (auto makeConditional = as<IRMakeConditionalValue>(inst->getOperand(0)))
+                {
+                    inst->replaceUsesWith(makeConditional->getValue());
+                    maybeRemoveOldInst(inst);
+                    changed = true;
+                }
+            }
+            break;
         case kIROp_OptionalHasValue:
             {
                 if (inst->getOperand(0)->getOp() == kIROp_MakeOptionalValue)
diff --git a/source/slang/slang-ir-typeflow-specialize.cpp b/source/slang/slang-ir-typeflow-specialize.cpp
index 5142a3f4dfa..fae8a0942b5 100644
--- a/source/slang/slang-ir-typeflow-specialize.cpp
+++ b/source/slang/slang-ir-typeflow-specialize.cpp
@@ -522,6 +522,24 @@ bool isConcreteType(IRInst* inst)
                isGlobalInst(cast<IRArrayType>(inst)->getElementCount());
     case kIROp_OptionalType:
         return isConcreteType(cast<IROptionalType>(inst)->getValueType());
+    case kIROp_ConditionalType:
+        {
+            auto conditionalType = cast<IRConditionalType>(inst);
+            auto hasValueInst = conditionalType->getHasValue();
+            if (auto boolLit = as<IRBoolLit>(hasValueInst))
+            {
+                if (!boolLit->getValue())
+                    return true;
+                return isConcreteType(conditionalType->getValueType());
+            }
+            else if (auto intLit = as<IRIntLit>(hasValueInst))
+            {
+                if (getIntVal(intLit) == 0)
+                    return true;
+                return isConcreteType(conditionalType->getValueType());
+            }
+            return false;
+        }
     case kIROp_DifferentialPairType:
         return isConcreteType(cast<IRDifferentialPairTypeBase>(inst)->getValueType());
     case kIROp_AttributedType:
diff --git a/source/slang/slang-ir-util.cpp b/source/slang/slang-ir-util.cpp
index de9212d8509..db97f8fffc9 100644
--- a/source/slang/slang-ir-util.cpp
+++ b/source/slang/slang-ir-util.cpp
@@ -298,6 +298,7 @@ bool isValueType(IRInst* dataType)
     case kIROp_TupleType:
     case kIROp_ResultType:
     case kIROp_OptionalType:
+    case kIROp_ConditionalType:
     case kIROp_DifferentialPairType:
     case kIROp_DynamicType:
     case kIROp_AnyValueType:
@@ -397,6 +398,7 @@ bool isWrapperType(IRInst* inst)
     case kIROp_HLSLConsumeStructuredBufferType:
     case kIROp_TupleType:
     case kIROp_OptionalType:
+    case kIROp_ConditionalType:
     case kIROp_TypePack:
         return true;
     default:
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index 0f7ecfccfce..0862f07e28b 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -9141,6 +9141,8 @@ bool IRInst::mightHaveSideEffects(SideEffectAnalysisOptions options)
     case kIROp_MakeOptionalNone:
     case kIROp_OptionalHasValue:
     case kIROp_GetOptionalValue:
+    case kIROp_MakeConditionalValue:
+    case kIROp_GetConditionalValue:
     case kIROp_DifferentialPairGetPrimal:
     case kIROp_DifferentialPairGetDifferential:
     case kIROp_MakeDifferentialPair:
@@ -9727,6 +9729,8 @@ bool isMovableInst(IRInst* inst)
     case kIROp_OptionalHasValue:
     case kIROp_GetOptionalValue:
     case kIROp_MakeOptionalValue:
+    case kIROp_MakeConditionalValue:
+    case kIROp_GetConditionalValue:
     case kIROp_MakeTuple:
     case kIROp_GetTupleElement:
     case kIROp_MakeStruct:
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index f37d0674d66..11a6c902b24 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -2165,7 +2165,7 @@ struct IRModule : RefObject
     // anything to do with serialization format
     //
     const static UInt k_minSupportedModuleVersion = 4;
-    const static UInt k_maxSupportedModuleVersion = 13;
+    const static UInt k_maxSupportedModuleVersion = 14;
     static_assert(k_minSupportedModuleVersion <= k_maxSupportedModuleVersion);
 
 private:
diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp
index 28e2cc37050..3e49dcedefd 100644
--- a/source/slang/slang-parameter-binding.cpp
+++ b/source/slang/slang-parameter-binding.cpp
@@ -2356,6 +2356,21 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter(
             ptrTypeLayout->valueTypeLayout = valueTypeLayout;
             return ptrTypeLayout;
         }
+        else if (auto conditionalType = as<ConditionalType>(type))
+        {
+            auto hasValueVal = conditionalType->getHasValue();
+            auto folded =
+                hasValueVal ? context->getTargetProgram()->getProgram()->tryFoldIntVal(hasValueVal)
+                            : nullptr;
+            if (folded && getIntVal(folded) != 0)
+                return processParamOfTypeFunc(
+                    _Move(processParamOfTypeFunc),
+                    conditionalType->getValueType());
+            RefPtr<TypeLayout> typeLayout = new TypeLayout();
+            typeLayout->type = type;
+            typeLayout->rules = context->layoutContext.rules;
+            return typeLayout;
+        }
         else if (auto optionalType = as<OptionalType>(type))
         {
             Array<Type*, 2> types =
diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp
index 94596c60f22..97edc24bbb4 100644
--- a/source/slang/slang-type-layout.cpp
+++ b/source/slang/slang-type-layout.cpp
@@ -5732,6 +5732,22 @@ static TypeLayoutResult _createTypeLayout(TypeLayoutContext& context, Type* type
 
         auto declRef = declRefType->getDeclRef();
 
+        if (auto conditionalType = as<ConditionalType>(declRefType))
+        {
+            auto hasValue = conditionalType->getHasValue();
+            auto hasValueVal = hasValue ? context.tryResolveLinkTimeVal(hasValue) : nullptr;
+            if (auto constVal = as<ConstantIntVal>(hasValueVal))
+            {
+                if (getIntVal(constVal) != 0)
+                    return _createTypeLayout(context, conditionalType->getValueType());
+            }
+            RefPtr<TypeLayout> typeLayout = new TypeLayout();
+            typeLayout->type = type;
+            typeLayout->rules = rules;
+            _addLayout(context, type, typeLayout);
+            return TypeLayoutResult(typeLayout, SimpleLayoutInfo());
+        }
+
         if (auto structDeclRef = declRef.as<StructDecl>())
         {
             StructTypeLayoutBuilder typeLayoutBuilder;


Test patch:
diff --git a/tests/language-feature/types/conditional-entrypoint.slang b/tests/language-feature/types/conditional-entrypoint.slang
new file mode 100644
index 00000000000..6e1598a5103
--- /dev/null
+++ b/tests/language-feature/types/conditional-entrypoint.slang
@@ -0,0 +1,163 @@
+//TEST:SIMPLE(filecheck=CHECK_IN_TRUE_SPV): -target spirv -entry mainTrue
+//TEST:SIMPLE(filecheck=CHECK_IN_TRUE_HLSL): -target hlsl -entry mainTrue
+//TEST:SIMPLE(filecheck=CHECK_IN_FALSE_SPV): -target spirv -entry mainFalse
+//TEST:SIMPLE(filecheck=CHECK_IN_FALSE_HLSL): -target hlsl -entry mainFalse
+//TEST:SIMPLE(filecheck=CHECK_OUT_TRUE_SPV): -target spirv -entry mainOutputTrue
+//TEST:SIMPLE(filecheck=CHECK_OUT_TRUE_HLSL): -target hlsl -entry mainOutputTrue
+//TEST:SIMPLE(filecheck=CHECK_OUT_FALSE_SPV): -target spirv -entry mainOutputFalse
+//TEST:SIMPLE(filecheck=CHECK_OUT_FALSE_HLSL): -target hlsl -entry mainOutputFalse
+//TEST:SIMPLE(filecheck=CHECK_MULTI_SPV): -target spirv -entry mainMulti
+//TEST:SIMPLE(filecheck=CHECK_MULTI_HLSL): -target hlsl -entry mainMulti
+//TEST:SIMPLE(filecheck=CHECK_LINK_TRUE_SPV): -target spirv -entry mainLinkTimeTrue
+//TEST:SIMPLE(filecheck=CHECK_LINK_TRUE_HLSL): -target hlsl -entry mainLinkTimeTrue
+//TEST:SIMPLE(filecheck=CHECK_LINK_FALSE_SPV): -target spirv -entry mainLinkTimeFalse
+//TEST:SIMPLE(filecheck=CHECK_LINK_FALSE_HLSL): -target hlsl -entry mainLinkTimeFalse
+
+extern static const bool hasInputTrue = true;
+extern static const bool hasInputFalse = false;
+
+struct VSInput
+{
+    float4 Position : POSITION0;
+}
+struct VSNormal
+{
+    float4 Normal : NORMAL0;
+}
+struct VSOutput
+{
+    float4 Position : SV_Position;
+}
+
+// CHECK_IN_TRUE_SPV: OpEntryPoint Vertex
+// CHECK_IN_TRUE_SPV: OpVariable {{.*}} Input
+// CHECK_IN_TRUE_HLSL: POSITION0
+// CHECK_IN_TRUE_HLSL: mainTrue({{.+}})
+[shader("vertex")]
+VSOutput mainTrue(in Conditional<VSInput, true> optInput)
+{
+    if (let input = optInput.get())
+    {
+        VSOutput output;
+        output.Position = input.Position;
+        return output;
+    }
+    else
+    {
+        VSOutput output;
+        output.Position = float4(0, 0, 0, 1);
+        return output;
+    }
+}
+
+// CHECK_IN_FALSE_SPV: OpEntryPoint Vertex
+// CHECK_IN_FALSE_SPV-NOT: OpVariable {{.*}} Input
+// CHECK_IN_FALSE_HLSL-NOT: POSITION
+// CHECK_IN_FALSE_HLSL: mainFalse(
+[shader("vertex")]
+VSOutput mainFalse(in Conditional<VSInput, false> optInput)
+{
+    if (let input = optInput.get())
+    {
+        VSOutput output;
+        output.Position = input.Position;
+        return output;
+    }
+    else
+    {
+        VSOutput output;
+        output.Position = float4(0, 0, 0, 1);
+        return output;
+    }
+}
+
+// CHECK_OUT_TRUE_SPV: OpEntryPoint Vertex
+// CHECK_OUT_TRUE_SPV: OpVariable {{.*}} Output
+// CHECK_OUT_TRUE_HLSL: SV_Position
+// CHECK_OUT_TRUE_HLSL: mainOutputTrue(
+[shader("vertex")]
+Conditional<VSOutput, true> mainOutputTrue(VSInput input)
+{
+    VSOutput output;
+    output.Position = input.Position;
+    return output;
+}
+
+// CHECK_OUT_FALSE_SPV: OpEntryPoint Vertex
+// CHECK_OUT_FALSE_SPV-NOT: OpVariable {{.*}} Output
+// CHECK_OUT_FALSE_HLSL-NOT: SV_Position
+// CHECK_OUT_FALSE_HLSL: void mainOutputFalse(
+[shader("vertex")]
+Conditional<VSOutput, false> mainOutputFalse(VSInput input)
+{
+    VSOutput output;
+    output.Position = input.Position;
+    return output;
+}
+
+// CHECK_MULTI_SPV: OpEntryPoint Vertex
+// CHECK_MULTI_HLSL: mainMulti(
+// CHECK_MULTI_HLSL-NOT: COLOR0
+// CHECK_MULTI_HLSL: COLOR1
+[shader("vertex")]
+VSOutput mainMulti(
+    in Conditional<VSInput, true> optInput,
+    in Conditional<VSNormal, false> optNormal,
+    in Conditional<float3, false> optColor0 : COLOR0,
+    in Conditional<float3, true> optColor1 : COLOR1)
+{
+    VSOutput output;
+    if (let input = optInput.get())
+        output.Position = input.Position;
+    else
+        output.Position = float4(0, 0, 0, 1);
+    if (let n = optNormal.get())
+        output.Position += n.Normal;
+    if (let c = optColor0.get())
+        output.Position += float4(c, 0);
+    if (let c = optColor1.get())
+        output.Position += float4(c, 0);
+    return output;
+}
+
+// CHECK_LINK_TRUE_SPV: OpEntryPoint Vertex
+// CHECK_LINK_TRUE_SPV: OpVariable {{.*}} Input
+// CHECK_LINK_TRUE_HLSL: POSITION0
+// CHECK_LINK_TRUE_HLSL: mainLinkTimeTrue({{.+}})
+[shader("vertex")]
+VSOutput mainLinkTimeTrue(in Conditional<VSInput, hasInputTrue> optInput)
+{
+    if (let input = optInput.get())
+    {
+        VSOutput output;
+        output.Position = input.Position;
+        return output;
+    }
+    else
+    {
+        VSOutput output;
+        output.Position = float4(0, 0, 0, 1);
+        return output;
+    }
+}
+
+// CHECK_LINK_FALSE_SPV: OpEntryPoint Vertex
+// CHECK_LINK_FALSE_SPV-NOT: OpVariable {{.*}} Input
+// CHECK_LINK_FALSE_HLSL-NOT: POSITION
+// CHECK_LINK_FALSE_HLSL: mainLinkTimeFalse(
+[shader("vertex")]
+VSOutput mainLinkTimeFalse(in Conditional<VSInput, hasInputFalse> optInput)
+{
+    if (let input = optInput.get())
+    {
+        VSOutput output;
+        output.Position = input.Position;
+        return output;
+    }
+    else
+    {
+        VSOutput output;
+        output.Position = float4(0, 0, 0, 1);
+        return output;
+    }
+}
diff --git a/tests/language-feature/types/conditional-lowering.slang b/tests/language-feature/types/conditional-lowering.slang
new file mode 100644
index 00000000000..55d3c475b93
--- /dev/null
+++ b/tests/language-feature/types/conditional-lowering.slang
@@ -0,0 +1,47 @@
+//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-vk -output-using-type
+//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):-dx11 -output-using-type
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+Conditional<int, true> returnConditionalTrue(int val)
+{
+    return val;
+}
+
+Conditional<int, false> returnConditionalFalse(int unused)
+{
+    return unused;
+}
+
+// CHECK: 42
+// CHECK-NEXT: -1
+// CHECK-NEXT: 10
+// CHECK-NEXT: -1
+[numthreads(1, 1, 1)]
+void computeMain()
+{
+    Conditional<int, true> ct = returnConditionalTrue(42);
+    if (let v = ct.get())
+        outputBuffer[0] = v;
+    else
+        outputBuffer[0] = -1;
+
+    Conditional<int, false> cf = returnConditionalFalse(99);
+    if (let v = cf.get())
+        outputBuffer[1] = v;
+    else
+        outputBuffer[1] = -1;
+
+    Conditional<int, true> direct = 10;
+    if (let v = direct.get())
+        outputBuffer[2] = v;
+    else
+        outputBuffer[2] = -1;
+
+    Conditional<int, false> empty;
+    if (let v = empty.get())
+        outputBuffer[3] = v;
+    else
+        outputBuffer[3] = -1;
+}