FS1103 with local function with constrained generic parameters, but only in release mode
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 131
Description
I have run into a problem where, when compiling in release mode (but not debug), I get the following warning:
> FS1103: Note: Lambda-lifting optimizations have not been applied because of the use of this local constrained generic function as a first class value. Adding type constraints may resolve this condition.
It seems to be due to a very specific (but not very exotic) combination of generic parameters in a local function and how that local function is used.
**Repro steps**
This is the smallest repro I've been able to distill it down to:
```fsharp
let f q =
let g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
```
Actually...
This is even more minimal (`#exn` instead of `#exn option`), but it is farther from what I was actually doing:
```fsharp
let f q =
let g (e : #exn) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g null []
q (fun () -> h ())
```
Note that in my actual code, I had no type annotations for the parameters corresponding to `e` or `props` and was not explicitly using a flexible type for `e`—it was inferred from usage—but explicitly annotating it gives the same outcome.
Put the above snippet in an app and build it in release mode, or just throw it in FSI:
```fsi
> let f q =
- let g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
- let h () = g None []
- q (fun () -> h ());;
let g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
--------^
stdin(2,9): warning FS1103: Note: Lambda-lifting optimizations have not been applied because of the use of this local constrained generic function as a first class value. Adding type constraints may resolve this condition.
val f : q:((unit -> unit) -> 'a) -> 'a
```
**Expected behavior**
I would not expect to get this warning—or at least I would hope to get the same warning in debug mode, so that I'm not surprised when I go to deploy my app...
**Actual behavior**
I get the warning, and I only get it in release mode. The code runs, but the generated code is very odd-looking and unoptimized indeed: [see it in SharpLab](https://edge.sharplab.io/#v2:DYLgZgzgPg9gDgUwHYAIDKBPCAXBBbAWAChjgFsUwUBHFAXmJSZTIoHMUAKBFEFAYgQAPVPGwBLGEgCUXOACd4EXiggJqAHhzzxSDgCpV2HXoB8suigW7sYVABIARAG8EAX0cBuKydsOXCkoejMysKAAWXBYoHAByUjwA2gC6IUy0nGAArqicsgC0phFR0kA).
Also worthy of note is that this warning does _not_ show up in the editor or error list in Visual Studio, and only shows up in the build output.
**Known workarounds**
1. If I change `#exn option` to `exn option`, the warning goes away.
2. If I change `seq` to `string []`, `string list`, etc., the warning goes away.
3. If I remove either parameter (`e` or `props`) from `g`, the warning goes away.
4. If I call `g` directly in the lambda passed into `q`, the warning goes away.
5. If I mark `g` as `inline`, the warning goes away.
6. If I pass `h` directly into `q` (as `q h`) rather than invoking it inside a lambda (`q (fun () -> h ())`), the warning goes away.
7. If `q` is, instead of a first-class function value, rather something like a module-bound function whose body is simple enough to be auto-inlined by the compiler, the warning goes away. However, even if `q` is, e.g., a module-bound function, as long as it is not auto-inlinable, the warning will apply.
Examples:
1. No warning if `#exn option` is instead annotated as `exn option` (i.e., the actual `exn` type rather than `#exn` ~ `'a when 'a :> exn`):
```fsharp
let f q =
let g (e : exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
```
2. No warning if `props` is changed to, e.g., `string []`:
```fsharp
let f q =
let g (e : #exn option) (props : string []) = printfn $"{e}"; printfn $"{props}"
let h () = g None [||]
q (fun () -> h ())
```
3. No warning if either parameter is removed:
```fsharp
let f q =
let g (e : #exn option) = printfn $"{e}"
let h () = g None
q (fun () -> h ())
```
```fsharp
let f q =
let g (props : seq) = printfn $"{props}"
let h () = g []
q (fun () -> h ())
```
4. No warning if `g` is called directly rather than being called via `h`:
```fsharp
let f q =
let g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
q (fun () -> g None [])
```
5. No warning if `g` is marked `inline`:
```fsharp
let f q =
let inline g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
```
6. No warning if `h` is passed as a value rather than being closed over and invoked inside of a lambda:
```fsharp
let f q =
let g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q h
```
7. No warning if `q` is auto-inlinable:
```fsharp
let q h = h ()
let f () =
let g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
```
But we do get the warning if `q` is not auto-inlinable:
```fsharp
let q h = if System.DateTime.Today.DayOfWeek = System.DayOfWeek.Monday then h () else ()
let f () =
let g (e : #exn option) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
```
The warning also still applies even if I unnest the flexible type (`#exn` instead of `#exn option`):
```fsharp
let f q =
let g (e : #exn) (props : seq) = printfn $"{e}"; printfn $"{props}"
let h () = g null []
q (fun () -> h ())
```
None of these workarounds is ideal:
1. In my actual code, I was not trying to be fancy with flexible types; the type `#exn option` for the parameter `e` was inferred from relatively straightforward usage inside of `g` (something like `match e with Some e -> (* Pass e into a method taking an exception. *) | None -> (* Some other method call *)`). I was definitely not expecting a warning.
2. The warning does not show in debug mode.
3. Even in release mode, the warning does not show in the editor or the error list in Visual Studio; it only shows in the build output.
4. The subtle distinctions between the above variations are just that—extremely subtle.
**Related information**
* .NET 5.0.202, Visual Studio 16.9.4, FSI 11.3.2.0 for F# 5.0. The issue is also reproducible using the .NET Core 3.1.x SDK (e.g., in `dotnet fsi`); I have not tried .NET Core versions earlier than that, or .NET Framework.
Contributor guide
Assessment
This issue has not been assessed yet.