(* TEST
 expect;
*)

(* You may constrain abstract types in packages. *)
module type S = sig
  type t
end

type m = (module S with type t = int);;
[%%expect{|
module type S = sig type t end
type m = (module S with type t = int)
|}];;

(* You may use variables in the current environment in the new definitions. *)
module type S = sig
  type t
end

type 'a m = (module S with type t = 'a);;
[%%expect{|
module type S = sig type t end
type 'a m = (module S with type t = 'a)
|}];;

(* It works with non-trivial paths. *)
module type S = sig
  module M : sig
    type t
  end
end

type m = (module S with type M.t = int)
[%%expect{|
module type S = sig module M : sig type t end end
type m = (module S with type M.t = int)
|}];;

(* It should respect immediacy - [m1] should typecheck but not [m2]. *)
module type S = sig
  type t [@@immediate]
end

type m1 = (module S with type t = int)
type m2 = (module S with type t = string);;
[%%expect{|
module type S = sig type t [@@immediate] end
type m1 = (module S with type t = int)
Line 6, characters 18-40:
6 | type m2 = (module S with type t = string);;
                      ^^^^^^^^^^^^^^^^^^^^^^
Error: In this "with" constraint, the new definition of "t"
       does not match its original definition in the constrained signature:
       Type declarations do not match:
         type t = string
       is not included in
         type t [@@immediate]
       The first is not an immediate type.
|}];;

(* You may not constrain types with a manifest in a package *)
module type S = sig
  type t = int
end

type m = (module S with type t = string);;
[%%expect{|
module type S = sig type t = int end
Line 5, characters 17-39:
5 | type m = (module S with type t = string);;
                     ^^^^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "t" is defined to be "int".
       Package "with" constraints may only be used on abstract types.
|}];;

(* Even if your constraint would be satisfied. *)
(* It would be nice if this worked. *)
module type S = sig
  type t = int
end

type m = (module S with type t = int);;
[%%expect{|
module type S = sig type t = int end
Line 5, characters 17-36:
5 | type m = (module S with type t = int);;
                     ^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "t" is defined to be "int".
       Package "with" constraints may only be used on abstract types.
|}];;

(* And even if the manifest is not obvious in the original definition. *)
module M = struct
  type t
end

module type S = sig
  module P = M
end

type m = (module S with type P.t = int);;
[%%expect{|
module M : sig type t end
module type S = sig module P = M end
Line 9, characters 17-38:
9 | type m = (module S with type P.t = int);;
                     ^^^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "P.t" is defined to be "M.t".
       Package "with" constraints may only be used on abstract types.
|}];;

(* If writing a package constraint in a mutually recursive group of type decls,
   checking that the constraint's immediacy may not rely on the definitions of
   other elements of the mutually recursive group. *)
(* It would be nice if this worked. *)
module type S = sig
  type t [@@immediate]
end

type t1 = int
and t2 = (module S with type t = t1);;
[%%expect{|
module type S = sig type t [@@immediate] end
Line 6, characters 17-35:
6 | and t2 = (module S with type t = t1);;
                     ^^^^^^^^^^^^^^^^^^
Error: In this "with" constraint, the new definition of "t"
       does not match its original definition in the constrained signature:
       Type declarations do not match:
         type t = t1
       is not included in
         type t [@@immediate]
       The first is not an immediate type.
|}];;

(* When using a package with constraint to give an abstract type a definition
   that is immediate, that immediacy information should be usable after
   unpacking. *)
module type S = sig
  type t
end

type m = (module S with type t = int)

module F (X : sig val x : m end) = struct
  module M = (val X.x)
  type t = M.t [@@immediate]
end;;
[%%expect{|
module type S = sig type t end
type m = (module S with type t = int)
module F :
  (X : sig val x : m end) ->
    sig module M : sig type t = int end type t = M.t [@@immediate] end
|}];;

(* Checking such a constraint may require expanding definitions from the module
   being updated. *)
module type S = sig
  module type S1 = sig
    type t
  end
  module M : S1
end

type t = (module S with type M.t = int)
[%%expect{|
module type S = sig module type S1 = sig type t end module M : S1 end
type t = (module S with type M.t = int)
|}];;

(* Ghosts haunted type definitions *)
module type Private_row = sig
  type a
  and t = private [< `A | `B ]
  and b
  and d = private [< `C ]
end

(* This is fine, the ghost type `t#row` is removed silently *)
module type Test = Private_row with type t = [ `A ]

(* This fails currently.  If we ever allow it, make sure the ghost type is
   removed as above. *)
type fail = (module Private_row with type t = [ `A ] )

[%%expect{|
module type Private_row =
  sig type a and t = private [< `A | `B ] and b and d = private [< `C ] end
module type Test =
  sig type a and t = [ `A ] and b and d = private [< `C ] end
Line 13, characters 20-52:
13 | type fail = (module Private_row with type t = [ `A ] )
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "t" is defined to be "[< `A | `B ]".
       Package "with" constraints may only be used on abstract types.
|}]

(* More row type examples to consider, if we ever start allowing package type
   constraints to replace compatible manifests. *)
module type Private_row = sig
  type t = private [< `A ]
end

type t1 = (module Private_row with type t = [ `A ])
[%%expect{|
module type Private_row = sig type t = private [< `A ] end
Line 5, characters 18-50:
5 | type t1 = (module Private_row with type t = [ `A ])
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "t" is defined to be "[< `A ]".
       Package "with" constraints may only be used on abstract types.
|}]

type t2 = (module Private_row with type t = [< `A ])
[%%expect{|
Line 1, characters 18-51:
1 | type t2 = (module Private_row with type t = [< `A ])
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "t" is defined to be "[< `A ]".
       Package "with" constraints may only be used on abstract types.
|}]

type 'a t3 = (module Private_row with type t = [< `A ]) as 'a
[%%expect{|
Line 1, characters 21-54:
1 | type 'a t3 = (module Private_row with type t = [< `A ]) as 'a
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "t" is defined to be "[< `A ]".
       Package "with" constraints may only be used on abstract types.
|}]

type 'a t4 = (module Private_row with type t = [< `A ] as 'a)
[%%expect{|
Line 1, characters 21-60:
1 | type 'a t4 = (module Private_row with type t = [< `A ] as 'a)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: In the constrained signature, type "t" is defined to be "[< `A ]".
       Package "with" constraints may only be used on abstract types.
|}]

(** Issue 13778: constraining a type should count as using it *)
module X: sig
  type t
end = struct
  module type S = sig type s end
  type t = (module S with type s = int)
end
[%%expect {|
module X : sig type t end
|}]
