Error_monad: change error wrapping to flatten the JSON format

This commit is contained in:
Benjamin Canou 2018-02-21 20:58:53 +01:00
parent bd3191059b
commit 19eb1c2520
19 changed files with 455 additions and 209 deletions

View File

@ -13,10 +13,7 @@
type error_category = [ `Branch | `Temporary | `Permanent ] type error_category = [ `Branch | `Temporary | `Permanent ]
type 'err full_error_category = (* hack: forward reference from [Data_encoding_ezjsonm] *)
[ error_category | `Wrapped of 'err -> error_category ]
(* HACK: forward reference from [Data_encoding_ezjsonm] *)
let json_to_string = ref (fun _ -> "") let json_to_string = ref (fun _ -> "")
let json_pp id encoding ppf x = let json_pp id encoding ppf x =
@ -26,77 +23,202 @@ let json_pp id encoding ppf x =
Data_encoding.(merge_objs (obj1 (req "id" string)) encoding) in Data_encoding.(merge_objs (obj1 (req "id" string)) encoding) in
Data_encoding.Json.construct encoding (id, x) Data_encoding.Json.construct encoding (id, x)
module Make() = struct let set_error_encoding_cache_dirty = ref (fun () -> ())
module Make(Prefix : sig val id : string end) = struct
type error = .. type error = ..
module type Wrapped_error_monad = sig
type unwrapped = ..
include Error_monad_sig.S with type error := unwrapped
val unwrap : error -> unwrapped option
val wrap : unwrapped -> error
end
type full_error_category =
| Main of error_category
| Wrapped of (module Wrapped_error_monad)
(* the toplevel store for error kinds *) (* the toplevel store for error kinds *)
type error_kind = type error_kind =
Error_kind : Error_kind :
{ id: string ; { id: string ;
title: string ;
description: string ;
from_error: error -> 'err option ; from_error: error -> 'err option ;
category: 'err full_error_category ; category: full_error_category ;
encoding_case: error Data_encoding.case ; encoding_case: error Data_encoding.case ;
pp: Format.formatter -> 'err -> unit ; } -> pp: Format.formatter -> 'err -> unit ; } ->
error_kind error_kind
type error_info =
{ category : error_category ;
id: string ;
title : string ;
description : string ;
schema : Data_encoding.json_schema }
let error_kinds let error_kinds
: error_kind list ref : error_kind list ref
= ref [] = ref []
let get_registered_errors () : error_info list =
List.flatten
(List.map
(function
| Error_kind { id = "" ; _ } -> []
| Error_kind { id ; title ; description ; category = Main category ; encoding_case ; _ } ->
[ { id ; title ; description ; category ;
schema = Data_encoding.Json.schema (Data_encoding.union [ encoding_case ]) } ]
| Error_kind { category = Wrapped (module WEM) ; _ } ->
List.map
(fun { WEM.id ; title ; description ; category ; schema } ->
{ id ; title ; description ; category ; schema })
(WEM.get_registered_errors ()))
!error_kinds)
let error_encoding_cache = ref None let error_encoding_cache = ref None
let () =
let cont = !set_error_encoding_cache_dirty in
set_error_encoding_cache_dirty := fun () ->
cont () ;
error_encoding_cache := None
let string_of_category = function let string_of_category = function
| `Permanent -> "permanent" | `Permanent -> "permanent"
| `Temporary -> "temporary" | `Temporary -> "temporary"
| `Branch -> "branch" | `Branch -> "branch"
| `Wrapped _ -> "wrapped"
let pp_info
ppf
{ category; id; title; description; schema } =
Format.fprintf
ppf
"@[<v 2>category : %s\nid : %s\ntitle : %s\ndescription : %s\nschema : %a@]"
(string_of_category category)
id title description
(Json_repr.pp (module Json_repr.Ezjsonm))
(Json_schema.to_json schema)
(* Catch all error when 'serializing' an error. *)
type error += Unclassified of string
let () =
let id = "" in
let category = Main `Temporary in
let to_error msg = Unclassified msg in
let from_error = function
| Unclassified msg -> Some msg
| error ->
let msg = Obj.(extension_name @@ extension_constructor error) in
Some ("Unclassified error: " ^ msg ^ ". Was the error registered?") in
let title = "Generic error" in
let description = "An unclassified error" in
let encoding_case =
let open Data_encoding in
case Json_only
(describe ~title ~description @@
conv (fun x -> ((), x)) (fun ((), x) -> x) @@
(obj2
(req "kind" (constant "generic"))
(req "error" string)))
from_error to_error in
let pp = Format.pp_print_string in
error_kinds :=
Error_kind { id ; title ; description ;
from_error ; category ; encoding_case ; pp } :: !error_kinds
(* Catch all error when 'deserializing' an error. *)
type error += Unregistred_error of Data_encoding.json
let () =
let id = "" in
let category = Main `Temporary in
let to_error msg = Unregistred_error msg in
let from_error = function
| Unregistred_error json -> Some json
| _ -> None in
let encoding_case =
let open Data_encoding in
case Json_only json from_error to_error in
let pp ppf json =
Format.fprintf ppf "@[<v 2>Unregistred error:@ %a@]"
Data_encoding.Json.pp json in
error_kinds :=
Error_kind { id ; title = "" ; description = "" ;
from_error ; category ; encoding_case ; pp } :: !error_kinds
let raw_register_error_kind let raw_register_error_kind
category ~id:name ~title ~description ?pp category ~id:name ~title ~description ?pp
encoding from_error to_error = encoding from_error to_error =
let name = Prefix.id ^ name in
if List.exists if List.exists
(fun (Error_kind { id ; _ }) -> name = id) (fun (Error_kind { id ; _ }) -> name = id)
!error_kinds then !error_kinds then
invalid_arg invalid_arg
(Printf.sprintf (Printf.sprintf
"register_error_kind: duplicate error name: %s" name) ; "register_error_kind: duplicate error name: %s" name) ;
if not (Data_encoding.is_obj encoding)
then invalid_arg
(Printf.sprintf
"Specified encoding for \"%s\" is not an object, but error encodings must be objects."
name) ;
let encoding_case = let encoding_case =
let open Data_encoding in let open Data_encoding in
case Json_only match category with
(describe ~title ~description @@ | Wrapped (module WEM) ->
conv (fun x -> (((), ()), x)) (fun (((),()), x) -> x) @@ let unwrap err =
match WEM.unwrap err with
| Some (WEM.Unclassified _) -> None
| Some (WEM.Unregistred_error _) ->
Format.eprintf "What %s@." name ;
None
| res -> res in
let wrap err =
match err with
| WEM.Unclassified _ ->
failwith "ignore wrapped error when serializing"
| WEM.Unregistred_error _ ->
failwith "ignore wrapped error when deserializing"
| res -> WEM.wrap res in
case Json_only WEM.error_encoding unwrap wrap
| Main category ->
let with_id_and_kind_encoding =
merge_objs merge_objs
(obj2 (obj2
(req "kind" (constant (string_of_category category))) (req "kind" (constant (string_of_category category)))
(req "id" (constant name))) (req "id" (constant name)))
encoding) encoding in
case Json_only
(describe ~title ~description
(conv (fun x -> (((), ()), x)) (fun (((),()), x) -> x)
with_id_and_kind_encoding))
from_error to_error in from_error to_error in
error_encoding_cache := None ; !set_error_encoding_cache_dirty () ;
error_kinds := error_kinds :=
Error_kind { id = name ; Error_kind
{ id = name ;
category ; category ;
title ;
description ;
from_error ; from_error ;
encoding_case ; encoding_case ;
pp = Option.unopt ~default:(json_pp name encoding) pp } :: !error_kinds pp = Option.unopt ~default:(json_pp name encoding) pp }
:: !error_kinds
let register_wrapped_error_kind let register_wrapped_error_kind
category ~id ~title ~description ?pp (module WEM : Wrapped_error_monad) ~id ~title ~description =
encoding from_error to_error =
raw_register_error_kind raw_register_error_kind
(`Wrapped category) (Wrapped (module WEM))
~id ~title ~description ?pp ~id ~title ~description
encoding from_error to_error ~pp:WEM.pp WEM.error_encoding WEM.unwrap WEM.wrap
let register_error_kind let register_error_kind
category ~id ~title ~description ?pp category ~id ~title ~description ?pp
encoding from_error to_error = encoding from_error to_error =
if not (Data_encoding.is_obj encoding)
then invalid_arg
(Printf.sprintf
"Specified encoding for \"%s%s\" is not an object, but error encodings must be objects."
Prefix.id id) ;
raw_register_error_kind raw_register_error_kind
(category :> _ full_error_category) (Main category)
~id ~title ~description ?pp ~id ~title ~description ?pp
encoding from_error to_error encoding from_error to_error
@ -105,7 +227,7 @@ module Make() = struct
| None -> | None ->
let cases = let cases =
List.map List.map
(fun (Error_kind { encoding_case ; _ }) -> encoding_case ) (fun (Error_kind { encoding_case ; _ }) -> encoding_case)
!error_kinds in !error_kinds in
let json_encoding = Data_encoding.union cases in let json_encoding = Data_encoding.union cases in
let encoding = let encoding =
@ -134,10 +256,13 @@ module Make() = struct
(* assert false (\* See "Generic error" *\) *) (* assert false (\* See "Generic error" *\) *)
| Error_kind { from_error ; category ; _ } :: rest -> | Error_kind { from_error ; category ; _ } :: rest ->
match from_error e with match from_error e with
| Some x -> begin | Some _ -> begin
match category with match category with
| `Wrapped f -> f x | Main error_category -> error_category
| #error_category as x -> x | Wrapped (module WEM) ->
match WEM.unwrap e with
| Some e -> WEM.classify_errors [ e ]
| None -> find e rest
end end
| None -> find e rest in | None -> find e rest in
find error !error_kinds find error !error_kinds
@ -413,7 +538,6 @@ module Make() = struct
(Format.pp_print_list pp) (Format.pp_print_list pp)
(List.rev errors) (List.rev errors)
(** Catch all error when 'serializing' an error. *) (** Catch all error when 'serializing' an error. *)
type error += Unclassified of string type error += Unclassified of string
@ -464,7 +588,7 @@ module Make() = struct
let () = let () =
let id = "" in let id = "" in
let category = `Permanent in let category = Main `Permanent in
let to_error (loc, msg) = Assert_error (loc, msg) in let to_error (loc, msg) = Assert_error (loc, msg) in
let from_error = function let from_error = function
| Assert_error (loc, msg) -> Some (loc, msg) | Assert_error (loc, msg) -> Some (loc, msg)
@ -487,7 +611,8 @@ module Make() = struct
loc loc
(if msg = "" then "." else ": " ^ msg) in (if msg = "" then "." else ": " ^ msg) in
error_kinds := error_kinds :=
Error_kind { id; from_error ; category; encoding_case ; pp } :: !error_kinds Error_kind { id ; title ; description ;
from_error ; category ; encoding_case ; pp } :: !error_kinds
let _assert b loc fmt = let _assert b loc fmt =
if b then if b then
@ -497,7 +622,7 @@ module Make() = struct
end end
include Make() include Make(struct let id = "" end)
let generic_error fmt = let generic_error fmt =
Format.kasprintf (fun s -> error (Unclassified s)) fmt Format.kasprintf (fun s -> error (Unclassified s)) fmt

View File

@ -18,6 +18,18 @@ type error_category =
include Error_monad_sig.S include Error_monad_sig.S
module type Wrapped_error_monad = sig
type unwrapped = ..
include Error_monad_sig.S with type error := unwrapped
val unwrap : error -> unwrapped option
val wrap : unwrapped -> error
end
val register_wrapped_error_kind :
(module Wrapped_error_monad) ->
id:string -> title:string -> description:string ->
unit
(** Erroneous result (shortcut for generic errors) *) (** Erroneous result (shortcut for generic errors) *)
val generic_error : val generic_error :
('a, Format.formatter, unit, 'b tzresult) format4 -> ('a, Format.formatter, unit, 'b tzresult) format4 ->
@ -38,8 +50,8 @@ val pp_exn : Format.formatter -> exn -> unit
val failure : ('a, Format.formatter, unit, error) format4 -> 'a val failure : ('a, Format.formatter, unit, error) format4 -> 'a
(** Wrapped OCaml/Lwt exception *)
type error += Exn of exn type error += Exn of exn
type error += Unclassified of string
type error += Canceled type error += Canceled
@ -53,7 +65,7 @@ val with_timeout:
?canceler:Lwt_canceler.t -> ?canceler:Lwt_canceler.t ->
unit Lwt.t -> (Lwt_canceler.t -> 'a tzresult Lwt.t) -> 'a tzresult Lwt.t unit Lwt.t -> (Lwt_canceler.t -> 'a tzresult Lwt.t) -> 'a tzresult Lwt.t
module Make() : Error_monad_sig.S module Make(Prefix : sig val id : string end) : Error_monad_sig.S
(**/**) (**/**)
val json_to_string : (Data_encoding.json -> string) ref val json_to_string : (Data_encoding.json -> string) ref

View File

@ -18,6 +18,11 @@ module type S = sig
type error = .. type error = ..
(** Catch all error when 'serializing' an error. *)
type error += private Unclassified of string
(** Catch all error when 'deserializing' an error. *)
type error += private Unregistred_error of Data_encoding.json
val pp: Format.formatter -> error -> unit val pp: Format.formatter -> error -> unit
val pp_print_error: Format.formatter -> error list -> unit val pp_print_error: Format.formatter -> error list -> unit
@ -26,6 +31,21 @@ module type S = sig
val json_of_error : error -> Data_encoding.json val json_of_error : error -> Data_encoding.json
val error_of_json : Data_encoding.json -> error val error_of_json : Data_encoding.json -> error
(** {2 Error documentation} ************************************************)
(** Error information *)
type error_info =
{ category : error_category ;
id : string ;
title : string ;
description : string ;
schema : Data_encoding.json_schema }
val pp_info: Format.formatter -> error_info -> unit
(** Retrieves information of registered errors *)
val get_registered_errors : unit -> error_info list
(** {2 Error classification} ***********************************************) (** {2 Error classification} ***********************************************)
(** For other modules to register specialized error serializers *) (** For other modules to register specialized error serializers *)
@ -37,14 +57,6 @@ module type S = sig
(error -> 'err option) -> ('err -> error) -> (error -> 'err option) -> ('err -> error) ->
unit unit
val register_wrapped_error_kind :
('err -> error_category) ->
id:string -> title:string -> description:string ->
?pp:(Format.formatter -> 'err -> unit) ->
'err Data_encoding.t ->
(error -> 'err option) -> ('err -> error) ->
unit
(** Classify an error using the registered kinds *) (** Classify an error using the registered kinds *)
val classify_errors : error list -> error_category val classify_errors : error list -> error_category

View File

@ -29,6 +29,19 @@ val error_encoding : error Data_encoding.t
val json_of_error : error -> Data_encoding.json val json_of_error : error -> Data_encoding.json
val error_of_json : Data_encoding.json -> error val error_of_json : Data_encoding.json -> error
(** Error information *)
type error_info =
{ category : error_category ;
id: string ;
title : string ;
description : string ;
schema : Data_encoding.json_schema }
val pp_info: Format.formatter -> error_info -> unit
(** Retrieves information of registered errors *)
val get_registered_errors : unit -> error_info list
(** For other modules to register specialized error serializers *) (** For other modules to register specialized error serializers *)
val register_error_kind : val register_error_kind :
error_category -> error_category ->

View File

@ -137,7 +137,7 @@ module Make (Context : CONTEXT) = struct
and type (+'m,'pr,'p,'q,'i,'o) RPC_service.t = ('m,'pr,'p,'q,'i,'o) RPC_service.t and type (+'m,'pr,'p,'q,'i,'o) RPC_service.t = ('m,'pr,'p,'q,'i,'o) RPC_service.t
and type Error_monad.shell_error = Error_monad.error and type Error_monad.shell_error = Error_monad.error
type error += Ecoproto_error of Error_monad.error list type error += Ecoproto_error of Error_monad.error
val wrap_error : 'a Error_monad.tzresult -> 'a tzresult val wrap_error : 'a Error_monad.tzresult -> 'a tzresult
module Lift (P : Updater.PROTOCOL) : PROTOCOL module Lift (P : Updater.PROTOCOL) : PROTOCOL
@ -193,30 +193,31 @@ module Make (Context : CONTEXT) = struct
type 'a shell_tzresult = 'a Error_monad.tzresult type 'a shell_tzresult = 'a Error_monad.tzresult
type shell_error = Error_monad.error = .. type shell_error = Error_monad.error = ..
type error_category = [ `Branch | `Temporary | `Permanent ] type error_category = [ `Branch | `Temporary | `Permanent ]
include Error_monad.Make() include Error_monad.Make(struct let id = Format.asprintf "proto.%s." Param.name end)
end end
type error += Ecoproto_error of Error_monad.error list type error += Ecoproto_error of Error_monad.error
module Wrapped_error_monad = struct
type unwrapped = Error_monad.error = ..
include (Error_monad : Error_monad_sig.S with type error := unwrapped)
let unwrap = function
| Ecoproto_error ecoerror -> Some ecoerror
| _ -> None
let wrap ecoerror =
Ecoproto_error ecoerror
end
let () = let () =
let id = Format.asprintf "Ecoproto.%s" Param.name in let id = Format.asprintf "proto.%s.wrapper" Param.name in
register_wrapped_error_kind register_wrapped_error_kind
(fun ecoerrors -> Error_monad.classify_errors ecoerrors) (module Wrapped_error_monad)
~id ~title:"Error returned by the protocol" ~id ~title: ("Error returned by protocol " ^ Param.name)
~description:"Wrapped error for the economic protocol." ~description: ("Wrapped error for economic protocol " ^ Param.name ^ ".")
~pp:(fun ppf ->
Format.fprintf ppf
"@[<v 2>Economic error:@ %a@]"
(Format.pp_print_list Error_monad.pp))
Data_encoding.(obj1 (req "ecoproto"
(list Error_monad.error_encoding)))
(function Ecoproto_error ecoerrors -> Some ecoerrors
| _ -> None )
(function ecoerrors -> Ecoproto_error ecoerrors)
let wrap_error = function let wrap_error = function
| Ok _ as ok -> ok | Ok _ as ok -> ok
| Error errors -> Error [Ecoproto_error errors] | Error errors -> Error (List.map (fun error -> Ecoproto_error error) errors)
module Block_hash = Block_hash module Block_hash = Block_hash
module Operation_hash = Operation_hash module Operation_hash = Operation_hash
@ -269,19 +270,19 @@ module Make (Context : CONTEXT) = struct
| `Created s -> Lwt.return (`Created s) | `Created s -> Lwt.return (`Created s)
| `No_content -> Lwt.return (`No_content) | `No_content -> Lwt.return (`No_content)
| `Unauthorized e -> | `Unauthorized e ->
let e = Option.map e ~f:(fun e -> [Ecoproto_error e]) in let e = Option.map e ~f:(List.map (fun e -> Ecoproto_error e)) in
Lwt.return (`Unauthorized e) Lwt.return (`Unauthorized e)
| `Forbidden e -> | `Forbidden e ->
let e = Option.map e ~f:(fun e -> [Ecoproto_error e]) in let e = Option.map e ~f:(List.map (fun e -> Ecoproto_error e)) in
Lwt.return (`Forbidden e) Lwt.return (`Forbidden e)
| `Not_found e -> | `Not_found e ->
let e = Option.map e ~f:(fun e -> [Ecoproto_error e]) in let e = Option.map e ~f:(List.map (fun e -> Ecoproto_error e)) in
Lwt.return (`Not_found e) Lwt.return (`Not_found e)
| `Conflict e -> | `Conflict e ->
let e = Option.map e ~f:(fun e -> [Ecoproto_error e]) in let e = Option.map e ~f:(List.map (fun e -> Ecoproto_error e)) in
Lwt.return (`Conflict e) Lwt.return (`Conflict e)
| `Error e -> | `Error e ->
let e = Option.map e ~f:(fun e -> [Ecoproto_error e]) in let e = Option.map e ~f:(List.map (fun e -> Ecoproto_error e)) in
Lwt.return (`Error e)) Lwt.return (`Error e))
let register dir service handler = let register dir service handler =

View File

@ -130,7 +130,7 @@ module Make (Context : CONTEXT) : sig
and type (+'m,'pr,'p,'q,'i,'o) RPC_service.t = ('m,'pr,'p,'q,'i,'o) RPC_service.t and type (+'m,'pr,'p,'q,'i,'o) RPC_service.t = ('m,'pr,'p,'q,'i,'o) RPC_service.t
and type Error_monad.shell_error = Error_monad.error and type Error_monad.shell_error = Error_monad.error
type error += Ecoproto_error of Error_monad.error list type error += Ecoproto_error of Error_monad.error
val wrap_error : 'a Error_monad.tzresult -> 'a tzresult val wrap_error : 'a Error_monad.tzresult -> 'a tzresult
module Lift (P : Updater.PROTOCOL) : PROTOCOL module Lift (P : Updater.PROTOCOL) : PROTOCOL

View File

@ -321,8 +321,7 @@ module Assert = struct
equal_pkh ~msg expected_delegate actual_delegate equal_pkh ~msg expected_delegate actual_delegate
let ecoproto_error f = function let ecoproto_error f = function
| Alpha_environment.Ecoproto_error errors -> | Alpha_environment.Ecoproto_error error -> f error
List.exists f errors
| _ -> false | _ -> false
let hash op = Tezos_base.Operation.hash op let hash op = Tezos_base.Operation.hash op

View File

@ -131,7 +131,7 @@ let print_typecheck_result
let type_map, errs = match res with let type_map, errs = match res with
| Ok type_map -> type_map, [] | Ok type_map -> type_map, []
| Error (Alpha_environment.Ecoproto_error | Error (Alpha_environment.Ecoproto_error
(Script_tc_errors.Ill_typed_contract (_, type_map ) :: _) (Script_tc_errors.Ill_typed_contract (_, type_map ))
:: _ as errs) -> :: _ as errs) ->
type_map, errs type_map, errs
| Error errs -> | Error errs ->

View File

@ -68,69 +68,97 @@ let print_type_map ppf (parsed, type_map) =
let first_error_location errs = let first_error_location errs =
let rec find = function let rec find = function
| [] -> 0 | [] -> 0
| Inconsistent_type_annotations (loc, _, _) :: _ | (Inconsistent_type_annotations (loc, _, _)
| Unexpected_annotation loc :: _ | Unexpected_annotation loc
| Ill_formed_type (_, _, loc) :: _ | Ill_formed_type (_, _, loc)
| Invalid_arity (loc, _, _, _) :: _ | Invalid_arity (loc, _, _, _)
| Invalid_namespace (loc, _, _, _) :: _ | Invalid_namespace (loc, _, _, _)
| Invalid_primitive (loc, _, _) :: _ | Invalid_primitive (loc, _, _)
| Invalid_kind (loc, _, _) :: _ | Invalid_kind (loc, _, _)
| Fail_not_in_tail_position loc :: _ | Fail_not_in_tail_position loc
| Undefined_binop (loc, _, _, _) :: _ | Undefined_binop (loc, _, _, _)
| Undefined_unop (loc, _, _) :: _ | Undefined_unop (loc, _, _)
| Bad_return (loc, _, _) :: _ | Bad_return (loc, _, _)
| Bad_stack (loc, _, _, _) :: _ | Bad_stack (loc, _, _, _)
| Unmatched_branches (loc, _, _) :: _ | Unmatched_branches (loc, _, _)
| Transfer_in_lambda loc :: _ | Transfer_in_lambda loc
| Transfer_in_dip loc :: _ | Transfer_in_dip loc
| Invalid_constant (loc, _, _) :: _ | Invalid_constant (loc, _, _)
| Invalid_contract (loc, _) :: _ | Invalid_contract (loc, _)
| Comparable_type_expected (loc, _) :: _ | Comparable_type_expected (loc, _)
| Michelson_v1_primitives.Invalid_primitive_name loc :: _ -> loc | Michelson_v1_primitives.Invalid_primitive_name (_, loc)) :: _ -> loc
| _ :: rest -> find rest in | _ :: rest -> find rest in
find errs find errs
let report_errors ppf (parsed, errs) = let report_errors ppf (parsed, errs) =
Format.fprintf ppf "(@[<v 0>%a@])" let eco, out =
(Format.pp_print_list List.fold_left
(fun ppf err -> (fun (eco, out) -> function
| Alpha_environment.Ecoproto_error err -> (err :: eco, out)
| err -> (eco, err :: out))
([], []) errs in
let eco, out = List.rev eco, List.rev out in
Format.fprintf ppf "(@[<v 0>%a@,%a@])"
(fun ppf errs ->
let find_location loc = let find_location loc =
let oloc = List.assoc loc parsed.Michelson_v1_parser.unexpansion_table in let oloc = List.assoc loc parsed.Michelson_v1_parser.unexpansion_table in
fst (List.assoc oloc parsed.expansion_table) in fst (List.assoc oloc parsed.expansion_table) in
match errs with
| top :: errs ->
let errs, loc = let errs, loc =
match err with List.map
| Alpha_environment.Ecoproto_error (top :: errs) -> (fun e -> Alpha_environment.Ecoproto_error e)
[ Alpha_environment.Ecoproto_error (top :: errs) ], (top :: errs),
begin match top with match top with
| Ill_typed_contract (expr, _) | Ill_typed_contract (expr, _)
| Ill_typed_data (_, expr, _) -> | Ill_typed_data (_, expr, _) ->
if expr = parsed.expanded then if expr = parsed.expanded then
find_location (first_error_location (top :: errs)) find_location
(first_error_location
(top :: errs))
else find_location 0 else find_location 0
| Michelson_v1_primitives.Invalid_primitive_name loc -> | Michelson_v1_primitives.Invalid_primitive_name (expr, loc) ->
if Micheline.strip_locations (Michelson_v1_macros.unexpand_rec (Micheline.root expr)) =
parsed.Michelson_v1_parser.unexpanded then
find_location loc find_location loc
else
find_location 0
| _ -> find_location 0 | _ -> find_location 0
end in
| Invalid_utf8_sequence (point, _)
| Unexpected_character (point, _)
| Undefined_escape_sequence (point, _)
| Missing_break_after_number point as err ->
[ err ], { start = point ; stop = point }
| Unterminated_string loc
| Unterminated_integer loc
| Unterminated_comment loc
| Unclosed { loc }
| Unexpected { loc }
| Extra { loc } as err ->
[ err ], loc
| Misaligned node as err ->
[ err ], location node
| err -> [ err ], find_location 0 in
let message = let message =
Format.asprintf "%a" Format.asprintf "%a"
(Michelson_v1_error_reporter.report_errors (Michelson_v1_error_reporter.report_errors
~details:false ~show_source:false ~parsed) ~details:false ~show_source:false ~parsed)
errs in errs in
let { start = { point = s } ; stop = { point = e } } = loc in let { start = { point = s } ; stop = { point = e } } = loc in
Format.fprintf ppf "(%d %d %S)" (s + 1) (e + 1) message
| [] -> ())
eco
(Format.pp_print_list
(fun ppf err ->
let find_location loc =
let oloc = List.assoc loc parsed.Michelson_v1_parser.unexpansion_table in
fst (List.assoc oloc parsed.expansion_table) in
let loc =
match err with
| Invalid_utf8_sequence (point, _)
| Unexpected_character (point, _)
| Undefined_escape_sequence (point, _)
| Missing_break_after_number point ->
{ start = point ; stop = point }
| Unterminated_string loc
| Unterminated_integer loc
| Unterminated_comment loc
| Unclosed { loc }
| Unexpected { loc }
| Extra { loc } -> loc
| Misaligned node -> location node
| _ -> find_location 0 in
let message =
Format.asprintf "%a"
(Michelson_v1_error_reporter.report_errors
~details:false ~show_source:false ~parsed)
[ err ] in
let { start = { point = s } ; stop = { point = e } } = loc in
Format.fprintf ppf "(%d %d %S)" (s + 1) (e + 1) message)) Format.fprintf ppf "(%d %d %S)" (s + 1) (e + 1) message))
errs out

View File

@ -54,11 +54,15 @@ let rec print_enumeration ppf = function
let collect_error_locations errs = let collect_error_locations errs =
let rec collect acc = function let rec collect acc = function
| Ill_formed_type (_, _, loc) :: _ -> loc :: acc | Alpha_environment.Ecoproto_error
| (Ill_typed_data (_, _, _) (Ill_formed_type (_, _, _)
| Runtime_contract_error (_, _)
| Michelson_v1_primitives.Invalid_primitive_name (_, _)
| Ill_typed_data (_, _, _)
| Ill_typed_contract (_, _)) :: _ | Ill_typed_contract (_, _)) :: _
| [] -> acc | [] -> acc
| (Invalid_arity (loc, _, _, _) | Alpha_environment.Ecoproto_error
(Invalid_arity (loc, _, _, _)
| Inconsistent_type_annotations (loc, _, _) | Inconsistent_type_annotations (loc, _, _)
| Unexpected_annotation loc | Unexpected_annotation loc
| Type_too_large (loc, _, _) | Type_too_large (loc, _, _)
@ -80,8 +84,7 @@ let collect_error_locations errs =
| Invalid_contract (loc, _) | Invalid_contract (loc, _)
| Comparable_type_expected (loc, _) | Comparable_type_expected (loc, _)
| Overflow loc | Overflow loc
| Reject loc | Reject loc) :: rest ->
| Michelson_v1_primitives.Invalid_primitive_name loc) :: rest ->
collect (loc :: acc) rest collect (loc :: acc) rest
| _ :: rest -> collect acc rest in | _ :: rest -> collect acc rest in
collect [] errs collect [] errs
@ -113,7 +116,26 @@ let report_errors ~details ~show_source ?parsed ppf errs =
(List.mapi (fun i l -> (i + 1, l)) lines) in (List.mapi (fun i l -> (i + 1, l)) lines) in
match errs with match errs with
| [] -> () | [] -> ()
| Ill_typed_data (name, expr, ty) :: rest -> | Alpha_environment.Ecoproto_error (Michelson_v1_primitives.Invalid_primitive_name (expr, loc)) :: rest ->
let parsed =
match parsed with
| Some parsed ->
if Micheline.strip_locations (Michelson_v1_macros.unexpand_rec (Micheline.root expr)) =
parsed.Michelson_v1_parser.unexpanded then
parsed
else
Michelson_v1_printer.unparse_invalid expr
| None -> Michelson_v1_printer.unparse_invalid expr in
let hilights = loc :: collect_error_locations rest in
if show_source then
Format.fprintf ppf
"@[<hov 0>@[<hov 2>Invalid primitive:@ %a@]@]"
print_source (parsed, hilights)
else
Format.fprintf ppf "Invalid primitive." ;
if rest <> [] then Format.fprintf ppf "@," ;
print_trace (parsed_locations parsed) rest
| Alpha_environment.Ecoproto_error (Ill_typed_data (name, expr, ty)) :: rest ->
let parsed = let parsed =
match parsed with match parsed with
| Some parsed when expr = parsed.Michelson_v1_parser.expanded -> parsed | Some parsed when expr = parsed.Michelson_v1_parser.expanded -> parsed
@ -130,12 +152,12 @@ let report_errors ~details ~show_source ?parsed ppf errs =
print_ty (None, ty) ; print_ty (None, ty) ;
if rest <> [] then Format.fprintf ppf "@," ; if rest <> [] then Format.fprintf ppf "@," ;
print_trace (parsed_locations parsed) rest print_trace (parsed_locations parsed) rest
| Ill_formed_type (_, expr, loc) :: rest -> | Alpha_environment.Ecoproto_error (Ill_formed_type (_, expr, loc)) :: rest ->
let parsed = let parsed =
match parsed with match parsed with
| Some parsed when expr = parsed.Michelson_v1_parser.expanded -> parsed | Some parsed when expr = parsed.Michelson_v1_parser.expanded -> parsed
| Some _ | None -> Michelson_v1_printer.unparse_expression expr in | Some _ | None -> Michelson_v1_printer.unparse_expression expr in
let hilights = collect_error_locations errs in let hilights = loc :: collect_error_locations errs in
if show_source then if show_source then
Format.fprintf ppf Format.fprintf ppf
"@[<v 2>%aill formed type:@ %a@]" "@[<v 2>%aill formed type:@ %a@]"
@ -145,7 +167,7 @@ let report_errors ~details ~show_source ?parsed ppf errs =
"Ill formed type." ; "Ill formed type." ;
if rest <> [] then Format.fprintf ppf "@," ; if rest <> [] then Format.fprintf ppf "@," ;
print_trace (parsed_locations parsed) rest print_trace (parsed_locations parsed) rest
| Ill_typed_contract (expr, type_map) :: rest -> | Alpha_environment.Ecoproto_error (Ill_typed_contract (expr, type_map)) :: rest ->
let parsed = let parsed =
match parsed with match parsed with
| Some parsed when not details && expr = parsed.Michelson_v1_parser.expanded -> parsed | Some parsed when not details && expr = parsed.Michelson_v1_parser.expanded -> parsed
@ -159,20 +181,20 @@ let report_errors ~details ~show_source ?parsed ppf errs =
Format.fprintf ppf "Ill typed contract."; Format.fprintf ppf "Ill typed contract.";
if rest <> [] then Format.fprintf ppf "@," ; if rest <> [] then Format.fprintf ppf "@," ;
print_trace (parsed_locations parsed) rest print_trace (parsed_locations parsed) rest
| Missing_field prim :: rest -> | Alpha_environment.Ecoproto_error (Missing_field prim) :: rest ->
Format.fprintf ppf "@[<v 0>Missing contract field: %s@]" Format.fprintf ppf "@[<v 0>Missing contract field: %s@]"
(Michelson_v1_primitives.string_of_prim prim) ; (Michelson_v1_primitives.string_of_prim prim) ;
print_trace locations rest print_trace locations rest
| Duplicate_field (loc, prim) :: rest -> | Alpha_environment.Ecoproto_error (Duplicate_field (loc, prim)) :: rest ->
Format.fprintf ppf "@[<v 0>%aduplicate contract field: %s@]" Format.fprintf ppf "@[<v 0>%aduplicate contract field: %s@]"
print_loc loc print_loc loc
(Michelson_v1_primitives.string_of_prim prim) ; (Michelson_v1_primitives.string_of_prim prim) ;
print_trace locations rest print_trace locations rest
| Unexpected_big_map loc :: rest -> | Alpha_environment.Ecoproto_error (Unexpected_big_map loc) :: rest ->
Format.fprintf ppf "%abig_map type only allowed on the left of the toplevel storage pair" Format.fprintf ppf "%abig_map type only allowed on the left of the toplevel storage pair"
print_loc loc ; print_loc loc ;
print_trace locations rest print_trace locations rest
| Runtime_contract_error (contract, expr) :: rest -> | Alpha_environment.Ecoproto_error (Runtime_contract_error (contract, expr)) :: rest ->
let parsed = let parsed =
match parsed with match parsed with
| Some parsed when expr = parsed.Michelson_v1_parser.expanded -> parsed | Some parsed when expr = parsed.Michelson_v1_parser.expanded -> parsed
@ -184,7 +206,7 @@ let report_errors ~details ~show_source ?parsed ppf errs =
print_source (parsed, hilights) ; print_source (parsed, hilights) ;
if rest <> [] then Format.fprintf ppf "@," ; if rest <> [] then Format.fprintf ppf "@," ;
print_trace (parsed_locations parsed) rest print_trace (parsed_locations parsed) rest
| err :: rest -> | Alpha_environment.Ecoproto_error err :: rest ->
begin match err with begin match err with
| Apply.Bad_contract_parameter (c, None, _) -> | Apply.Bad_contract_parameter (c, None, _) ->
Format.fprintf ppf Format.fprintf ppf
@ -366,16 +388,20 @@ let report_errors ~details ~show_source ?parsed ppf errs =
@[<hov 2>is not compatible with type@ %a.@]@]" @[<hov 2>is not compatible with type@ %a.@]@]"
print_ty (None, tya) print_ty (None, tya)
print_ty (None, tyb) print_ty (None, tyb)
| Reject _ -> Format.fprintf ppf "Script reached FAIL instruction" | Reject loc ->
| Overflow _ -> Format.fprintf ppf "Unexpected arithmetic overflow" Format.fprintf ppf "%ascript reached FAIL instruction"
print_loc loc
| Overflow loc ->
Format.fprintf ppf "%aunexpected arithmetic overflow"
print_loc loc
| err -> Format.fprintf ppf "%a" Alpha_environment.Error_monad.pp err | err -> Format.fprintf ppf "%a" Alpha_environment.Error_monad.pp err
end ; end ;
if rest <> [] then Format.fprintf ppf "@," ; if rest <> [] then Format.fprintf ppf "@," ;
print_trace locations rest
| err :: rest ->
Format.fprintf ppf "%a" Error_monad.pp err ;
if rest <> [] then Format.fprintf ppf "@," ;
print_trace locations rest in print_trace locations rest in
Format.fprintf ppf "@[<v 0>%a@]" Format.fprintf ppf "@[<v 0>" ;
(Format.pp_print_list print_trace (fun _ -> None) errs ;
(fun ppf -> function Format.fprintf ppf "@]"
| Alpha_environment.Ecoproto_error errs ->
print_trace (fun _ -> None) errs
| err -> pp ppf err))
errs

View File

@ -503,6 +503,30 @@ let expand original =
expand_if_some ; expand_if_some ;
expand_if_right ] expand_if_right ]
let expand_rec expr =
let rec error_map (expanded, errors) f = function
| [] -> (List.rev expanded, List.rev errors)
| hd :: tl ->
let (new_expanded, new_errors) = f hd in
error_map
(new_expanded :: expanded, List.rev_append new_errors errors)
f tl in
let error_map = error_map ([], []) in
let rec expand_rec expr =
match expand expr with
| Ok expanded ->
begin
match expanded with
| Seq (loc, items, annot) ->
let items, errors = error_map expand_rec items in
(Seq (loc, items, annot), errors)
| Prim (loc, name, args, annot) ->
let args, errors = error_map expand_rec args in
(Prim (loc, name, args, annot), errors)
| Int _ | String _ as atom -> (atom, []) end
| Error errors -> (expr, errors) in
expand_rec expr
let unexpand_caddadr expanded = let unexpand_caddadr expanded =
let rec rsteps acc = function let rec rsteps acc = function
| [] -> Some acc | [] -> Some acc
@ -840,6 +864,14 @@ let unexpand original =
unexpand_if_some ; unexpand_if_some ;
unexpand_if_right ] unexpand_if_right ]
let rec unexpand_rec expr =
match unexpand expr with
| Seq (loc, items, annot) ->
Seq (loc, List.map unexpand_rec items, annot)
| Prim (loc, name, args, annot) ->
Prim (loc, name, List.map unexpand_rec args, annot)
| Int _ | String _ as atom -> atom
let () = let () =
let open Data_encoding in let open Data_encoding in
register_error_kind register_error_kind

