Skip to content

Fix #893: return trailing expression value from func literals#898

Merged
DavidObando merged 1 commit into
mainfrom
fix/issue-893-func-literal-invalid-il
Jun 20, 2026
Merged

Fix #893: return trailing expression value from func literals#898
DavidObando merged 1 commit into
mainfrom
fix/issue-893-func-literal-invalid-il

Conversation

@DavidObando

Copy link
Copy Markdown
Owner

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. LINQ Single/Where) compiled cleanly but threw System.InvalidProgramException at runtime:

System.InvalidProgramException : Common Language Runtime detected an invalid program.
   at ...<lambda>(DoctorCheck c)
   at System.Linq.Enumerable.Single[TSource](IEnumerable`1, Func`2)

Root cause (why the IL was invalid)

For a function literal func(p T) R { ... <trailing-expr> }, LambdaBinder.BindFunctionLiteralExpression bound the block body verbatim. A bare trailing expression therefore became a BoundExpressionStatementnot a return. The method-body emitter emits an expression statement by evaluating the expression and pop-ing its value, and it only caps a body with a synthetic trailing ret when the function return type is void (ReflectionMetadataEmitter.cs). So a non-void literal like func(c string) bool { c == "network" } produced a method with a bool return signature, no ret that 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 converted return from the trailing expression (EmitTrailingExpression); the func(...) {} block form did not.

The fix

In LambdaBinder.BindFunctionLiteralExpression, after binding the body, rewrite a value-returning literal's trailing bare expression statement into a return statement, converting the expression to the declared return type (SynthesizeFunctionLiteralTrailingReturn). This mirrors the arrow-lambda semantics:

  • Declared return type is a real value type (!= void, != error) and the last statement is a non-void expression statement → it becomes return <converted-expr>.
  • void literals 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).
  • Literals already ending in an explicit return are unaffected.

Tests

test/Compiler.Tests/Emit/Issue893FuncLiteralTrailingReturnEmitTests.cs — each case runs ilverify on the emitted assembly and executes it (asserting no InvalidProgramException):

  • func(c string) bool { c == "network" } passed to LINQ Single (faithful issue shape)
  • func(n int32) bool { n % 2 == 0 } passed to LINQ Where
  • trailing expression with prefix statements
  • trailing expression requiring a widening conversion (int32 -> int64)
  • predicate literal created inside an async function capturing an outer local, passed to Single (closure/state-machine context, matching the issue's async func test)
  • regression guards: void statement-body literal (Cannot pass lambda as parameter to function that receives System.Action #889) and explicit-return literal

Verification

  • 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.
  • Emitted IL now passes ilverify and executes without InvalidProgramException.

Files changed:

  • src/Core/CodeAnalysis/Binding/LambdaBinder.cs
  • test/Compiler.Tests/Emit/Issue893FuncLiteralTrailingReturnEmitTests.cs

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>
@DavidObando DavidObando merged commit 84c66de into main Jun 20, 2026
7 checks passed
@DavidObando DavidObando deleted the fix/issue-893-func-literal-invalid-il branch June 20, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid program using func () R as parameter to method call

1 participant