diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..d715b44 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,4 @@ +Unreleased +----- + +- Implementation of standard deriver: `make` diff --git a/README.md b/README.md index 83c5fee..44bad2f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Unless there's a good reason not to, we'll stick to the same naming and syntax a #### Hygienic base ecosystem -We'll make sure that the derivers here meet good standards so that people can trust them. That includes good error reporting, using fully qualified names for the derived values, and having a solid set of tests. +We'll make sure that the derivers here meet good standards so that people can trust them. That includes good error reporting, using fully qualified names for the derived values, and having a solid set of tests. We also make sure not to use polymorphic comparisons, because the code generated by a deriver should not depend on the environment in scope. E.g. rather than `foo = []`, we do `match foo with [] ->`, to eliminate the use of `=`, which might be overwritten in the environment. #### Serve as how-to guides @@ -40,4 +40,23 @@ All those points mentioned above might help folks move even more from the sustai @ayc9 is implementing this project as part of an [Outreachy internship](https://www.outreachy.org/) with the OCaml community. The derivers we plan to include are originally written by Jane Street (`ord` and `eq` will come from [`ppx_compare`](https://github.com/janestreet/ppx_compare); the basic structure of `make` will come from [`ppx_fields_conv`](https://github.com/janestreet/ppx_fields_conv)) and by @thierry-martinez (`show` and `pp` will come from [`ppx_show`](https://github.com/thierry-martinez/ppx_show)). - +## `make` + +The `make` deriver generates a constructor function for a given record type `t`. The derived function, `make_t`, accepts labelled arguments for each field in the record. `make_t` is then used to construct records of type `t`. Note that: +- A `[@main]` annotation can be added to specify a field to be the last argument of the constructor function. This main argument will not be labelled. +- A `[@default]` annotation can be added to specify a default value for a given field. Its corresponding argument will be optional. +- Fields of `list` type are automatically set with a default of `[]`. +- If the generated constructor function has optional arguments, it will require the unit `()` as a last argument, except in the case it also has a main argument. + +``` ocaml +type t = { + x : int; + l : int list; + o : int option; + m : int [@main]; + d : int [@default 0] +} [@@deriving make];; + +val make_t : x:int -> ?l:int list -> ?o:int -> ?d:int -> int -> t +let make_t ~x ?(l= []) ?o ?(d= 0) m = { x; l; o; m; d } +``` diff --git a/dune-project b/dune-project new file mode 100644 index 0000000..f510af2 --- /dev/null +++ b/dune-project @@ -0,0 +1,19 @@ +(lang dune 3.0) + +(cram enable) + +(generate_opam_files true) + +(name standard_derivers) + +(source + (github ocaml-ppx/standard_derivers)) + +(package + (name standard_derivers) + (synopsis "Standard PPX derivers") + (description "Useful and up-to-date PPX derivers for common tasks.") + (depends + (ocaml (>= 4.08)) + (ppxlib (>= 0.18.0))) + (allow_empty)) diff --git a/src/make/dune b/src/make/dune new file mode 100644 index 0000000..5f96808 --- /dev/null +++ b/src/make/dune @@ -0,0 +1,3 @@ +(library (name ppx_make) + (kind ppx_deriver) (ppx_runtime_libraries fieldslib) (libraries ppxlib) + (preprocess (pps ppxlib.metaquot))) diff --git a/src/make/ppx_make.ml b/src/make/ppx_make.ml new file mode 100644 index 0000000..01ab966 --- /dev/null +++ b/src/make/ppx_make.ml @@ -0,0 +1,216 @@ + open Ppxlib + open Ast_builder.Default + + module Annotations = struct + let default_attr = + Attribute.declare + "standard_derivers.make.default" + Attribute.Context.label_declaration + Ast_pattern.(single_expr_payload __) + (fun expr -> expr) + + let main_attr = + Attribute.declare + "standard_derivers.make.main" + Attribute.Context.label_declaration + Ast_pattern.(pstr nil) + () + + let find_main labels = + let main_labels, labels = List.fold_left (fun (main_labels, labels) label -> + match Attribute.get main_attr label with + | Some _ -> label::main_labels, labels + | None -> main_labels, label :: labels + ) ([], []) labels in + match main_labels with + | [] -> Ok (None, labels) + | [ main_label ] -> Ok (Some main_label, labels) + | main_labels -> Error (List.map(fun ({ pld_loc; _ }) -> + Location.error_extensionf ~loc:pld_loc "deriver make: Duplicate [@deriving.make.main] annotation" + ) main_labels ) + end + + module Check = struct + let is_derivable ~loc rec_flag tds = + match rec_flag with + | Nonrecursive -> + Error (Location.error_extensionf ~loc "deriver make: nonrec is not compatible with the `make' preprocessor.") + | _ -> + let is_record td = + match td.ptype_kind with + | Ptype_record _ -> true + | _ -> false + in + if not (List.exists is_record tds) + then + Error (Location.error_extensionf + ~loc + (match tds with + | [ _ ] -> "deriver make: Unsupported use of make (you can only use it on records)." + | _ -> + "deriver make: make can only be applied on type definitions in which at least one \ + type definition is a record.")) + else Ok () + + let is_optional labels = List.exists (fun (name, _) -> match name with + | Optional _ -> true + | _ -> false) labels + end + + module Construct = struct + (* Additional AST construction helpers *) + let lambda ~loc patterns body = + List.fold_left (fun expr (arg_label, pat, default) -> + pexp_fun ~loc arg_label default pat expr) body patterns + ;; + + let lambda_sig ~loc arg_tys body_ty = + List.fold_left (fun return_ty (arg_label, arg_ty) -> + ptyp_arrow ~loc arg_label arg_ty return_ty) body_ty arg_tys + ;; + + let record ~loc pairs = + pexp_record + ~loc + (List.map (fun (name, exp) -> Located.lident ~loc name, exp) pairs) + None + + let return_type ~loc ~ty_name ~ty_params = + ptyp_constr ~loc (Located.lident ~loc ty_name) ty_params + + let sig_item ~loc name typ = + psig_value ~loc (value_description ~loc ~name:(Located.mk ~loc name) ~type_:typ ~prim:[]) + + let str_item ~loc name body = + pstr_value ~loc Nonrecursive [ value_binding ~loc ~pat:(pvar ~loc name) ~expr:body ] + end + + module Gen_sig = struct + let label_arg label_decl = + let { pld_name = name; pld_type = ty; _ } = label_decl in + match (Attribute.get Annotations.default_attr label_decl), ty with + (* [@default _ ] -> Optional *) + | Some _, _ -> Optional name.txt, ty + (* `option` type -> Optional *) + | _, [%type: [%t? a'] option] -> Optional name.txt, a' + (* `list` type -> Optional *) + | _, [%type: [%t? _] list] -> Optional name.txt, ty + (* Regular field -> Labelled *) + | _ -> Labelled name.txt, ty + + let create_make_sig ~loc ~ty_name ~ty_params label_decls = + let record = Construct.return_type ~loc ~ty_name ~ty_params in + match Annotations.find_main label_decls with + | Error e -> List.map(fun e -> psig_extension ~loc (e) [] ) e + | Ok (main_arg, label_decls) -> + let types = List.map label_arg label_decls in + let add_unit types = ( + Nolabel, + Ast_helper.Typ.constr ~loc { txt = Lident "unit"; loc } [] + )::types in + let types = match main_arg with + | Some { pld_type ; _ } + -> (Nolabel, pld_type)::types + | None when Check.is_optional types -> add_unit types + | None -> types + in + let t = Construct.lambda_sig ~loc types record in + let fun_name = "make_" ^ ty_name in + [Construct.sig_item ~loc fun_name t] + + let derive_per_td (td : type_declaration) : signature = + let { ptype_name = { txt = ty_name; loc } + ; ptype_params + ; ptype_kind + ; _ + } + = + td + in + let ty_params = List.map (fun (tp, _variance) -> tp) ptype_params in + match ptype_kind with + | Ptype_record label_decls -> + create_make_sig ~loc ~ty_name ~ty_params label_decls + (* Does not derive when matching with the non-record types + of a recursive type definition. See tests for examples. *) + | _ -> [] + + let generate ~ctxt (rec_flag, tds) = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + match Check.is_derivable ~loc rec_flag tds with + | Error e -> [psig_extension ~loc (e) [] ] + | Ok () -> List.concat_map (derive_per_td) tds + end + + module Gen_struct = struct + let derive_pattern ~loc label_decl = + let { pld_name = name; pld_type = ty; _ } = label_decl in + let default_attr = (Attribute.get Annotations.default_attr label_decl) in + match default_attr, ty with + | Some default_attr, _ -> Optional name.txt, pvar ~loc name.txt, Some default_attr + | _ , [%type: [%t? _] list] -> Optional name.txt, pvar ~loc name.txt, Some (elist ~loc []) + | _, [%type: [%t? _] option] -> Optional name.txt, pvar ~loc name.txt, None + | None, _ -> Labelled name.txt, pvar ~loc name.txt, None + + let is_optional labels = List.exists (fun (name, _, _) -> match name with + | Optional _ -> true + | _ -> false) labels + + let create_make_fun ~loc ~record_name label_decls = + let field_labels = List.map (fun { pld_name = n; _ } -> n.txt, evar ~loc n.txt) label_decls in + match Annotations.find_main label_decls with + | Error e -> List.map(fun e -> pstr_extension ~loc (e) []) e + | Ok (main_arg, label_decls) -> + let patterns = List.map (derive_pattern ~loc) label_decls in + let add_unit patterns = (Nolabel, punit ~loc, None)::patterns in + let patterns = match main_arg with + | Some ({ pld_name = { txt = name ; _ } ; pld_loc; _ } as pld) + -> (match (Attribute.get Annotations.default_attr pld) with + | Some _ -> Error (Location.error_extensionf ~loc:pld_loc "deriver make: Cannot use both @default and @main") + | None -> Ok ((Nolabel, pvar ~loc name, None)::patterns)) + | None when is_optional patterns -> Ok (add_unit patterns) + | None -> Ok patterns + in + match patterns with + | Error e -> [pstr_extension ~loc (e) []] + | Ok patterns -> + let create_record = Construct.record ~loc field_labels in + let derive_lambda = Construct.lambda ~loc patterns create_record in + let fun_name = "make_" ^ record_name in + [Construct.str_item ~loc fun_name derive_lambda] + + let derive_per_td (td : type_declaration) : structure = + let { ptype_name = { txt = record_name; loc } + ; ptype_kind + ; _ + } + = + td + in + match ptype_kind with + | Ptype_record label_decls -> + create_make_fun ~loc ~record_name label_decls + (* Does not derive when matching with the non-record types + of a recursive type definition. See tests for examples. *) + | _ -> [] + + let generate ~ctxt (rec_flag, tds) = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + match Check.is_derivable ~loc rec_flag tds with + | Error e -> [pstr_extension ~loc (e) [] ] + | Ok () -> List.concat_map (derive_per_td) tds + end + + let make = + let attributes = + (Attribute.T Annotations.default_attr)::[Attribute.T Annotations.main_attr] + in + Deriving.add "make" + ~str_type_decl: + (Deriving.Generator.V2.make_noarg + ~attributes + Gen_struct.generate) + ~sig_type_decl: + (Deriving.Generator.V2.make_noarg + ~attributes + Gen_sig.generate) diff --git a/standard_derivers.opam b/standard_derivers.opam new file mode 100644 index 0000000..e243416 --- /dev/null +++ b/standard_derivers.opam @@ -0,0 +1,27 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "Standard PPX derivers" +description: "Useful and up-to-date PPX derivers for common tasks." +homepage: "https://github.com/ocaml-ppx/standard_derivers" +bug-reports: "https://github.com/ocaml-ppx/standard_derivers/issues" +depends: [ + "dune" {>= "3.0"} + "ocaml" {>= "4.08"} + "ppxlib" {>= "0.18.0"} + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/ocaml-ppx/standard_derivers.git" diff --git a/test/make/annotations.t b/test/make/annotations.t new file mode 100644 index 0000000..a0d9432 --- /dev/null +++ b/test/make/annotations.t @@ -0,0 +1,136 @@ +--------------------------------------------------- +NOTICE: @@ocaml.doc and @@merlin.hide annotations +& `include struct` boilerplate are added by ppxlib. +--------------------------------------------------- +The `-deriving-keep-w32 both` flag added after the +driver removes anonymous functions of the type: +- let _ = fun (_ : t) -> () +- let _ = make_t +which are automatically added by ppxlib. +--------------------------------------------------- + +Test 1: Given a record type a annotated with `@main` for +one field, make_a will accept the main field as its last +parameter + $ test1=" + > type a = { + > x: int [@main] ; + > y: bool }[@@deriving make]" + $ echo "$test1" > test.ml + $ driver -deriving-keep-w32 both test.ml + type a = { + x: int [@main ]; + y: bool }[@@deriving make] + include struct let make_a ~y x = { x; y } end[@@ocaml.doc "@inline"] + [@@merlin.hide ] + $ echo "$test1" > test.mli + $ driver test.mli + type a = { + x: int [@main ]; + y: bool }[@@deriving make] + include sig [@@@ocaml.warning "-32"] val make_a : y:bool -> int -> a end + [@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 2: Given a record type annotated with `@main` for +more than 1 field, embed an error for each duplicate + $ test2=" + > type b = { + > x: int [@main] ; + > y: bool [@main] ; + > z : string [@main]}[@@deriving make]" + $ echo "$test2" > test.ml + $ driver -deriving-keep-w32 both test.ml + type b = { + x: int [@main ]; + y: bool [@main ]; + z: string [@main ]}[@@deriving make] + include + struct + [%%ocaml.error + "deriver make: Duplicate [@deriving.make.main] annotation"] + [%%ocaml.error + "deriver make: Duplicate [@deriving.make.main] annotation"] + [%%ocaml.error + "deriver make: Duplicate [@deriving.make.main] annotation"] + end[@@ocaml.doc "@inline"][@@merlin.hide ] + $ echo "$test2" > test.mli + $ driver test.mli + type b = { + x: int [@main ]; + y: bool [@main ]; + z: string [@main ]}[@@deriving make] + include + sig + [@@@ocaml.warning "-32"] + [%%ocaml.error + "deriver make: Duplicate [@deriving.make.main] annotation"] + [%%ocaml.error + "deriver make: Duplicate [@deriving.make.main] annotation"] + [%%ocaml.error + "deriver make: Duplicate [@deriving.make.main] annotation"] + end[@@ocaml.doc "@inline"][@@merlin.hide ] + + +Test 3: @default makes the field optional + $ test3=" + > type c = { + > x: int [@default 5]; + > y: bool }[@@deriving make]" + $ echo "$test3" > test.mli + $ driver test.mli + type c = { + x: int [@default 5]; + y: bool }[@@deriving make] + include + sig [@@@ocaml.warning "-32"] val make_c : ?x:int -> y:bool -> unit -> c end + [@@ocaml.doc "@inline"][@@merlin.hide ] + $ echo "$test3" > test.ml + $ driver -deriving-keep-w32 both test.ml + type c = { + x: int [@default 5]; + y: bool }[@@deriving make] + include struct let make_c ?(x= 5) ~y () = { x; y } end[@@ocaml.doc + "@inline"][@@merlin.hide + ] + +Test 4: Given a record type with both `@main` and +`@default` for the same field, embed error + $ test4=" + > type d = { + > x: int [@default 5] [@main] ; + > y: bool }[@@deriving make]" + $ echo "$test4" > test.ml + $ driver -deriving-keep-w32 both test.ml + type d = { + x: int [@default 5][@main ]; + y: bool }[@@deriving make] + include + struct + [%%ocaml.error "deriver make: Cannot use both @default and @main"] + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 5: Testing ppxlib: Unexpected attribute payload + $ test5=" + > type e = { + > x: int [@main 5] ; + > y: bool }[@@deriving make]" + $ echo "$test5" > test.mli + $ driver test.mli + File "test.mli", line 3, characters 16-17: + 3 | x: int [@main 5] ; + ^ + Error: [] expected + [1] + +Test 6: Testing ppxlib: Unrecognized annotation + $ test6=" + > type f = { + > x: int [@mein 5] ; + > y: bool }[@@deriving make]" + $ echo "$test6" > test.mli + $ driver -check test.mli + File "test.mli", line 3, characters 11-15: + 3 | x: int [@mein 5] ; + ^^^^ + Error: Attribute `mein' was not used + [1] diff --git a/test/make/driver.ml b/test/make/driver.ml new file mode 100644 index 0000000..e3cba40 --- /dev/null +++ b/test/make/driver.ml @@ -0,0 +1 @@ +let () = Ppxlib.Driver.standalone () diff --git a/test/make/dune b/test/make/dune new file mode 100644 index 0000000..08499cb --- /dev/null +++ b/test/make/dune @@ -0,0 +1,10 @@ +(executable + (name driver) + (libraries ppx_make ppxlib)) + +(env + (_ + (binaries driver.exe))) + +(cram + (deps %{bin:driver})) diff --git a/test/make/optionals.t b/test/make/optionals.t new file mode 100644 index 0000000..bddb521 --- /dev/null +++ b/test/make/optionals.t @@ -0,0 +1,115 @@ +--------------------------------------------------- +NOTICE: @@ocaml.doc and @@merlin.hide annotations +& `include struct` boilerplate are added by ppxlib. +--------------------------------------------------- +The `-deriving-keep-w32 both` flag added after the +driver removes anonymous functions of the type: +- let _ = fun (_ : t) -> () +- let _ = make_t +which are automatically added by ppxlib. +--------------------------------------------------- + +Test 1: Given a record type `a` with an `option` field, +make_a will accept an optional param and have a unit at +the end of its signature + $ test1=" + > type a = { + > x: int ; + > y: bool option }[@@deriving make]" + $ echo "$test1" > test.ml + $ driver -deriving-keep-w32 both test.ml + type a = { + x: int ; + y: bool option }[@@deriving make] + include struct let make_a ~x ?y () = { x; y } end[@@ocaml.doc "@inline"] + [@@merlin.hide ] + $ echo "$test1" > test.mli + $ driver test.mli + type a = { + x: int ; + y: bool option }[@@deriving make] + include + sig [@@@ocaml.warning "-32"] val make_a : x:int -> ?y:bool -> unit -> a end + [@@ocaml.doc "@inline"][@@merlin.hide ] + + +Test 2: Given a record type b with an option field & a +@main field, make_b accepts the main field as the last +parameter, and does not have a unit in the signature + $ test2=" + > type b = { + > x: int ; + > y: bool [@main] ; + > z : string option}[@@deriving make]" + $ echo "$test2" > test.ml + $ driver -deriving-keep-w32 both test.ml + type b = { + x: int ; + y: bool [@main ]; + z: string option }[@@deriving make] + include struct let make_b ~x ?z y = { x; y; z } end[@@ocaml.doc "@inline"] + [@@merlin.hide ] + $ echo "$test2" > test.mli + $ driver test.mli + type b = { + x: int ; + y: bool [@main ]; + z: string option }[@@deriving make] + include + sig [@@@ocaml.warning "-32"] val make_b : x:int -> ?z:string -> bool -> b + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 3: Given record type c with 2 option fields, one +of which is also annotated with @main, make_c accepts +the main field as the last param, which is of type +`option` but is not optional + $ test3=" + > type c = { + > x: int ; + > y: bool option [@main] ; + > z : string option}[@@deriving make]" + $ echo "$test3" > test.ml + $ driver -deriving-keep-w32 both test.ml + type c = { + x: int ; + y: bool option [@main ]; + z: string option }[@@deriving make] + include struct let make_c ~x ?z y = { x; y; z } end[@@ocaml.doc "@inline"] + [@@merlin.hide ] + $ echo "$test3" > test.mli + $ driver test.mli + type c = { + x: int ; + y: bool option [@main ]; + z: string option }[@@deriving make] + include + sig + [@@@ocaml.warning "-32"] + val make_c : x:int -> ?z:string -> bool option -> c + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 4: Given a record type `d` with a `list` field, +make_d will accept an optional param with default value +`[]` and will have a unit at the end of its signature + $ test4=" + > type d = { + > x: int list ; + > y: bool }[@@deriving make]" + $ echo "$test4" > test.mli + $ driver test.mli + type d = { + x: int list ; + y: bool }[@@deriving make] + include + sig + [@@@ocaml.warning "-32"] + val make_d : ?x:int list -> y:bool -> unit -> d + end[@@ocaml.doc "@inline"][@@merlin.hide ] + $ echo "$test4" > test.ml + $ driver -deriving-keep-w32 both test.ml + type d = { + x: int list ; + y: bool }[@@deriving make] + include struct let make_d ?(x= []) ~y () = { x; y } end[@@ocaml.doc + "@inline"] + [@@merlin.hide ] diff --git a/test/make/signature.t b/test/make/signature.t new file mode 100644 index 0000000..4c0ef1e --- /dev/null +++ b/test/make/signature.t @@ -0,0 +1,119 @@ +--------------------------------------------------- +NOTICE: @@ocaml.doc and @@merlin.hide annotations +& `include struct` boilerplate are added by ppxlib. +--------------------------------------------------- + +Test 1: Given a regular record type a, expose make_a + $ test1=" + > type a = { + > x: int ; + > y: bool }[@@deriving make]" + $ echo "$test1" > test.mli + $ driver test.mli + type a = { + x: int ; + y: bool }[@@deriving make] + include sig [@@@ocaml.warning "-32"] val make_a : x:int -> y:bool -> a end + [@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 2: Given a nonrec type, embed error + $ test2=" + > type nonrec b = { + > x: int ; + > y: bool }[@@deriving make]" + $ echo "$test2" > test.mli + $ driver test.mli + type nonrec b = { + x: int ; + y: bool }[@@deriving make] + include + sig + [@@@ocaml.warning "-32"] + [%%ocaml.error + "deriver make: nonrec is not compatible with the `make' preprocessor."] + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 3: Given a non-record type, embed error + $ test3=" + > type c = int * int + > [@@deriving make]" + $ echo "$test3" > test.mli + $ driver test.mli + type c = (int * int)[@@deriving make] + include + sig + [@@@ocaml.warning "-32"] + [%%ocaml.error + "deriver make: Unsupported use of make (you can only use it on records)."] + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 4: Given a private type, expose make_d + $ test4=" + > type d = private { + > x: int ; + > y: bool }[@@deriving make]" + $ echo "$test4" > test.mli + $ driver test.mli + type d = private { + x: int ; + y: bool }[@@deriving make] + include sig [@@@ocaml.warning "-32"] val make_d : x:int -> y:bool -> d end + [@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 5: Given recursive types which are exclusively +record types, expose 1 make function for each record + $ test5=" + > type e = { + > v: f ; + > w: bool } + > and f = { + > x: int ; + > mutable y: bool ; + > z: e }[@@deriving make]" + $ echo "$test5" > test.mli + $ driver test.mli + type e = { + v: f ; + w: bool } + and f = { + x: int ; + mutable y: bool ; + z: e }[@@deriving make] + include + sig + [@@@ocaml.warning "-32"] + val make_e : v:f -> w:bool -> e + val make_f : x:int -> y:bool -> z:e -> f + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 6: Given recursive types with at least one +record type, expose 1 make function for each record + $ test6=" + > type g = int*h + > and h = { + > v: g ; + > w: bool }[@@deriving make]" + $ echo "$test6" > test.mli + $ driver test.mli + type g = (int * h) + and h = { + v: g ; + w: bool }[@@deriving make] + include sig [@@@ocaml.warning "-32"] val make_h : v:g -> w:bool -> h end + [@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 7: Given recursive types without any record +types, embed error + $ test7=" + > type i = int*j + > and j = bool*i [@@deriving make]" + $ echo "$test7" > test.mli + $ driver test.mli + type i = (int * j) + and j = (bool * i)[@@deriving make] + include + sig + [@@@ocaml.warning "-32"] + [%%ocaml.error + "deriver make: make can only be applied on type definitions in which at least one type definition is a record."] + end[@@ocaml.doc "@inline"][@@merlin.hide ] diff --git a/test/make/structure.t b/test/make/structure.t new file mode 100644 index 0000000..25fc595 --- /dev/null +++ b/test/make/structure.t @@ -0,0 +1,121 @@ +--------------------------------------------------- +NOTICE: @@ocaml.doc and @@merlin.hide annotations +& `include struct` boilerplate are added by ppxlib. +--------------------------------------------------- +The `-deriving-keep-w32 both` flag added after the +driver removes anonymous functions of the type: +- let _ = fun (_ : t) -> () +- let _ = make_t +which are automatically added by ppxlib. +--------------------------------------------------- + +Test 1: Given a regular record type a, derive make_a + $ test1=" + > type a = { + > x: int ; + > y: bool }[@@deriving make]" + $ echo "$test1" > test.ml + $ driver -deriving-keep-w32 both test.ml + type a = { + x: int ; + y: bool }[@@deriving make] + include struct let make_a ~x ~y = { x; y } end[@@ocaml.doc "@inline"] + [@@merlin.hide ] + +Test 2: Given a nonrec type, embed error + $ test2=" + > type nonrec b = { + > x: int ; + > y: bool }[@@deriving make]" + $ echo "$test2" > test.ml + $ driver -deriving-keep-w32 both test.ml + type nonrec b = { + x: int ; + y: bool }[@@deriving make] + include + struct + [%%ocaml.error + "deriver make: nonrec is not compatible with the `make' preprocessor."] + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 3: Given a non-record type, embed error + $ test3=" + > type c = int * int + > [@@deriving make]" + $ echo "$test3" > test.ml + $ driver -deriving-keep-w32 both test.ml + type c = (int * int)[@@deriving make] + include + struct + [%%ocaml.error + "deriver make: Unsupported use of make (you can only use it on records)."] + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 4: Given a private record type d, derive make_d + $ test4=" + > type d = private { + > x: int ; + > y: bool }[@@deriving make]" + $ echo "$test4" > test.ml + $ driver -deriving-keep-w32 both test.ml + type d = private { + x: int ; + y: bool }[@@deriving make] + include struct let make_d ~x ~y = { x; y } end[@@ocaml.doc "@inline"] + [@@merlin.hide ] + +Test 5: Given recursive types which are exclusively +record types, derive 1 make function for each record + $ test5=" + > type e = { + > v: f ; + > w: bool } + > and f = { + > x: int ; + > mutable y: bool ; + > z: e }[@@deriving make]" + $ echo "$test5" > test.ml + $ driver -deriving-keep-w32 both test.ml + type e = { + v: f ; + w: bool } + and f = { + x: int ; + mutable y: bool ; + z: e }[@@deriving make] + include + struct + let make_e ~v ~w = { v; w } + let make_f ~x ~y ~z = { x; y; z } + end[@@ocaml.doc "@inline"][@@merlin.hide ] + +Test 6: Given recursive types with at least one +record type, derive one make function for each record + $ test6=" + > type g = int*h + > and h = { + > v: g ; + > w: bool }[@@deriving make]" + $ echo "$test6" > test.ml + $ driver -deriving-keep-w32 both test.ml + type g = (int * h) + and h = { + v: g ; + w: bool }[@@deriving make] + include struct let make_h ~v ~w = { v; w } end[@@ocaml.doc "@inline"] + [@@merlin.hide ] + +Test 7: Given recursive types without any record +types, embed error + $ test7=" + > type i = int*j + > and i = bool*j [@@deriving make]" + $ echo "$test7" > test.ml + $ driver -deriving-keep-w32 both test.ml + type i = (int * j) + and i = (bool * j)[@@deriving make] + include + struct + [%%ocaml.error + "deriver make: make can only be applied on type definitions in which at least one type definition is a record."] + end[@@ocaml.doc "@inline"][@@merlin.hide ]