View File

@ -16,6 +16,7 @@ type error += Sequence_expected of string
type error += Invalid_arity of string * int * int type error += Invalid_arity of string * int * int
val expand : 'l node -> 'l node tzresult val expand : 'l node -> 'l node tzresult
val expand_rec : 'l node -> 'l node * error list
val expand_caddadr : 'l node -> 'l node option tzresult val expand_caddadr : 'l node -> 'l node option tzresult
val expand_set_caddadr : 'l node -> 'l node option tzresult val expand_set_caddadr : 'l node -> 'l node option tzresult
@ -30,6 +31,7 @@ val expand_if_some : 'l node -> 'l node option tzresult
val expand_if_right : 'l node -> 'l node option tzresult val expand_if_right : 'l node -> 'l node option tzresult
val unexpand : 'l node -> 'l node val unexpand : 'l node -> 'l node
val unexpand_rec : 'l node -> 'l node
val unexpand_caddadr : 'l node -> 'l node option val unexpand_caddadr : 'l node -> 'l node option
val unexpand_set_caddadr : 'l node -> 'l node option val unexpand_set_caddadr : 'l node -> 'l node option

View File

@ -23,28 +23,8 @@ type parsed =
let expand_all source ast errors = let expand_all source ast errors =
let unexpanded, loc_table = let unexpanded, loc_table =
extract_locations ast in extract_locations ast in
let rec error_map (expanded, errors) f = function let expanded, expansion_errors =
| [] -> (List.rev expanded, List.rev errors) Michelson_v1_macros.expand_rec (root unexpanded) in
| hd :: tl ->
let (new_expanded, new_errors) = f hd in
error_map
(new_expanded :: expanded, List.rev_append new_errors errors)
f tl in
let error_map = error_map ([], []) in
let rec expand expr =
match Michelson_v1_macros.expand expr with
| Ok expanded ->
begin
match expanded with
| Seq (loc, items, annot) ->
let items, errors = error_map expand items in
(Seq (loc, items, annot), errors)
| Prim (loc, name, args, annot) ->
let args, errors = error_map expand args in
(Prim (loc, name, args, annot), errors)
| Int _ | String _ as atom -> (atom, []) end
| Error errors -> (expr, errors) in
let expanded, expansion_errors = expand (root unexpanded) in
let expanded, unexpansion_table = let expanded, unexpansion_table =
extract_locations expanded in extract_locations expanded in
let expansion_table = let expansion_table =
@ -76,7 +56,7 @@ let expand_all source ast errors =
{ source ; unexpanded ; { source ; unexpanded ;
expanded = Micheline.strip_locations (Seq ((), [], None)) ; expanded = Micheline.strip_locations (Seq ((), [], None)) ;
expansion_table ; unexpansion_table }, expansion_table ; unexpansion_table },
errs @ errors @ expansion_errors errors @ expansion_errors @ errs
let parse_toplevel ?check source = let parse_toplevel ?check source =
let tokens, lexing_errors = Micheline_parser.tokenize source in let tokens, lexing_errors = Micheline_parser.tokenize source in

