module Fable.Tests.TypeTests

open System
open System.Runtime.InteropServices
open Fable.Core
open Util.Testing

// Check if custom attributes can be created
type MyAttribute() =
    inherit System.Attribute()

type ITest = interface end
type ITest2 = interface end

type ITest3 =
    abstract Add2: int * int -> int

type ITest4 =
    inherit ITest3
    abstract Add: int * int -> int

type TestType(s: string) =
    member _.Value = s
    interface ITest

type TestType2(s: string) =
    member _.Value = s
    interface ITest

type TestType3() =
    member _.Value = "Hi"
    interface ITest

type TestType4() =
    inherit TestType3()
    member _.Value2 = "Bye"
    interface ITest2

type TestType5(greeting: string) =
    member _.Value = greeting
    member _.Overload(x) = x + x
    member _.Overload(x, y) = x + y

type TestType8(?greeting) =
    member _.Value = defaultArg greeting "Hi"

type TestType9() =
    inherit TestType8()
    let foo = TestType8("Hello")
    member _.Greet(name) = foo.Value + " " + name

type TestType10Base() =
    interface ITest4 with
        member _.Add2(x, y) = x - y
        member _.Add(x, y) = x + y

type TestType10Child() =
    inherit TestType10Base()

type RenderState =
    { Now : int
      Players : Map<int, string>
      Map : string }

type T4 = TestType4

type TestType6(x: int) =
    let mutable i = x
    member val Value1 = i with get, set
    member _.Value2 = i + i
    member _.Value3 with get() = i * i and set(v) = i <- v

type TestType7(a1, a2, a3) =
    let arr = [|a1; a2; a3|]
    member _.Value with get(i) = arr.[i] and set(i) (v) = arr.[i] <- v

[<Fable.Core.AttachMembers>]
type TestTypeAttached(a1, a2, a3) =
    let arr = [| a1; a2; a3 |]
    member _.Value1
        with get () = arr.[1]
        and set (v) = arr.[1] <- v
    member _.Value
        with get (i) = arr.[i]
        and set (i) (v) = arr.[i] <- v
    member _.Item
        with get (i) = arr.[i]
        and set (i) (v) = arr.[i] <- v

type ITestProps =
    abstract Value1: float with get, set
    abstract Value: int -> float with get, set
    abstract Item: int -> float with get, set

type TestProps(arr: float[]) =
    interface ITestProps with
        member _.Value1
            with get () = arr.[1]
            and set (v) = arr.[1] <- v
        member _.Value
            with get (i) = arr.[i]
            and set (i) (v) = arr.[i] <- v
        member _.Item
            with get (i) = arr.[i]
            and set (i) (v) = arr.[i] <- v

type A  = { thing: int } with
    member x.show() = string x.thing
    static member show (x: A) = "Static: " + (string x.thing)

type B  = { label: string } with
    member x.show() = x.label
    static member show (x: B) = "Static: " + x.label

let inline show< ^T when ^T : (member show : unit -> string)> (x:^T) : string =
   (^T : (member show : unit -> string) (x))

let inline showStatic< ^T when ^T : (static member show : ^T -> string)> (x:^T) : string =
   (^T : (static member show : ^T -> string) (x))

[<AllowNullLiteral>]
type Serializable(?i: int) =
    let mutable deserialized = false
    let mutable publicValue = 1
    let mutable privateValue = defaultArg i 0
    member x.PublicValue
        with get() = publicValue
        and set(i) = deserialized <- true; publicValue <- i
    override x.ToString() =
        sprintf "Public: %i - Private: %i - Deserialized: %b"
                publicValue privateValue deserialized

type SecondaryCons(x: int) =
    new () = SecondaryCons(5)
    member _.Value = x

// type SecondaryConsChild() =
//     inherit SecondaryCons()

type MultipleCons(x: int, y: int) =
    new () = MultipleCons(2,3)
    new (x:int) = MultipleCons(x,4)
    member _.Value = x + y

#if !FABLE_COMPILER_TYPESCRIPT
[<AbstractClass>]
type AbstractClassWithDefaults () =
    abstract MethodWithDefault : unit -> string
    default x.MethodWithDefault () = "Hello "

    abstract MustImplement: unit -> string

    member x.CallMethodWithDefault () =
        x.MethodWithDefault() + x.MustImplement()

type ConcreteClass () =
    inherit AbstractClassWithDefaults()
    override x.MustImplement () = "World!!"

type ConcreteClass2 () =
    inherit AbstractClassWithDefaults()
    override x.MethodWithDefault () = "Hi "
    override x.MustImplement () = "World!!"
#endif

[<AbstractClass>]
type AbstractClass3() =
    abstract MyProp: int with get, set

type ConcreteClass3() =
    inherit AbstractClass3()
    let mutable v = 5
    override __.MyProp with get() = v and set(v2) = v <- v + v2

type ISomeInterface =
    abstract OnlyGetProp: int with get
    abstract OnlyProp: int
    abstract Sender : int with get, set

type XISomeInterface () =
    let mutable i = 0
    interface ISomeInterface with
        member x.OnlyGetProp
            with get () = 0
        member x.OnlyProp = 3
        member x.Sender
            with get () = i
            and set i' = i <- i'

type IFoo =
    abstract Foo: unit -> string
    abstract Bar: string
    abstract MySetter: int with get, set

let mangleFoo(x: IFoo) = x.Foo()

type FooImplementor(i: int) =
    let mutable mut1 = 0
    let mutable mut2 = 5
    new () = FooImplementor(1)

    member x.Foo() = String.replicate i "foo"
    member x.Bar = "he"
    member x.MySetter with get() = mut1 and set(v) = mut1 <- v + 2

    interface IFoo with
        member x.Foo() = x.Foo() + "bar"
        member x.Bar = x.Bar + "ho"
        member x.MySetter with get() = mut1 + mut2 and set(v) = mut2 <- v + 3

type FooImplementorChild() =
    inherit FooImplementor(3)

#if !FABLE_COMPILER_TYPESCRIPT
[<AbstractClass>]
type AbstractFoo() =
    abstract member Foo2: unit -> string
    interface IFoo with
        member this.Foo() = this.Foo2() + "FOO"
        member x.Bar = ""
        member x.MySetter with get() = 0 and set(v) = ()

type ChildFoo() =
    inherit AbstractFoo()
    override this.Foo2() = "BAR"
