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
- Declare the collection as a composition array in the mixin (e.g. stream/composition group) so it can be iterated.
- Fix the foreach collection name to match the declared composition array.
- Verify the base shader that declares the composition is actually composed into this shader.
- 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
- Only use foreach on names declared as composition/stream arrays in the shader.
- Keep collection declarations in the base shader that also contains the foreach.
- Compile shaders as part of CI so bad foreach targets fail at build time, not runtime.
- Avoid renaming composition arrays without updating foreach sites; grep for foreach usages first.
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
- Shader compositions can only be indexed with a constant
- No composition was supplied for
- Composition variable
- Struct not found in shader
- External variable not found
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)