dotnet / dotnet/fsharp

Delegate returned from inline method is not itself inlined

Open
#12,798 2 comments 0 reactions 0 assignees View on GitHub
Area-Compiler-SRTP Feature Improvement Impact-Low
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.