Fix #889: support arrow lambdas as delegate (System.Action/Func) arguments#895
Merged
Merged
Conversation
…ments
Arrow lambdas (`() -> expr`) could not be passed where a void-returning
delegate such as System.Action was expected, while the `func() { ... }`
statement-body form worked. The root cause is natural typing: an arrow
lambda whose trailing expression yields a value (e.g. `() -> called =
called + 1`) infers a value return type (`() -> int32`), which is not
convertible to `() -> void` (System.Action). The `func` statement-body
form naturally yields void.
The fix target-types/void-izes a func/arrow *literal* to a void-returning
delegate by discarding its trailing value, mirroring C#'s behaviour and
the existing func-statement-body form:
- LambdaBinder: pin inferred return type to void when the target delegate
returns void; the erased adapter rewriter now drops the trailing value
(`return <v>;` -> `<v>; return;`) producing valid void IL.
- ConversionClassifier: void-ize function literals on direct BindConversion
paths (covers `let a Action = () -> ...`).
- OverloadResolver: void-ize literal arguments at user function/constructor/
instance call sites before the wrong-argument-type gate.
- OverloadResolution: add a lowest-priority LambdaToVoidDelegate conversion
kind so a value-returning Func overload still wins betterness, enabling
imported-CLR method resolution (e.g. RegisterRestore(Action)).
- ExpressionBinder.Calls: void-ize literal args on the static imported-call
path, mirroring the instance path.
Restricting void-izing to function *literals* preserves natural typing for
function-typed values and keeps existing overload-resolution behaviour
(e.g. Task.Run(() -> ...) still prefers the Func overload).
Adds Issue889ArrowLambdaActionEmitTests covering variable assignment,
compound assignment, user-function Action/Action<int> args, value-returning
Func natural typing, and an imported static method taking System.Action.
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 #889
Root cause
Arrow lambdas (
() -> expr) could not be passed where a void-returning delegate such asSystem.Actionwas expected, while thefunc() { ... }statement-body form worked.This is a natural-typing gap. An arrow lambda whose trailing expression yields a value — e.g.
() -> called = called + 1(an assignment evaluates toint32) — infers the natural type() -> int32, which is not convertible to() -> void(System.Action). Thefunc() { ... }form has a statement body, so it naturally returns void. This matches C#, wherevar f = () => x++;is aFunc<int>; the fix is therefore target-typing the literal to the delegate parameter, not changing its natural type.Fix
Void-izes a
func/arrow literal to a void-returning delegate by discarding its trailing value — mirroring the existingfuncstatement-body behaviour and C#'s lambda-to-Actionconversion:return <v>;→<v>; return;), producing valid void IL.BindConversionpaths (coverslet a Action = () -> ...).LambdaToVoidDelegateconversion kind so that a value-returningFuncoverload still wins betterness, while enabling imported-CLR method resolution (e.g.CliEnvironment.RegisterRestore(Action)).TryGetDelegateFunctionTypeFromSymbolto resolve the function-type shape of native function types, named delegates, and imported CLR delegates.Void-izing is restricted to function literals, so natural typing for function-typed values is preserved and existing overload-resolution behaviour is unchanged (e.g.
Task.Run(() -> ...)still prefers theFuncoverload).Tests
New
test/Compiler.Tests/Emit/Issue889ArrowLambdaActionEmitTests.cs(CompileAndRun + ilverify) covering:System.Actionvariable (assignment and+=),Action/func(int32),func() int32keeps natural typing,System.Action(mirrors the issue; helper assembly emitted viaPersistedAssemblyBuilder).Local results
dotnet build GSharp.sln -c Release --no-restore -graph→ Build succeeded (0 warnings, 0 errors).dotnet test GSharp.sln -c Release --no-build→ all green: Sdk 38, Extensions 104, Interpreter 308, LanguageServer 326, Core 3340 (1 skipped), Compiler 1347. 0 failures.