{"record":{"id":"35185d8c50d30de9","repo":"stride3d/stride","slug":"no-function-termination-but-a-return-value-is-expected","errorCode":null,"errorMessage":"No function termination, but a return value is expected","messagePattern":"No function termination, but a return value is expected","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Functions.cs","lineNumber":39,"sourceCode":"\n    public void BeginFunction(SpirvContext context, SpirvFunction function, FunctionControlMask mask = FunctionControlMask.None)\n    {\n        Buffer.Add(new OpFunction(context.GetOrRegister(function.FunctionType.ReturnType), function.Id, mask, context.GetOrRegister(function.FunctionType)));\n        Position = Buffer.Count;\n        CurrentFunction = function;\n    }\n\n    public void EndFunction()\n    {\n        if (CurrentFunction is null)\n            throw new InvalidOperationException(\"Trying to end a function which was not started\");\n\n        // If there was no explicit return, add one\n        var lastInstruction = Buffer[Position - 1];\n        if (!IsBlockTermination(lastInstruction.Op))\n        {\n            if (CurrentFunction.Value.FunctionType.ReturnType != ScalarType.Void)\n                throw new InvalidOperationException(\"No function termination, but a return value is expected\");\n\n            Return(null);\n        }\n\n        Buffer.Insert(Position++, new OpFunctionEnd());\n    }\n\n    public SpirvValue EmitFunctionParameter(SpirvContext context, string name, SymbolType type)\n    {\n        var p = Buffer.Insert(Position++, new OpFunctionParameter(context.GetOrRegister(type), context.Bound++));\n        context.AddName(p, name);\n        var value = new SpirvValue(p.ResultId, p.ResultType, name);\n        CurrentFunction!.Value.Parameters.Add(name, value);\n        return value;\n    }\n\n    public static OpFunctionParameter GetFunctionParameter(SpirvBuffer buffer, Symbol method, int functionParameterIndex)\n    {","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Functions.cs#L21-L57","documentation":"EndFunction auto-inserts a Return when the last block isn't terminated. If the function's return type is non-void, no value-less return can be synthesized, so it throws InvalidOperationException: the function body ended without returning the required value.","triggerScenarios":"Emitting a non-void function whose control flow reaches OpFunctionEnd without an OpReturnValue/OpReturn — e.g. all branches returned but a trailing path (or a missed branch) falls through, or Return(value) was never emitted on some path.","commonSituations":"Shader functions with if/else where one branch omits the return, early code-generated functions with dynamic branches (discard/clip) that skip the final return, or translation bugs from HLSL where all paths were assumed to return.","solutions":["Ensure every execution path in the function ends with Return(value) for non-void functions.","Add a final Return(defaultValue) after branch merges as a fallback.","Make helper-generated control flow always terminate each branch with a return.","Verify with SPIR-V validation (spirv-val) during development to catch missing terminators early."],"exampleFix":"// before\nif (cond) { Return(a); } else { Branch(elseBlock); } // falls off the end\n// after\nif (cond) { Return(a); } else { Return(b); } // or: merge block ends with Return(result)","handlingStrategy":"validation","validationCode":"if (functionType.ReturnType != ScalarType.Void) ensureAllPathsReturn(bodyBlocks);","typeGuard":"bool needsExplicitReturn = fn.FunctionType.ReturnType != ScalarType.Void;","tryCatchPattern":"try { builder.EndFunction(); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"return value\")) { /* append a fallback Return(value) before retrying */ }","preventionTips":["Terminate every control-flow path with Return for non-void functions","Add a final fallback Return after branch merges","Run spirv-val on generated modules in CI","Check discard/clip helpers don't skip the return path"],"tags":["spirv","shader-compiler","missing-return"],"backgroundTag":"internal-invariant-violation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}