View File

@ -61,20 +61,13 @@ let inject_types type_map parsed =
inject_expr (root parsed.unexpanded) inject_expr (root parsed.unexpanded)
let unparse ?type_map parse expanded = let unparse ?type_map parse expanded =
let rec unexpand expr =
match Michelson_v1_macros.unexpand expr with
| Seq (loc, items, annot) ->
Seq (loc, List.map unexpand items, annot)
| Prim (loc, name, args, annot) ->
Prim (loc, name, List.map unexpand args, annot)
| Int _ | String _ as atom -> atom in
let source = let source =
match type_map with match type_map with
| Some type_map -> | Some type_map ->
let unexpanded, unexpansion_table = let unexpanded, unexpansion_table =
expanded expanded
|> Michelson_v1_primitives.strings_of_prims |> Michelson_v1_primitives.strings_of_prims
|> root |> unexpand |> Micheline.extract_locations in |> root |> Michelson_v1_macros.unexpand_rec |> Micheline.extract_locations in
let rec inject_expr = function let rec inject_expr = function
| Seq (loc, items, annot) -> | Seq (loc, items, annot) ->
Seq (inject_loc `before loc, List.map inject_expr items, annot) Seq (inject_loc `before loc, List.map inject_expr items, annot)
@ -97,12 +90,20 @@ let unparse ?type_map parse expanded =
|> Format.asprintf "%a" Micheline_printer.print_expr |> Format.asprintf "%a" Micheline_printer.print_expr
| None -> | None ->
expanded |> Michelson_v1_primitives.strings_of_prims expanded |> Michelson_v1_primitives.strings_of_prims
|> root |> unexpand |> Micheline.strip_locations |> root |> Michelson_v1_macros.unexpand_rec |> Micheline.strip_locations
|> Micheline_printer.printable (fun n -> n) |> Micheline_printer.printable (fun n -> n)
|> Format.asprintf "%a" Micheline_printer.print_expr in |> Format.asprintf "%a" Micheline_printer.print_expr in
match parse source with match parse source with
| res, [] -> res | res, [] -> res
| _, _ :: _ -> Pervasives.failwith "Michelson_v1_printer.unexpand" | _, _ :: _ -> Pervasives.failwith "Michelson_v1_printer.unparse"
let unparse_toplevel ?type_map = unparse ?type_map Michelson_v1_parser.parse_toplevel let unparse_toplevel ?type_map = unparse ?type_map Michelson_v1_parser.parse_toplevel
let unparse_expression = unparse Michelson_v1_parser.parse_expression let unparse_expression = unparse Michelson_v1_parser.parse_expression
let unparse_invalid expanded =
let source =
expanded
|> root |> Michelson_v1_macros.unexpand_rec |> Micheline.strip_locations
|> Micheline_printer.printable (fun n -> n)
|> Format.asprintf "%a" Micheline_printer.print_expr_unwrapped in
fst (Michelson_v1_parser.parse_toplevel source)

