(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2021 Nomadic Labs, *) (* *) (* Permission is hereby granted, free of charge, to any person obtaining a *) (* copy of this software and associated documentation files (the "Software"),*) (* to deal in the Software without restriction, including without limitation *) (* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) (* and/or sell copies of the Software, and to permit persons to whom the *) (* Software is furnished to do so, subject to the following conditions: *) (* *) (* The above copyright notice and this permission notice shall be included *) (* in all copies or substantial portions of the Software. *) (* *) (* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) (* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) (* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) (* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) (* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) (* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) (* DEALINGS IN THE SOFTWARE. *) (* *) (*****************************************************************************) (** Autocompletion functions (removing holes from Mikhailsky terms). *) open Sampling_helpers (* ------------------------------------------------------------------------- *) (* Helpers *) let rec stack_length (stack : Type.Stack.t) acc = match stack.node with | Empty_t -> acc | Stack_var_t _ -> acc + 1 | Item_t (_, tl) -> stack_length tl (acc + 1) (* We need to sort and remove duplicate elements of sets and maps to make them Michelson-compatible. *) let sort_set_elements elements = List.sort_uniq (Structural_compare.compare ~prim_compare:Mikhailsky.Mikhailsky_signature.compare) elements let sort_map_elements elements = let open Micheline in List.sort_uniq (fun node1 node2 -> match (node1, node2) with | ( Prim (_, Mikhailsky_prim.D_Elt, [k1; _v1], _), Prim (_, Mikhailsky_prim.D_Elt, [k2; _v2], _) ) -> Structural_compare.compare ~prim_compare:Mikhailsky.Mikhailsky_signature.compare k1 k2 | _ -> Stdlib.failwith "Autocomp.sort_map_elements: invalid Michelson map") elements (* ------------------------------------------------------------------------- *) (* Error handling *) type error_case = | Cannot_complete_data of Mikhailsky.node * Kernel.Path.t | Cannot_complete_code of Mikhailsky.node * Kernel.Path.t exception Autocompletion_error of error_case let cannot_complete_data node path = raise (Autocompletion_error (Cannot_complete_data (node, path))) let cannot_complete_code node path = raise (Autocompletion_error (Cannot_complete_code (node, path))) (* ------------------------------------------------------------------------- *) (* Code & data autocompletion *) (* By default, comparable values are unit. *) let default_comparable_type = Type.unit let generate_comparable _sp = Mikhailsky.Data.unit (* Instantiates variables in a base type, remaining variables are mapped to some consistent choice of ground type (this is made complicated by comparability constraints) *) let rec instantiate_and_set ty = let open Inference.M in Inference.instantiate_base ty >>= fun ty -> replace_vars ty and replace_vars (ty : Type.Base.t) = let open Inference.M in let node = ty.node in match node with | Type.Base.Unit_t | Type.Base.Int_t | Type.Base.Nat_t | Type.Base.Bool_t | Type.Base.String_t | Type.Base.Bytes_t | Type.Base.Key_hash_t | Type.Base.Timestamp_t | Type.Base.Mutez_t | Type.Base.Key_t -> return ty | Type.Base.Var_t v -> ( get_repr_exn v >>= fun repr -> match repr with | Inference.Stack_type _ -> assert false | Inference.Base_type {comparable = _; repr = Some _} -> assert false | Inference.Base_type {comparable; repr = None} -> ( match comparable with | Inference.Comparable -> return default_comparable_type | Inference.Unconstrained | Inference.Not_comparable -> return Type.unit)) | Type.Base.Option_t ty -> replace_vars ty >>= fun ty -> return (Type.option ty) | Type.Base.Pair_t (lt, rt) -> replace_vars lt >>= fun lt -> replace_vars rt >>= fun rt -> return (Type.pair lt rt) | Type.Base.Or_t (lt, rt) -> replace_vars lt >>= fun lt -> replace_vars rt >>= fun rt -> return (Type.or_ lt rt) | Type.Base.List_t ty -> replace_vars ty >>= fun ty -> return (Type.list ty) | Type.Base.Set_t ty -> replace_vars ty >>= fun ty -> return (Type.set ty) | Type.Base.Map_t (k, v) -> replace_vars k >>= fun k -> replace_vars v >>= fun v -> return (Type.map k v) | Type.Base.Lambda_t (dom, range) -> replace_vars dom >>= fun dom -> replace_vars range >>= fun range -> return (Type.lambda dom range) let rec instantiate_and_set_stack (stack_ty : Type.Stack.t) = let open Inference.M in let node = stack_ty.node in match node with | Type.Stack.Empty_t -> return Type.empty | Type.Stack.Stack_var_t _ -> return Type.empty | Type.Stack.Item_t (hd, tl) -> instantiate_and_set hd >>= fun hd -> instantiate_and_set_stack tl >>= fun tl -> return (Type.item hd tl) (* In the following we perform computations in the composite monad (sampler o Inference.M.t), it is convenient to define the bind and return explicitly. *) module SM = struct type 'a t = 'a Inference.M.t sampler let ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t = fun m f rng_state s -> let x, s = m rng_state s in f x rng_state s [@@inline] let sample : 'a sampler -> 'a Inference.M.t sampler = fun x rng_state st -> (x rng_state, st) [@@inline] let deterministic : 'a Inference.M.t -> 'a t = fun x _rng_state -> x let return x _ s = (x, s) [@@inline] end module Make (Michelson_base : Michelson_samplers_base.S) (Crypto_samplers : Crypto_samplers.Finite_key_pool_S) = struct (* Generates minimally sized random data of specified type. Used in autocompletion. *) (* /!\ Always call [instantiate_and_set] on the type argument of [generate_data]. /!\ *) let rec generate_data : Type.Base.t -> Mikhailsky.node SM.t = fun ty -> let open SM in let open Type.Base in let desc = ty.node in match desc with | Var_t _v -> assert false | Unit_t -> return Mikhailsky.Data.unit | Int_t -> sample @@ Michelson_base.int >>= fun i -> let i = Protocol.Script_int.to_zint i in return (Mikhailsky.Data.big_integer i) | Nat_t -> sample @@ Michelson_base.nat >>= fun n -> let n = Protocol.Script_int.to_zint n in return (Mikhailsky.Data.big_natural n) | Bool_t -> sample Base_samplers.uniform_bool >>= fun b -> if b then return Mikhailsky.Data.true_ else return Mikhailsky.Data.false_ | String_t -> sample Michelson_base.string >>= fun str -> let str = Protocol.Script_string.to_string str in return (Mikhailsky.Data.string str) | Bytes_t -> sample Michelson_base.bytes >>= fun bytes -> return (Mikhailsky.Data.bytes bytes) | Key_hash_t -> sample Crypto_samplers.pkh >>= fun pkh -> return (Mikhailsky.Data.key_hash pkh) | Timestamp_t -> sample Michelson_base.timestamp >>= fun tstamp -> return (Mikhailsky.Data.timestamp tstamp) | Mutez_t -> sample Michelson_base.tez >>= fun tz -> return (Mikhailsky.Data.mutez tz) | Key_t -> sample Crypto_samplers.pk >>= fun pk -> return (Mikhailsky.Data.key pk) | Option_t ty -> sample Base_samplers.uniform_bool >>= fun b -> if b then return Mikhailsky.Data.none else generate_data ty >>= fun res -> return (Mikhailsky.Data.some res) | Pair_t (lty, rty) -> generate_data lty >>= fun lv -> generate_data rty >>= fun rv -> return (Mikhailsky.Data.pair lv rv) | Or_t (lty, rty) -> sample Base_samplers.uniform_bool >>= fun b -> if b then generate_data lty >>= fun v -> return (Mikhailsky.Data.left v) else generate_data rty >>= fun v -> return (Mikhailsky.Data.right v) | List_t _ty -> return (Mikhailsky.Data.list []) | Set_t _ty -> return (Mikhailsky.Data.set []) | Map_t (_kty, _vty) -> return (Mikhailsky.Data.map []) | Lambda_t (dom, range) -> invent_term Type.(item dom empty) Type.(item range empty) >>= fun code -> return (Mikhailsky.Data.lambda code) and invent_term (bef : Type.Stack.t) (aft : Type.Stack.t) : Mikhailsky.node list SM.t = let open SM in install_dummy_stack aft [] >>= fun code -> let terms = drop_stack bef code in return terms and drop_stack (stack : Type.Stack.t) code = Mikhailsky.Instructions.dropn (stack_length stack 0) :: code and install_dummy_stack (stack : Type.Stack.t) (acc : Mikhailsky.node list) = let open SM in match stack.node with | Empty_t -> return acc | Stack_var_t _ -> let acc = Mikhailsky.(Instructions.push unit_ty Data.unit) :: acc in return acc | Item_t (hd, tl) -> deterministic @@ instantiate_and_set hd >>= fun hd -> (match hd.node with | Lambda_t (dom, range) -> invent_term Type.(item dom empty) Type.(item range empty) >>= fun code -> let instr = Mikhailsky.(prim I_LAMBDA [seq code] []) in return instr | _ -> generate_data hd >>= fun term -> let ty = Mikhailsky.unparse_ty_exn hd in return (Mikhailsky.Instructions.push ty term)) >>= fun instr -> install_dummy_stack tl (instr :: acc) (* Autocomplete Mikhailsky data. When encountering a hole, we lookup its type and instantiate some random data of the specified type. *) let rec complete_data : Mikhailsky.node -> Kernel.Path.t -> Mikhailsky.node SM.t = let open SM in fun node path -> match node with | Micheline.Int (_, _) | Micheline.String (_, _) | Micheline.Bytes (_, _) -> return node | Micheline.Prim (_, D_Hole, _, _) -> ( deterministic @@ Inference.M.get_data_annot path >>= fun ty_opt -> match ty_opt with | None -> cannot_complete_data node path | Some ty -> deterministic @@ instantiate_and_set ty >>= fun ty -> generate_data ty) | Micheline.Prim (_, A_Set, [Micheline.Seq (_, elements)], _) -> complete_data_list (Kernel.Path.at_index 0 path) 0 elements [] >>= fun elements -> let elements = sort_set_elements elements in return (Mikhailsky.Data.set elements) | Micheline.Prim (_, A_Map, [Micheline.Seq (_, elements)], _) -> complete_data_list (Kernel.Path.at_index 0 path) 0 elements [] >>= fun elements -> let elements = sort_map_elements elements in return (Mikhailsky.Data.map elements) | Micheline.Prim (_, prim, subterms, _) -> complete_data_list path 0 subterms [] >>= fun subterms -> return (Mikhailsky.prim prim subterms []) | Micheline.Seq (_, subterms) -> complete_data_list path 0 subterms [] >>= fun subterms -> return (Mikhailsky.seq subterms) and complete_data_list path i subterms acc = let open SM in match subterms with | [] -> return (List.rev acc) | subterm :: tl -> let path' = Kernel.Path.at_index i path in complete_data subterm path' >>= fun term -> complete_data_list path (i + 1) tl (term :: acc) let complete_data typing node rng_state = let root_type_opt, _ = Inference.M.get_data_annot Kernel.Path.root typing in match root_type_opt with | None -> Stdlib.failwith "Autocomp.complete_data: cannot get type of expr" | Some ty -> let _, typing = Inference.instantiate_base ty typing in let result, _ = try complete_data node Kernel.Path.root rng_state typing with Autocompletion_error (Cannot_complete_data (subterm, path)) -> Format.eprintf "Cannot complete data@." ; Format.eprintf "at path %s@." (Kernel.Path.to_string path) ; Format.eprintf "%a@." Mikhailsky.pp subterm ; Stdlib.failwith "in autocomp.ml: unrecoverable failure" in let typ, _typing = try Inference.infer_data_with_state result with Inference.Ill_typed_script error -> Format.eprintf "%a@." Inference.pp_inference_error error ; Format.eprintf "%a@." Mikhailsky.pp result ; assert false in (result, typ) (* Autocomplete Mikhailsky code. *) let rec complete_code : Mikhailsky.node -> Kernel.Path.t -> Mikhailsky.node SM.t = let open SM in fun node path -> match node with | Micheline.Int (_, _) | Micheline.String (_, _) | Micheline.Bytes (_, _) -> return node | Micheline.Prim (_, I_Hole, _, _) -> ( deterministic @@ Inference.M.get_instr_annot path >>= function | None -> cannot_complete_code node path | Some {bef; aft} -> deterministic @@ Inference.instantiate bef >>= fun bef -> deterministic @@ Inference.instantiate aft >>= fun aft -> invent_term bef aft >>= fun code -> return (Mikhailsky.seq code)) | Micheline.Prim (_, prim, subterms, _) -> complete_code_list path 0 subterms [] >>= fun subterms -> return (Mikhailsky.prim prim subterms []) | Micheline.Seq (_, subterms) -> complete_code_list path 0 subterms [] >>= fun subterms -> return (Mikhailsky.seq subterms) and complete_code_list path i subterms acc = let open SM in match subterms with | [] -> return (List.rev acc) | subterm :: tl -> let path' = Kernel.Path.at_index i path in complete_code subterm path' >>= fun term -> complete_code_list path (i + 1) tl (term :: acc) let complete_code typing node rng_state = let root_type_opt, _ = Inference.M.get_instr_annot Kernel.Path.root typing in match root_type_opt with | None -> Stdlib.failwith "Autocomp.complete_code: cannot get type of expr" | Some {bef; aft} -> let _, typing = Inference.instantiate bef typing in let _, typing = Inference.instantiate aft typing in let result, _ = try complete_code node Kernel.Path.root rng_state typing with | Autocompletion_error (Cannot_complete_code (subterm, path)) -> Format.eprintf "Cannot complete code@." ; Format.eprintf "at path %s@." (Kernel.Path.to_string path) ; Format.eprintf "%a@." Mikhailsky.pp subterm ; Stdlib.failwith "in autocomp.ml: unrecoverable failure" | _ -> assert false in let (bef, aft), typing = try Inference.infer_with_state result with Inference.Ill_typed_script error -> Format.eprintf "%a@." Inference.pp_inference_error error ; Format.eprintf "%a@." Mikhailsky.pp result ; assert false in let bef, typing = instantiate_and_set_stack bef typing in let aft, typing = instantiate_and_set_stack aft typing in (result, (bef, aft), typing) end