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:
let f q =
let g (e : #exn option) (props : seq<string>) = 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:
let f q =
let g (e : #exn) (props : seq<string>) = 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:
> let f q =
- let g (e : #exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
- let h () = g None []
- q (fun () -> h ());;
let g (e : #exn option) (props : seq<string>) = 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.
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
- If I change
#exn option
toexn option
, the warning goes away. - If I change
seq<string>
tostring []
,string list
, etc., the warning goes away. - If I remove either parameter (
e
orprops
) fromg
, the warning goes away. - If I call
g
directly in the lambda passed intoq
, the warning goes away. - If I mark
g
asinline
, the warning goes away. - If I pass
h
directly intoq
(asq h
) rather than invoking it inside a lambda (q (fun () -> h ())
), the warning goes away. - 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 ifq
is, e.g., a module-bound function, as long as it is not auto-inlinable, the warning will apply.
Examples:
- No warning if
#exn option
is instead annotated asexn option
(i.e., the actualexn
type rather than#exn
~'a when 'a :> exn
):
let f q =
let g (e : exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
- No warning if
props
is changed to, e.g.,string []
:
let f q =
let g (e : #exn option) (props : string []) = printfn $"{e}"; printfn $"{props}"
let h () = g None [||]
q (fun () -> h ())
- No warning if either parameter is removed:
let f q =
let g (e : #exn option) = printfn $"{e}"
let h () = g None
q (fun () -> h ())
let f q =
let g (props : seq<string>) = printfn $"{props}"
let h () = g []
q (fun () -> h ())
- No warning if
g
is called directly rather than being called viah
:
let f q =
let g (e : #exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
q (fun () -> g None [])
- No warning if
g
is markedinline
:
let f q =
let inline g (e : #exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
- No warning if
h
is passed as a value rather than being closed over and invoked inside of a lambda:
let f q =
let g (e : #exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q h
- No warning if
q
is auto-inlinable:
let q h = h ()
let f () =
let g (e : #exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
let h () = g None []
q (fun () -> h ())
But we do get the warning if q
is not auto-inlinable:
let q h = if System.DateTime.Today.DayOfWeek = System.DayOfWeek.Monday then h () else ()
let f () =
let g (e : #exn option) (props : seq<string>) = 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
):
let f q =
let g (e : #exn) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
let h () = g null []
q (fun () -> h ())
None of these workarounds is ideal:
- In my actual code, I was not trying to be fancy with flexible types; the type
#exn option
for the parametere
was inferred from relatively straightforward usage inside ofg
(something likematch e with Some e -> (* Pass e into a method taking an exception. *) | None -> (* Some other method call *)
). I was definitely not expecting a warning. - The warning does not show in debug mode.
- 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.
- 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.
Metadata
Metadata
Assignees
Type
Projects
Status