View File

@ -29,3 +29,8 @@ val inject_types :
contracts extracted from the blockchain and not local files. *) contracts extracted from the blockchain and not local files. *)
val unparse_toplevel : ?type_map: Script_tc_errors.type_map -> Script.expr -> Michelson_v1_parser.parsed val unparse_toplevel : ?type_map: Script_tc_errors.type_map -> Script.expr -> Michelson_v1_parser.parsed
val unparse_expression : Script.expr -> Michelson_v1_parser.parsed val unparse_expression : Script.expr -> Michelson_v1_parser.parsed
(** Unexpand the macros and produce the result of parsing an
intermediate pretty printed source. Works on generic trees,for
programs that fail to be converted to a specific script version. *)
val unparse_invalid : string Micheline.canonical -> Michelson_v1_parser.parsed

View File

@ -119,11 +119,20 @@ let commands () =
program program
res res
cctxt cctxt
| res_with_errors -> | res_with_errors when emacs_mode ->
cctxt#message cctxt#message
"(@[<v 0>(types . ())@ (errors . %a)@])" "(@[<v 0>(types . ())@ (errors . %a)@])"
Michelson_v1_emacs.report_errors res_with_errors >>= fun () -> Michelson_v1_emacs.report_errors res_with_errors >>= fun () ->
return ()) ; return ()
| (parsed, errors) ->
cctxt#message "%a"
(fun ppf () ->
Michelson_v1_error_reporter.report_errors
~details:(not no_print_source) ~parsed
~show_source:(not no_print_source)
ppf errors) () >>= fun () ->
return ()
) ;
command ~group ~desc: "Ask the node to typecheck a data expression." command ~group ~desc: "Ask the node to typecheck a data expression."
(args1 no_print_source_flag) (args1 no_print_source_flag)

