stride3d/stride · error · InvalidOperationException

foreach: Array type is expected

Error message

foreach: Array type is expected

What it means

During symbol processing of a foreach statement, the collection expression's resolved type must be a PointerType whose base is an ArrayType (i.e. iterate over a pointer-to-array). Anything else (scalar, struct, non-array pointer) throws this InvalidOperationException, because foreach expansion in SDSL only supports arrays.

Solutions

  1. Declare the collection as an array (or a pointer to an array) and pass that to foreach.
  2. Check the collection variable's declared type resolves correctly — fix upstream type errors that leave Collection.Type null or wrong.
  3. Rewrite the loop as an explicit for loop with an index if iterating a non-array.

Example fix

// before
foreach (var v in singleValue) { ... }
// after
float values[4];
foreach (var v in values) { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

if (collection.Type is not PointerType { BaseType: ArrayType })
    throw new InvalidOperationException("foreach requires a pointer-to-array collection");

Type guard

bool IsForEachable(SymbolType? t) => t is PointerType { BaseType: ArrayType };

Try / catch

try { foreachStmt.ProcessSymbol(table); }
catch (InvalidOperationException ex) when (ex.Message.Contains("foreach")) { reportDiagnostic(foreachStmt.Info, ex.Message); }

Prevention

When it happens

Trigger: foreach over a non-array collection, e.g. a plain struct, a scalar, or a variable whose type failed to resolve to a pointer-to-array during symbol processing.

Common situations: Porting C#-style foreach loops over lists to SDSL; typos where the collection variable is a single element; forgetting to declare the collection as an array or to take its pointer.

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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/Statements.Flow.cs:68

        builder.Insert(new OpBranch(escapeBlocks.ContinueBlock));
    }
}


public partial class ForEach(TypeName typename, Identifier variable, Expression collection, Statement body, TextLocation info) : Loop(info)
{
    public TypeName TypeName { get; set; } = typename;
    public Identifier Variable { get; set; } = variable;
    public Expression Collection { get; set; } = collection;
    public Statement Body { get; set; } = body;

    public SymbolFrame? SymbolFrame { get; set; }

    public override void ProcessSymbol(SymbolTable table)
    {
        Collection.ProcessSymbol(table);
        if (!(Collection.Type is PointerType p && p.BaseType is ArrayType arrayType))
            throw new InvalidOperationException("foreach: Array type is expected");


        var variableType = new PointerType(arrayType.BaseType, Specification.StorageClass.Function);
        Variable.Type = variableType;

        if (TypeName.Name != "var")
            TypeName.ProcessSymbol(table, variableType);
        else
            TypeName.Type = arrayType.BaseType;

        // TODO: check conversions
        if (variableType.BaseType != TypeName.Type)
            throw new InvalidOperationException("foreach: collection and variable type not matching");

        table.Push();
        var variableSymbol = new Symbol(new(Variable.Name, SymbolKind.Variable), Variable.Type, 0, OwnerType: table.CurrentShader);
        table.CurrentFrame.Add(Variable.Name, variableSymbol);
        Body.ProcessSymbol(table);

View on GitHub (pinned to 96fad776d2)