stride3d/stride · error · InvalidOperationException

Composition variable

Error message

Composition variable {variable.Key} is not an array but had {compositionMixins.Length} entries

What it means

A composition variable not declared as an array (pointer base type is a plain ShaderSymbol, not ArrayType of ShaderSymbol) must receive exactly one mixin. If the supplied composition has more than one entry, MergeMixinNode throws InvalidOperationException because multiple shaders cannot fill a non-array composition slot.

Solutions

  1. Declare the variable as an array composition (`compose MyShader[]`) if multiple entries are intended
  2. Supply exactly one mixin for the non-array composition key
  3. Deduplicate or pick one source when building the composition list

Example fix

// before
shaderClass: `compose ShadingModel Shader;` // with 2 entries supplied
// after
shaderClass: `compose ShadingModel[] Shader;` // or supply a single entry
Defensive patterns

Strategy: validation

Validate before calling

if (!isDeclaredAsArray && compositionEntries.Count != 1)
    throw new InvalidOperationException("Non-array composition needs exactly one entry");

Type guard

bool IsArrayComposition(PointerType p) => p.BaseType is ArrayType { BaseType: ShaderSymbol };

Try / catch

try { result = mixer.MergeSDSL(tree); }
catch (InvalidOperationException ex) when (ex.Message.Contains("is not an array but had")) {
    reduceToOneCompositionEntry(key);
}

Prevention

When it happens

Trigger: Assigning multiple shader sources to a composition key whose declaration is `compose ShaderClass` (not `compose ShaderClass[]`); pushing several mixins into a non-array compose channel during tree building.

Common situations: Programmatically appending multiple effects to a single compose slot; mis-declaring `compose` without `[]` while feeding a list; copy-pasted array composition code applied to a scalar composition.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

        {
            foreach (var variable in shader.Variables)
            {
                if (variable.Value.Type is PointerType pointer && pointer.BaseType is ShaderSymbol or ArrayType { BaseType: ShaderSymbol })
                {
                    // Every piece of context needed to act on this is in scope, and the dictionary
                    // indexer would throw a bare "The given key was not present" instead.
                    if (!mixinSource.Compositions.TryGetValue(variable.Key, out var compositionMixins))
                        throw new InvalidOperationException(
                            $"No composition was supplied for '{variable.Key}', declared as '{variable.Value.Type}' by shader '{shader.ShaderName}', "
                            + $"while merging the mixin node '{currentCompositionPath ?? "<root>"}' (root: {mixinNode.IsRoot}). "
                            + $"That node only has [{string.Join(", ", mixinSource.Compositions.Keys)}]. "
                            + $"A `stage compose` is the usual cause: the shader declaring it was promoted to this node, "
                            + $"but its value was supplied at a nested composition path and nothing carried it up.");

                    var isCompositionArray = pointer.BaseType is ArrayType { BaseType: ShaderSymbol };

                    if (!isCompositionArray && compositionMixins.Length != 1)
                        throw new InvalidOperationException($"Composition variable {variable.Key} is not an array but had {compositionMixins.Length} entries");

                    var compositionResults = new MixinNode[compositionMixins.Length];
                    for (int i = 0; i < compositionMixins.Length; ++i)
                    {
                        var localKey = variable.Key;
                        if (isCompositionArray)
                            localKey += $"[{i}]";
                        // TODO: Review: it seems like Stride compose variable the opposite way that we expect
                        //       Let's change it so that it becomes {currentCompositionPath}.{localKey}!
                        var compositionPath = currentCompositionPath != null ? $"{localKey}.{currentCompositionPath}" : localKey;
                        compositionResults[i] = MergeMixinNode(globalContext, context, buffer, compositionMixins[i], mixinNode.IsRoot ? mixinNode : mixinNode.Stage, compositionPath);
                    }

                    if (isCompositionArray)
                        mixinNode.CompositionArrays.Add(variable.Value.Id, compositionResults);
                    else
                        mixinNode.Compositions.Add(variable.Value.Id, compositionResults[0]);
                }

View on GitHub (pinned to 96fad776d2)