Skip to content

Crash when using code quotations in tests #14225

Description

@tmoers

We do our unit tests using the unquote library to write our assertions. This relies a lot on F# code quotations. Most of the time our tests run just fine but once in a while a test fails with a NullReferenceException. The weird thing is that both the test and the code under test are pure so I find it hard to understand where the flaky behavior comes from.

I've also reported the bug to unquote, but unfortunately the author also doesn't know. Because the stack trace only contains FSharp.Core calls I was hoping maybe someone here might have an idea:

   System.NullReferenceException : Object reference not set to an instance of an object.
   Stack Trace:
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(112,0): at Microsoft.FSharp.Quotations.FSharpVar..ctor(String name, Type typ, FSharpOption`1 isMutable)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2070,0): at Microsoft.FSharp.Quotations.PatternsModule.u_VarDecl@2070.Invoke(BindingEnv env)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2048,0): at Microsoft.FSharp.Quotations.PatternsModule.u_Expr@2048-2.Invoke(BindingEnv env)
     D:\a\_work\1\s\src\FSharp.Core\local.fs(236,0): at Microsoft.FSharp.Primitives.Basics.List.mapToFreshConsTail[a,b](FSharpList`1 cons, FSharpFunc`2 f, FSharpList`1 x)
     D:\a\_work\1\s\src\FSharp.Core\local.fs(246,0): at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2031,0): at Microsoft.FSharp.Quotations.PatternsModule.u_Expr@2030.Invoke(BindingEnv env)
     D:\a\_work\1\s\src\FSharp.Core\local.fs(245,0): at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2031,0): at Microsoft.FSharp.Quotations.PatternsModule.u_Expr@2030.Invoke(BindingEnv env)
     D:\a\_work\1\s\src\FSharp.Core\local.fs(245,0): at Microsoft.FSharp.Primitives.Basics.List.map[T,TResult](FSharpFunc`2 mapping, FSharpList`1 x)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2031,0): at Microsoft.FSharp.Quotations.PatternsModule.u_Expr@2030.Invoke(BindingEnv env)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2059,0): at Microsoft.FSharp.Quotations.PatternsModule.u_Expr@2059-5.Invoke(BindingEnv env)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2568,0): at Microsoft.FSharp.Quotations.PatternsModule.deserialize(Type localAssembly, Type[] referencedTypeDefs, Type[] spliceTypes, FSharpExpr[] spliceExprs, Byte[] bytes)
     D:\a\_work\1\s\src\FSharp.Core\quotations.fs(2781,0): at Microsoft.FSharp.Quotations.FSharpExpr.Deserialize40(Type qualifyingType, Type[] referencedTypes, Type[] spliceTypes, FSharpExpr[] spliceExprs, Byte[] bytes)
        at Tomra.Ts.Core.MapTests.Keys works as expected()

This is the test:

    let [<Fact>] ``Keys works as expected``() =
        let map = Map.ofList [ (1, "One"); (2, "Two"); (3, "Three") ]
        test <@ map |> Map.keys |> List.ofSeq = [1; 2; 3] @>

And this is the code being tested:

namespace global

open System

[<RequireQualifiedAccess>]
module Map =

    let keys (map: Map<_, _>) =
        seq {
            for KeyValue(key, _) in map do
                yield key
        }

Related information

I'm using .NET SDK 6.0.300 and originally experienced the problem when using F# 5. This week we were finally able to switch to F# 6 and I was secretly hoping this might fix the problem but unfortunately it did not.

Activity

  1. added this to the Backlog milestone on Nov 3, 2022
  2. added
    Impact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.
    and removed on Nov 28, 2022
  3. bartelink commented on Oct 10, 2026

    @bartelink
    Contributor

    Still happening in 10.0.303 SwensenSoftware/unquote#160 (comment)

    NRE is on call to getStamp

    static let getStamp =
    let mutable lastStamp = -1L // first value retrieved will be 0
    fun () -> System.Threading.Interlocked.Increment &lastStamp
    static let globals = Dictionary<string * Type, Var>(11)
    let stamp = getStamp ()

    Semi-related: #14362 (re guarantees and/or having warnings re triggering of initialization)

  4. bartelink commented on Oct 10, 2026

    @bartelink
    Contributor

    Test rig with a repro and my best [incomplete] ideas for a fix https://gist.github.com/bartelink/283ee9446a46d5b00a30a944501de611
    tl;dr would involve stamp generation being spread over 2 files - no good ideas
    doing a static member val with DefaultValue has same issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugImpact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.

    Type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions