locked
Weird inconsistency using Assert RRS feed

  • Question

  • I'm writing some unit tests in F# and have noticed a very strange inconsistency in how the Assert functions work. See below. Why doesn't the first example compile?

            let x = 4
            let y = 4
    
            // Won't compile; error is "The member or object constructor
            // 'IsTrue' does not take 0 argument(s)."
            Assert.IsTrue(x=y)
    
            // Works just fine.
            Assert.IsTrue(4=4)
            Assert.IsFalse(4=5)
    
            // Work as expected, but why the extra parenthesis? What do
            // they signify?
            Assert.IsTrue((x=y)) 
    
            // Works as expected, but how is this different than the 
            // first Assert above?
            Assert.IsTrue(x.GetHashCode()=y.GetHashCode())
    

    Thursday, May 9, 2013 8:37 PM

Answers

  • F# supports named arguments and also something called "settable return properties".  The basic idea is that you can set properties of an object as part of a constructor or method call:

    type T() =
        member val IntValue = 4 with get, set
        static member Create() = T()
    
    let t1 = T(IntValue = 1)
    let t2 = T.Create(IntValue = 4)
    

    Unfortunately, in rare cases such as yours a Boolean expression can look like trying to set an argument by name or performing one of these property assignments, and the compiler won't be able to parse your call as you expect.  The workaround is to parenthesize it, as you've discovered.
    • Proposed as answer by Sehnsucht_Fr Friday, May 10, 2013 10:28 PM
    • Marked as answer by JustinMag Wednesday, May 29, 2013 11:08 PM
    Friday, May 10, 2013 8:49 PM
    Moderator

All replies

  • What library/assembly your Assert object from?

    What F# shows as signature for Assert.IsTrue() function?


    Petr

    Friday, May 10, 2013 1:43 PM
  • I'm using the Assert function from Microsoft.VisualStudio.TestTools.UnitTesting. The signature shows Assert.IsTrue(condition: bool) : unit
    Friday, May 10, 2013 2:29 PM
  • Well it looks like bug to me. For example this function will work:

    let myassert (exp: bool) = if exp = false then failwith "asert" else () 
    myassert(x=y)
    


    Petr

    Friday, May 10, 2013 3:19 PM
  • Microsoft.VisualStudio.TestTools.UnitTesting.Assert.IsTrue(x=y) //error FS0505
    System.Diagnostics.Debug.Assert(x=y) //error FS0505

    but when i write:

    let test = Microsoft.VisualStudio.TestTools.UnitTesting.Assert.IsTrue
    test(x=y)

    and:

    assert(x=y) //'assert' is a keyword

    it works. maybe it's about the syntax analysis, and i found this:

    https://github.com/fsharp/fsharp/blob/master/src/fsharp/tc.fs //line: 6263

    //-------------------------------------------------------------------------
    // TcAssertExpr
    //-------------------------------------------------------------------------
    // Check an 'assert(x)' expression.
    and TcAssertExpr cenv overallTy env (m:range) tpenv x  =
        let synm = m.MakeSynthetic() // Mark as synthetic so the language service won't pick it up.
        let callDiagnosticsExpr = SynExpr.App(ExprAtomicFlag.Atomic, false, mkSynLidGet synm ["System";"Diagnostics";"Debug"] "Assert",
                                               // wrap an extra parentheses so 'assert(x=1) isn't considered a named argument to a method call
                                               SynExpr.Paren(x,range0,None,synm), synm)
        TcExpr cenv overallTy env tpenv callDiagnosticsExpr
    Friday, May 10, 2013 5:23 PM
  • F# supports named arguments and also something called "settable return properties".  The basic idea is that you can set properties of an object as part of a constructor or method call:

    type T() =
        member val IntValue = 4 with get, set
        static member Create() = T()
    
    let t1 = T(IntValue = 1)
    let t2 = T.Create(IntValue = 4)
    

    Unfortunately, in rare cases such as yours a Boolean expression can look like trying to set an argument by name or performing one of these property assignments, and the compiler won't be able to parse your call as you expect.  The workaround is to parenthesize it, as you've discovered.
    • Proposed as answer by Sehnsucht_Fr Friday, May 10, 2013 10:28 PM
    • Marked as answer by JustinMag Wednesday, May 29, 2013 11:08 PM
    Friday, May 10, 2013 8:49 PM
    Moderator