From c22f3ad9f5552f0c04040c4562be29969f2c77d0 Mon Sep 17 00:00:00 2001 From: public-release Date: Thu, 26 Sep 2024 19:08:47 +0100 Subject: [PATCH] v0.18~preview.129.42+498 --- src/array.mli | 7 +- src/base.ml | 10 +- src/comparable.ml | 48 +++++-- src/comparable_intf.ml | 38 +++-- src/dune | 2 +- src/either.ml | 2 +- src/either_intf.ml | 12 +- src/float.mli | 4 +- src/fn.ml | 8 +- src/fn.mli | 9 +- src/int.ml | 15 +- src/list.mli | 7 +- src/map.ml | 10 +- src/option.mli | 6 +- src/ordering.mli | 7 +- src/pretty_printer.mli | 7 +- src/queue_intf.ml | 6 +- src/runtime.js | 29 ++++ src/runtime.wat | 73 ++++++++++ src/sequence.mli | 11 +- src/set.ml | 113 ++++----------- src/set_intf.ml | 19 ++- src/string.ml | 32 +---- src/string_intf.ml | 5 +- src/string_stubs.c | 45 ++++++ src/sys.mli | 6 +- src/sys0.ml | 6 +- src/type_equal_intf.ml | 88 ------------ test/coverage/set/constructor.ml | 14 +- test/coverage/set/functor.ml | 10 +- test/dune | 4 +- test/test_base.ml | 66 +++++++++ test/test_map.ml | 67 +++++++++ test/test_set.ml | 240 +++++++++++++++++++++++++++++++ test/test_string.ml | 2 +- 35 files changed, 717 insertions(+), 311 deletions(-) create mode 100644 src/string_stubs.c diff --git a/src/array.mli b/src/array.mli index 3aa35b92..907ae3ab 100644 --- a/src/array.mli +++ b/src/array.mli @@ -2,10 +2,13 @@ open! Import -type 'a t = 'a array [@@deriving_inline compare ~localize, globalize, sexp, sexp_grammar] +type 'a t = 'a array +[@@deriving_inline compare ~localize, equal ~localize, globalize, sexp, sexp_grammar] include Ppx_compare_lib.Comparable.S1 with type 'a t := 'a t include Ppx_compare_lib.Comparable.S_local1 with type 'a t := 'a t +include Ppx_compare_lib.Equal.S1 with type 'a t := 'a t +include Ppx_compare_lib.Equal.S_local1 with type 'a t := 'a t val globalize : ('a -> 'a) -> 'a t -> 'a t @@ -263,8 +266,6 @@ val sorted_copy : 'a t -> compare:('a -> 'a -> int) -> 'a t val last : 'a t -> 'a [@@deprecated "[since 2024-07] This was renamed to [last_exn]"] val last_exn : 'a t -> 'a -val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool -val equal__local : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool (** The input array is copied internally so that future modifications of it do not change the sequence. *) diff --git a/src/base.ml b/src/base.ml index 111ac689..fed9ef97 100644 --- a/src/base.ml +++ b/src/base.ml @@ -652,10 +652,12 @@ module Export = struct (* This is declared as an external to be optimized away in more contexts. *) (** Reverse application operator. [x |> g |> f] is equivalent to [f (g (x))]. *) - external ( |> ) : 'a -> (('a -> 'b)[@local_opt]) -> 'b = "%revapply" + external ( |> ) : 'a 'b. 'a -> (('a -> 'b)[@local_opt]) -> 'b = "%revapply" + [@@layout_poly] (** Application operator. [g @@ f @@ x] is equivalent to [g (f (x))]. *) - external ( @@ ) : (('a -> 'b)[@local_opt]) -> 'a -> 'b = "%apply" + external ( @@ ) : 'a 'b. (('a -> 'b)[@local_opt]) -> 'a -> 'b = "%apply" + [@@layout_poly] (** Boolean operations *) @@ -665,7 +667,7 @@ module Export = struct external not : (bool[@local_opt]) -> bool = "%boolnot" (* This must be declared as an external for the warnings to work properly. *) - external ignore : (_[@local_opt]) -> unit = "%ignore" + external ignore : 'a. ('a[@local_opt]) -> unit = "%ignore" [@@layout_poly] (** Common string operations *) let ( ^ ) = String.( ^ ) @@ -712,5 +714,3 @@ exception Not_found_s = Not_found_s program refers to at least one value directly in [Base]; referring to values in [Base.Bool], for example, is not sufficient. *) let () = Backtrace.initialize_module () - -module Caml = struct end [@@deprecated "[since 2023-01] use Stdlib instead of Caml"] diff --git a/src/comparable.ml b/src/comparable.ml index f5a17dcc..18073243 100644 --- a/src/comparable.ml +++ b/src/comparable.ml @@ -69,11 +69,16 @@ end let gt cmp a b = cmp a b > 0 let lt cmp a b = cmp a b < 0 let geq cmp a b = cmp a b >= 0 +let geq_local cmp a b = cmp a b >= 0 let leq cmp a b = cmp a b <= 0 +let leq_local cmp a b = cmp a b <= 0 let equal cmp a b = cmp a b = 0 +let equal_local cmp a b = cmp a b = 0 let not_equal cmp a b = cmp a b <> 0 let min cmp t t' = Bool0.select (leq cmp t t') t t' +let min_local cmp t t' = Bool0.select (leq_local cmp t t') t t' let max cmp t t' = Bool0.select (geq cmp t t') t t' +let max_local cmp t t' = Bool0.select (geq_local cmp t t') t t' module Infix (T : sig type t [@@deriving_inline compare] @@ -189,19 +194,46 @@ Make (struct end) (* compare [x] and [y] lexicographically using functions in the list [cmps] *) -let lexicographic cmps x y = - let rec loop = function - | cmp :: cmps -> - let res = cmp x y in - if res = 0 then loop cmps else res - | [] -> 0 - in - loop cmps +let rec lexicographic_gen ~apply cmps x y = + match cmps with + | cmp :: cmps -> + let res = apply cmp x y in + if res = 0 then lexicographic_gen ~apply cmps x y else res + | [] -> 0 +;; + +let[@inline] lexicographic cmps x y = + let open Modes.Export in + lexicographic_gen + ~apply:(fun [@inline] cmp { global = x } { global = y } -> cmp x y) + cmps + { global = x } + { global = y } +;; + +let[@inline] lexicographic_local cmps x y = + lexicographic_gen ~apply:(fun [@inline] cmp x y -> cmp x y) cmps x y ;; let lift cmp ~f x y = cmp (f x) (f y) +let lift_local cmp ~f x y = cmp (f x) (f y) [@nontail] let reverse cmp x y = cmp y x +let reverse_local cmp x y = cmp y x type 'a reversed = 'a let compare_reversed cmp x y = cmp y x +let compare_reversed_local cmp x y = cmp y x + +module Local = struct + let lexicographic = lexicographic_local + let lift = lift_local + let reverse = reverse_local + + type 'a reversed = 'a + + let compare_reversed = compare_reversed_local + let equal = equal_local + let max = max_local + let min = min_local +end diff --git a/src/comparable_intf.ml b/src/comparable_intf.ml index 2d1056e6..9f76507d 100644 --- a/src/comparable_intf.ml +++ b/src/comparable_intf.ml @@ -7,15 +7,17 @@ module type Comparisons_with_zero_alloc = Comparisons.S_with_zero_alloc module Sign = Sign0 (** @canonical Base.Sign *) -module type With_compare = sig - (** Various combinators for [compare] and [equal] functions. *) +module type With_compare_gen = sig + type ('a, 'b) compare_fn + type ('a, 'b) fn + type 'a select_fn (** [lexicographic cmps x y] compares [x] and [y] lexicographically using functions in the list [cmps]. *) - val lexicographic : ('a -> 'a -> int) list -> 'a -> 'a -> int + val lexicographic : ('a, int) compare_fn list -> ('a, int) compare_fn (** [lift cmp ~f x y] compares [x] and [y] by comparing [f x] and [f y] via [cmp]. *) - val lift : ('a -> 'a -> 'result) -> f:('b -> 'a) -> 'b -> 'b -> 'result + val lift : ('a, 'result) compare_fn -> f:('b, 'a) fn -> ('b, 'result) compare_fn (** [reverse cmp x y = cmp y x] @@ -26,7 +28,7 @@ module type With_compare = sig [Comparable.S] provides [ascending] and [descending], which are more readable as a pair than [compare] and [reverse compare]. Similarly, [<=] is more idiomatic than [reverse (>=)]. *) - val reverse : ('a -> 'a -> 'result) -> 'a -> 'a -> 'result + val reverse : ('a, 'result) compare_fn -> ('a, 'result) compare_fn (** {!reversed} is the identity type but its associated compare function is the same as the {!reverse} function above. It allows you to get reversed comparisons with @@ -34,16 +36,32 @@ module type With_compare = sig have strings ordered in the reverse order. *) type 'a reversed = 'a - val compare_reversed : ('a -> 'a -> int) -> 'a reversed -> 'a reversed -> int + val compare_reversed : ('a, int) compare_fn -> ('a reversed, int) compare_fn (** The functions below are analogues of the type-specific functions exported by the [Comparable.S] interface. *) - val equal : ('a -> 'a -> int) -> 'a -> 'a -> bool - val max : ('a -> 'a -> int) -> 'a -> 'a -> 'a - val min : ('a -> 'a -> int) -> 'a -> 'a -> 'a + val equal : ('a, int) compare_fn -> ('a, bool) compare_fn + val max : ('a, int) compare_fn -> 'a select_fn + val min : ('a, int) compare_fn -> 'a select_fn end +(** Various combinators for [compare] and [equal] functions. *) +module type With_compare = + With_compare_gen + with type ('a, 'b) compare_fn := 'a -> 'a -> 'b + and type ('a, 'b) fn := 'a -> 'b + and type 'a select_fn := 'a -> 'a -> 'a +(** @inline *) + +(** Various combinators for [compare__local] and [equal__local] functions. *) +module type With_compare_local = + With_compare_gen + with type ('a, 'b) compare_fn := 'a -> 'a -> 'b + and type ('a, 'b) fn := 'a -> 'b + and type 'a select_fn := 'a -> 'a -> 'a +(** @inline *) + module type With_zero = sig type t @@ -149,9 +167,11 @@ module type Comparable = sig module type Comparisons = Comparisons module type Comparisons_with_zero_alloc = Comparisons_with_zero_alloc module type With_compare = With_compare + module type With_compare_local = With_compare_local module type With_zero = With_zero include With_compare + module Local : With_compare_local (** Derive [Infix] or [Comparisons] functions from just [[@@deriving compare]], without need for the [sexp_of_t] required by [Make*] (see below). *) diff --git a/src/dune b/src/dune index 894e0967..677df3c7 100644 --- a/src/dune +++ b/src/dune @@ -17,7 +17,7 @@ (foreign_stubs (language c) (names bytes_stubs exn_stubs float_stubs int_math_stubs hash_stubs - obj_stubs am_testing) + obj_stubs string_stubs am_testing) (flags :standard -D_LARGEFILE64_SOURCE diff --git a/src/either.ml b/src/either.ml index 0ee7ce17..4b3df3bf 100644 --- a/src/either.ml +++ b/src/either.ml @@ -44,7 +44,7 @@ let equal eq1 eq2 t1 t2 = | First _, Second _ | Second _, First _ -> false ;; -let local_equal eq1 eq2 t1 t2 = +let equal__local eq1 eq2 t1 t2 = match t1, t2 with | First x, First y -> eq1 x y | Second x, Second y -> eq2 x y diff --git a/src/either_intf.ml b/src/either_intf.ml index 7d2daf13..0aa12245 100644 --- a/src/either_intf.ml +++ b/src/either_intf.ml @@ -34,10 +34,12 @@ module type Either = sig type ('f, 's) t = ('f, 's) Either0.t = | First of 'f | Second of 's - [@@deriving_inline compare ~localize, hash, sexp, sexp_grammar] + [@@deriving_inline compare ~localize, equal ~localize, hash, sexp, sexp_grammar] include Ppx_compare_lib.Comparable.S2 with type ('f, 's) t := ('f, 's) t include Ppx_compare_lib.Comparable.S_local2 with type ('f, 's) t := ('f, 's) t + include Ppx_compare_lib.Equal.S2 with type ('f, 's) t := ('f, 's) t + include Ppx_compare_lib.Equal.S_local2 with type ('f, 's) t := ('f, 's) t include Ppx_hash_lib.Hashable.S2 with type ('f, 's) t := ('f, 's) t include Sexplib0.Sexpable.S2 with type ('f, 's) t := ('f, 's) t @@ -55,14 +57,6 @@ module type Either = sig val iter : ('a, 'b) t -> first:('a -> unit) -> second:('b -> unit) -> unit val value_map : ('a, 'b) t -> first:('a -> 'c) -> second:('b -> 'c) -> 'c val map : ('a, 'b) t -> first:('a -> 'c) -> second:('b -> 'd) -> ('c, 'd) t - val equal : ('f -> 'f -> bool) -> ('s -> 's -> bool) -> ('f, 's) t -> ('f, 's) t -> bool - - val local_equal - : ('f -> 'f -> bool) - -> ('s -> 's -> bool) - -> ('f, 's) t - -> ('f, 's) t - -> bool module type Focused = Focused diff --git a/src/float.mli b/src/float.mli index 2cc84ea8..90ee7476 100644 --- a/src/float.mli +++ b/src/float.mli @@ -127,8 +127,8 @@ val to_int64 : t -> int64 v} For convenience, versions of these functions with the [dir] argument hard-coded are - provided. If you are writing performance-critical code you should use the - versions with the hard-coded arguments (e.g. [iround_down_exn]). The [_exn] ones + provided. If you are writing performance-critical code you should use the + versions with the hard-coded arguments (e.g. [iround_down_exn]). The [_exn] ones are the fastest. The following properties hold: diff --git a/src/fn.ml b/src/fn.ml index d0c9ef3b..30c39c51 100644 --- a/src/fn.ml +++ b/src/fn.ml @@ -2,7 +2,7 @@ open! Import let const c _ = c -external ignore : (_[@local_opt]) -> unit = "%ignore" +external ignore : 'a. ('a[@local_opt]) -> unit = "%ignore" [@@layout_poly] (* this has the same behavior as [Stdlib.ignore] *) @@ -17,8 +17,10 @@ let forever f = | e -> e ;; -external id : ('a[@local_opt]) -> ('a[@local_opt]) = "%identity" -external ( |> ) : 'a -> (('a -> 'b)[@local_opt]) -> 'b = "%revapply" +external id : 'a. ('a[@local_opt]) -> ('a[@local_opt]) = "%identity" [@@layout_poly] + +external ( |> ) : 'a 'b. 'a -> (('a -> 'b)[@local_opt]) -> 'b = "%revapply" +[@@layout_poly] (* The typical use case for these functions is to pass in functional arguments and get functions as a result. *) diff --git a/src/fn.mli b/src/fn.mli index 2468495f..ed076b6a 100644 --- a/src/fn.mli +++ b/src/fn.mli @@ -6,13 +6,15 @@ open! Import See {{:https://github.com/janestreet/ppx_pipebang} ppx_pipebang} for further details. *) -external ( |> ) : 'a -> (('a -> 'b)[@local_opt]) -> 'b = "%revapply" +external ( |> ) : 'a 'b. 'a -> (('a -> 'b)[@local_opt]) -> 'b = "%revapply" +[@@layout_poly] (** Produces a function that just returns its first argument. *) val const : 'a -> _ -> 'a (** Ignores its argument and returns [()]. *) -external ignore : (_[@local_opt]) -> unit = "%ignore" +external ignore : 'a. ('a[@local_opt]) -> unit = "%ignore" +[@@layout_poly] (** Negates a boolean function. *) val non : ('a -> bool) -> 'a -> bool @@ -27,7 +29,8 @@ val apply_n_times : n:int -> ('a -> 'a) -> 'a -> 'a (** The identity function. See also: {!Sys.opaque_identity}. *) -external id : ('a[@local_opt]) -> ('a[@local_opt]) = "%identity" +external id : 'a. ('a[@local_opt]) -> ('a[@local_opt]) = "%identity" +[@@layout_poly] (** [compose f g x] is [f (g x)]. *) val compose : ('b -> 'c) -> ('a -> 'b) -> 'a -> 'c diff --git a/src/int.ml b/src/int.ml index 4a948d8c..3347a793 100644 --- a/src/int.ml +++ b/src/int.ml @@ -209,16 +209,19 @@ module Pow2 = struct let clz = Ocaml_intrinsics_kernel.Int.count_leading_zeros let ctz = Ocaml_intrinsics_kernel.Int.count_trailing_zeros + let[@cold] [@inline never] [@local never] [@specialise never] log2_bad_input i = + raise_s (Sexp.message "[Int.floor_log2] got invalid input" [ "", sexp_of_int i ]) + ;; + (** Hacker's Delight Second Edition p106 *) - let floor_log2 i = - if i <= 0 - then raise_s (Sexp.message "[Int.floor_log2] got invalid input" [ "", sexp_of_int i ]); - num_bits - 1 - clz i + let floor_log2 i = if i <= 0 then log2_bad_input i else num_bits - 1 - clz i + + let[@cold] [@inline never] [@local never] [@specialise never] log2_bad_input i = + raise_s (Sexp.message "[Int.ceil_log2] got invalid input" [ "", sexp_of_int i ]) ;; let ceil_log2 i = - if i <= 0 - then raise_s (Sexp.message "[Int.ceil_log2] got invalid input" [ "", sexp_of_int i ]); + if i <= 0 then log2_bad_input i; if i = 1 then 0 else num_bits - clz (i - 1) ;; end diff --git a/src/list.mli b/src/list.mli index 00cee4f8..f4ca91fd 100644 --- a/src/list.mli +++ b/src/list.mli @@ -5,10 +5,13 @@ open! Import type 'a t = 'a list -[@@deriving_inline compare ~localize, globalize, hash, sexp, sexp_grammar] +[@@deriving_inline + compare ~localize, equal ~localize, globalize, hash, sexp, sexp_grammar] include Ppx_compare_lib.Comparable.S1 with type 'a t := 'a t include Ppx_compare_lib.Comparable.S_local1 with type 'a t := 'a t +include Ppx_compare_lib.Equal.S1 with type 'a t := 'a t +include Ppx_compare_lib.Equal.S_local1 with type 'a t := 'a t val globalize : ('a -> 'a) -> 'a t -> 'a t @@ -487,8 +490,6 @@ val random_element_exn : ?random_state:Random.State.t -> 'a t -> 'a val is_sorted : 'a t -> compare:('a -> 'a -> int) -> bool val is_sorted_strictly : 'a t -> compare:('a -> 'a -> int) -> bool -val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool -val equal__local : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool module Infix : sig val ( @ ) : 'a t -> 'a t -> 'a t diff --git a/src/map.ml b/src/map.ml index 8f714fa0..9ac3cd6d 100644 --- a/src/map.ml +++ b/src/map.ml @@ -1368,9 +1368,9 @@ module Tree0 = struct Enum.compare compare_key compare_data e1 e2 ;; - let equal compare_key compare_data t1 t2 = + let equal compare_key data_equal t1 t2 = let e1, e2 = Enum.drop_phys_equal_prefix t1 End t2 End in - Enum.equal compare_key compare_data e1 e2 + Enum.equal compare_key data_equal e1 e2 ;; let iter2 t1 t2 ~f ~compare_key = @@ -2295,7 +2295,7 @@ module Accessors = struct Tree0.compare (compare_key t1) compare_data t1.tree t2.tree ;; - let equal compare_data t1 t2 = Tree0.equal (compare_key t1) compare_data t1.tree t2.tree + let equal data_equal t1 t2 = Tree0.equal (compare_key t1) data_equal t1.tree t2.tree let keys t = Tree0.keys t.tree let data t = Tree0.data t.tree let to_alist ?key_order t = Tree0.to_alist ?key_order t.tree @@ -2729,8 +2729,8 @@ module Tree = struct Tree0.compare comparator.Comparator.compare compare_data t1 t2 ;; - let equal ~comparator compare_data t1 t2 = - Tree0.equal comparator.Comparator.compare compare_data t1 t2 + let equal ~comparator data_equal t1 t2 = + Tree0.equal comparator.Comparator.compare data_equal t1 t2 ;; let keys t = Tree0.keys t diff --git a/src/option.mli b/src/option.mli index 42f2568a..a6e1111b 100644 --- a/src/option.mli +++ b/src/option.mli @@ -22,10 +22,12 @@ open! Import type 'a t = 'a option = | None | Some of 'a -[@@deriving_inline compare ~localize, globalize, hash, sexp_grammar] +[@@deriving_inline compare ~localize, equal ~localize, globalize, hash, sexp_grammar] include Ppx_compare_lib.Comparable.S1 with type 'a t := 'a t include Ppx_compare_lib.Comparable.S_local1 with type 'a t := 'a t +include Ppx_compare_lib.Equal.S1 with type 'a t := 'a t +include Ppx_compare_lib.Equal.S_local1 with type 'a t := 'a t val globalize : ('a -> 'a) -> 'a t -> 'a t @@ -35,8 +37,6 @@ val t_sexp_grammar : 'a Sexplib0.Sexp_grammar.t -> 'a t Sexplib0.Sexp_grammar.t [@@@end] -include Equal.S1 with type 'a t := 'a t -include Ppx_compare_lib.Equal.S_local1 with type 'a t := 'a t include Invariant.S1 with type 'a t := 'a t include Sexpable.S1 with type 'a t := 'a t diff --git a/src/ordering.mli b/src/ordering.mli index 95fea1ee..4c0fc684 100644 --- a/src/ordering.mli +++ b/src/ordering.mli @@ -30,10 +30,12 @@ type t = | Less | Equal | Greater -[@@deriving_inline compare ~localize, hash, sexp, sexp_grammar] +[@@deriving_inline compare ~localize, equal ~localize, hash, sexp, sexp_grammar] include Ppx_compare_lib.Comparable.S with type t := t include Ppx_compare_lib.Comparable.S_local with type t := t +include Ppx_compare_lib.Equal.S with type t := t +include Ppx_compare_lib.Equal.S_local with type t := t include Ppx_hash_lib.Hashable.S with type t := t include Sexplib0.Sexpable.S with type t := t @@ -44,9 +46,6 @@ val t_sexp_grammar : t Sexplib0.Sexp_grammar.t (*_ Avoid [@@deriving_inline enumerate] due to circular dependency *) val all : t list -include Equal.S with type t := t -include Ppx_compare_lib.Equal.S_local with type t := t - (** [of_int n] is: {v diff --git a/src/pretty_printer.mli b/src/pretty_printer.mli index 9bbac51c..427bc4f4 100644 --- a/src/pretty_printer.mli +++ b/src/pretty_printer.mli @@ -33,7 +33,12 @@ end (** [Register] builds a [pp] function from a [to_string] function, and adds the [module_name ^ ".pp"] to the list of pretty printers. The idea is to statically guarantee that one has the desired [pp] function at the same point where the [name] is - added. *) + added. + + [module_name ^ ".pp"] must be a valid OCaml identifier. It is recommended to not have + any "."s in [module_name]. For example, if [module_name] is "A.B" and "A.B" is not a + valid identifier because "A" is a valid library that doesn't expose a module "B", then + "A.B.pp" will not be a valid identifier. *) module Register (M : sig type t diff --git a/src/queue_intf.ml b/src/queue_intf.ml index a8b36215..73c07c15 100644 --- a/src/queue_intf.ml +++ b/src/queue_intf.ml @@ -101,18 +101,18 @@ module type Queue = sig module type S = S - type 'a t [@@deriving_inline compare ~localize, globalize] + type 'a t [@@deriving_inline compare ~localize, equal ~localize, globalize] include Ppx_compare_lib.Comparable.S1 with type 'a t := 'a t include Ppx_compare_lib.Comparable.S_local1 with type 'a t := 'a t + include Ppx_compare_lib.Equal.S1 with type 'a t := 'a t + include Ppx_compare_lib.Equal.S_local1 with type 'a t := 'a t val globalize : ('a -> 'a) -> 'a t -> 'a t [@@@end] include S with type 'a t := 'a t - include Equal.S1 with type 'a t := 'a t - include Ppx_compare_lib.Equal.S_local1 with type 'a t := 'a t include Invariant.S1 with type 'a t := 'a t (** Create an empty queue. *) diff --git a/src/runtime.js b/src/runtime.js index 88bdbc63..9a86b5e9 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -97,3 +97,32 @@ function Base_caml_modf_positive_float_unboxed_exn(a, b) { function Base_caml_modf_positive_float_exn(a, b) { return Base_caml_modf_positive_float_unboxed_exn(a, b); } + +//Provides: Base_string_concat_array +//Requires: caml_ml_string_length, caml_create_bytes, caml_blit_bytes +//Requires: caml_string_of_bytes, caml_string_of_jsstring +function Base_string_concat_array(v_string_array, v_sep) { + // Arrays have a header element at the beginning, so the indices in this function + // are off by one. Here, checking for length === 1 means the OCaml array is empty. + if (v_string_array.length === 1) { + return caml_string_of_jsstring(""); + } + const sep_len = caml_ml_string_length(v_sep); + let string_len = sep_len * (v_string_array.length - 2); + for (let i = 1; i < v_string_array.length; i++) { + string_len += caml_ml_string_length(v_string_array[i]); + } + const result = caml_create_bytes(string_len); + let pos = 0; + for (let i = 1; i < v_string_array.length; i++) { + if (i !== 1) { + caml_blit_bytes(v_sep, 0, result, pos, sep_len); + pos += sep_len; + } + const string = v_string_array[i]; + const len = caml_ml_string_length(string); + caml_blit_bytes(string, 0, result, pos, len); + pos += len; + } + return caml_string_of_bytes(result); +} diff --git a/src/runtime.wat b/src/runtime.wat index 2da2e03b..a09a126c 100644 --- a/src/runtime.wat +++ b/src/runtime.wat @@ -26,9 +26,13 @@ (func $fmod (param f64) (param f64) (result f64))) (import "env" "caml_invalid_argument" (func $caml_invalid_argument (param (ref eq)))) + (import "env" "caml_string_cat" + (func $caml_string_cat + (param (ref eq)) (param (ref eq)) (result (ref eq)))) (type $string (array (mut i8))) (type $float (struct (field f64))) + (type $block (array (mut (ref eq)))) (func (export "Base_int_math_int_popcount") (param (ref eq)) (result (ref eq)) @@ -192,4 +196,73 @@ (call $Double_val (local.get $y))) (then (local.get $x)) (else (local.get $y)))) + + (func (export "Base_string_concat_array") + (param $str_array (ref eq)) (param $sep_ref (ref eq)) (result (ref eq)) + (local $i i32) + (local $len i32) + (local $b (ref $block)) + (local $v (ref $string)) + (local $sep (ref $string)) + (local $str (ref $string)) + (local $total_len i32) + (local $sep_len i32) + (local $offset i32) + (local $local_len i32) + (local.set $sep (ref.cast (ref $string) (local.get $sep_ref))) + (local.set $sep_len (array.len (local.get $sep))) + (local.set $b (ref.cast (ref $block) (local.get $str_array))) + (local.set $len (array.len (local.get $b))) + (local.set $i (i32.const 1)) + (local.set $total_len (i32.const 0)) + (loop $compute_length + (if (i32.lt_s (local.get $i) (local.get $len)) + (then + (local.set $v (ref.cast + (ref $string) + (array.get $block (local.get $b) (local.get $i)))) + (local.set $total_len + (i32.add + (local.get $total_len) + (array.len (local.get $v)))) + (if (i32.lt_s (local.get $i) (i32.sub (local.get $len) (i32.const 1))) + (then + (local.set $total_len + (i32.add + (local.get $total_len) + (local.get $sep_len))))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $compute_length)))) + + (local.set $offset (i32.const 0)) + (local.set $local_len (i32.const 0)) + (local.set $i (i32.const 1)) + (local.set $str (array.new $string (i32.const 0) (local.get $total_len))) + (loop $loop + (if (i32.lt_s (local.get $i) (local.get $len)) + (then + (local.set $v + (ref.cast + (ref $string) + (array.get $block (local.get $b) (local.get $i)))) + (local.set $local_len (array.len (local.get $v))) + (array.copy $string $string + (local.get $str) + (local.get $offset) + (local.get $v) + (i32.const 0) + (local.get $local_len)) + (local.set $offset (i32.add (local.get $offset) (local.get $local_len))) + (if (i32.lt_s (local.get $i) (i32.sub (local.get $len) (i32.const 1))) + (then + (array.copy $string $string + (local.get $str) + (local.get $offset) + (local.get $sep) + (i32.const 0) + (local.get $sep_len)) + (local.set $offset (i32.add (local.get $offset) (local.get $sep_len))))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $loop)))) + (local.get $str)) ) diff --git a/src/sequence.mli b/src/sequence.mli index 38e43e0b..68faecd1 100644 --- a/src/sequence.mli +++ b/src/sequence.mli @@ -31,7 +31,12 @@ open! Import -type +'a t [@@deriving_inline globalize, sexp_of] +type +'a t [@@deriving_inline compare ~localize, equal ~localize, globalize, sexp_of] + +include Ppx_compare_lib.Comparable.S1 with type +'a t := 'a t +include Ppx_compare_lib.Comparable.S_local1 with type +'a t := 'a t +include Ppx_compare_lib.Equal.S1 with type +'a t := 'a t +include Ppx_compare_lib.Equal.S_local1 with type +'a t := 'a t val globalize : ('a -> 'a) -> 'a t -> 'a t val sexp_of_t : ('a -> Sexplib0.Sexp.t) -> 'a t -> Sexplib0.Sexp.t @@ -40,10 +45,6 @@ val sexp_of_t : ('a -> Sexplib0.Sexp.t) -> 'a t -> Sexplib0.Sexp.t type 'a sequence := 'a t -include Ppx_compare_lib.Equal.S1 with type 'a t := 'a t -include Ppx_compare_lib.Equal.S_local1 with type 'a t := 'a t -include Ppx_compare_lib.Comparable.S1 with type 'a t := 'a t -include Ppx_compare_lib.Comparable.S_local1 with type 'a t := 'a t include Indexed_container.S1 with type 'a t := 'a t include Monad.S with type 'a t := 'a t diff --git a/src/set.ml b/src/set.ml index 86a584a9..e2f5a570 100644 --- a/src/set.ml +++ b/src/set.ml @@ -268,45 +268,20 @@ module Tree0 = struct else create l v r ;; + let return_none () = None + (* Smallest and greatest element of a set *) - let rec min_elt = function - | Empty -> None + let rec call_with_min_elt ~none ~some = function + | Empty -> none () | Leaf { elt = v } | Node { left = Empty; elt = v; right = _; height = _; size = _ } - -> Some v - | Node { left = l; elt = _; right = _; height = _; size = _ } -> min_elt l - ;; - - exception Set_min_elt_exn_of_empty_set [@@deriving_inline sexp] - - let () = - Sexplib0.Sexp_conv.Exn_converter.add - [%extension_constructor Set_min_elt_exn_of_empty_set] - (function - | Set_min_elt_exn_of_empty_set -> - Sexplib0.Sexp.Atom "set.ml.Tree0.Set_min_elt_exn_of_empty_set" - | _ -> assert false) + -> some v + | Node { left = l; elt = _; right = _; height = _; size = _ } -> + call_with_min_elt l ~none ~some ;; - [@@@end] - - exception Set_max_elt_exn_of_empty_set [@@deriving_inline sexp] - - let () = - Sexplib0.Sexp_conv.Exn_converter.add - [%extension_constructor Set_max_elt_exn_of_empty_set] - (function - | Set_max_elt_exn_of_empty_set -> - Sexplib0.Sexp.Atom "set.ml.Tree0.Set_max_elt_exn_of_empty_set" - | _ -> assert false) - ;; - - [@@@end] - - let min_elt_exn t = - match min_elt t with - | None -> raise Set_min_elt_exn_of_empty_set - | Some v -> v - ;; + let raise_min_elt_exn () = Error.raise_s (Atom "Set.min_elt_exn: empty set") + let min_elt t = call_with_min_elt t ~none:return_none ~some:Option.return + let min_elt_exn t = call_with_min_elt t ~none:raise_min_elt_exn ~some:Fn.id let fold_until t ~init ~f ~finish = let rec fold_until_helper ~f t acc = @@ -326,18 +301,17 @@ module Tree0 = struct | Stop x -> x ;; - let rec max_elt = function - | Empty -> None + let rec call_with_max_elt ~none ~some = function + | Empty -> none () | Leaf { elt = v } | Node { left = _; elt = v; right = Empty; height = _; size = _ } - -> Some v - | Node { left = _; elt = _; right = r; height = _; size = _ } -> max_elt r + -> some v + | Node { left = _; elt = _; right = r; height = _; size = _ } -> + call_with_max_elt r ~none ~some ;; - let max_elt_exn t = - match max_elt t with - | None -> raise Set_max_elt_exn_of_empty_set - | Some v -> v - ;; + let raise_max_elt_exn () = Error.raise_s (Atom "Set.max_elt_exn: empty set") + let max_elt t = call_with_max_elt t ~none:return_none ~some:Option.return + let max_elt_exn t = call_with_max_elt t ~none:raise_max_elt_exn ~some:Fn.id (* Remove the smallest element of the given set *) @@ -974,23 +948,15 @@ module Tree0 = struct let elements s = elements_aux [] s - let choose t = - match t with - | Empty -> None - | Leaf { elt = v } -> Some v - | Node { left = _; elt = v; right = _; height = _; size = _ } -> Some v + let call_with_choose ~none ~some = function + | Empty -> none () + | Leaf { elt = v } -> some v + | Node { left = _; elt = v; right = _; height = _; size = _ } -> some v ;; - let choose_exn = - let not_found = Not_found_s (Atom "Set.choose_exn: empty set") in - let choose_exn t = - match choose t with - | None -> raise not_found - | Some v -> v - in - (* named to preserve symbol in compiled binary *) - choose_exn - ;; + let raise_choose_exn () = Error.raise_s (Atom "Set.choose_exn: empty set") + let choose t = call_with_choose t ~none:return_none ~some:Option.return + let choose_exn t = call_with_choose t ~none:raise_choose_exn ~some:Fn.id let of_list lst ~compare_elt = List.fold lst ~init:empty ~f:(fun t x -> add t x ~compare_elt) @@ -1079,7 +1045,7 @@ module Tree0 = struct let find_exn t ~f = match find t ~f with - | None -> failwith "Set.find_exn failed to find a matching element" + | None -> Error.raise_s (Atom "Set.find_exn: failed to find a matching element") | Some e -> e ;; @@ -1096,18 +1062,6 @@ module Tree0 = struct if c < 0 then nth l i else if c = 0 then Some v else nth r (i - l_size - 1)) ;; - let stable_dedup_list xs ~compare_elt = - let rec loop xs leftovers already_seen = - match xs with - | [] -> List.rev leftovers - | hd :: tl -> - if mem already_seen hd ~compare_elt - then loop tl leftovers already_seen - else loop tl (hd :: leftovers) (add already_seen hd ~compare_elt) - in - loop xs [] empty - ;; - let t_of_sexp_direct a_of_sexp sexp ~compare_elt = match sexp with | Sexp.List lst -> @@ -1368,11 +1322,6 @@ module Tree = struct let of_sorted_array ~comparator a = Tree0.of_sorted_array a ~compare_elt:(ce comparator) let union_list ~comparator l = Tree0.union_list l ~to_tree:Fn.id ~comparator - - let stable_dedup_list ~comparator xs = - Tree0.stable_dedup_list xs ~compare_elt:(ce comparator) - ;; - let group_by t ~equiv = Tree0.group_by t ~equiv let split ~comparator t a = Tree0.split t a ~compare_elt:(ce comparator) let split_le_gt ~comparator t a = Tree0.split_le_gt t a ~compare_elt:(ce comparator) @@ -1475,10 +1424,6 @@ module Using_comparator = struct { comparator; tree = Tree0.of_array a ~compare_elt:comparator.Comparator.compare } ;; - let stable_dedup_list ~comparator xs = - Tree0.stable_dedup_list xs ~compare_elt:comparator.Comparator.compare - ;; - let map ~comparator t ~f = { comparator; tree = Tree0.map t.tree ~f ~compare_elt:comparator.Comparator.compare } ;; @@ -1509,11 +1454,6 @@ let of_sorted_array m a = Using_comparator.of_sorted_array ~comparator:(to_compa let of_list m a = Using_comparator.of_list ~comparator:(to_comparator m) a let of_sequence m a = Using_comparator.of_sequence ~comparator:(to_comparator m) a let of_array m a = Using_comparator.of_array ~comparator:(to_comparator m) a - -let stable_dedup_list m a = - Using_comparator.stable_dedup_list ~comparator:(to_comparator m) a -;; - let map m a ~f = Using_comparator.map ~comparator:(to_comparator m) a ~f let filter_map m a ~f = Using_comparator.filter_map ~comparator:(to_comparator m) a ~f let to_tree = Using_comparator.to_tree @@ -1612,7 +1552,6 @@ module Poly = struct let of_list a = Using_comparator.of_list ~comparator a let of_sequence a = Using_comparator.of_sequence ~comparator a let of_array a = Using_comparator.of_array ~comparator a - let stable_dedup_list a = Using_comparator.stable_dedup_list ~comparator a let map a ~f = Using_comparator.map ~comparator a ~f let filter_map a ~f = Using_comparator.filter_map ~comparator a ~f let of_tree tree = { comparator; tree } diff --git a/src/set_intf.ml b/src/set_intf.ml index a8c18fe1..cda472b6 100644 --- a/src/set_intf.ml +++ b/src/set_intf.ml @@ -164,6 +164,11 @@ module type Transformers_generic = sig : ('a, 'cmp, ('a, 'cmp) t -> 'a elt -> ('a, 'cmp) t * ('a, 'cmp) t) access_options val group_by : ('a, 'cmp) t -> equiv:('a elt -> 'a elt -> bool) -> ('a, 'cmp) t list + [@@deprecated + "[since 2024-08] This function is slow (O(n^2)) and pretty much never the right \ + thing to use. Consider using [to_list] along with [List.sort_and_group] or \ + [List.group]."] + val remove_index : ('a, 'cmp, ('a, 'cmp) t -> int -> ('a, 'cmp) t) access_options end @@ -187,9 +192,6 @@ module type Creators_generic = sig val of_increasing_iterator_unchecked : ('a, 'cmp, len:int -> f:(int -> 'a elt) -> ('a, 'cmp) t) create_options - val stable_dedup_list : ('a, _, 'a elt list -> 'a elt list) create_options - [@@deprecated "[since 2023-04] Use [List.stable_dedup] instead."] - (** The types of [map] and [filter_map] are subtle. The input set, [('a, _) set], reflects the fact that these functions take a set of *any* type, with any comparator, while the output set, [('b, 'cmp) t], reflects that the output set has @@ -520,13 +522,6 @@ module type Set = sig -> f:(int -> 'a) -> ('a, 'cmp) t - (** [stable_dedup_list] is here rather than in the [List] module because the - implementation relies crucially on sets, and because doing so allows one to avoid uses - of polymorphic comparison by instantiating the functor at a different implementation - of [Comparator] and using the resulting [stable_dedup_list]. *) - val stable_dedup_list : ('a, _) Comparator.Module.t -> 'a list -> 'a list - [@@deprecated "[since 2023-04] Use [List.stable_dedup] instead."] - (** [map c t ~f] returns a new set created by applying [f] to every element in [t]. The returned set is based on the provided [comparator]. [O(n log n)]. *) val map : ('b, 'cmp) Comparator.Module.t -> ('a, _) t -> f:('a -> 'b) -> ('b, 'cmp) t @@ -641,6 +636,10 @@ module type Set = sig [group_by] runs in O(n^2) time, so if you have a comparison function, it's usually much faster to use [Set.of_list]. *) val group_by : ('a, 'cmp) t -> equiv:('a -> 'a -> bool) -> ('a, 'cmp) t list + [@@deprecated + "[since 2024-08] This function is slow (O(n^2)) and pretty much never the right \ + thing to use. Consider using [to_list] along with [List.sort_and_group] or \ + [List.group]."] (** [to_sequence t] converts the set [t] to a sequence of the elements between [greater_or_equal_to] and [less_or_equal_to] inclusive in the order indicated by diff --git a/src/string.ml b/src/string.ml index 72bb3c26..ca73bee2 100644 --- a/src/string.ml +++ b/src/string.ml @@ -863,35 +863,9 @@ let tr_multi ~target ~replacement = else s)) ;; -(* fast version, if we ever need it: - {[ - let concat_array ~sep ar = - let ar_len = Array.length ar in - if ar_len = 0 then "" - else - let sep_len = length sep in - let res_len_ref = ref (sep_len * (ar_len - 1)) in - for i = 0 to ar_len - 1 do - res_len_ref := !res_len_ref + length ar.(i) - done; - let res = create !res_len_ref in - let str_0 = ar.(0) in - let len_0 = length str_0 in - blit ~src:str_0 ~src_pos:0 ~dst:res ~dst_pos:0 ~len:len_0; - let pos_ref = ref len_0 in - for i = 1 to ar_len - 1 do - let pos = !pos_ref in - blit ~src:sep ~src_pos:0 ~dst:res ~dst_pos:pos ~len:sep_len; - let new_pos = pos + sep_len in - let str_i = ar.(i) in - let len_i = length str_i in - blit ~src:str_i ~src_pos:0 ~dst:res ~dst_pos:new_pos ~len:len_i; - pos_ref := new_pos + len_i - done; - res - ]} *) - -let concat_array ?sep ar = concat ?sep (Array.to_list ar) +external concat_array : string array -> sep:string -> string = "Base_string_concat_array" + +let concat_array ?(sep = "") ar = concat_array ar ~sep let concat_map ?sep s ~f = concat_array ?sep (Array.map (to_array s) ~f) let concat_mapi ?sep t ~f = concat_array ?sep (Array.mapi (to_array t) ~f) diff --git a/src/string_intf.ml b/src/string_intf.ml index e570079d..bbc22293 100644 --- a/src/string_intf.ml +++ b/src/string_intf.ml @@ -104,6 +104,7 @@ module type String = sig include Indexed_container.S0_with_creators with type t := t with type elt = char include Identifiable.S with type t := t include Ppx_compare_lib.Comparable.S_local with type t := t + include Ppx_compare_lib.Equal.S_local with type t := t include Invariant.S with type t := t (** Maximum length of a string. *) @@ -489,10 +490,6 @@ module type String = sig external hash : t -> int = "Base_hash_string" [@@noalloc] - (** Fast equality function on strings, doesn't use [compare_val]. *) - val equal : t -> t -> bool - - val equal__local : t -> t -> bool val of_char : char -> t val of_char_list : char list -> t diff --git a/src/string_stubs.c b/src/string_stubs.c new file mode 100644 index 00000000..ee554207 --- /dev/null +++ b/src/string_stubs.c @@ -0,0 +1,45 @@ +#include + +#include +#include +#include + +static inline mlsize_t string_concat_from_array(value v_string_array, mlsize_t i, + char *restrict result_contents) { + value string = Field(v_string_array, i); + mlsize_t len = caml_string_length(string); + memcpy(result_contents, String_val(string), len); + return len; +} + +CAMLprim value Base_string_concat_array(value v_string_array, value v_sep) { + CAMLparam2(v_string_array, v_sep); + CAMLlocal1(result); + const mlsize_t array_len = Wosize_val(v_string_array); + if (array_len == 0) { + result = caml_alloc_string(0); + CAMLreturn(result); + } + const mlsize_t sep_len = caml_string_length(v_sep); + mlsize_t string_len = (array_len - 1) * sep_len; + for (mlsize_t i = 0; i < array_len; i++) { + string_len += caml_string_length(Field(v_string_array, i)); + } + result = caml_alloc_string(string_len); + // This is freshly allocated and therefore safe to mark as [restrict] + char *restrict result_contents = (char *)(String_val(result)); + if (sep_len == 0) { + for (mlsize_t i = 0; i < array_len; i++) { + result_contents += string_concat_from_array(v_string_array, i, result_contents); + } + } else { + const char *sep = String_val(v_sep); + result_contents += string_concat_from_array(v_string_array, 0, result_contents); + for (mlsize_t i = 1; i < array_len; i++) { + memcpy(result_contents, sep, sep_len); + result_contents += sep_len; + result_contents += string_concat_from_array(v_string_array, i, result_contents); + } + } + CAMLreturn(result); +} diff --git a/src/sys.mli b/src/sys.mli index 949df619..64d9fe84 100644 --- a/src/sys.mli +++ b/src/sys.mli @@ -118,7 +118,9 @@ val getenv_exn : string -> string ignore (Sys.opaque_identity (my_pure_computation ())) done ]} *) -external opaque_identity : ('a[@local_opt]) -> ('a[@local_opt]) = "%opaque" +external opaque_identity : 'a. ('a[@local_opt]) -> ('a[@local_opt]) = "%opaque" +[@@layout_poly] (** Like [opaque_identity]. Forces its argument to be globally allocated. *) -external opaque_identity_global : 'a -> 'a = "%opaque" +external opaque_identity_global : 'a. 'a -> 'a = "%opaque" +[@@layout_poly] diff --git a/src/sys0.ml b/src/sys0.ml index 2857f80c..c0b62f23 100644 --- a/src/sys0.ml +++ b/src/sys0.ml @@ -50,7 +50,9 @@ let getenv var = | exception Stdlib.Not_found -> None ;; -external opaque_identity : ('a[@local_opt]) -> ('a[@local_opt]) = "%opaque" -external opaque_identity_global : 'a -> 'a = "%opaque" +external opaque_identity : 'a. ('a[@local_opt]) -> ('a[@local_opt]) = "%opaque" +[@@layout_poly] + +external opaque_identity_global : 'a. 'a -> 'a = "%opaque" [@@layout_poly] exception Break = Stdlib.Sys.Break diff --git a/src/type_equal_intf.ml b/src/type_equal_intf.ml index ba3a00ef..b61a83fb 100644 --- a/src/type_equal_intf.ml +++ b/src/type_equal_intf.ml @@ -72,94 +72,6 @@ module Type_equal_defns (Type_equal : T.T2) = struct -> ('a4, 'b4) Type_equal.t -> (('a1, 'a2, 'a3, 'a4) t, ('b1, 'b2, 'b3, 'b4) t) Type_equal.t end - - (** [Injective] is an interface that states that a type is injective, where the type is - viewed as a function from types to other types. It predates OCaml's support for - explicit injectivity annotations in the type system. - - The typical prior usage was: - - {[ - type 'a t - include Injective with type 'a t := 'a t - ]} - - For example, ['a list] is an injective type, because whenever ['a list = 'b list], - we know that ['a] = ['b]. On the other hand, if we define: - - {[ - type 'a t = unit - ]} - - then clearly [t] isn't injective, because, e.g., [int t = bool t], but - [int <> bool]. - - If [module M : Injective], then [M.strip] provides a way to get a proof that two - types are equal from a proof that both types transformed by [M.t] are equal. A - typical implementation looked like this: - - {[ - let strip (type a) (type b) - (Type_equal.T : (a t, b t) Type_equal.t) : (a, b) Type_equal.t = - Type_equal.T - ]} - - This will not type check for all type constructors (certainly not for non-injective - ones!), but it's always safe to try the above implementation if you are unsure. If - OCaml accepts this definition, then the type is injective. On the other hand, if - OCaml doesn't, then the type may or may not be injective. For example, if the - definition of the type depends on abstract types that match [Injective], OCaml will - not automatically use their injectivity, and one will have to write a more - complicated definition of [strip] that causes OCaml to use that fact. For example: - - {[ - module F (M : Type_equal.Injective) : Type_equal.Injective = struct - type 'a t = 'a M.t * int - - let strip (type a) (type b) - (e : (a t, b t) Type_equal.t) : (a, b) Type_equal.t = - let e1, _ = Type_equal.detuple2 e in - M.strip e1 - ;; - end - ]} - - If in the definition of [F] we had written the simpler implementation of [strip] that - didn't use [M.strip], then OCaml would have reported a type error. - *) - module type Injective = sig - type 'a t - - val strip : ('a t, 'b t) Type_equal.t -> ('a, 'b) Type_equal.t - end - [@@deprecated - "[since 2023-08] OCaml now supports injectivity annotations. [type !'a t] declares \ - that ['a t] is injective with respect to ['a]."] - - (** [Injective2] is for a binary type that is injective in both type arguments. *) - module type Injective2 = sig - type ('a1, 'a2) t - - val strip - : (('a1, 'a2) t, ('b1, 'b2) t) Type_equal.t - -> ('a1, 'b1) Type_equal.t * ('a2, 'b2) Type_equal.t - end - [@@deprecated - "[since 2023-08] OCaml now supports injectivity annotations. [type !'a t] declares \ - that ['a t] is injective with respect to ['a]."] - - (** [Composition_preserves_injectivity] is a functor that proves that composition of - injective types is injective. *) - module Composition_preserves_injectivity (M1 : Injective) (M2 : Injective) : - Injective with type 'a t = 'a M1.t M2.t = struct - type 'a t = 'a M1.t M2.t - - let strip e = M1.strip (M2.strip e) - end - [@@alert "-deprecated"] - [@@deprecated - "[since 2023-08] OCaml now supports injectivity annotations. [type !'a t] declares \ - that ['a t] is injective with respect to ['a]."] end module Type_equal_id_defns (Id : sig diff --git a/test/coverage/set/constructor.ml b/test/coverage/set/constructor.ml index 7bad4b1d..8f304605 100644 --- a/test/coverage/set/constructor.ml +++ b/test/coverage/set/constructor.ml @@ -361,7 +361,7 @@ module Constructor = struct (access Impl.split_lt_ge) (value t) elt |> Side.select side | Group_by (fn, n, t) -> let list = - Impl.group_by + (Impl.group_by [@alert "-deprecated"]) (value t) ~equiv:(Comparable.lift Int.equal ~f:(Func.apply fn (module Elt))) in @@ -400,10 +400,15 @@ module Make (Instance : Instance) (Impl : Impl with module Types := Instance.Typ struct open Instance - type t = Elt.t Constructor.t [@@deriving equal, quickcheck, sexp_of] + type t = Elt.t Constructor.t [@@deriving equal, quickcheck] include Constructor.Make (Instance) (Impl) + let sexp_of_t cons = + let value = value cons in + [%sexp { value : Instance.t; cons : Elt.t Constructor.t }] + ;; + let quickcheck_generator = Generator.map quickcheck_generator ~f:normalize end @@ -501,9 +506,6 @@ let%test_module (_ [@tags "64-bits-only"]) = let of_tree = Set.of_tree - (* Deprecated, not covered *) - let stable_dedup_list = (Set.stable_dedup_list [@alert "-deprecated"]) - (* Tests *) let empty = Set.empty @@ -945,7 +947,7 @@ let%test_module (_ [@tags "64-bits-only"]) = |}] ;; - let group_by = Set.group_by + let group_by = (Set.group_by [@alert "-deprecated"]) let%expect_test _ = test (function diff --git a/test/coverage/set/functor.ml b/test/coverage/set/functor.ml index d3a58198..5f6208d1 100644 --- a/test/coverage/set/functor.ml +++ b/test/coverage/set/functor.ml @@ -97,10 +97,6 @@ module Test_creators let () = print_endline "Functor.Test_creators: running tests." - (** deprecated, not testing *) - - and stable_dedup_list = (stable_dedup_list [@alert "-deprecated"]) - (** creators *) and empty = empty @@ -823,7 +819,7 @@ module Test_transformers require_equal (module Inst_and_inst.Value) by_partition_tf expect; require_equal (module Inst_and_inst.Value) by_filter expect) - and group_by = group_by + and group_by = (group_by [@alert "-deprecated"]) and () = quickcheck_m @@ -831,7 +827,9 @@ module Test_transformers ~f:(fun (t, i) -> let t = Inst.value t in let f e = Elt.to_int e % i in - let actual = group_by t ~equiv:(Comparable.lift Int.equal ~f) in + let actual = + (group_by [@alert "-deprecated"]) t ~equiv:(Comparable.lift Int.equal ~f) + in let expect = List.sort_and_group ~compare:(Comparable.lift Int.compare ~f) (to_list t) |> List.map ~f:(create of_list) diff --git a/test/dune b/test/dune index 186e16ca..55bc4cd9 100644 --- a/test/dune +++ b/test/dune @@ -1,7 +1,7 @@ (library (name base_test) (libraries base base_container_tests core.base_for_tests base_test_helpers - bignum.bigint expect_test_helpers_core.expect_test_helpers_base + bignum.bigint expect_test_helpers_core.expect_test_helpers_base expectable sexp_grammar_validation sexplib stdio) (preprocess - (pps ppx_jane -dont-apply=pipebang -no-check-on-extensions))) + (pps ppx_jane -dont-apply=pipebang -no-check-on-extensions ppx_template))) diff --git a/test/test_base.ml b/test/test_base.ml index c354a848..17fcd2e0 100644 --- a/test/test_base.ml +++ b/test/test_base.ml @@ -15,3 +15,69 @@ let%expect_test "exp is present at the toplevel" = print_s [%sexp (2 ** 8 : int)]; [%expect {| 256 |}] ;; + +(* The goal of these tests is to check the behavior of layout-polymorphic primitives like + [ignore]. We use ppx_template as a convenient way to bind functions of different + layouts to mangled identifiers that we can access with [@kind] syntax. This prevents + some code duplication, and makes it obvious which layout(s) we're testing. *) +let%test_module "layout polymorphism" = + (module [%template + include ( + struct + type t = int [@@kind immediate] + and t = nativeint [@@kind word] + and t = int32 [@@kind bits32] + and t = int64 [@@kind bits64] + and t = float [@@kind float64] + + let[@kind immediate] zero () = 0 + and[@kind word] zero () = 0n + and[@kind bits32] zero () = 0l + and[@kind bits64] zero () = 0L + and[@kind float64] zero () = 0. + + let[@kind immediate] is_zero = function + | 0 -> true + | _ -> false + + and[@kind word] is_zero = function + | 0n -> true + | _ -> false + + and[@kind bits32] is_zero = function + | 0l -> true + | _ -> false + + and[@kind bits64] is_zero = function + | 0L -> true + | _ -> false + + and[@kind float64] is_zero = function + | 0. -> true + | _ -> false + ;; + end : + sig + [@@@kind k = (immediate, word, bits32, bits64, float64)] + + type t [@@kind k] + + val zero : unit -> (t[@kind k]) [@@kind k] + val is_zero : (t[@kind k]) -> bool [@@kind k] + end) + + [@@@kind k = (immediate, word, bits32, bits64, float64)] + + (* Ignore a value with each of the specified layouts. *) + let%test_unit "ignore" = ignore ((zero [@kind k]) () : (t[@kind k])) + + (* Apply [Fn.id >> is_zero] to a value with each of the specified layouts. *) + let%test "apply/revapply identity" = + Fn.id @@ (zero [@kind k]) () |> (is_zero [@kind k]) + ;; + + (* As above but testing %opaque rather than %identity. *) + let%test "apply/revapply opaque" = + Sys.opaque_identity @@ (zero [@kind k]) () |> (is_zero [@kind k]) + ;;]) +;; diff --git a/test/test_map.ml b/test/test_map.ml index 0c875abb..a97fbcaf 100644 --- a/test/test_map.ml +++ b/test/test_map.ml @@ -470,3 +470,70 @@ let%expect_test "[merge_disjoint_exn] failure" = show_raise (fun () -> Map.merge_disjoint_exn map1 map2); [%expect {| (raised ("Map.merge_disjoint_exn: duplicate key" 2)) |}] ;; + +let create_balanced array = Map.of_sorted_array_unchecked (module Int) array + +let create_left_to_right array = + Array.fold + array + ~init:(Map.empty (module Int)) + ~f:(fun map (key, data) -> Map.add_exn map ~key ~data) +;; + +let create_right_to_left array = + Array.fold_right + array + ~init:(Map.empty (module Int)) + ~f:(fun (key, data) map -> Map.add_exn map ~key ~data) +;; + +let create_random array = + Array.permute ~random_state:(Random.State.make [| Array.length array |]) array; + create_left_to_right array +;; + +let%expect_test ("space" [@tags "no-js"]) = + let create ~length ~construction = + let array = Array.init length ~f:(fun x -> x + 1, x + 1) in + match construction with + | `l_to_r -> create_left_to_right array + | `r_to_l -> create_right_to_left array + | `balanced -> create_balanced array + | `random -> create_random array + in + let sexps = + let%bind.List length = [ 1; 100; 10_000; 1_000_000 ] in + let%map.List construction = [ `l_to_r; `r_to_l; `balanced; `random ] in + let t = create ~length ~construction in + let words = Stdlib.Obj.reachable_words (Stdlib.Obj.repr t) in + [%sexp + { length : int + ; construction : [ `l_to_r | `r_to_l | `balanced | `random ] + ; words : int + }] + in + Expectable.print sexps; + [%expect + {| + ┌───────────┬──────────────┬───────────┐ + │ length │ construction │ words │ + ├───────────┼──────────────┼───────────┤ + │ 1 │ l_to_r │ 17 │ + │ 1 │ r_to_l │ 17 │ + │ 1 │ balanced │ 17 │ + │ 1 │ random │ 17 │ + │ 100 │ l_to_r │ 464 │ + │ 100 │ r_to_l │ 464 │ + │ 100 │ balanced │ 503 │ + │ 100 │ random │ 509 │ + │ 10_000 │ l_to_r │ 45_014 │ + │ 10_000 │ r_to_l │ 45_014 │ + │ 10_000 │ balanced │ 47_726 │ + │ 10_000 │ random │ 48_887 │ + │ 1_000_000 │ l_to_r │ 4_500_014 │ + │ 1_000_000 │ r_to_l │ 4_500_014 │ + │ 1_000_000 │ balanced │ 4_572_875 │ + │ 1_000_000 │ random │ 4_884_050 │ + └───────────┴──────────────┴───────────┘ + |}] +;; diff --git a/test/test_set.ml b/test/test_set.ml index 009af980..0c84fc04 100644 --- a/test/test_set.ml +++ b/test/test_set.ml @@ -87,3 +87,243 @@ let%test_module "Poly" = ;; end) ;; + +let create_balanced array = Set.of_sorted_array_unchecked (module Int) array + +let create_left_to_right array = + Array.fold array ~init:(Set.empty (module Int)) ~f:(fun set elt -> Set.add set elt) +;; + +let create_right_to_left array = + Array.fold_right + array + ~init:(Set.empty (module Int)) + ~f:(fun elt set -> Set.add set elt) +;; + +let create_random array = + Array.permute ~random_state:(Random.State.make [| Array.length array |]) array; + create_left_to_right array +;; + +(* Shows which element is selected, for functions that choose/scan among elements. Some of + the functions do not, or did not, guarantee which element or which order, so it helps + to have a view of their choice and whether it is deterministic based on the elements or + whether it depends on the specific "shape" or "balance" of the tree. *) +let%test_module "element selection" = + (module struct + let create ~len = + let array = Array.init len ~f:Int.succ in + [ "balanced", create_balanced array + ; "left-heavy", create_left_to_right array + ; "right-heavy", create_right_to_left array + ] + ;; + + module type S = sig + type t [@@deriving compare, sexp_of] + end + + let test1 (type a) (module M : S with type t = a) fn = + Ref.set_temporarily sexp_style Sexp_style.simple_pretty ~f:(fun () -> + for len = 0 to 8 do + let results = + create ~len |> List.map ~f:(fun (set_name, set) -> fn set, set_name) + in + match results |> List.Assoc.sort_and_group ~compare:[%compare: M.t] with + | [ (singleton, _) ] -> + print_s [%message "" ~_:(len : int) ~_:(singleton : M.t)] + | _ -> + print_s + [%message "" ~_:(len : int) "multiple" ~_:(results : (M.t * string) list)] + done) + ;; + + let test2 fn_opt fn_exn = + test1 + (module struct + type t = int Or_error.t [@@deriving compare, sexp_of] + end) + (fun set -> + let option = fn_opt set in + let result = Or_error.try_with (fun () -> fn_exn set) in + require + ([%equal: int option] option (Or_error.ok result)) + ~if_false_then_print_s: + [%lazy_message + "output mismatch" (option : int option) (result : int Or_error.t)]; + result) + ;; + + let%expect_test "min_elt" = + test2 Set.min_elt Set.min_elt_exn; + [%expect + {| + (0 (Error "Set.min_elt_exn: empty set")) + (1 (Ok 1)) + (2 (Ok 1)) + (3 (Ok 1)) + (4 (Ok 1)) + (5 (Ok 1)) + (6 (Ok 1)) + (7 (Ok 1)) + (8 (Ok 1)) + |}] + ;; + + let%expect_test "max_elt" = + test2 Set.max_elt Set.max_elt_exn; + [%expect + {| + (0 (Error "Set.max_elt_exn: empty set")) + (1 (Ok 1)) + (2 (Ok 2)) + (3 (Ok 3)) + (4 (Ok 4)) + (5 (Ok 5)) + (6 (Ok 6)) + (7 (Ok 7)) + (8 (Ok 8)) + |}] + ;; + + let%expect_test "choose" = + test2 Set.choose Set.choose_exn; + [%expect + {| + (0 (Error "Set.choose_exn: empty set")) + (1 (Ok 1)) + (2 multiple (((Ok 2) balanced) ((Ok 1) left-heavy) ((Ok 2) right-heavy))) + (3 multiple (((Ok 2) balanced) ((Ok 1) left-heavy) ((Ok 3) right-heavy))) + (4 multiple (((Ok 3) balanced) ((Ok 2) left-heavy) ((Ok 3) right-heavy))) + (5 multiple (((Ok 3) balanced) ((Ok 2) left-heavy) ((Ok 4) right-heavy))) + (6 multiple (((Ok 4) balanced) ((Ok 2) left-heavy) ((Ok 5) right-heavy))) + (7 (Ok 4)) + (8 multiple (((Ok 5) balanced) ((Ok 4) left-heavy) ((Ok 5) right-heavy))) + |}] + ;; + + let%expect_test "find" = + let f x = x land 1 = 0 in + test2 (Set.find ~f) (Set.find_exn ~f); + [%expect + {| + (0 (Error "Set.find_exn: failed to find a matching element")) + (1 (Error "Set.find_exn: failed to find a matching element")) + (2 (Ok 2)) + (3 (Ok 2)) + (4 (Ok 2)) + (5 multiple (((Ok 2) balanced) ((Ok 2) left-heavy) ((Ok 4) right-heavy))) + (6 multiple (((Ok 4) balanced) ((Ok 2) left-heavy) ((Ok 2) right-heavy))) + (7 (Ok 4)) + (8 multiple (((Ok 2) balanced) ((Ok 4) left-heavy) ((Ok 2) right-heavy))) + |}] + ;; + + let%expect_test "find_map" = + let f x = if x land 1 = 0 then Some (x asr 1) else None in + test1 + (module struct + type t = int option [@@deriving compare, sexp_of] + end) + (Set.find_map ~f); + [%expect + {| + (0 ()) + (1 ()) + (2 (1)) + (3 (1)) + (4 (1)) + (5 multiple (((1) balanced) ((1) left-heavy) ((2) right-heavy))) + (6 multiple (((2) balanced) ((1) left-heavy) ((1) right-heavy))) + (7 (2)) + (8 multiple (((1) balanced) ((2) left-heavy) ((1) right-heavy))) + |}] + ;; + + let%expect_test "group_by" = + test1 + (module struct + type t = Set.M(Int).t list [@@deriving compare, sexp_of] + end) + ((Set.group_by [@alert "-deprecated"]) ~equiv:(fun x y -> + Int.popcount x = Int.popcount y)); + [%expect + {| + (0 ()) + (1 ((1))) + (2 ((1 2))) + (3 + multiple + ((((3) (1 2)) balanced) (((3) (1 2)) left-heavy) (((1 2) (3)) right-heavy))) + (4 + multiple + ((((1 2 4) (3)) balanced) + (((3) (1 2 4)) left-heavy) + (((1 2 4) (3)) right-heavy))) + (5 + multiple + ((((1 2 4) (3 5)) balanced) + (((3 5) (1 2 4)) left-heavy) + (((3 5) (1 2 4)) right-heavy))) + (6 + multiple + ((((3 5 6) (1 2 4)) balanced) + (((3 5 6) (1 2 4)) left-heavy) + (((1 2 4) (3 5 6)) right-heavy))) + (7 ((7) (3 5 6) (1 2 4))) + (8 + multiple + ((((7) (1 2 4 8) (3 5 6)) balanced) + (((7) (3 5 6) (1 2 4 8)) left-heavy) + (((7) (1 2 4 8) (3 5 6)) right-heavy))) + |}] + ;; + end) +;; + +let%expect_test ("space" [@tags "no-js"]) = + let create ~length ~construction = + let array = Array.init length ~f:Int.succ in + match construction with + | `l_to_r -> create_left_to_right array + | `r_to_l -> create_right_to_left array + | `balanced -> create_balanced array + | `random -> create_random array + in + let sexps = + let%bind.List length = [ 1; 100; 10_000; 1_000_000 ] in + let%map.List construction = [ `l_to_r; `r_to_l; `balanced; `random ] in + let t = create ~length ~construction in + let words = Stdlib.Obj.reachable_words (Stdlib.Obj.repr t) in + [%sexp + { length : int + ; construction : [ `l_to_r | `r_to_l | `balanced | `random ] + ; words : int + }] + in + Expectable.print sexps; + [%expect + {| + ┌───────────┬──────────────┬───────────┐ + │ length │ construction │ words │ + ├───────────┼──────────────┼───────────┤ + │ 1 │ l_to_r │ 15 │ + │ 1 │ r_to_l │ 15 │ + │ 1 │ balanced │ 15 │ + │ 1 │ random │ 15 │ + │ 100 │ l_to_r │ 413 │ + │ 100 │ r_to_l │ 413 │ + │ 100 │ balanced │ 465 │ + │ 100 │ random │ 473 │ + │ 10_000 │ l_to_r │ 40_013 │ + │ 10_000 │ r_to_l │ 40_013 │ + │ 10_000 │ balanced │ 43_629 │ + │ 10_000 │ random │ 45_177 │ + │ 1_000_000 │ l_to_r │ 4_000_013 │ + │ 1_000_000 │ r_to_l │ 4_000_013 │ + │ 1_000_000 │ balanced │ 4_097_161 │ + │ 1_000_000 │ random │ 4_512_061 │ + └───────────┴──────────────┴───────────┘ + |}] +;; diff --git a/test/test_string.ml b/test/test_string.ml index 9b4b92ab..4f72d0c3 100644 --- a/test/test_string.ml +++ b/test/test_string.ml @@ -818,7 +818,7 @@ let%test_module "tr_multi" = let quickcheck_generator = let open Base_quickcheck.Generator in let open Base_quickcheck.Generator.Let_syntax in - let%bind size = size in + let%bind size in let%bind target_len = int_log_uniform_inclusive 1 255 in let%bind target = string_with_length ~length:target_len in let%bind replacement_len = int_inclusive 1 target_len in