module Js_of_ocaml = struct
  module Js = struct

    class type js_string = object end

    type +'a t

    type (-'a, +'b) meth_callback

    type +'a opt

    type +'a optdef

    type +'a meth

    type +'a gen_prop

    type 'a readonly_prop = < get : 'a > gen_prop

    type 'a writeonly_prop = < set : 'a -> unit > gen_prop

    type 'a prop = < get : 'a ; set : 'a -> unit > gen_prop

    type 'a optdef_prop = < get : 'a optdef ; set : 'a -> unit > gen_prop

    type +'a constr

    (****)

    type 'a callback = (unit, 'a) meth_callback

    module Unsafe = struct
      type any

      type any_js_array = any

      let inject : 'a -> any = fun _ -> assert false

      let get : 'a -> 'b -> 'c = fun _ _ -> assert false

      let set : 'a -> 'b -> 'c -> unit = fun _ _ _ -> assert false

      let meth_call : 'a -> string -> any array -> 'b = fun _ _ _ -> assert false

      let obj : (string * any) array -> 'a = fun _ -> assert false
    end

    let wrap_meth_callback : ('a -> 'b) -> ('a, 'b) meth_callback = fun _ -> assert false

    let string : string -> js_string t = fun _ -> assert false

    let undefined : unit -> 'a optdef = fun () -> assert false

    let def : 'a -> 'a optdef = fun _ -> assert false
  end
end

open Js_of_ocaml

[%%expect
{|
module Js_of_ocaml :
  sig
    module Js :
      sig
        class type js_string = object  end
        type +'a t
        type (-'a, +'b) meth_callback
        type +'a opt
        type +'a optdef
        type +'a meth
        type +'a gen_prop
        type 'a readonly_prop = < get : 'a > gen_prop
        type 'a writeonly_prop = < set : 'a -> unit > gen_prop
        type 'a prop = < get : 'a; set : 'a -> unit > gen_prop
        type 'a optdef_prop = < get : 'a optdef; set : 'a -> unit > gen_prop
        type +'a constr
        type 'a callback = (unit, 'a) meth_callback
        module Unsafe :
          sig
            type any
            type any_js_array = any
            val inject : 'a -> any
            val get : 'a -> 'b -> 'c
            val set : 'a -> 'b -> 'c -> unit
            val meth_call : 'a -> string -> any array -> 'b
            val obj : (string * any) array -> 'a
          end
        val wrap_meth_callback : ('a -> 'b) -> ('a, 'b) meth_callback
        val string : string -> js_string t
        val undefined : unit -> 'a optdef
        val def : 'a -> 'a optdef
      end
  end
|}]

(* Test methods. *)

;;
fun (obj : int) -> obj##m

[%%expect
{|
Line _, characters 19-22:
Error: The value obj has type int but an expression was expected of type
         < .. > Js_of_ocaml.Js.t
|}]

;;
fun (obj : < > Js.t) -> obj##m

[%%expect
{|
Line _, characters 24-27:
Error: This expression has type <  >
       It has no method m
|}]

;;
fun (obj : < m : float Js.prop > Js.t) -> obj##m

[%%expect
{|
Line _, characters 42-48:
Error: The method call x#m has type
         float Js_of_ocaml.Js.prop =
           < get : float; set : float -> unit > Js_of_ocaml.Js.gen_prop
       but an expression was expected of type 'res Js_of_ocaml.Js.meth
|}]

;;
fun (obj : < m : float Js.meth > Js.t) -> obj##m + 1

[%%expect
{|
Line _, characters 42-48:
Error: This expression has type float but an expression was expected of type
         int
|}]

;;
fun (obj : < m : int -> int Js.meth > Js.t) -> obj##m + 1

[%%expect
{|
Line _, characters 47-53:
Error: The method call x#m has type int -> int Js_of_ocaml.Js.meth
       but an expression was expected of type 'res Js_of_ocaml.Js.meth
|}]

;;
fun (obj : < m : int Js.meth > Js.t) -> obj##m 1

[%%expect
{|
Line _, characters 40-46:
Error: The method call x#m has type int Js_of_ocaml.Js.meth
       but an expression was expected of type int -> 'res Js_of_ocaml.Js.meth
|}]

;;
fun (obj : < m : int -> float Js.meth > Js.t) -> obj##m 1 + 1

[%%expect
{|
Line _, characters 49-57:
Error: This expression has type float but an expression was expected of type
         int
|}]

