stride3d/stride · error · InvalidOperationException

could not resolve symbol

Error message

{this} could not resolve symbol

What it means

Thrown by CompileSymbol when the AST node's ResolvedSymbol property is null, i.e. symbol resolution (name binding) failed but the node still reached SPIR-V emission. The compiler cannot emit code for an identifier it could not bind to a declared symbol.

Solutions

  1. Fix the shader source so the identifier refers to a declared, in-scope symbol
  2. Ensure symbol resolution runs (and reports errors) before SPIR-V compilation
  3. Check inheritance/mixin setup so referenced stage variables are actually imported

Example fix

// before
float c = undeclaredVar; // ResolvedSymbol stays null
// after
float declaredVar = 1.0;
float c = declaredVar;
Defensive patterns

Strategy: validation

Validate before calling

if (node.ResolvedSymbol is null)
    table.AddError(new(node.Info, SDSLErrorMessages.SDSL0110)); // report instead of emitting

Type guard

bool IsResolved(ShaderLiteral node) => node.ResolvedSymbol is not null;

Try / catch

try { node.CompileSymbol(table, builder, context, constantOnly); }
catch (InvalidOperationException ex) { table.AddError(new(node.Info, ex.Message)); }

Prevention

When it happens

Trigger: Compiling a ShaderLiteral/identifier node whose name did not match any declaration in the SymbolTable, so the earlier resolution pass left ResolvedSymbol null; calling CompileSymbol directly without running resolution.

Common situations: Typos in variable/function names in SDSL code; referencing symbols from a mixin/inherit chain that was not fully imported; using a variable before its declaration; SDSL0110-style lookup failures silently ignored upstream.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/Literals.cs:440

            base.SetValue(table, compiler, rvalue);
            return;
        }

        rvalue = builder.Convert(context, rvalue, ((PointerType)Type).BaseType);
        builder.Insert(new OpStore(target.Id, rvalue.Id, null, []));
    }

    public override SpirvValue CompileImpl(SymbolTable table, CompilerUnit compiler)
    {
        var (builder, context) = compiler;

        return CompileSymbol(table, builder, context, builder.CurrentFunction == null);
    }

    protected virtual SpirvValue CompileSymbol(SymbolTable table, SpirvBuilder builder, SpirvContext context, bool constantOnly)
    {
        if (ResolvedSymbol is null)
            throw new InvalidOperationException($"{this} could not resolve symbol");

        var symbol = ShaderDefinition.ImportSymbol(table, context, ResolvedSymbol);

        // Track when a stage method accesses a non-stage variable (without composition qualifier).
        // This forces the shader to be fully imported at root level instead of stage-only during mixin.
        if (symbol.MemberAccessWithImplicitThis != null && !symbol.Id.IsStage && builder.CurrentFunction is { IsStage: true })
        {
            var varOwner = symbol.OwnerType;
            if (varOwner != null && varOwner != table.CurrentShader)
            {
                foreach (var inst in context)
                {
                    if (inst.Op == Spirv.Specification.Op.OpMixinInheritSDSL && (OpMixinInheritSDSL)inst is { } inherit
                        && table.ResolveShader(inherit.Shader) is { } lss && lss.Name == varOwner.Name)
                    {
                        inherit.Flags |= Spirv.Specification.MixinInheritFlagsMask.NeedsFullImport;
                        break;
                    }

View on GitHub (pinned to 96fad776d2)