You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Mutually recursive non-function values with intermediate module definition not initialized incorrectly #12384
typeNode={
Next:Node
Prev:Node
Value:int}let recone={
Next = two
Prev = two
Value =1}andtwo={
Next = one
Prev = one
Value =2}
printfn "%A" one
printfn "%A" two
Expected behavior
Either:
Correctly initialize the mutually recursive values.
Reject the code as being invalid.
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 }
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.
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
}
changed the title [-]Mutually recursive values initialized incorrectly[/-][+]Mutually recursive non-function values with intermediate module definition not initialized incorrectly[/+]on Nov 19, 2021
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?
This issue appears to be fixed in recent versions of the F# compiler. Mutually recursive non-function values are now correctly initialized — one.Next and one.Prev no longer end up as null.
Verified with current compiler:
typeNode={ Next:Node; Prev:Node; Value:int }let recone={ Next = two; Prev = two; Value =1}andtwo={ Next = one; Prev = one; Value =2}
printfn "one.Next is null: %b"(obj.ReferenceEquals(one.Next :> obj,null))// Output: one.Next is null: false ✅
printfn "one.Next.Value = %d" one.Next.Value
// Output: one.Next.Value = 2 ✅ (correctly points to `two`)
Test12384 creates the exact mutually recursive record values from the issue (let rec one = { Next = two; Value = 1 } and two = { Next = one; Value = 2 }). It asserts one.Next.Value = 2 and one.Next.Next.Value = 1, which would fail if one.Next were null as described in the bug.
Additional variants at L671-L700 (with nested module after bindings) and L702-L731 (module rec) cover related configurations.
None of those tests replicate Don's example, which still fails in the latest version of Visual Studio (18.4.1):
modulerecTest12384d =typeNode={
Next:Node
Value:int}letone={
Next = two
Value =1}moduleM =letx()= one
lettwo={
Next = one
Value =2}
printfn "%A" one
printfn "%A" two
Output:
{ Next = null
Value = 1 }
{ Next = { Next = null
Value = 1 }
Value = 2 }
Note that Don even retitled this issue to describe the remaining problem precisely: "Mutually recursive non-function values with intermediate module definition not initialized incorrectly", but none of your tests cover the case of a module defined BETWEEN one and two. In your test file, the failing test (Test12384d) is actually commented out for some reason:
Note, this case doesn't initialize successfully because of the intervening module. Tracked by #12384
Activity
dsyme commentedon Nov 15, 2021
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 commentedon Nov 19, 2021
There is one case not fixed by #12395. I'll leave this open to track this, though won't immediately seek to fix it
[-]Mutually recursive values initialized incorrectly[/-][+]Mutually recursive non-function values with intermediate module definition not initialized incorrectly[/+]plainionist commentedon Mar 23, 2024
I tried this
and i get "Children" printed as "null"
am i doing something wrong or is this bug really still open?
7 remaining items
Fix struct/closure byref capture and let-rec initialization order
Fix struct/closure byref capture and let-rec initialization order
Fix struct/closure byref capture and let-rec initialization order
github-actions commentedon Mar 22, 2026
🤖 This is an automated response from Repo Assist.
This issue appears to be fixed in recent versions of the F# compiler. Mutually recursive non-function values are now correctly initialized —
one.Nextandone.Prevno longer end up asnull.Verified with current compiler:
Maintainer: please verify and close if confirmed.
brianberns commentedon Mar 22, 2026
This does NOT appear to be completely fixed. While my original example now works, Don's example does not.
FWIW, this is the second time in the last few days that this bot has incorrectly prompted me to close an issue.
auduchinok commentedon Mar 23, 2026
@T-Gro The issue is not fixed, per the comment above.
Please, let's not close issues based on the bot responses only?
T-Gro commentedon Mar 24, 2026
Related test:
fsharp/tests/fsharp/core/letrec/test.fsx
Lines 644 to 669 in d04f46f
Test12384creates the exact mutually recursive record values from the issue (let rec one = { Next = two; Value = 1 } and two = { Next = one; Value = 2 }). It assertsone.Next.Value = 2andone.Next.Next.Value = 1, which would fail ifone.Nextwere null as described in the bug.Additional variants at L671-L700 (with nested module after bindings) and L702-L731 (
module rec) cover related configurations.brianberns commentedon Mar 24, 2026
None of those tests replicate Don's example, which still fails in the latest version of Visual Studio (18.4.1):
Output:
Note that Don even retitled this issue to describe the remaining problem precisely: "Mutually recursive non-function values with intermediate module definition not initialized incorrectly", but none of your tests cover the case of a module defined BETWEEN
oneandtwo. In your test file, the failing test (Test12384d) is actually commented out for some reason:T-Gro commentedon Mar 24, 2026
My apologies, reopening
dsyme commentedon Mar 24, 2026
Thanks for the vigilance @auduchinok and @brianberns