-
Notifications
You must be signed in to change notification settings - Fork 833
Description
In a Slack chat on the F# channel (see this thread, available for a short while I guess), Vincenzo Ciancia noticed that, quote:
I am very surprised that, in a constructor,
Seq.iter (fun x -> ...) sqis legal, butfor x in sq do ...is not legal because it could throw an exception. I would be tempted to believe that the two syntaxes have exactly the same semantics.
The error he talks about is FS0420:
error FS0420: Object constructors cannot directly use try/with and try/finally prior to the initialization of the object. This includes constructs such as 'for x in ...' that may elaborate to uses of these constructs. This is a limitation imposed by Common IL.
I think this error is a remnant of .NET 1.0 times, as this very old test in the repo shows, or if necessary, can so easily be circumvented that the below workarounds are actually bugs. The test literally says:
// #Regression #Diagnostics
// Regression test for FSHARP1.0:1980
In this repo, the error is defined here in FsComp.txt and it is only checked once during post inference checks.
Repro steps
You get FS0420 with the following code for the for .. in do statement (or on SharpLab):
open System.Collections.Generic
type Foo(dict: Dictionary<string, string>) =
new(sq: seq<string * string>) =
let dict = new Dictionary<_, _>()
for x, y in sq do
dict.Add(x, y)
Foo(dict)Rewriting this with then will have the error go away. As does rewriting with Seq.iter, which is basically the same code.
Expected behavior
Both versions should compile. For instance, if we expose the for-loop in an inlined function, or if we use Seq.iter, the error goes away. Inspecting the IL shows that the compiled code places the try-finally before the callvirt call to the constructor (see this Sharplab)
open System.Collections.Generic
module Z =
let inline makeDir (dict: Dictionary<string, string>) sq =
for x, y in sq do
dict.Add(x, y)
type Foo(dict: Dictionary<string, string>) =
new(sq: seq<string * string>) =
let dict = new Dictionary<_, _>()
Z.makeDir dict sq
Foo(dict)Similarly, without for, you can create a small function that gets compiler-inlined (even without inline) into the same body of the contructor where the compiler won't let you write try..with/finally. See this example, which gets inlined automatically:
open System
open System.Collections.Generic
module Z =
let f() =
try
Console.WriteLine("hello world")
finally
()
type Foo(dict: Dictionary<string, string>) =
new(sq: seq<string * string>) =
let dict = new Dictionary<_, _>()
Z.f()
Foo(dict)Actual behavior
FS0420 is raised on code that can easily be circumvented. Comparing this with C# code (which allows try/catch in constructors), it does appear that C# typically compiles the constructor after the IL object construction call, but I didn't test this thoroughly.
It should be noted that FS0420 is not raised when the for-loop compiles to a while loop, as with for x in 1..2 do ....
Known workarounds
Use then, if possible, or place the offending code in an (inline or not) function.
Related information
- Operating system: any
- .NET Runtime kind: any, from .NET 1.0 until .NET 6 and F# 1.0 until latest
- Editing Tools (e.g. Visual Studio Version, Visual Studio): any, it's the compiler complaining
Metadata
Metadata
Assignees
Labels
Type
Projects
Status