Skip to content

FS0420 a remnant of .NET 1.0? Try/With/Finally *can* be placed in contructor. #13533

@abelbraaksma

Description

@abelbraaksma

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 -> ...) sq is legal, but for 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

Type

No type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions