stride3d/stride · error · NotSupportedException

Unsupported ternary operand types: condition=

Error message

Unsupported ternary operand types: condition={Condition.ValueType}, left={Left.ValueType}, right={Right.ValueType}

What it means

Type inference for the ternary (?:) operator only supports a fixed set of operand combinations: vector condition, scalar/scalar, scalar+vector mixes, and vector/vector pairs, all requiring a valid scalar element type. Any combination outside these patterns — e.g. matrix or struct operands, or a non-scalar element type — throws this NotSupportedException at Expression.cs:1630.

Solutions

  1. Ensure the condition is a scalar or vector bool and both branches are scalars or vectors of compatible element types.
  2. For matrices/structs, replace ?: with an explicit if/else or per-component ternary on vectors.
  3. If a new valid combination should be supported, add a pattern arm to the switch at Expression.cs:1630.

Example fix

// before
float3x3 m = b ? m1 : m2;   // matrix operands
// after
float3x3 m;
if (b) m = m1; else m = m2;
Defensive patterns

Strategy: validation

Validate before calling

// check ternary operand kinds before compiling
bool ok = cond is ScalarType or VectorType && l is (ScalarType or VectorType) && r is (ScalarType or VectorType);
if (!ok) throw new ShaderCompileHint("Ternary requires scalar/vector condition and branches");

Type guard

static bool IsTernaryCompatible(BaseType c, BaseType l, BaseType r) => c is ScalarType or VectorType && l is ScalarType or VectorType && r is ScalarType or VectorType;

Try / catch

try { result = CompileExpression(expr, table, compiler); }
catch (NotSupportedException ex) when (ex.Message.Contains("Unsupported ternary operand types"))
{ diagnostics.Report(expr.Info, ex.Message); }

Prevention

When it happens

Trigger: Evaluating `cond ? a : b` where the condition is not scalar/vector or where Left/Right are matrices, structs, samplers, or other non-vector/scalar types; also when scalarType inference (the element type of the branches) fails.

Common situations: Selecting between two matrices with ?:, applying ?: to struct/sampler operands, or writing `matrixA ? m1 : m2` expecting component-wise selection like HLSL allows.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/862fa496f58cf489. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/Expression.cs:1630

    {
        Condition.ProcessSymbol(table);

        if (Condition.ValueType!.GetElementType() is not ScalarType)
            table.AddError(new(Condition.Info, SDSLErrorMessages.SDSL0106));

        Left.ProcessSymbol(table);
        Right.ProcessSymbol(table);

        var scalarType = SpirvBuilder.FindCommonBaseTypeForBinaryOperation(Left.ValueType!.GetElementType(), Right.ValueType!.GetElementType());
        Type = (Condition.ValueType, Left.ValueType, Right.ValueType) switch
        {
            // If condition is a vector, we need to use this vector size instead
            (VectorType c, _, _) => new VectorType(scalarType, c.Size),
            (ScalarType c, ScalarType, ScalarType) => scalarType,
            (ScalarType c, VectorType v1, ScalarType s2) => v1.WithElementType(scalarType),
            (ScalarType c, ScalarType s1, VectorType v2) => v2.WithElementType(scalarType),
            (ScalarType c, VectorType v1, VectorType v2) => new VectorType(scalarType, Math.Min(v1.Size, v2.Size)),
            _ => throw new NotSupportedException($"Unsupported ternary operand types: condition={Condition.ValueType}, left={Left.ValueType}, right={Right.ValueType}"),
        };
    }

    public override SpirvValue CompileImpl(SymbolTable table, CompilerUnit compiler)
    {
        var (builder, context) = compiler;

        var conditionValue = Condition.CompileAsValue(table, compiler);

        // Might need implicit conversion from float/int to bool, preserving vector shape
        if (Condition.ValueType is VectorType condVec)
            conditionValue = builder.Convert(context, conditionValue, new VectorType(ScalarType.Boolean, condVec.Size));
        else
            conditionValue = builder.Convert(context, conditionValue, ScalarType.Boolean);

        // TODO: Review choice between if/else like branch (OpBranchConditional) which evaluate only one side, or select (OpSelect) which evaluate both side but can work per component but is limited to specific types
        //       It seems HLSL 2021 changed the behavior to align it with C-style short-circuiting.
        // For now, we use OpSelect only with per-component, otherwise we use if/else branching

View on GitHub (pinned to 96fad776d2)