#endif

type BaseClass (x: int) =
    abstract member Init: unit -> int
    default __.Init () = x
    abstract member Prop: string
    default __.Prop = "base"

type ExtendedClass () =
    inherit BaseClass(5)
    override __.Init() = base.Init() + 2
    override __.Prop = base.Prop + "-extension"

type BaseClass2() =
    let field = 1
    member _.A() = field

type ExtendedClass2() =
    inherit BaseClass2()
    member _.A() = 2
    member _.B() = base.A()

type Employee = { name: string; age: float; location: Location }
and Location = { name: string; mutable employees: Employee list }

[<Struct>]
type ValueType<'T> =
    new (v) = { value = v }
    val value : 'T
    member x.Value = x.value

[<Struct>]
type ValueType1<'T>(value: 'T) =
    member x.Value = value

[<Struct>]
type ValueType2(i: int, j: int) =
    member x.Value = i + j

#if !FABLE_COMPILER_TYPESCRIPT
[<Struct>]
type ValueType3 =
  struct
    val mutable public X : int
  end
#endif

[<Struct>]
type StructUnion = Value of string

[<Struct>]
type SimpleRecord = { A: string; B: string }

type Point2D =
   struct
      val X: float
      val Y: float
      new(xy: float) = { X = xy; Y = xy }
   end

exception MyEx of int*string

type MyEx2(f: float) =
  inherit exn(sprintf "Code: %i" (int f))
  member _.Code = f

type ThisContextInConstructor(v) =
    let f () = v
    member val Value = f

type DowncastTest(value: int) =
    member _.Value = value
    interface System.IDisposable with
        member _.Dispose() = ()

[<Class>]
type TypeWithClassAttribute =
    val Pos : int
    new (pos) = { Pos=pos }

// -------------------------------------------------------------
// Issue #1975: https://github.com/fable-compiler/Fable/issues/1975
// In previous version of Fable, using type with parameterized units of measure was causing an endless loops in the compiler

type TestTypeWithParameterizedUnitMeasure<[<Measure>] 't> =
    private | TestTypeWithParameterizedUnitMeasureType of float<'t>

    member this.Value =
        match this with
        | TestTypeWithParameterizedUnitMeasureType value -> value

let makeTestTypeWithParameterizedUnitMeasureType (value: float<_>) : TestTypeWithParameterizedUnitMeasure<_> =
    TestTypeWithParameterizedUnitMeasureType value

open FSharp.Data.UnitSystems.SI.UnitSymbols

type Test_TestTypeWithParameterizedUnitMeasure = {
    Field: TestTypeWithParameterizedUnitMeasure<m>
}

// -------------------------------------------------------------

// Test ported from https://github.com/fable-compiler/Fable/pull/1336/files
// type TestTypeWithDefaultValue() =
//     [<DefaultValue>] val mutable IntValue: int
//     [<DefaultValue>] val mutable StringValue: string
//     [<DefaultValue>] val mutable ObjValue: System.Collections.Generic.Dictionary<string, string>

type Default1 = int

type Distinct1 =
    // Overloads only distinguished by generic constrain work, see #1908
    static member inline Distinct1 (x: ^``Collection<'T>``, _impl: Default1) = (^``Collection<'T>`` : (static member Distinct1 : _->_) x) : '``Collection<'T>``
    static member inline Distinct1 (_: ^t when ^t : null and ^t : struct, _mthd: Default1) = id //must

    // Overloads only distinguished by struct tuple work, see #2417
    static member OfList(_elements: list<'K * 'V>) = ()
    static member OfList(_elements: list<struct('K * 'V)>) = ()

type InfoA = {
    Foo: string
}

type InfoB = {
    InfoA: InfoA
    Bar: string
}

#if !FABLE_COMPILER_TYPESCRIPT
[<AbstractClass>]
type InfoAClass(info: InfoA) =
    abstract WithInfo: InfoA -> InfoAClass
    member _.Foo = info.Foo
    member this.WithFoo foo =
        this.WithInfo({ info with Foo = foo })

type InfoBClass(info: InfoB) =
    inherit InfoAClass(info.InfoA)
    override this.WithInfo(infoA) =
        InfoBClass({ info with InfoA = infoA }) :> InfoAClass

type FooInterface =
    abstract Foo: string with get, set
    abstract DoSomething: f: (float -> float -> float) * v: float -> float
    abstract Item: int -> char with get, set
    abstract Sum: [<ParamArray>] items: string[] -> string

[<Fable.Core.Mangle>]
type BarInterface =
    abstract Bar: string with get, set
    abstract DoSomething: f: (float -> float -> float) * v: float -> float
    abstract Item: int -> char with get, set
    abstract Item: char -> bool with get
    abstract Sum: [<ParamArray>] items: string[] -> string

[<AbstractClass>]
type FooAbstractClass(x: float) =
    member _.Value = x
    member _.DoSomething(x, y) = x * y
    abstract DoSomething: float -> float

type FooClass(x) =
    inherit FooAbstractClass(5.)
    let mutable x = x
    override this.DoSomething(x) =
        this.DoSomething(x, this.Value)
    static member ChangeChar(s: string, i: int, c: char) =
        s.ToCharArray() |> Array.mapi (fun i2 c2 -> if i = i2 then c else c2) |> String
    interface FooInterface with
        member _.Foo with get() = x and set(y) = x <- y
        member this.DoSomething(f, x) =
            let f = f x
            let x = f 2.
            let y = f 8.
            this.DoSomething(x + y)
        member _.Item with get(i) = x.[i] and set i c = x <- FooClass.ChangeChar(x, i, c)
        member _.Sum(items) = Array.reduce (fun x y -> x + y + x + y) items

[<AbstractClass>]
type BarAbstractClass(x: float) =
    member _.Value = x
    member _.DoSomething(x, y) = x ** y
    abstract DoSomething: float -> float

type BarClass(x) =
    inherit BarAbstractClass(10.)
    let mutable x = x
    override this.DoSomething(x) =
        this.DoSomething(x, this.Value)
    interface BarInterface with
        member _.Bar with get() = x and set(y) = x <- y
        member this.DoSomething(f, x) =
            let f = f x
            let x = f 4.5
            let y = f 7.
            this.DoSomething(x - y)
        member _.Item with get(i) = x.[i] and set i c = x <- FooClass.ChangeChar(x, i + 1, c)
        member _.Item with get(c) = x.ToCharArray() |> Array.exists ((=) c)
        member _.Sum(items) = Array.reduce (fun x y -> x + x + y + y) items
#endif

type Interface2 =
    abstract Value: int
    abstract Add: unit -> int

type Interface1 =
    abstract Create: int -> Interface2

type MixedThese(x: int) =
    member _.Value = x
    interface Interface1 with
        member this1.Create(y: int) =
            { new Interface2 with
                member _.Value = y
                member this2.Add() = this1.Value + this2.Value }

let areEqual (x: obj) (y: obj) =
    x = y

type MyUnion1 = Foo of int * int | Bar of float | Baz
type MyUnion2 = Foo of int * int
    with override _.ToString() = "ffff"

type MyRecord1 = { Foo: int; Bar: string }
type MyRecord2 = { Foo: int; Bar: string }

type SubclassTest1() = class end
type SubclassTest2() = inherit SubclassTest1()
type SubclassTest3() = inherit SubclassTest2()

[<Measure>] type a
[<Measure>] type b
[<Measure>] type c = a * b
[<Measure>] type d = a / b

type MeasureTest<[<Measure>] 'T> = { X: float<'T> }
type MeasureTestGen<[<Measure>] 'T, 'V> = { X: float<'T>; Y: 'V }

type MeasureTest1_ = { Y: MeasureTestGen<a, int> }
type MeasureTest1 = { Y: MeasureTest<a*b> }
type MeasureTest2 = { Y: float<c> }
type MeasureTest3 = { Y: MeasureTest<a/b> }
type MeasureTest4 = { Y: float<d> }

// Check that types with product measures compile, see #2532
type MeasureTest5 = { Y: MeasureTest<c> }
type MeasureTest6 = { Y: MeasureTest<d> }

type EnumFoo =
  | Foo = 0
  | Bar = 1

[<AbstractClass>]
type MangledAbstractClass1() =
    class end

[<AbstractClass>]
type MangledAbstractClass2(v: int) =
    inherit MangledAbstractClass1()
    abstract MyMethod: int -> int
    default _.MyMethod(x: int) = x * v

[<AbstractClass>]
type MangledAbstractClass3(v) =
    inherit MangledAbstractClass2(v + 3)

[<AbstractClass>]
type MangledAbstractClass4(v) =
    inherit MangledAbstractClass3(v + 4)
    override _.MyMethod(x: int) = base.MyMethod(x) - v

[<AbstractClass>]
type MangledAbstractClass5(v) =
    inherit MangledAbstractClass4(v + 5)
    override _.MyMethod(x: int) = base.MyMethod(x) + v + 7

type ConcreteClass1() =
    inherit MangledAbstractClass5(2)

type IndexedProps(v: int) =
    let mutable v = v
    member _.Item with get (v2: int) = v + v2 and set v2 (s: string) = v <- v2 + int s
    member _.Item with get (v2: float) = float v + v2 / 2.

[<Interface>]
type ITesting =
    static member Testing x = x

// type IOne =
//     static abstract member GetSum: int * int -> int
//     static abstract member GetOne: unit -> int
//     static abstract member Two: int

// type I32() =
//     interface IOne with
//         static member GetSum(x, y) = x + y
//         static member GetOne() = 1
//         static member Two = 2

// let getSum<'T when 'T :> IOne>() = 'T.GetSum(1, 2)
// let getOne<'T when 'T :> IOne>() = 'T.GetOne()
// let getTwo<'T when 'T :> IOne>() = 'T.Two

type TypeWithByRefMember() =
    static member DoubleIntByRef (x: byref<int>) : unit = x <- 2 * x

let inline doubleIntByRef (x: ^a) (input: int) : int =
    let mutable value = input
    (^a: (static member DoubleIntByRef: byref<int> -> unit) &value)
    value

let byrefFunc(n: byref<int>) =
    n <- n + n

let inline callWithByrefCreatedFromByrefInlined(n: byref<int>) =
    let f = &n
    byrefFunc &f

let inline inlinedFunc(n: 't[]) =
    n.Length

let genericByrefFunc(n: byref<'t[]>) =
    inlinedFunc n

[<AttachMembersAttribute>]
type MyOptionalClass(?arg1: float, ?arg2: string, ?arg3: int) =
    member val P1 = defaultArg arg1 1.0
    member val P2 = defaultArg arg2 "1"
    member val P3 = defaultArg arg3 1

type VeryOptionalInterface =
    abstract Bar: int option
    abstract Baz: bool option
    abstract Bax: float option
    abstract Wrapped: unit option
    abstract Foo: string option with get, set
    abstract Fn: (int -> int -> int) option
    abstract Fn2: (int -> int -> int)

type VeryOptionalClass () =
    let mutable foo = "ab"
    interface VeryOptionalInterface with
        member _.Bar = Some 3
        member _.Baz = None
        member _.Wrapped = Some ()
        member _.Bax =
            let mutable h = ["6"] |> List.tryHead
            h |> Option.map float
        member _.Foo
            with get() = Some foo
            and set(v) = foo <- match v with Some v -> foo + v | None -> foo
        member _.Fn = Some (+)
        member _.Fn2 = (*)

type StaticClass =
    [<NamedParams>]
    static member NamedParams(foo: string, ?bar: int) = int foo + (defaultArg bar -3)
    [<NamedParams>]
    static member NamedParams(?name : string, ?age: int) =
        let name = defaultArg name "John"
        let age = defaultArg age 30
        $"%s{name} is %d{age} years old"
    static member FSharpOptionalParam(?value: bool) = defaultArg value true
    static member FSharpOptionalParam2(?value: unit) = Option.isSome value
    static member DefaultParam([<Optional; DefaultParameterValue(true)>] value: bool) = value
    static member DefaultParam2([<Optional>] value: Nullable<int>) = if value.HasValue then value.Value + 2 else 3
    static member DefaultNullParam([<Optional; DefaultParameterValue(null:obj)>] x: obj) = x
    static member inline InlineAdd(x: int, ?y: int) = x + (defaultArg y 2)

let tests =
  testList "Types" [

    testCase "Optional arguments work" <| fun () ->
        let x = MyOptionalClass(?arg2 = Some "2")
        (x.P1, x.P2, x.P3) |> equal (1.0, "2", 1)
        let y = MyOptionalClass(2.0)
        (y.P1, y.P2, y.P3) |> equal (2.0, "1", 1)
        let z = MyOptionalClass(?arg3 = Some 2)
        (z.P1, z.P2, z.P3) |> equal (1.0, "1", 2)

    testCase "Can implement interface optional properties" <| fun () ->
        let veryOptionalValue = VeryOptionalClass() :> VeryOptionalInterface
        veryOptionalValue.Bar |> equal (Some 3)
        veryOptionalValue.Baz |> Option.isSome |> equal false
        veryOptionalValue.Wrapped |> Option.isSome |> equal true
        veryOptionalValue.Bax |> equal (Some 6.)
        veryOptionalValue.Foo <- Some "z"
        veryOptionalValue.Foo |> equal (Some "abz")
        veryOptionalValue.Foo <- None
        veryOptionalValue.Foo |> equal (Some "abz")
        let fn = veryOptionalValue.Fn
        let fn2 = veryOptionalValue.Fn2
        let f1 = fn |> Option.map (fun f -> f 6 9) |> Option.defaultValue -3
        let f2 = match fn with Some f -> f 1 8 | None -> -5
        f1 + f2 - fn2 9 3 |> equal -3

    testCase "Can implement interface optional properties with object expression" <| fun () ->
        let veryOptionalValue =
            let mutable foo = "ab"
            { new VeryOptionalInterface with
                member _.Bar = Some 3
                member _.Baz = None
                member _.Wrapped = Some ()
                member _.Bax = ["6"] |> List.tryHead |> Option.map float
                member _.Foo
                    with get() = Some foo
                    and set(v) = foo <- match v with Some v -> foo + v | None -> foo
                member _.Fn = Some (+)
                member _.Fn2 = (*)
            }

        veryOptionalValue.Bar |> equal (Some 3)
        veryOptionalValue.Baz |> Option.isSome |> equal false
        veryOptionalValue.Wrapped |> Option.isSome |> equal true
        veryOptionalValue.Bax |> equal (Some 6.)
        veryOptionalValue.Foo <- Some "z"
        veryOptionalValue.Foo |> equal (Some "abz")
        veryOptionalValue.Foo <- None
        veryOptionalValue.Foo |> equal (Some "abz")
        let fn = veryOptionalValue.Fn
        let fn2 = veryOptionalValue.Fn2
        let f1 = fn |> Option.map (fun f -> f 6 9) |> Option.defaultValue -3
        let f2 = match fn with Some f -> f 1 8 | None -> -5
        f1 + f2 - fn2 9 3 |> equal -3

    testCase "Optional named params work" <| fun () ->
        StaticClass.NamedParams(foo="5", bar=4) |> equal 9
        StaticClass.NamedParams(foo="3", ?bar=Some 4) |> equal 7
        StaticClass.NamedParams(foo="14") |> equal 11
        StaticClass.NamedParams() |> equal "John is 30 years old"

    testCase "F# optional param works" <| fun () ->
        let mutable f1 = fun (v: bool) ->
            StaticClass.FSharpOptionalParam(value=v)
        let mutable f2 = fun (v: bool option) ->
            StaticClass.FSharpOptionalParam(?value=v)
        StaticClass.FSharpOptionalParam() |> equal true
        StaticClass.FSharpOptionalParam(true) |> equal true
        StaticClass.FSharpOptionalParam(false) |> equal false
        StaticClass.FSharpOptionalParam(?value=None) |> equal true
        StaticClass.FSharpOptionalParam(?value=Some true) |> equal true
        StaticClass.FSharpOptionalParam(?value=Some false) |> equal false
        f1 true |> equal true
        f1 false |> equal false
        None |> f2 |> equal true
        Some false |> f2 |> equal false
        Some true |> f2 |> equal true

    testCase "F# optional param works with no-wrappable options" <| fun () ->
        let mutable f1 = fun (v: unit) ->
            StaticClass.FSharpOptionalParam2(value=v)
        let mutable f2 = fun (v: unit option) ->
            StaticClass.FSharpOptionalParam2(?value=v)
        StaticClass.FSharpOptionalParam2() |> equal false
        StaticClass.FSharpOptionalParam2(()) |> equal true
        StaticClass.FSharpOptionalParam2(?value=None) |> equal false
        StaticClass.FSharpOptionalParam2(?value=Some ()) |> equal true
        f1 () |> equal true
        None |> f2 |> equal false
        Some () |> f2 |> equal true

    testCase "DefaultParameterValue works" <| fun () ->
        StaticClass.DefaultParam() |> equal true
        StaticClass.DefaultParam(true) |> equal true
        StaticClass.DefaultParam(false) |> equal false

        StaticClass.DefaultParam2(5) |> equal 7
        StaticClass.DefaultParam2(Nullable()) |> equal 3
        StaticClass.DefaultParam2() |> equal 3

    testCase "DefaultParameterValue works with null" <| fun () -> // See #3326
        StaticClass.DefaultNullParam() |> isNull |> equal true
        StaticClass.DefaultNullParam(5) |> isNull |> equal false

    testCase "Inlined methods can have optional arguments" <| fun () ->
        StaticClass.InlineAdd(2, 3) |> equal 5
        StaticClass.InlineAdd(5) |> equal 7

    // TODO: This test produces different results in Fable and .NET
    // See Fable.Transforms.FSharp2Fable.TypeHelpers.makeTypeGenArgs
    // testCase "Reflection for types with measures work" <| fun () ->
    //     Reflection.FSharpType.GetRecordFields(typeof<MeasureTest1_>)
    //     |> Array.item 0
    //     |> fun fi -> fi.PropertyType.GetGenericArguments().Length
    //     |> equal 1

    testCase "Indexed properties work" <| fun () ->
        let f = IndexedProps(5)
        f[4] |> equal 9
        f[3] <- "6"
        f[4] |> equal 13
        f[4.] |> equal 11

    testCase "Static interface members work" <| fun () ->
        let a = ITesting.Testing 5
        a |> equal 5

    // testCase "Static interface calls work" <| fun () ->
    //     getOne<I32>() |> equal 1
    //     getTwo<I32>() |> equal 2
    //     getSum<I32>() |> equal 3

    testCase "Types can instantiate their parent in the constructor" <| fun () ->
        let t = TestType9()
        t.Greet("Maxime") |> equal "Hello Maxime"

    testCase "Type testing" <| fun () ->
        let x = TestType "test" :> obj
        let y = TestType2 "test" :> obj
        x :? TestType |> equal true
        x :? TestType2 |> equal false
        y :? TestType |> equal false
        y :? TestType2 |> equal true

    testCase "Type testing in pattern matching" <| fun () ->
        let x = TestType "test" :> obj
        match x with
        | :? TestType as x -> x.Value
        | _ -> "FAIL"
        |> equal "test"
        match x with
        | :? TestType2 as x -> x.Value
        | _ -> "FAIL"
        |> equal "FAIL"

    // TODO: Should we make interface testing work in Fable 2?
    // testCase "Children inherits parent interfaces" <| fun () ->
    //     let t4 = TestType4() |> box
    //     t4 :? ITest |> equal true

    // testCase "Interface testing" <| fun () ->
    //     let x = Union1 "test" :> obj
    //     let y = Union2 "test" :> obj
    //     x :? ITest |> equal true
    //     x :? ITest2 |> equal false
    //     y :? ITest |> equal true
    //     y :? ITest2 |> equal false

    // testCase "Interface testing in pattern matching" <| fun () ->
    //     let x = Union2 "test" :> obj
    //     match x with | :? ITest -> true | _ -> false
    //     |> equal true
    //     match x with | :? ITest2 -> true | _ -> false
    //     |> equal false

    testCase "Type testing with JS primitive types works" <| fun () ->
        let test (o: obj) =
            match o with
            | :? string -> "string"
            | :? float -> "number"
            | :? bool -> "boolean"
            | :? unit -> "unit"
            | :? System.Text.RegularExpressions.Regex -> "RegExp"
            | :? (int[]) | :? (string[]) -> "Array"
            | _ -> "unknown"
        "A" :> obj |> test |> equal "string"
        3. :> obj |> test |> equal "number"
        false :> obj |> test |> equal "boolean"
        () :> obj |> test |> equal "unit"
        // Workaround to make sure Fable is passing the argument
        let a = () :> obj in test a |> equal "unit"
        System.Text.RegularExpressions.Regex(".") :> obj |> test |> equal "RegExp"
        [|"A"|] :> obj |> test |> equal "Array"
        [|1;2|] :> obj |> test |> equal "Array"

    testCase "Type test with Date" <| fun () ->
        let isDate (x: obj) =
            match x with
            | :? DateTime -> true
            | _ -> false
        DateTime.Now |> box |> isDate |> equal true
        box 5 |> isDate |> equal false

    testCase "Type test with Long" <| fun () ->
        let isLong (x: obj) =
            match x with
            | :? int64 -> true
            | _ -> false
        box 5L |> isLong |> equal true
        box 50 |> isLong |> equal false

    testCase "Type test with BigInt" <| fun () ->
        let isBigInd (x: obj) =
            match x with
            | :? bigint -> true
            | _ -> false
        box 5I |> isBigInd |> equal true
        box 50 |> isBigInd |> equal false

    testCase "Property names don't clash with built-in JS objects" <| fun () -> // See #168
        let gameState = {
            Now = 1
            Map = "dungeon"
            Players = Map.empty
        }
        gameState.Players.ContainsKey(1) |> equal false

    testCase "Overloads work" <| fun () ->
        let t = TestType5("")
        t.Overload(2) |> equal 4
        t.Overload(2, 3) |> equal 5

    testCase "Type abbreviation works" <| fun () ->
        let t = T4()
        t.Value2 |> equal "Bye"

    testCase "Getter and Setter work" <| fun () ->
        let t = TestType6(5)
        t.Value1 |> equal 5
        t.Value2 |> equal 10
        t.Value3 |> equal 25
        t.Value3 <- 10
        t.Value1 |> equal 5
        t.Value2 |> equal 20
        t.Value3 |> equal 100
        t.Value1 <- 20
        t.Value1 |> equal 20
        t.Value2 |> equal 20
        t.Value3 |> equal 100

    testCase "Getter and Setter with indexer work" <| fun () ->
        let t = TestType7(1, 2, 3)
        t.Value(1) |> equal 2
        t.Value(2) |> equal 3
        t.Value(1) <- 5
        t.Value(1) |> equal 5
        t.Value(2) |> equal 3

    testCase "Attached Getters Setters and Indexers work" <| fun () ->
        let t = TestTypeAttached(1, 2, 3)
        t.Value1 |> equal 2
        t.Value1 <- 22
        t.Value1 |> equal 22
        t.Value(0) |> equal 1
        t.Value(0) <- 11
        t.Value(0) |> equal 11
        t[2] |> equal 3
        t[2] <- 33
        t[2] |> equal 33

    testCase "Interface Getters Setters and Indexers work" <| fun () ->
        let t = TestProps([| 1; 2; 3 |]) :> ITestProps
        t.Value1 |> equal 2
        t.Value1 <- 22
        t.Value1 |> equal 22
        t.Value(0) |> equal 1
        t.Value(0) <- 11
        t.Value(0) |> equal 11
        t[2] |> equal 3
        t[2] <- 33
        t[2] |> equal 33

    testCase "Statically resolved instance calls work" <| fun () ->
        let a = { thing = 5 }
        let b = { label = "five" }
        show a |> equal "5"
        show b |> equal "five"

    testCase "Statically resolved static calls work" <| fun () ->
        let a = { thing = 5 }
        let b = { label = "five" }
        showStatic a |> equal "Static: 5"
        showStatic b |> equal "Static: five"

    testCase "Guid.NewGuid works" <| fun () ->
        let g1 = Guid.NewGuid()
        let g2 = Guid.NewGuid()
        g1 = g2 |> equal false
        let s1 = string g1
        equal 36 s1.Length
        Text.RegularExpressions.Regex.IsMatch(
            s1, "^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$")
        |> equal true
        let g3 = Guid.Parse s1
        g1 = g3 |> equal true

    testCase "Guid.Empty works" <| fun () ->
        let g1 = Guid.Empty
        string g1 |> equal "00000000-0000-0000-0000-000000000000"

    testCase "Guid.ToString works" <| fun () ->
        let g1 = Guid.Parse "dec42487-c02b-42a6-9a10-0263a5a7fdf1"
        string g1 |> equal "dec42487-c02b-42a6-9a10-0263a5a7fdf1"

    testCase "lazy works" <| fun () ->
        let mutable snitch = 0
        let lazyVal =
            lazy
                snitch <- snitch + 1
                5
        equal 0 snitch
        equal 5 lazyVal.Value
        equal 1 snitch
        lazyVal.Force() |> equal 5
        equal 1 snitch

    testCase "Lazy.CreateFromValue works" <| fun () ->
        let mutable snitch = 0
        let lazyVal =
            Lazy<_>.CreateFromValue(
                snitch <- snitch + 1
                5)
        equal 1 snitch
        equal 5 lazyVal.Value
        equal 1 snitch

    testCase "lazy.IsValueCreated works" <| fun () ->
        let mutable snitch = 0
        let lazyVal =
            Lazy<_>.Create(fun () ->
                snitch <- snitch + 1
                5)
        equal 0 snitch
        equal false lazyVal.IsValueCreated
        equal 5 lazyVal.Value
        equal true lazyVal.IsValueCreated
        lazyVal.Force() |> equal 5
        equal true lazyVal.IsValueCreated

    testCase "Lazy constructor works" <| fun () ->
        let items = Lazy<string list>(fun () -> ["a";"b";"c"])
        let search e = items.Value |> List.tryFind (fun m -> m = e)
        search "b" |> equal (Some "b")
        search "d" |> equal None

    testCase "Secondary constructors work" <| fun () ->
        let s1 = SecondaryCons(3)
        let s2 = SecondaryCons()
        equal 3 s1.Value
        equal 5 s2.Value

    // testCase "Inheriting from secondary constructors works" <| fun () ->
    //     let s = SecondaryConsChild()
    //     equal 5 s.Value

    testCase "Multiple constructors work" <| fun () ->
        let m1 = MultipleCons()
        let m2 = MultipleCons(5)
        let m3 = MultipleCons(7,7)
        equal 5 m1.Value
        equal 9 m2.Value
        equal 14 m3.Value

#if !FABLE_COMPILER_TYPESCRIPT
    testCase "Abstract methods with default work" <| fun () -> // See #505
        let x = ConcreteClass()
        x.MethodWithDefault() |> equal "Hello "
        x.MustImplement() |> equal "World!!"
        x.CallMethodWithDefault() |> equal "Hello World!!"
        let x = ConcreteClass2()
        x.CallMethodWithDefault() |> equal "Hi World!!"

    testCase "Abstract properties with getters and setters work" <| fun () ->
        let x = ConcreteClass3() :> AbstractClass3
        x.MyProp <- 2
        equal 7 x.MyProp
#endif

    testCase "Interface setters don't conflict" <| fun () -> // See #505
        let x = XISomeInterface () :> ISomeInterface
        x.Sender |> equal 0
        x.Sender <- 5
        x.Sender |> equal 5

    testCase "A type can overload an interface method" <| fun () ->
        let foo = FooImplementor()
        foo.Foo() |> equal "foo"
        (foo :> IFoo).Foo() |> equal "foobar"
        mangleFoo foo |> equal "foobar"

    testCase "A child can be casted to parent's interface" <| fun () ->
        let foo = FooImplementorChild()
        foo.Foo() |> equal "foofoofoo"
        (foo :> IFoo).Foo() |> equal "foofoofoobar"
        mangleFoo foo |> equal "foofoofoobar"

    testCase "A type can overload an interface getter" <| fun () ->
        let foo = FooImplementor()
        foo.Bar |> equal "he"
        (foo :> IFoo).Bar |> equal "heho"

    testCase "A type can overload an interface setter" <| fun () ->
        let foo = FooImplementor()
        foo.MySetter <- 7
        foo.MySetter |> equal 9
        (foo :> IFoo).MySetter <- 7
        (foo :> IFoo).MySetter |> equal 19

#if !FABLE_COMPILER_TYPESCRIPT
    // TODO: Interface and abstract methods with same name clash
    testCase "A type overloading an interface method can be inherited" <| fun () ->
        let foo = ChildFoo() :> AbstractFoo
        foo.Foo2() |> equal "BAR"
        (foo :> IFoo).Foo() |> equal "BARFOO"
        mangleFoo foo |> equal "BARFOO"
#endif

    testCase "Interface casting round-trip" <| fun () -> // See #1452
        let d = new DowncastTest(3) :> System.IDisposable
        let t = d :?> DowncastTest
        t.Value |> equal 3
        equal 3 <|
            match d with
            | :? DowncastTest as t2 -> t2.Value
            | _ -> 5

    testCase "Calling default implementation of base members don't cause infinite recursion" <| fun () -> // See #701
        let x = ExtendedClass()
        x.Init() |> equal 7
        (x :> BaseClass).Init() |> equal 7

    testCase "Calling default implementation of base properties don't cause infinite recursion" <| fun () -> // See #701
        let x = ExtendedClass()
        x.Prop |> equal "base-extension"
        (x :> BaseClass).Prop |> equal "base-extension"

    testCase "Calling base members works" <| fun () -> // See #1464
        let bar = ExtendedClass2()
        bar.B() |> equal 1

    testCase "Circular dependencies work" <| fun () -> // See #569
        let location = { name="NY"; employees=[] }
        let alice = { name="Alice"; age=20.0; location=location  }
        location.name |> equal "NY"
        alice.age |> equal 20.

    testCase "Value Type records work" <| fun () -> // See #568
        let foo1 = ValueType<_>("foo")
        let foo2 = ValueType<_>("foo")
        foo1.Value |> equal "foo"
        foo1.value |> equal "foo"
        foo1 = foo2 |> equal true

    testCase "Value Type unions work" <| fun () ->
        let du1 = StructUnion.Value "du"
        let du2 = StructUnion.Value "du"
        du1 = du2 |> equal true

    testCase "Value Type tuples work" <| fun () ->
        let tu1 = struct ("a","b")
        let tu2 = struct ("a","b")
        tu1 = tu2 |> equal true

    testCase "Value Types work" <| fun () ->
        let bar1 = ValueType1("bar")
        let bar2 = ValueType1("bar")
        bar1.Value |> equal "bar"
        bar1 = bar2 |> equal true

    testCase "Other Value Types work" <| fun () ->
        let test2 = ValueType2(3, 4)
        test2.Value |> equal 7
        let p = Point2D(2.)
        p.Y |> equal 2.

#if !FABLE_COMPILER_TYPESCRIPT
    testCase "struct without explicit ctor works" <| fun () ->
        let t1 = ValueType3(X=10)
        t1.X |> equal 10
        let mutable t2 = ValueType3()
        t2.X |> equal 0
        t1 |> notEqual t2
        (compare t1 t2) |> equal 1
        t2.X <- 10
        t1 |> equal t2
        (compare t1 t2) |> equal 0
#endif

    testCase "copying struct records works" <| fun () -> // See #3371
        let simple : SimpleRecord = { A = ""; B = "B" }
        let simpleRecord = { simple with A = "A" }
        simpleRecord.A |> equal "A"
        simpleRecord.B |> equal "B"
        simple.A |> equal ""
        simple.B |> equal "B"

    testCase "Custom F# exceptions work" <| fun () ->
        try
            MyEx(4,"ERROR") |> raise
        with
        | MyEx(4, msg) as e -> (box e :? Exception, msg + "!!")
        | MyEx(_, msg) as e -> (box e :? Exception, msg + "??")
        | ex -> (false, "unknown")
        |> equal (true, "ERROR!!")

    testCase "Custom exceptions work" <| fun () ->
        try
            MyEx2(5.5) |> raise
        with
        | :? MyEx2 as ex -> (box ex :? Exception, ex.Message, ex.Code)
        | ex -> (false, "unknown", 0.)
        |> equal (true, "Code: 5", 5.5)

    testCase "reraise works" <| fun () ->
        try
            try
                Exception("Will I be reraised?") |> raise
            with _ ->
                try
                    reraise()
                with _ -> reraise()
            "foo"
        with ex -> ex.Message
        |> equal "Will I be reraised?"

    testCase "This context is not lost in closures within implicit constructor" <| fun () -> // See #1444
        ThisContextInConstructor(7).Value() |> equal 7

    testCase "Type can be casted to an interface implemented by parent" <| fun () ->
        let c = TestType10Child()
        let f1 = c :> ITest4
        let f2 = c :> ITest3
        f1.Add(4, 5) |> equal 9
        f1.Add2(4, 5) |> equal -1
        f2.Add2(4, 5) |> equal -1

    testCase "ClassAttribute works" <| fun () -> // See #573
        let t1 = TypeWithClassAttribute(8)
        t1.Pos |> equal 8

    testCase "Issue #1975: Compile type with parameterized units of measure as generic" <| fun () ->
        let a = makeTestTypeWithParameterizedUnitMeasureType 2.
        equal 2. a.Value

    // Test ported from https://github.com/fable-compiler/Fable/pull/1336/files
//    testCase "default value attributes works" <| fun _ ->
//        let withDefaultValue = TestTypeWithDefaultValue()
//
//        withDefaultValue.IntValue |> equal Unchecked.defaultof<int>
//        withDefaultValue.IntValue |> equal 0
//
//        withDefaultValue.StringValue |> equal Unchecked.defaultof<string>
//        withDefaultValue.StringValue |> equal null
//
//        withDefaultValue.ObjValue |> equal Unchecked.defaultof<System.Collections.Generic.Dictionary<string, string>>
//        withDefaultValue.ObjValue |> equal null

#if !FABLE_COMPILER_TYPESCRIPT
    testCase "Private fields don't conflict with parent classes" <| fun _ -> // See #2070
        let a1 = InfoBClass({ InfoA = { Foo = "foo" }; Bar = "bar" }) :> InfoAClass
        let a2 = a1.WithFoo("foo2")
        a1.Foo |> equal "foo"
        a2.Foo |> equal "foo2"

    // See #2084
    testCase "Non-mangled interfaces work with object expressions" <| fun _ ->
        let mutable foo = "Foo"
        let foo = { new FooInterface with
                        member _.Foo with get() = foo and set x = foo <- x
                        member _.DoSomething(f, x) = let f = f 1. in f x * f 0.2
                        member _.Item with get(i) = foo.[i] and set i c = foo <- FooClass.ChangeChar(foo, i - 1, c)
                        member _.Sum(items) = Array.reduce (+) items }

        let addPlus2 x y = x + y + 2.
        let multiplyTwice x y = x * y * y

        foo.[3] <- 'W'
        foo.Foo <- foo.Foo + foo.DoSomething(addPlus2, 3.).ToString("F2").Replace(",", ".") + foo.[2].ToString()
        foo.Foo <- foo.Foo + foo.Sum("a", "bc", "d")

        foo.Foo |> equal "FoW19.20Wabcd"

    // See #2084
    testCase "Mangled interfaces work with object expressions" <| fun _ ->
        let mutable bar = "Bar"
        let bar = { new BarInterface with
                        member _.Bar with get() = bar and set x = bar <- x
                        member _.DoSomething(f, x) = let f = f 4.3 in f x + f x
                        member _.Item with get(i) = bar.[i] and set _ c = bar <- FooClass.ChangeChar(bar, 0, c)
                        member _.Item with get(c) = bar.ToCharArray() |> Array.exists ((=) c)
                        member _.Sum(items) = Array.rev items |> Array.reduce (+)  }

        let addPlus2 x y = x + y + 2.
        let multiplyTwice x y = x * y * y

        bar.[3] <- 'Z'
        bar.Bar <- bar.Bar + bar.DoSomething(multiplyTwice, 3.).ToString("F2").Replace(",", ".") + bar.[2].ToString() + (sprintf "%b%b" bar.['B'] bar.['x'])
        bar.Bar <- bar.Bar + bar.Sum("a", "bc", "d")

        bar.Bar |> equal "Zar77.40rfalsefalsedbca"

    // See #2084
    testCase "Non-mangled interfaces work with classes" <| fun _ ->
        let addPlus2 x y = x + y + 2.
        let multiplyTwice x y = x * y * y
        let foo2 = FooClass("Foo") :> FooInterface
        foo2.[0] <- 'W'
        foo2.Foo <- foo2.Foo + foo2.DoSomething(multiplyTwice, 3.).ToString("F2").Replace(',', '.') + foo2.[2].ToString()
        foo2.Foo <- foo2.Foo + foo2.Sum("a", "bc", "d")
        foo2.Foo |> equal "Woo1020.00oabcabcdabcabcd"

    // See #2084
    testCase "Mangled interfaces work with classes" <| fun _ ->
        let addPlus2 x y = x + y + 2.
        let multiplyTwice x y = x * y * y
        let bar2 = BarClass("Bar") :> BarInterface
        bar2.[0] <- 'Z'
        bar2.Bar <- bar2.Bar + bar2.DoSomething(addPlus2, 3.).ToString("F2").Replace(",", ".") + bar2.[2].ToString() + (sprintf "%b%b" bar2.['B'] bar2.['x'])
        bar2.Bar <- bar2.Bar + bar2.Sum("a", "bc", "d")
        bar2.Bar |> equal "BZr9536.74rtruefalseaabcbcaabcbcdd"
#endif

    testCase "Multiple `this` references work in nested attached members" <| fun _ ->
        (MixedThese(2) :> Interface1).Create(3).Add() |> equal 5

    testCase "Two unions of different type with same shape are not equal" <| fun () ->
        areEqual (MyUnion1.Foo(1,2)) (MyUnion2.Foo(1,2)) |> equal false
        areEqual (MyUnion1.Foo(1,2)) (MyUnion1.Foo(1,2)) |> equal true

    testCase "Two records of different type with same shape are not equal" <| fun () ->
        areEqual { MyRecord1.Foo = 2; Bar = "oh" } { MyRecord2.Foo = 2; Bar = "oh" } |> equal false
        areEqual { MyRecord1.Foo = 2; Bar = "oh" } { MyRecord1.Foo = 2; Bar = "oh" } |> equal true

    testCase "IsSubclassOf checks whole hierarchy" <| fun () ->
        typeof<SubclassTest2>.IsSubclassOf(typeof<SubclassTest1>) |> equal true
        typeof<SubclassTest3>.IsSubclassOf(typeof<SubclassTest1>) |> equal true

    testCase "IsSubclassOf returns true for System.Object" <| fun () ->
        typeof<SubclassTest2>.IsSubclassOf(typeof<obj>) |> equal true
        typeof<string>.IsSubclassOf(typeof<obj>) |> equal true
        typeof<int>.IsSubclassOf(typeof<obj>) |> equal true
        typeof<unit -> unit>.IsSubclassOf(typeof<obj>) |> equal true

    testCase "IsInstanceOfType works with class types" <| fun () ->
        let s1, s2 = SubclassTest1(), SubclassTest2()
        typeof<obj>.IsInstanceOfType(s1) |> equal true
        typeof<SubclassTest1>.IsInstanceOfType(s1) |> equal true
        typeof<SubclassTest2>.IsInstanceOfType(s1) |> equal false
        typeof<SubclassTest3>.IsInstanceOfType(s1) |> equal false
        typeof<SubclassTest1>.IsInstanceOfType(s2) |> equal true
        typeof<SubclassTest2>.IsInstanceOfType(s2) |> equal true
        typeof<SubclassTest3>.IsInstanceOfType(s2) |> equal false

    testCase "IsInstanceOfType works with nominal records" <| fun () ->
        typeof<MyRecord1>.IsInstanceOfType({ MyRecord1.Foo = 2; Bar = "oh" }) |> equal true
        typeof<obj>.IsInstanceOfType({ MyRecord1.Foo = 2; Bar = "oh" }) |> equal true
        typeof<MyRecord2>.IsInstanceOfType({ MyRecord1.Foo = 2; Bar = "oh" }) |> equal false

    testCase "IsInstanceOfType works with nominal unions" <| fun () ->
        typeof<MyUnion1>.IsInstanceOfType(MyUnion1.Foo(1,2)) |> equal true
        typeof<obj>.IsInstanceOfType(MyUnion1.Foo(1,2)) |> equal true
        typeof<MyUnion2>.IsInstanceOfType(MyUnion1.Foo(1,2)) |> equal false

    // Expected to always return true for any numeric type, just like :? operator
    testCase "IsInstanceOfType works with enums" <| fun () ->
        typeof<EnumFoo>.IsInstanceOfType(EnumFoo.Foo) |> equal true
        typeof<EnumFoo>.IsInstanceOfType(EnumFoo.Bar) |> equal true
        typeof<obj>.IsInstanceOfType(EnumFoo.Bar) |> equal true

    // Expected to always return true for any function and function type, just like :? operator
    testCase "IsInstanceOfType works with functions" <| fun () ->
        typeof<unit -> unit>.IsInstanceOfType(fun () -> ()) |> equal true
        typeof<obj>.IsInstanceOfType(fun () -> ()) |> equal true
        //typeof<unit -> int>.IsInstanceOfType(fun () -> ()) |> equal false
        typeof<string -> int>.IsInstanceOfType(String.length) |> equal true
        typeof<obj>.IsInstanceOfType(String.length) |> equal true
        //typeof<int -> int>.IsInstanceOfType(String.length) |> equal false

    testCase "IsInstanceOfType works with primitives" <| fun () ->
        typeof<string>.IsInstanceOfType("hello") |> equal true
        typeof<obj>.IsInstanceOfType("hello") |> equal true
        typeof<string>.IsInstanceOfType(5) |> equal false
        typeof<int>.IsInstanceOfType(5) |> equal true
        typeof<obj>.IsInstanceOfType(5) |> equal true
        typeof<int>.IsInstanceOfType("hello") |> equal false

#if FABLE_COMPILER
    testCase "Choice with arity 3+ is represented correctly" <| fun () -> // See #2485
        Choice2Of3 55 |> Fable.Core.Reflection.getCaseName |> equal "Choice2Of3"
        Choice3Of3 55 |> Fable.Core.Reflection.getCaseName |> equal "Choice3Of3"
#endif

    testCase "Can call the base version of a mangled abstract method that was declared above in the hierarchy" <| fun () ->
        let c = ConcreteClass1()
        c.MyMethod(4) |> equal 58

    // See #3328
    testCase "SRTP works with byref" <| fun () ->
      let result = doubleIntByRef (TypeWithByRefMember()) 7
      result |> equal 14

    // See #3328
    testCase "inline byref works" <| fun () ->
        let mutable an_int = 22
        callWithByrefCreatedFromByrefInlined &an_int
        an_int |> equal 44

    // See #3328
    testCase "inline byref works (with separate binding for reference)" <| fun () ->
        let mutable an_int = 33
        let intRef = &an_int
        ignore intRef
        callWithByrefCreatedFromByrefInlined &intRef
        an_int |> equal 66

    testCase "inline with generic byref works" <| fun () ->
        let mutable arr = [| 1; 2; 3 |]
        let result = genericByrefFunc &arr
        result |> equal 3
  ]