;;
fun (obj : < m : 'a. 'a -> unit Js.meth > Js.t) ->
  obj##m "string";
  obj##m ();
  obj##w

[%%expect
{|
Line _, characters 2-5:
Error: This expression has type < m : 'a. 'a -> unit Js_of_ocaml.Js.meth >
       It has no method w
|}]

(* Test prop reading. *)

;;
fun (obj : int) -> obj##.p

[%%expect
{|
Line _, characters 19-22:
Error: The value obj has type int but an expression was expected of type
         < .. > Js_of_ocaml.Js.t
|}]

;;
fun (obj : < > Js.t) -> obj##.p

[%%expect
{|
Line _, characters 24-27:
Error: This expression has type <  >
       It has no method p
|}]

;;
fun (obj : < p : float Js.writeonly_prop > Js.t) -> obj##.p + 1

[%%expect
{|
Line _, characters 58-59:
Error: The method call x#p has type
         float Js_of_ocaml.Js.writeonly_prop =
           < set : float -> unit > Js_of_ocaml.Js.gen_prop
       but an expression was expected of type
         < get : 'res; .. > Js_of_ocaml.Js.gen_prop
       The first object type has no method get
|}]

;;
fun (obj : < p : float Js.prop > Js.t) -> obj##.p + 1

[%%expect
{|
Line _, characters 42-49:
Error: This expression has type float but an expression was expected of type
         int
|}]

(* Test prop writing. *)

;;
fun (obj : int) -> obj##.p := 2

[%%expect
{|
Line _, characters 19-22:
Error: The value obj has type int but an expression was expected of type
         < .. > Js_of_ocaml.Js.t
|}]

;;
fun (obj : < > Js.t) -> obj##.p := 2

[%%expect
{|
Line _, characters 24-27:
Error: This expression has type <  >
       It has no method p
|}]

;;
fun (obj : < p : float Js.readonly_prop > Js.t) -> obj##.p := 1

[%%expect
{|
Line _, characters 51-58:
Error: The method call x#p has type
         float Js_of_ocaml.Js.readonly_prop =
           < get : float > Js_of_ocaml.Js.gen_prop
       but an expression was expected of type
         < set : int -> unit; .. > Js_of_ocaml.Js.gen_prop
       The first object type has no method set
|}]

;;
fun (obj : < p : float Js.prop > Js.t) -> obj##.p := 1

[%%expect
{|
Line _, characters 42-49:
Error: The method call x#p has type
         float Js_of_ocaml.Js.prop =
           < get : float; set : float -> unit > Js_of_ocaml.Js.gen_prop
       but an expression was expected of type
         < set : int -> unit; .. > Js_of_ocaml.Js.gen_prop
       The method set has type float -> unit,
       but the expected method type was int -> unit
|}]

