Skip to content

FS1103 with local function with constrained generic parameters, but only in release mode #11478

Open
@brianrourkeboll

Description

@brianrourkeboll

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

  1. If I change #exn option to exn option, the warning goes away.
  2. If I change seq<string> 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):
let f q =
    let g (e : exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
    let h () = g None []
    q (fun () -> h ())
  1. 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 ())
  1. 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 ())
  1. No warning if g is called directly rather than being called via h:
let f q =
    let g (e : #exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
    q (fun () -> g None [])
  1. No warning if g is marked inline:
let f q =
    let inline g (e : #exn option) (props : seq<string>) = printfn $"{e}"; printfn $"{props}"
    let h () = g None []
    q (fun () -> h ())
  1. 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
  1. 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:

  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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions