Fix #893: return trailing expression value from func literals#898
Merged
Conversation
A value-returning function literal `func(p T) R { ... <expr> }` whose block
body ends in a bare trailing expression silently emitted invalid IL: the
trailing expression bound to a discarded `BoundExpressionStatement`, so the
emitted method had a non-void signature but no `ret` returning a value. The
emitter only caps a missing trailing `ret` for void methods, so the body fell
off the end and the CLR rejected it with `System.InvalidProgramException` at
runtime (ilverify likewise flagged it). This surfaced when such a literal was
passed as a `Func<TSource,bool>` predicate to a generic LINQ method such as
`Single` / `Where`.
Fix: in `LambdaBinder.BindFunctionLiteralExpression`, rewrite a value-returning
literal's trailing bare expression statement into a converted `return`
statement (mirroring the arrow-lambda `EmitTrailingExpression` path). Void
literals are left untouched so the issue #889 Action-style statement-body path
keeps emitting a body with no value return.
Adds emit tests covering ilverify + runtime execution for trailing-expression
func-literal predicates passed to LINQ Single/Where, including inside an async
closure context, plus regression guards for void and explicit-return literals.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #893
Symptom
A G# program using a value-returning function literal
func(c DoctorCheck) bool { c.Id == "audible-api" }as a predicate to a generic method (e.g. LINQSingle/Where) compiled cleanly but threwSystem.InvalidProgramExceptionat runtime:Root cause (why the IL was invalid)
For a function literal
func(p T) R { ... <trailing-expr> },LambdaBinder.BindFunctionLiteralExpressionbound the block body verbatim. A bare trailing expression therefore became aBoundExpressionStatement— not a return. The method-body emitter emits an expression statement by evaluating the expression andpop-ing its value, and it only caps a body with a synthetic trailingretwhen the function return type isvoid(ReflectionMetadataEmitter.cs). So a non-voidliteral likefunc(c string) bool { c == "network" }produced a method with aboolreturn signature, noretthat returns a value, and the body fell off the end — invalid IL that ilverify rejects and the CLR refuses to run.The arrow-lambda path (
(c) -> c == "network") already handled this by synthesizing a convertedreturnfrom the trailing expression (EmitTrailingExpression); thefunc(...) {}block form did not.The fix
In
LambdaBinder.BindFunctionLiteralExpression, after binding the body, rewrite a value-returning literal's trailing bare expression statement into areturnstatement, converting the expression to the declared return type (SynthesizeFunctionLiteralTrailingReturn). This mirrors the arrow-lambda semantics:!= void,!= error) and the last statement is a non-voidexpression statement → it becomesreturn <converted-expr>.voidliterals are left untouched, preserving the issue Cannot pass lambda as parameter to function that receives System.Action #889 Action-style statement-body / void-delegate behavior (no implicit value return).returnare unaffected.Tests
test/Compiler.Tests/Emit/Issue893FuncLiteralTrailingReturnEmitTests.cs— each case runs ilverify on the emitted assembly and executes it (asserting noInvalidProgramException):func(c string) bool { c == "network" }passed to LINQSingle(faithful issue shape)func(n int32) bool { n % 2 == 0 }passed to LINQWhereint32->int64)asyncfunction capturing an outer local, passed toSingle(closure/state-machine context, matching the issue'sasync functest)returnliteralVerification
dotnet build GSharp.sln -c Release✅dotnet test GSharp.sln -c Release✅ — Compiler.Tests 1360, Core.Tests 3340 (1 skip), Interpreter.Tests 308, LanguageServer.Tests 337, Extensions.Tests 104, Sdk.Tests 38 — all green.InvalidProgramException.Files changed:
src/Core/CodeAnalysis/Binding/LambdaBinder.cstest/Compiler.Tests/Emit/Issue893FuncLiteralTrailingReturnEmitTests.cs