;;
fun (obj : < p : int Js.prop > Js.t) -> (obj##.p := 1) + 1

[%%expect
{|
Line _, characters 40-54:
Error: This expression has type unit but an expression was expected of type
         int
|}]

(* Test objects. *)

;;
fun (obj : < > Js.t) ->
  obj
  = object%js
      val m = ()
    end

[%%expect
{|
Line _, characters 4-38:
Error: This expression has type
         < m : unit Js_of_ocaml.Js.readonly_prop > Js_of_ocaml.Js.t
       but an expression was expected of type <  > Js_of_ocaml.Js.t
       The second object type has no method m
|}]

;;
fun (obj : < m : float Js.prop > Js.t) ->
  obj
  = object%js
      val mutable m = 0
    end

[%%expect
{|
Line _, characters 4-45:
Error: This expression has type
         < m : int Js_of_ocaml.Js.prop > Js_of_ocaml.Js.t
       but an expression was expected of type
         < m : float Js_of_ocaml.Js.prop > Js_of_ocaml.Js.t
       Type
         int Js_of_ocaml.Js.prop =
           < get : int; set : int -> unit > Js_of_ocaml.Js.gen_prop
       is not compatible with type
         float Js_of_ocaml.Js.prop =
           < get : float; set : float -> unit > Js_of_ocaml.Js.gen_prop
       The method get has type int, but the expected method type was float
|}]

;;
fun () ->
  (object%js
     val r = 2

     val mutable w = 3.

     method m = ""
   end
    : < m : int Js.meth ; .. > Js.t)

[%%expect
{|
Line _, characters 3-79:
Error: This expression has type
         < m : string Js_of_ocaml.Js.meth;
           r : int Js_of_ocaml.Js.readonly_prop;
           w : float Js_of_ocaml.Js.prop >
         Js_of_ocaml.Js.t
       but an expression was expected of type
         < m : int Js_of_ocaml.Js.meth; .. > Js_of_ocaml.Js.t
       The method m has type string Js_of_ocaml.Js.meth,
       but the expected method type was int Js_of_ocaml.Js.meth
|}]

;;
fun () ->
  object%js
    val r = 2

    val _r = 2
  end

[%%expect
{|
Line _, characters 8-10:
Error: Duplicated val or method "_r" (normalized to "r").
Line _, characters 8-9:
  Duplicated val or method "r".
|}]

;;
fun () ->
  (object%js
     val _r_a = 2

     val _r_b = 2
   end
    : < _r_a : int Js.readonly_prop > Js.t)

[%%expect
{|
Line _, characters 9-13:
Error: Duplicated val or method "_r_b" (normalized to "r").
Line _, characters 9-13:
  Duplicated val or method "_r_a" (normalized to "r").
|}]

;;
fun () ->
  object%js
    val mutable w = 2

    val mutable _w = 2
  end

[%%expect
{|
Line _, characters 16-18:
Error: Duplicated val or method "_w" (normalized to "w").
Line _, characters 16-17:
  Duplicated val or method "w".
|}]

;;
fun () ->
  (object%js
     val mutable _w_a = 2

     val mutable _w_b = 2
   end
    : < _r_a : int Js.prop > Js.t)

[%%expect
{|
Line _, characters 17-21:
Error: Duplicated val or method "_w_b" (normalized to "w").
Line _, characters 17-21:
  Duplicated val or method "_w_a" (normalized to "w").
|}]

;;
fun () ->
  object%js
    method m = ""

    method _m = ""
  end

[%%expect
{|
Line _, characters 11-13:
Error: Duplicated val or method "_m" (normalized to "m").
Line _, characters 11-12:
  Duplicated val or method "m".
|}]

;;
fun () ->
  (object%js
     method _m_a = ""

     method _m_bc = ""
   end
    : < _m_a : string Js.meth ; .. > Js.t)

[%%expect
{|
Line _, characters 12-17:
Error: Duplicated val or method "_m_bc" (normalized to "m").
Line _, characters 12-16:
  Duplicated val or method "_m_a" (normalized to "m").
|}]

;;
fun () ->
  (object%js
     val r = Js.def 2 [@@jsoo.optdef]

     val mutable w = 3.

     method m = ""
   end
    : < r : int Js.prop ; .. > Js.t)

[%%expect
{|
Line _, characters 3-102:
Error: This expression has type
         < m : string Js_of_ocaml.Js.meth;
           r : int Js_of_ocaml.Js.optdef_prop;
           w : float Js_of_ocaml.Js.prop >
         Js_of_ocaml.Js.t
       but an expression was expected of type
         < r : int Js_of_ocaml.Js.prop; .. > Js_of_ocaml.Js.t
       Type
         int Js_of_ocaml.Js.optdef_prop =
           < get : int Js_of_ocaml.Js.optdef; set : int -> unit >
           Js_of_ocaml.Js.gen_prop
       is not compatible with type
         int Js_of_ocaml.Js.prop =
           < get : int; set : int -> unit > Js_of_ocaml.Js.gen_prop
       The method get has type int Js_of_ocaml.Js.optdef,
       but the expected method type was int
|}]

;;
fun () ->
  (object%js
     val r = 2 [@@jsoo.writeonly]

     val mutable w = 3.

     method m = ""
   end
    : < r : int Js.prop ; .. > Js.t)

[%%expect
{|
Line _, characters 3-98:
Error: This expression has type
         < m : string Js_of_ocaml.Js.meth;
           r : int Js_of_ocaml.Js.writeonly_prop;
           w : float Js_of_ocaml.Js.prop >
         Js_of_ocaml.Js.t
       but an expression was expected of type
         < r : int Js_of_ocaml.Js.prop; .. > Js_of_ocaml.Js.t
       Type
         int Js_of_ocaml.Js.writeonly_prop =
           < set : int -> unit > Js_of_ocaml.Js.gen_prop
       is not compatible with type
         int Js_of_ocaml.Js.prop =
           < get : int; set : int -> unit > Js_of_ocaml.Js.gen_prop
       The first object type has no method get
|}]

;;
fun () ->
  (object%js
     val mutable r = 2 [@@jsoo.readonly]

     val mutable w = 3.

     method m = ""
   end
    : < r : int Js.prop ; .. > Js.t)

[%%expect {|
Line _, characters 5-40:
Error: A mutable field cannot be readonly.
|}]

;;
fun () ->
  (object%js
     val r = 2 [@@jsoo.readonly]

     val mutable w = 3.

     method m = ""
   end
    : < r : int Js.prop ; .. > Js.t)

[%%expect
{|
Line _, characters 3-97:
Error: This expression has type
         < m : string Js_of_ocaml.Js.meth;
           r : int Js_of_ocaml.Js.readonly_prop;
           w : float Js_of_ocaml.Js.prop >
         Js_of_ocaml.Js.t
       but an expression was expected of type
         < r : int Js_of_ocaml.Js.prop; .. > Js_of_ocaml.Js.t
       Type
         int Js_of_ocaml.Js.readonly_prop =
           < get : int > Js_of_ocaml.Js.gen_prop
       is not compatible with type
         int Js_of_ocaml.Js.prop =
           < get : int; set : int -> unit > Js_of_ocaml.Js.gen_prop
       The first object type has no method set
|}]

;;
fun () ->
  (object%js
     val mutable r = 2 [@@jsoo.readonly] [@@writeonly]

     val mutable w = 3.

     method m = ""
   end
    : < r : int Js.prop ; .. > Js.t)

[%%expect {|
Line _, characters 5-54:
Error: Too many attributes.
|}]

;;
fun () ->
  (object%js
     val mutable r = 2 [@@jsoo.wrong]

     val mutable w = 3.

     method m = ""
   end
    : < r : int Js.prop ; .. > Js.t)

[%%expect {|
Line _, characters 5-37:
Error: Unknown jsoo attribute ([@@jsoo.wrong]).
|}]

;;
fun () ->
  object%js
    val r = 2 [@@jsoo.readonly]
  end

[%%expect
{|
- : unit -> < r : int Js_of_ocaml.Js.readonly_prop > Js_of_ocaml.Js.t = <fun>
|}]

;;
fun () ->
  object%js
    val r = 2 [@@jsoo.writeonly]
  end

[%%expect
{|
- : unit -> < r : int Js_of_ocaml.Js.writeonly_prop > Js_of_ocaml.Js.t =
<fun>
|}]

;;
fun () ->
  object%js
    val r = 2 [@@jsoo.readwrite]
  end

[%%expect {|
- : unit -> < r : int Js_of_ocaml.Js.prop > Js_of_ocaml.Js.t = <fun>
|}]

;;
fun () ->
  object%js
    val r = Js.def 2 [@@jsoo.optdef]
  end

[%%expect
{|
- : unit -> < r : int Js_of_ocaml.Js.optdef_prop > Js_of_ocaml.Js.t = <fun>
|}]

;;
fun () ->
  object%js
    val r = Js.def 2 [@@jsoo.optdef]
  end

[%%expect
{|
- : unit -> < r : int Js_of_ocaml.Js.optdef_prop > Js_of_ocaml.Js.t = <fun>
|}]

;;
fun () ->
  object%js (self)
    (* Equivalent of this *)
    val x1 = 3

    (* read-only prop *)
    val x2 = 3 [@@readonly]

    (* read-only prop *)
    val mutable y1 = 4

    (* read/write prop *)
    val y2 = 4 [@@readwrite]

    (* read/write prop *)
    val z = 5 [@@writeonly]

    (* write-only prop *)
    val t1 = Js.def 6 [@@optdef]

    (* optdef prop *)
    val t2 = Js.undefined () [@@jsoo.optdef]

    (* optdef prop *)
    method foo i = self##.y1 := self##.x1 + i
  end

[%%expect
{|
- : unit ->
    < foo : int -> unit Js_of_ocaml.Js.meth;
      t1 : int Js_of_ocaml.Js.optdef_prop;
      t2 : 't57 Js_of_ocaml.Js.optdef_prop;
      x1 : int Js_of_ocaml.Js.readonly_prop;
      x2 : int Js_of_ocaml.Js.readonly_prop; y1 : int Js_of_ocaml.Js.prop;
      y2 : int Js_of_ocaml.Js.prop; z : int Js_of_ocaml.Js.writeonly_prop >
    Js_of_ocaml.Js.t
= <fun>
|}]
