stride3d/stride · error · InvalidOperationException

Could not find compositions for expression

Error message

Could not find compositions for expression [{@foreach.Collection}]

What it means

The SDSL mixer expands foreach instructions over composition arrays (streams/buffer compositions). Before expansion it verifies the collection name exists as a CompositionArray in the current mixin node or its Stage. If the name cannot be resolved, the foreach cannot be expanded and mixing fails.

Solutions

  1. Declare the collection as a composition array in the mixin (e.g. stream/composition group) so it can be iterated.
  2. Fix the foreach collection name to match the declared composition array.
  3. Verify the base shader that declares the composition is actually composed into this shader.
  4. Check whether the composition lives in a Stage and that the Stage is attached (Stage != null).

Example fix

// before
foreach (var c in myCompositions) { ... }
// after (declare it in the shader first)
stream MyCompositions[4]; // or composition array declaration
foreach (var c in MyCompositions) { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Before expanding foreach, verify the composition array exists
bool hasComposition = mixinNode.CompositionArrays.ContainsKey(collectionName)
    || (mixinNode.Stage?.CompositionArrays.ContainsKey(collectionName) ?? false);
if (!hasComposition)
    throw new InvalidOperationException($"'{collectionName}' is not a declared composition array");

Try / catch

try { var mixed = mixer.Process(...); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Could not find compositions"))
{
    log.Error(ex, "foreach over a name that is not a composition array; check stream/composition declarations");
    throw;
}

Prevention

When it happens

Trigger: ExpandForeach (called from ProcessMemberAccessAndForeach) hits a foreach whose Collection name is absent from both mixinNode.CompositionArrays and mixinNode.Stage.CompositionArrays.

Common situations: foreach over a composition/stream name misspelled in the shader; the composition was removed or renamed in a base shader; using foreach on a variable that is not a composition array; shader layering (stage) not set up so the stage-side lookup is skipped.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Compilers/SDSL/ShaderMixer.cs:847

        var depth = 1;
        var endIndex = index;
        while (depth > 0 && ++endIndex < buffer.Count - 1)
        {
            if (buffer[endIndex].Op == Op.OpForeachSDSL)
                depth++;
            else if (buffer[endIndex].Op == Op.OpForeachEndSDSL)
                depth--;
        }
        endIndex++;

        if (depth > 0)
            throw new InvalidOperationException("Could not find end of foreach instruction");

        // Check the variable (both in current mixin node or in stage)
        // TODO: should we register Compositions by ID in the global context instead, to avoid having to check Stage all the time?)
        if (!mixinNode.CompositionArrays.TryGetValue(@foreach.Collection, out var compositions)
            && (mixinNode.Stage == null || !mixinNode.Stage.CompositionArrays.TryGetValue(@foreach.Collection, out compositions)))
            throw new InvalidOperationException($"Could not find compositions for expression [{@foreach.Collection}]");

        // Extract foreach buffer (with the foreach start/end)
        var foreachBuffer = buffer[index..endIndex];
        buffer.RemoveRange(index, foreachBuffer.Count, false);

        var foreachBufferCopy = new List<OpData>();
        // Note: Make sure we replace the OpForeachSDSL with a first OpNop, so that if a for() loop works fine and don't miss an instruction without having to do index--
        foreachBufferCopy.Add(new OpData(new OpNop().InstructionMemory));
        for (int j = 0; j < compositions.Length; ++j)
        {
            var idRemapping = new Dictionary<int, int>();

            // Setup variable for iterator access
            var accessChain = new OpAccessChain(0, context.Bound++, @foreach.Collection, [context.CompileConstant(j).Id]);
            foreachBufferCopy.Add(new(accessChain.InstructionMemory));
            idRemapping.Add(@foreach.ResultId, accessChain);

            // Do a first pass to find all IDs (OpBranch might point to OpLabel which are defined further)

View on GitHub (pinned to 96fad776d2)