stride3d/stride · error · InvalidOperationException

Can't cast between array of different sizes

Error message

Can't cast between array of different sizes (cast from {valueType} to {castType})

What it means

When converting between array types with the same base element type, sizes must match; only Size == -1 (undetermined size from incomplete generics) is allowed to pass through unchecked. A definite size mismatch (e.g. float[3] to float[4]) cannot be lowered, so Convert throws InvalidOperationException.

Solutions

  1. Make both arrays the same length
  2. Declare the target/parameter size to match the source array (or use the same constant)
  3. Wrap the fixed-size array in a type with matching size before conversion
  4. If the size is genuinely dynamic, model it via generics or a runtime-length construct supported by the compiler

Example fix

// before
float[4] a = (float[4])float3Array; // different sizes
// after
float[3] a = (float[3])float3Array; // sizes match
Defensive patterns

Strategy: validation

Validate before calling

if (valueType is ArrayType a1 && castType is ArrayType a2 && a1.BaseType == a2.BaseType && a1.Size != -1 && a1.Size != a2.Size)
    throw new InvalidOperationException($"Array size mismatch {a1.Size} vs {a2.Size}");

Type guard

bool ArraysMatch(ArrayType a, ArrayType b) => a.BaseType == b.BaseType && (a.Size == -1 || b.Size == -1 || a.Size == b.Size);

Try / catch

try { converted = Builder.Convert(value, castType); }
catch (InvalidOperationException ex) when (ex.Message.Contains("array of different sizes"))
{ /* reject the call or resize the array at source */ }

Prevention

When it happens

Trigger: Calling Convert where both types are ArrayType with equal base types but different known sizes (a1.Size != -1 && a1.Size != a2.Size).

Common situations: Passing an array of the wrong length to a shader function or parameter; changing an array constant's length without updating its consumer; generics instantiation leaving stale sizes.

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/30d563326a7f0f5d. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Expressions.cs:501

    public SpirvValue Convert(SpirvContext context, in SpirvValue value, in SymbolType castType)
    {
        var valueId = value.Id;
        var valueType = context.ReverseTypes[value.TypeId];
        var originalType = valueType;

        // No conversion necessary?
        if (castType == valueType)
            return value;

        if (castType is StructType || valueType is StructType)
            throw new NotImplementedException($"Can't cast between structures (cast from {valueType} to {castType})");

        if (castType is ArrayType a1 && valueType is ArrayType a2)
        {
            if (a1.BaseType == a2.BaseType)
            {
                if (a1.Size != -1 && a1.Size != a2.Size)
                    throw new InvalidOperationException(($"Can't cast between array of different sizes (cast from {valueType} to {castType})"));
                // Some sizes are undetermined; this can only happen during compilation due to incomplete generics type
                // TODO: do a "check pass" later to make sure this won't happen after generics instantiation?
                return value;
            }
            else
            {
                throw new InvalidOperationException(($"Can't cast between array of different types (cast from {valueType} to {castType})"));
            }
        }

        if (castType is StreamsType { Kind: var targetKind }
            && valueType is StreamsType { Kind: var sourceKind }
            // Note: order is Input, Streams, Output
            && targetKind > sourceKind)
        {
            return new(InsertData(new OpCopyLogical(context.GetOrRegister(castType), context.Bound++, value.Id)));
        }

View on GitHub (pinned to 96fad776d2)