Delegate returned from inline method is not itself inlined
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 131
Description
This is a bit complex because there are multiple levels of inlining involved. Given the following computation expression, which concatenates strings using a StringBuilder, I would expect all the code to be inlined:
```fsharp
type StringGen = delegate of StringBuilder -> StringBuilder
type StringGenBuilder() =
member inline _.Yield(s: string) =
StringGen(fun sb -> sb.Append(s))
member inline _.Delay([] f: unit -> StringGen) =
StringGen(fun sb -> f().Invoke(sb))
member inline _.Combine([] a1: StringGen, [] a2: StringGen) =
StringGen(fun sb -> a2.Invoke(a1.Invoke(sb)))
member inline _.Run([] a: StringGen) =
a.Invoke(StringBuilder()).ToString()
let stringGen = StringGenBuilder()
let f (x: string) (y: string) =
stringGen {
x + y
x + y
}
```
**Expected behavior**
The code is fully inlined.
**Actual behavior**
The second yield is properly inlined, but the first one instantiates a lambda object. When adding more yields, only the last one is inlined.
```csharp
// Generated code, as decompiled by ILSpy:
public static string f(string x, string y)
{
StringBuilder stringBuilder = new StringBuilder();
string s = x + y;
StringGen stringGen = new f@301-2(s).Invoke; // <-- First yield is not inlined
return stringGen(stringBuilder).Append(x + y).ToString(); // <-- Second yield is inlined
}
```
Presumably this is related to the fact that in the CE desugaring, the last yield is inside a `builder.Delay(fun () -> ...)` whereas previous ones are passed directly as argument to `builder.Combine(...)`:
```fsharp
// Desugared CE:
stringGen.Run(
stringGen.Delay(fun () ->
stringGen.Combine(
stringGen.Yield(x + y),
stringGen.Delay(fun () -> stringGen.Yield(x + y)))))
```
**Known workarounds**
The reason why I believe this is a bug, and not a feature request, is that the inlining works correctly if `Yield` returns an F# function instead of a delegate:
```fsharp
member inline _.Yield(s: string) =
fun (sb: StringBuilder) -> sb.Append(s)
```
```csharp
// Generated code, as decompiled by ILSpy:
public static string f(string x, string y)
{
return new StringBuilder().Append(x + y).Append(x + y).ToString();
}
```
However this workaround is not usable in my case, because I need several Yield overloads that return different types (see [here](https://gist.github.com/Tarmil/afcf5f50e45e90200eb7b01615b0ffc0)), and the workaround breaks as soon as there are several corresponding `Delay` overloads.
**Related information**
* .NET SDK 6.0.200
Contributor guide
Assessment
This issue has not been assessed yet.