From e2565e9f5bafadb7adb5160c948622338431308f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franc=CC=A7ois=20Pottier?= <francois.pottier@inria.fr> Date: Tue, 6 Oct 2020 11:54:59 +0200 Subject: [PATCH] Attempt to use polymorphic variants as a more flexible encoding. --- src/BuiltinBool.ml | 41 +++++++++- src/BuiltinExn.ml | 2 + src/BuiltinInt.ml | 21 +++-- src/Engine.ml | 36 ++++++--- src/Monolith.mli | 191 ++++++++++++++++++++++++++------------------- src/Ops.mli | 4 +- src/Spec.ml | 51 ++++++++---- 7 files changed, 227 insertions(+), 119 deletions(-) diff --git a/src/BuiltinBool.ml b/src/BuiltinBool.ml index 1c8b51f..41403d4 100644 --- a/src/BuiltinBool.ml +++ b/src/BuiltinBool.ml @@ -15,7 +15,42 @@ open Spec (* A concrete type: [bool]. *) -let bool = +let generate = + Gen.bool + +let print = + Print.bool + +(* In order to work around the value restriction, we must either inline away + [ifpol], [constructible_], and [deconstructible], so that [bool] is + recognized as a value; or make [bool] a function that takes a unit + argument. We choose the first approach, which is more painful for us, but + more efficient and less painful for the user. *) + +(* The trouble is that the user does not have such a choice when defining her + own constants. She must use the second approach. *) + +(* I would have liked to write this: + +let bool () = ifpol - (constructible_ Gen.bool Print.bool) - (deconstructible Print.bool) + (constructible_ generate print) + (deconstructible print) + + *) + +let appearance = + Code.infix "=" (* not a value; monomorphic *) + +let equal = + (=), appearance (* polymorphic *) + +let generate () = + let value = generate() in + value, Code.code (print value) + +let bool = + SpecIfPol ( + SpecConstructible { generate }, + SpecDeconstructible { equal; print } + ) diff --git a/src/BuiltinExn.ml b/src/BuiltinExn.ml index b1f97bd..bbdb3c6 100644 --- a/src/BuiltinExn.ml +++ b/src/BuiltinExn.ml @@ -45,3 +45,5 @@ let equal = let exn = SpecDeconstructible { equal; print } + +(* See BuiltinBool for comments about the value restriction. *) diff --git a/src/BuiltinInt.ml b/src/BuiltinInt.ml index b7831f2..c2b9969 100644 --- a/src/BuiltinInt.ml +++ b/src/BuiltinInt.ml @@ -15,19 +15,30 @@ open Spec (* A concrete type: [int]. *) -(* We do not equip the type [int] with a default generator, because that - would not make much sense; we have no idea what range is relevant. - Thus, [int] is deconstructible, not constructible. *) +(* We do not equip the type [int] with a default generator, because + that would not make much sense; we have no idea what range is + relevant. Thus, [int] is deconstructible, not constructible. *) + +(* See BuiltinBool for comments about the value restriction. *) + +let appearance = + Code.infix "=" (* not a value; monomorphic *) + +let equal = + (=), appearance (* polymorphic *) + +let print = + Print.int let int = - deconstructible Print.int + SpecDeconstructible { equal; print } (* Equipping the type [int] with a generator. *) let int_ (generate : unit -> int) = ifpol (constructible_ generate Print.int) - (int) + int let interval i j = int_ (Gen.interval i j) diff --git a/src/Engine.ml b/src/Engine.ml index 94b36d9..b694eb0 100644 --- a/src/Engine.ml +++ b/src/Engine.ml @@ -496,7 +496,7 @@ exception ConstructArgFailure of failure backtrack and try some other instruction. *) let rec construct_arg -: type r c . env -> (r, c) spec -> (r -> bool) -> arg * r * c +: type r c con . env -> (r, c, con) spec -> (r -> bool) -> arg * r * c = fun env spec p -> match spec with @@ -540,7 +540,7 @@ let rec construct_arg if p rv then outcome else Gen.reject() and construct_arg_aux -: type r c . env -> (r, c) spec -> arg * r * c +: type r c con . env -> (r, c, con) spec -> arg * r * c = fun env spec -> match spec with @@ -611,6 +611,11 @@ and construct_arg_aux | SpecDeferred spec -> construct_arg env (force spec) no_requirement + (* [SpecIfPol] is eliminated on the fly. We are working at a negative + polarity, so the left branch must be selected. *) + | SpecIfPol (spec, _) -> + construct_arg env spec no_requirement + (* We do not expect any of the following cases to arise. *) | SpecDeconstructible _ -> assert false @@ -624,12 +629,11 @@ and construct_arg_aux assert false | SpecMapInto _ -> assert false - | SpecIfPol _ -> - assert false and construct_arg_list -: type r c . env -> (r, c) spec -> int -> - arg list -> r list -> c list -> arg list * r list * c list +: type r c con . + env -> (r, c, con) spec -> int -> + arg list -> r list -> c list -> arg list * r list * c list = fun env spec n args rvs cvs -> if n = 0 then args, rvs, cvs @@ -675,8 +679,8 @@ let construct_arg env spec = in this context. *) let rec use -: type r c . - env -> (r, c) spec -> expr -> r -> c -> expr * (value, failure) result +: type r c con . + env -> (r, c, con) spec -> expr -> r -> c -> expr * (value, failure) result = fun env spec expr rv cv -> match spec with @@ -736,6 +740,11 @@ let rec use | SpecDeferred spec -> use env (force spec) expr rv cv + (* [SpecIfPol] is eliminated on the fly. We are working at a positive + polarity, so the right branch must be selected. *) + | SpecIfPol (_, spec) -> + use env spec expr rv cv + | spec -> (* Done. *) log "Done generating a context.\n%!"; @@ -780,8 +789,8 @@ let generate_instruction env : instruction * (value, failure) result = problem; if it turned out to be a problem, we could easily reset it to its initial height. *) -let rec deconstruct : type r c . - env -> r -> c -> (r, c) spec -> bool * pat +let rec deconstruct : type r c con . + env -> r -> c -> (r, c, con) spec -> bool * pat = fun env rv cv spec -> match spec, rv, cv with @@ -904,6 +913,11 @@ let rec deconstruct : type r c . | SpecDeferred spec, _, _ -> deconstruct env rv cv (force spec) + (* [SpecIfPol] is eliminated on the fly. We are working at a positive + polarity, so the right branch must be selected. *) + | SpecIfPol (_, spec), _, _ -> + deconstruct env rv cv spec + (* We do not expect the following cases to arise. *) | SpecConstructible _, _, _ -> assert false @@ -917,8 +931,6 @@ let rec deconstruct : type r c . assert false | SpecMapOutof _, _, _ -> assert false - | SpecIfPol _, _, _ -> - assert false (* -------------------------------------------------------------------------- *) diff --git a/src/Monolith.mli b/src/Monolith.mli index 1e6ce6c..1ca3777 100644 --- a/src/Monolith.mli +++ b/src/Monolith.mli @@ -232,10 +232,21 @@ type 'r nondet = these arguments should satisfy, what results are produced, what to do with these results, and so on. *) -(** A specification of type [('r, 'c) spec] describes an operation (or, more - generally, a value) whose type on the reference side is ['r] and whose type - on the candidate side is ['c]. *) -type (_, _) spec +(** A specification of type [('r, 'c, 'con) spec] describes a + family of values whose type on the reference side is ['r] and whose + type on the candidate side is ['c]. + + The phantom parameter ['con] indicates whether this specification is + constructible, that is, whether Monolith is capable of constructing + the values that inhabit it. A typical example of a nonconstructible + specification is an (ordinary or dependent) function specification. + A typical example of a place where a constructible specification is + required is in the left-hand side of a function. *) +type ('r, 'c, 'con) spec + +(** TODO comment must be constructible ; is not constructible *) +type 'a constructible = [< `Constructible ] as 'a +type 'a nonconstructible = [> `NonConstructible ] as 'a (* -------------------------------------------------------------------------- *) @@ -255,7 +266,7 @@ type (_, _) spec [('t, 't) spec]. *) val constructible: (unit -> 't code) -> - ('t, 't) spec + ('t, 't, _) spec (** [constructible_ generate print] describes a constructible type. @@ -266,7 +277,7 @@ val constructible: val constructible_: 't Gen.gen -> 't Print.printer -> - ('t, 't) spec + ('t, 't, _) spec (** [deconstructible ~equal print] describes a basic deconstructible type, that is, a type ['t] that is equipped with an equality test and with a @@ -278,7 +289,7 @@ val constructible_: val deconstructible: ?equal:(('t -> 't -> bool) code) -> ('t Print.printer) -> - ('t, 't) spec + ('t, 't, _ nonconstructible) spec (** [declare_abstract_type "t"] declares a new abstract type named "t". This name is used in certain error messages. Which types ['r] and ['c] are @@ -311,44 +322,44 @@ val declare_abstract_type: ?check: ('r -> ('c -> unit) code) -> ?var: string -> (* name: *) string -> - ('r, 'c) spec + ('r, 'c, _) spec (** [unit] represents the base type [unit]. This type is constructible and deconstructible. *) -val unit: (unit, unit) spec +val unit: (unit, unit, _) spec (** [bool] represents the base type [bool]. This type is constructible and deconstructible. *) -val bool: (bool, bool) spec +val bool: (bool, bool, _) spec (** [int] represents the basic deconstructible type [int]. It is {b not} constructible (that is, not equipped with a generator), because it usually does not make sense to generate an integer in the huge interval [\[min_int, max_int\]]. *) -val int: (int, int) spec +val int: (int, int, _ nonconstructible) spec (** [int_ g] represents the basic type [int], equipped with the generator [g]. This type is both constructible and deconstructible. *) -val int_: int Gen.gen -> (int, int) spec +val int_: int Gen.gen -> (int, int, _) spec (** [interval i j] is [int_ (Gen.interval i j)]. *) -val interval: int -> int -> (int, int) spec +val interval: int -> int -> (int, int, _) spec (** [interval_ i j] is [int_ (Gen.interval_ i j)]. *) -val interval_: int -> int -> (int, int) spec +val interval_: int -> int -> (int, int, _) spec (** [lt j] is [int_ (Gen.lt j)]. *) -val lt: int -> (int, int) spec +val lt: int -> (int, int, _) spec (** [le j] is [int_ (Gen.le j)]. *) -val le: int -> (int, int) spec +val le: int -> (int, int, _) spec (** [sequential()] is [int_ (Gen.sequential())]. *) -val sequential: unit -> (int, int) spec +val sequential: unit -> (int, int, _) spec -(** [exn] represents the base type [exn]. This type is deconstructible, +(** [exn()] represents the base type [exn]. This type is deconstructible, but not constructible. *) -val exn: (exn, exn) spec +val exn: (exn, exn, _ nonconstructible) spec (** [override_exn_eq f] overrides the notion of equality that is associated with the type [exn]. This notion of equality is used to compare an @@ -362,24 +373,24 @@ val exn: (exn, exn) spec val override_exn_eq: ((exn -> exn -> bool) -> (exn -> exn -> bool)) -> unit (** [ignored] is used to describe an output that must be ignored. *) -val ignored: ('r, 'c) spec +val ignored: ('r, 'c, _ nonconstructible) spec (** [***] is the pair type constructor. *) val ( *** ): - ('r1, 'c1) spec -> - ('r2, 'c2) spec -> - ('r1 * 'r2, 'c1 * 'c2) spec + ('r1, 'c1, 'con) spec -> + ('r2, 'c2, 'con) spec -> + ('r1 * 'r2, 'c1 * 'c2, 'con) spec (** [option] is the option type constructor. *) val option: - ('r, 'c) spec -> - ('r option, 'c option) spec + ('r, 'c, 'con) spec -> + ('r option, 'c option, 'con) spec (** [result] is the result type constructor. *) val result: - ('r1, 'c1) spec -> - ('r2, 'c2) spec -> - (('r1, 'r2) result, ('c1, 'c2) result) spec + ('r1, 'c1, 'con) spec -> + ('r2, 'c2, 'con) spec -> + (('r1, 'r2) result, ('c1, 'c2) result, 'con) spec (** [list ~length:n] is the list type constructor. It is equipped with a generator that constructs lists whose length is comprised between 0 @@ -388,25 +399,26 @@ val result: irrelevant. *) val list: ?length: int -> - ('r, 'c) spec -> - ('r list, 'c list) spec + ('r, 'c, 'con) spec -> + ('r list, 'c list, 'con) spec (** [^>] is the ordinary arrow combinator. It is used to describe a function of one argument. By using it several times, one can also describe curried functions of several arguments, as is usual in OCaml. This combinator imposes the absence of exceptions: the reference and candidate implementations are expected to not raise any exception. *) +(* TODO comment constructibility *) val (^>): - ('r1, 'c1) spec -> - ('r2, 'c2) spec -> - ('r1 -> 'r2, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r2, 'c2, _) spec -> + ('r1 -> 'r2, 'c1 -> 'c2, _ nonconstructible) spec (** [^?>] is the nondeterministic arrow combinator. [spec1 ^?> spec2] is a short-hand for [spec1 ^> nondet spec2]. *) val (^?>): - ('r1, 'c1) spec -> - ('r2, 'c2) spec -> - ('r1 -> 'c2 -> 'r2 diagnostic, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r2, 'c2, _) spec -> + ('r1 -> 'c2 -> 'r2 diagnostic, 'c1 -> 'c2, _ nonconstructible) spec (** [^!>] is the exceptional arrow combinator. It describes a function that may raise an exception, and it requests that this exception be caught and @@ -415,9 +427,9 @@ val (^?>): expected to raise comparable exceptions, according to the notion of equality at type [exn]. *) val (^!>): - ('r1, 'c1) spec -> - ('r2, 'c2) spec -> - ('r1 -> 'r2, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r2, 'c2, _ nonconstructible) spec -> + ('r1 -> 'r2, 'c1 -> 'c2, _ nonconstructible) spec (** [^!?>] is an arrow combinator that combines the exception effect and the nondeterminism effect. It describes a function that may raise an exception. @@ -432,9 +444,13 @@ val (^!>): then it must return its own behavior as a value of type [('r2, exn) result]. The reference implementation must never raise an exception. *) val (^!?>): - ('r1, 'c1) spec -> - ('r2, 'c2) spec -> - ('r1 -> ('c2, exn) result -> ('r2, exn) result diagnostic, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r2, 'c2, _ nonconstructible) spec -> + ( + 'r1 -> ('c2, exn) result -> ('r2, exn) result diagnostic, + 'c1 -> 'c2, + _ nonconstructible + ) spec (** [^>>] is the dependent arrow constructor. It is used to describe a function of one argument and give a name to this argument, which can be @@ -447,27 +463,31 @@ val (^!?>): implementation, so the [length] function must be the one provided by the reference implementation. *) val (^>>): - ('r1, 'c1) spec -> - ('r1 -> ('r2, 'c2) spec) -> - ('r1 -> 'r2, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r1 -> ('r2, 'c2, _) spec) -> + ('r1 -> 'r2, 'c1 -> 'c2, _ nonconstructible) spec (** [^?>>] is the nondeterministic dependent arrow combinator. *) val (^?>>): - ('r1, 'c1) spec -> - ('r1 -> ('r2, 'c2) spec) -> - ('r1 -> 'c2 -> 'r2 diagnostic, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r1 -> ('r2, 'c2, _) spec) -> + ('r1 -> 'c2 -> 'r2 diagnostic, 'c1 -> 'c2, _ nonconstructible) spec (** [^!>>] is the exceptional dependent arrow combinator. *) val (^!>>): - ('r1, 'c1) spec -> - ('r1 -> ('r2, 'c2) spec) -> - ('r1 -> 'r2, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r1 -> ('r2, 'c2, _ nonconstructible) spec) -> + ('r1 -> 'r2, 'c1 -> 'c2, _ nonconstructible) spec (** [^!?>>] is the exceptional nondeterministic dependent arrow combinator. *) val (^!?>>): - ('r1, 'c1) spec -> - ('r1 -> ('r2, 'c2) spec) -> - ('r1 -> ('c2, exn) result -> ('r2, exn) result diagnostic, 'c1 -> 'c2) spec + ('r1, 'c1, _ constructible) spec -> + ('r1 -> ('r2, 'c2, _ nonconstructible) spec) -> + ( + 'r1 -> ('c2, exn) result -> ('r2, exn) result diagnostic, + 'c1 -> 'c2, + _ nonconstructible + ) spec (** [%] is the subset constructor. It is used to restrict the set of arguments that can be passed to an operation; in other words, it is used @@ -477,18 +497,22 @@ val (^!?>>): (reference) stack is nonempty, and [stack] is the abstract type of stacks. This constructor currently cannot be applied to the result of an operation (where it would express a postcondition). *) -val (%): ('r -> bool) -> ('r, 'c) spec -> ('r, 'c) spec +val (%): + ('r -> bool) -> + ('r, 'c, 'con) spec -> + ('r, 'c, 'con) spec (** [map_into] specifies that a transformation must be applied to a value. The user must provide the reference side of the transformation, the candidate side of the transformation (under a displayable form), and a description of the output type of the transformation. This is typically used to transform the result of an operation, or an operation itself. *) +(* TODO comment *) val map_into: ('r1 -> 'r2) -> ('c1 -> 'c2) code -> - ('r2, 'c2) spec -> - ('r1, 'c1) spec + ('r2, 'c2, _) spec -> + ('r1, 'c1, _ nonconstructible) spec (** [map_outof] specifies that a transformation must be applied to a value. The user must provide the reference side of the transformation, the candidate @@ -498,36 +522,36 @@ val map_into: val map_outof: ('r1 -> 'r2) -> ('c1 -> 'c2) code -> - ('r1, 'c1) spec -> - ('r2, 'c2) spec + ('r1, 'c1, 'con) spec -> + ('r2, 'c2, 'con) spec (** [flip] exchanges the first two arguments of a curried function. *) val flip: - ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3) spec -> - ('r2 -> 'r1 -> 'r3, 'c2 -> 'c1 -> 'c3) spec + ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3, _) spec -> + ('r2 -> 'r1 -> 'r3, 'c2 -> 'c1 -> 'c3, _ nonconstructible) spec (** [rot2] moves the second argument of a curried function to the first position. It is synonymous with [flip]. *) val rot2: - ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3) spec -> - ('r2 -> 'r1 -> 'r3, 'c2 -> 'c1 -> 'c3) spec + ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3, _) spec -> + ('r2 -> 'r1 -> 'r3, 'c2 -> 'c1 -> 'c3, _ nonconstructible) spec (** [rot3] moves the third argument of a curried function to the first position. It is synonymous with [flip]. *) val rot3: - ('r3 -> 'r1 -> 'r2 -> 'r4, 'c3 -> 'c1 -> 'c2 -> 'c4) spec -> - ('r1 -> 'r2 -> 'r3 -> 'r4, 'c1 -> 'c2 -> 'c3 -> 'c4) spec + ('r3 -> 'r1 -> 'r2 -> 'r4, 'c3 -> 'c1 -> 'c2 -> 'c4, _) spec -> + ('r1 -> 'r2 -> 'r3 -> 'r4, 'c1 -> 'c2 -> 'c3 -> 'c4, _ nonconstructible) spec (** [curry] transforms a function that expects a pair into a function that expects two separate arguments. *) val curry: - ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3) spec -> - ('r1 * 'r2 -> 'r3, 'c1 * 'c2 -> 'c3) spec + ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3, _) spec -> + ('r1 * 'r2 -> 'r3, 'c1 * 'c2 -> 'c3, _ nonconstructible) spec (** [uncurry] performs the reverse transformation. *) val uncurry: - ('r1 * 'r2 -> 'r3, 'c1 * 'c2 -> 'c3) spec -> - ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3) spec + ('r1 * 'r2 -> 'r3, 'c1 * 'c2 -> 'c3, _) spec -> + ('r1 -> 'r2 -> 'r3, 'c1 -> 'c2 -> 'c3, _ nonconstructible) spec (** The constructor [nondet] is used to annotate the result of an operation whose {i specification} is nondeterministic. This indicates that several @@ -551,8 +575,8 @@ val uncurry: same result. It is recommended to use [nondet ignored] instead, which suppresses the redundant equality check. *) val nondet: - ('r, 'c) spec -> - ('c -> 'r diagnostic, 'c) spec + ('r, 'c, _) spec -> + ('c -> 'r diagnostic, 'c, _ nonconstructible) spec (** The conditional specification [ifpol neg pos] is interpreted as the specification [neg] when it is used in a negative position (that is, @@ -560,10 +584,15 @@ val nondet: specification [pos] when it is used in a positive position (that is, to describe the output of an operation). [ifpol] is a low-level combinator that can be used to define higher-level abstractions. *) -val ifpol: ('r, 'c) spec -> ('r, 'c) spec -> ('r, 'c) spec +val ifpol: + ('r, 'c, _ constructible) spec -> + ('r, 'c, _) spec -> + ('r, 'c, _) spec (** [fix] builds a recursive specification. *) -val fix: (('r, 'c) spec -> ('r, 'c) spec) -> ('r, 'c) spec +val fix: + (('r, 'c, 'con) spec -> ('r, 'c, 'con) spec) -> + ('r, 'c, 'con) spec (** The function call [abstract spec] declares a new abstract type [t] and equips it with a one-way conversion, from [t] to [spec]. It is @@ -578,12 +607,14 @@ val fix: (('r, 'c) spec -> ('r, 'c) spec) -> ('r, 'c) spec that is, before [main] is invoked. This implies that [abstract] cannot be used under a dependent arrow [^>>]. If you would like to so, then you must manually hoist the call to [abstract] outside of the arrow. *) -val abstract: ('r, 'c) spec -> ('r, 'c) spec +val abstract: + ('r, 'c, _) spec -> + ('r, 'c, _ constructible) spec (* TODO not good *) (* TODO document *) val affine: - ('r Seq.t, 'c Seq.t) spec -> - ('r Seq.t, 'c Seq.t) spec + ('r Seq.t, 'c Seq.t, 'con) spec -> + ('r Seq.t, 'c Seq.t, 'con) spec (* [declare_seq ~length:n] is a persistent sequence type constructor. It can be used both in input and output positions. When it is used in input @@ -600,8 +631,8 @@ val affine: all such calls be hoisted to the top level. *) val declare_seq: ?length: int -> - ('r, 'c) spec -> - ('r Seq.t, 'c Seq.t) spec + ('r, 'c, _ constructible) spec -> + ('r Seq.t, 'c Seq.t, _) spec (* -------------------------------------------------------------------------- *) @@ -613,7 +644,7 @@ val declare_seq: All calls to [declare] must take place before the engine is started, that is, before [main] is invoked. *) -val declare: string -> ('r, 'c) spec -> 'r -> 'c -> unit +val declare: string -> ('r, 'c, _) spec -> 'r -> 'c -> unit (** [declare_] is analogous to [declare], but expects a suspension instead of a string. Such a suspension is typically constructed using [define]. This @@ -622,7 +653,7 @@ val declare: string -> ('r, 'c) spec -> 'r -> 'c -> unit All calls to [declare_] must take place before the engine is started, that is, before [main] is invoked. *) -val declare_: string Lazy.t -> ('r, 'c) spec -> 'r -> 'c -> unit +val declare_: string Lazy.t -> ('r, 'c, _) spec -> 'r -> 'c -> unit (** [main fuel] is the main entry point. The parameter [fuel] is the maximum length of a run (expressed as a number of operations). A small value, such diff --git a/src/Ops.mli b/src/Ops.mli index 6abd715..85ef040 100644 --- a/src/Ops.mli +++ b/src/Ops.mli @@ -19,10 +19,10 @@ type op = string deferred (* See Monolith.mli for documentation. *) -val declare : string -> ('r, 'c) spec -> 'r -> 'c -> unit +val declare : string -> ('r, 'c, _) spec -> 'r -> 'c -> unit (* See Monolith.mli for documentation. *) -val declare_ : op -> ('r, 'c) spec -> 'r -> 'c -> unit +val declare_ : op -> ('r, 'c, _) spec -> 'r -> 'c -> unit (* [pick()] chooses an operation among those that have been declared. It returns a pair of the operation's name and value. *) diff --git a/src/Spec.ml b/src/Spec.ml index 1777758..5c7dfdc 100644 --- a/src/Spec.ml +++ b/src/Spec.ml @@ -41,14 +41,23 @@ type 'r nondet = (* See Monolith.mli for an explanation of the parameters of this type. *) -(* The constant [constructible] is used at an occurrence that requires a - constructible type. The constant [nonconstructible] is used to describe - a specification that is not constructible. A product or a sum unifies - the ['con] parameters of its components. Therefore, a type error occurs - if one descendant of a must-be-constructible occurrence is found to be - nonconstructible. *) -type constructible -type nonconstructible +(* At an occurrence that requires a constructible specification, the upper + bound [< `Constructible] is imposed on the index ['con]. + + At a specification that is not constructible, the lower bound + [> `NonConstructible] is imposed on the index ['con]. + + At a product or a sum, the ['con] parameters of the components are unified. + + Therefore, a type error occurs if one descendant of a must-be-constructible + occurrence is found to be nonconstructible. + + This encoding, based on polymorphic variants, does not require the index + ['con] to be universally quantified. It therefore works even in the face + of the value restriction. *) + +type 'a constructible = [< `Constructible ] as 'a +type 'a nonconstructible = [> `NonConstructible ] as 'a type ('r, 'c, 'con) spec = @@ -140,7 +149,7 @@ type ('r, 'c, 'con) spec = | SpecNondet : ('r, 'c, _) spec -> - ('c -> 'r diagnostic, 'c, nonconstructible) spec + ('c -> 'r diagnostic, 'c, _ nonconstructible) spec (* A deconstructible type is an OCaml type that is equipped with an equality test and a printer. This must be a concrete type: the types ['r] and ['c] @@ -152,23 +161,27 @@ type ('r, 'c, 'con) spec = | SpecDeconstructible : { equal : ('t -> 't -> bool) code; print : 't -> PPrint.document } -> - ('t, 't, nonconstructible) spec + ('t, 't, _ nonconstructible) spec (* [SpecTop] describes an output that must be ignored. *) (* It is not constructible. *) | SpecTop : - ('r, 'c, nonconstructible) spec + ('r, 'c, _ nonconstructible) spec (* Arrows. *) (* An arrow is not constructible: this reflects the fact that we cannot generate a function. *) + (* The left-hand side of an arrow must be constructible: this reflects + the fact that we need to generate an argument for a function that + we wish to invoke. *) + | SpecArrow : - ('r1, 'c1, _) spec * ('r2, 'c2, _) spec -> - ('r1 -> 'r2, 'c1 -> 'c2, nonconstructible) spec + ('r1, 'c1, _ constructible) spec * ('r2, 'c2, _) spec -> + ('r1 -> 'r2, 'c1 -> 'c2, _ nonconstructible) spec (* A dependent arrow allows the codomain to depend on a value of the domain. This allows naming an argument and referring to this name in the rest of @@ -179,9 +192,13 @@ type ('r, 'c, 'con) spec = (* A dependent arrow is not constructible. *) + (* The left-hand side of a dependent arrow must be constructible: this + reflects the fact that we need to generate an argument for a function + that we wish to invoke. *) + | SpecDependentArrow : - ('r1, 'c1, _) spec * ('r1 -> ('r2, 'c2, _) spec) -> - ('r1 -> 'r2, 'c1 -> 'c2, nonconstructible) spec + ('r1, 'c1, _ constructible) spec * ('r1 -> ('r2, 'c2, _) spec) -> + ('r1 -> 'r2, 'c1 -> 'c2, _ nonconstructible) spec (* [SpecMapInto] indicates that the user provides a value of type ['r1, 'c1] but wishes it to be transformed on the fly to a value of type ['r2, 'c2]. @@ -197,7 +214,7 @@ type ('r, 'c, 'con) spec = ('r1 -> 'r2) * ('c1 -> 'c2) code * ('r2, 'c2, _) spec -> - ('r1, 'c1, nonconstructible) spec + ('r1, 'c1, _ nonconstructible) spec (* [SpecMapOutof] indicates that the user provides a value of type ['r1, 'c1] but wishes it to be transformed on the fly to a value of type ['r2, 'c2]. @@ -220,7 +237,7 @@ type ('r, 'c, 'con) spec = (* Its left-hand side must be constructible, and it is constructible. *) | SpecIfPol : - ('r, 'c, constructible) spec * ('r, 'c, _) spec -> + ('r, 'c, _ constructible) spec * ('r, 'c, _) spec -> ('r, 'c, _) spec (* [SpecDeferred] allows constructing recursive (cyclic) specifications. *) -- GitLab