dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.87k stars 782 forks source link

Mutually recursive non-function values with intermediate module definition not initialized incorrectly #12384

Open brianberns opened 2 years ago

brianberns commented 2 years ago

Repro steps

Consider the following code:

type Node =
    {
        Next: Node
        Prev: Node
        Value: int
    }

let rec one =
    {
        Next = two
        Prev = two
        Value = 1
    }

and two =
    {
        Next = one
        Prev = one
        Value = 2
    }
printfn "%A" one
printfn "%A" two

Expected behavior

Either:

Actual behavior

The code compiles with no warnings/errors. two is initialized correctly, but one.Next and one.Prev are both null:

{ Next = null
  Prev = null
  Value = 1 }
{ Next = { Next = null
           Prev = null
           Value = 1 }
  Prev = { Next = null
           Prev = null
           Value = 1 }
  Value = 2 }

Known workarounds

None

Related information

Decompiled C# code with incorrect initialization.

Note that a single self-referencing value is actually initialized correctly:

let rec zero =
    {
        Next = zero
        Prev = zero
        Value = 0
    }
dsyme commented 2 years ago

Ugh, thanks for the bug report. We have an extensive systematic test suite somewhere for almost exactly this case and I can't quite believe this one slipped through, but eveidently it did. I'll get it fixed. I'm impressed you found this.

dsyme commented 2 years ago

There is one case not fixed by #12395. I'll leave this open to track this, though won't immediately seek to fix it

module rec Test12384d =
    type Node =
        {
            Next: Node
            Value: int
        }

    let one =
        {
            Next = two
            Value = 1
        }

    module M =
        let x() = one

    let two =
        {
            Next = one
            Value = 2
        }
plainionist commented 6 months ago

I tried this

  type Parent = {
      Name : string
      Age : int
      Children : Child list }
  and Child = {
      Name : string
      Parent : Parent }    

  let createParent name children = 
    { Name = name
      Age = 42
      Children = children }

  let createChild name parent = 
    { Name = name
      Parent = parent }

  let create name kids =
      let rec makeChild name = createChild name parent
      and makeParent name = createParent name children
      and parent = name |> makeParent 
      and children = kids |> List.map makeChild
      parent

  create "Peter" [ "Sarah"; "Max" ]
  |> printfn "%A"

and i get "Children" printed as "null"

am i doing something wrong or is this bug really still open?