View File

@ -11,7 +11,7 @@ open Micheline
type error += Unknown_primitive_name of string type error += Unknown_primitive_name of string
type error += Invalid_case of string type error += Invalid_case of string
type error += Invalid_primitive_name of Micheline.canonical_location type error += Invalid_primitive_name of string Micheline.canonical * Micheline.canonical_location
type prim = type prim =
| K_parameter | K_parameter
@ -348,7 +348,7 @@ let prims_of_strings expr =
| Int _ | String _ as expr -> ok expr | Int _ | String _ as expr -> ok expr
| Prim (loc, prim, args, annot) -> | Prim (loc, prim, args, annot) ->
Error_monad.record_trace Error_monad.record_trace
(Invalid_primitive_name loc) (Invalid_primitive_name (expr, loc))
(prim_of_string prim) >>? fun prim -> (prim_of_string prim) >>? fun prim ->
List.fold_left List.fold_left
(fun acc arg -> (fun acc arg ->
@ -491,7 +491,7 @@ let () =
~title: "Unknown primitive name (typechecking error)" ~title: "Unknown primitive name (typechecking error)"
~description: ~description:
"In a script or data expression, a primitive was unknown." "In a script or data expression, a primitive was unknown."
~pp:(fun ppf n -> Format.fprintf ppf "Unknown primitive %s.@," n) ~pp:(fun ppf n -> Format.fprintf ppf "Unknown primitive %s." n)
Data_encoding.(obj1 (req "wrongPrimitiveName" string)) Data_encoding.(obj1 (req "wrongPrimitiveName" string))
(function (function
| Unknown_primitive_name got -> Some got | Unknown_primitive_name got -> Some got
@ -520,9 +520,11 @@ let () =
"In a script or data expression, a primitive name is \ "In a script or data expression, a primitive name is \
unknown or has a wrong case." unknown or has a wrong case."
~pp:(fun ppf _ -> Format.fprintf ppf "Invalid primitive.") ~pp:(fun ppf _ -> Format.fprintf ppf "Invalid primitive.")
Data_encoding.(obj1 (req "location" Micheline.canonical_location_encoding)) Data_encoding.(obj2
(req "expression" (Micheline.canonical_encoding ~variant:"generic" string))
(req "location" Micheline.canonical_location_encoding))
(function (function
| Invalid_primitive_name loc -> Some loc | Invalid_primitive_name (expr, loc) -> Some (expr, loc)
| _ -> None) | _ -> None)
(fun loc -> (fun (expr, loc) ->
Invalid_primitive_name loc) Invalid_primitive_name (expr, loc))

View File

@ -9,7 +9,7 @@
type error += Unknown_primitive_name of string (* `Permanent *) type error += Unknown_primitive_name of string (* `Permanent *)
type error += Invalid_case of string (* `Permanent *) type error += Invalid_case of string (* `Permanent *)
type error += Invalid_primitive_name of Micheline.canonical_location (* `Permanent *) type error += Invalid_primitive_name of string Micheline.canonical * Micheline.canonical_location (* `Permanent *)
type prim = type prim =
| K_parameter | K_parameter

View File

@ -135,8 +135,7 @@ let equal_cents_balance ~tc ?msg (contract, cents_balance) =
(contract, Helpers_cast.cents_of_int cents_balance) (contract, Helpers_cast.cents_of_int cents_balance)
let ecoproto_error f = function let ecoproto_error f = function
| Alpha_environment.Ecoproto_error errors -> | Alpha_environment.Ecoproto_error error -> f error
List.exists f errors
| _ -> false | _ -> false
let contain_error ?(msg="") ~f = function let contain_error ?(msg="") ~f = function