Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.08 support #193

Merged
merged 13 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ env:
- OCAML_VERSION=4.05
- OCAML_VERSION=4.06
- OCAML_VERSION=4.07
- OCAML_VERSION=4.08
os:
- linux
2 changes: 2 additions & 0 deletions dune
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
(env
(_
(flags -w -9)))

(copy_files# src_plugins/compat_macros.cppo)
2 changes: 1 addition & 1 deletion ppx_deriving.opam
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ depends: [
"ppx_tools" {>= "4.02.3"}
"result"
"ounit" {with-test}
"ocaml" {>= "4.02"}
"ocaml" {>= "4.02.2"}
]
synopsis: "Type-driven code generation for OCaml >=4.02"
description: """
Expand Down
76 changes: 59 additions & 17 deletions src/api/ppx_deriving.cppo.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@
#define Psig_type(rec_flag, type_decls) Psig_type(type_decls)
#endif

#if OCAML_VERSION < (4, 08, 0)
#define Attribute_expr(loc_, txt_, payload) ({txt = txt_; loc = loc_}, payload)
#define Attribute_patt(loc_, txt_, payload) ({txt = txt_; loc = loc_}, payload)
#else
#define Attribute_expr(loc_, txt_, payload) { attr_name = \
{ txt = txt_; loc = loc_ }; \
attr_payload = payload; \
attr_loc = loc_ }
#define Attribute_patt(loc_, txt_, payload) { attr_name = \
{ txt = txt_; loc = loc_ }; \
attr_payload = payload; \
attr_loc = _ }
#endif

#if OCAML_VERSION < (4, 08, 0)
#define Rtag_patt(label, constant, args) Rtag(label, _, constant, args)
#define Rinherit_patt(typ) Rinherit(typ)
#else
#define Rtag_patt(label, constant, args) {prf_desc = Rtag(label, constant, args); _}
#define Rinherit_patt(typ) {prf_desc = Rinherit(typ); _}
#endif

open Longident
open Location
open Asttypes
Expand Down Expand Up @@ -70,10 +92,18 @@ let lookup name =
| Some (Internal d) -> Some d
| Some (External _) | None -> None

let raise_errorf ?sub ?if_highlight ?loc message =
message |> Printf.kprintf (fun str ->
let err = Location.error ?sub ?if_highlight ?loc str in
raise (Location.Error err))
let raise_errorf ?sub ?loc fmt =
let raise_msg str =
#if OCAML_VERSION >= (4, 08, 0)
let sub =
let msg_of_error err =
{ txt = (fun fmt -> Location.print_report fmt err);
loc = err.Location.main.loc } in
Option.map (List.map msg_of_error) sub in
#endif
let err = Location.error ?sub ?loc str in
raise (Location.Error err) in
Printf.kprintf raise_msg fmt

let create =
let def_ext_str name ~options ~path typ_ext =
Expand Down Expand Up @@ -163,20 +193,21 @@ module Arg = struct
let get_attr ~deriver conv attr =
match attr with
| None -> None
| Some ({ txt = name }, PStr [{ pstr_desc = Pstr_eval (expr, []) }]) ->
| Some (Attribute_patt(loc, name,
PStr [{ pstr_desc = Pstr_eval (expr, []) }])) ->
begin match conv expr with
| Ok v -> Some v
| Error desc ->
raise_errorf ~loc:expr.pexp_loc "%s: invalid [@%s]: %s expected" deriver name desc
end
| Some ({ txt = name; loc }, _) ->
| Some (Attribute_patt(loc, name, _)) ->
raise_errorf ~loc "%s: invalid [@%s]: value expected" deriver name

let get_flag ~deriver attr =
match attr with
| None -> false
| Some ({ txt = name }, PStr []) -> true
| Some ({ txt = name; loc }, _) ->
| Some (Attribute_patt(_loc, name, PStr [])) -> true
| Some (Attribute_patt(loc, name, _)) ->
raise_errorf ~loc "%s: invalid [@%s]: empty structure expected" deriver name

let get_expr ~deriver conv expr =
Expand All @@ -188,7 +219,7 @@ end
let attr_warning expr =
let loc = !default_loc in
let structure = {pstr_desc = Pstr_eval (expr, []); pstr_loc = loc} in
{txt = "ocaml.warning"; loc}, PStr [structure]
Attribute_expr(loc, "ocaml.warning", PStr [structure])

type quoter = {
mutable next_id : int;
Expand All @@ -205,9 +236,16 @@ let quote ~quoter expr =

let sanitize ?(module_=Lident "Ppx_deriving_runtime") ?(quoter=create_quoter ()) expr =
let body =
Exp.open_
~attrs:[attr_warning [%expr "-A"]]
Override { txt=module_; loc=(!Ast_helper.default_loc) } expr in
let loc = !Ast_helper.default_loc in
let attrs = [attr_warning [%expr "-A"]] in
let modname = { txt = module_; loc } in
Exp.open_ ~loc ~attrs
#if OCAML_VERSION < (4, 08, 0)
Override modname
#else
(Opn.mk ~loc ~attrs ~override:Override (Mod.ident ~loc ~attrs modname))
#endif
expr in
match quoter.bindings with
| [] -> body
| bindings -> Exp.let_ Nonrecursive bindings body
Expand Down Expand Up @@ -247,12 +285,14 @@ let mangle_lid ?fixpoint affix lid =
| Lapply _ -> assert false

let attr ~deriver name attrs =
let starts str prefix =
let starts prefix str =
String.length str >= String.length prefix &&
String.sub str 0 (String.length prefix) = prefix
in
let attr_starts prefix (Attribute_patt(_loc, txt, _)) = starts prefix txt in
let attr_is name (Attribute_patt(_loc, txt, _)) = name = txt in
let try_prefix prefix f =
if List.exists (fun ({ txt }, _) -> starts txt prefix) attrs
if List.exists (attr_starts prefix) attrs
then prefix ^ name
else f ()
in
Expand All @@ -261,14 +301,16 @@ let attr ~deriver name attrs =
try_prefix (deriver^".") (fun () ->
name))
in
try Some (List.find (fun ({ txt }, _) -> txt = name) attrs)
try Some (List.find (attr_is name) attrs)
with Not_found -> None

let attr_nobuiltin ~deriver attrs =
attrs |> attr ~deriver "nobuiltin" |> Arg.get_flag ~deriver

let rec remove_pervasive_lid = function
| Lident _ as lid -> lid
| Ldot (Lident "Pervasives", s) -> Lident s
| Ldot (Lident "Stdlib", s) -> Lident s
| Ldot (lid, s) -> Ldot (remove_pervasive_lid lid, s)
| Lapply (lid, lid2) ->
Lapply (remove_pervasive_lid lid, remove_pervasive_lid lid2)
Expand Down Expand Up @@ -351,8 +393,8 @@ let free_vars_in_core_type typ =
List.filter (fun y -> not (List.mem y bound)) (free_in x)
| { ptyp_desc = Ptyp_variant (rows, _, _) } ->
List.map (
function Rtag (_,_,_,ts) -> List.map free_in ts
| Rinherit t -> [free_in t]
function Rtag_patt(_,_,ts) -> List.map free_in ts
| Rinherit_patt(t) -> [free_in t]
) rows |> List.concat |> List.concat
| _ -> assert false
in
Expand Down
5 changes: 1 addition & 4 deletions src/api/ppx_deriving.cppo.mli
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,7 @@ val create :
val lookup : string -> deriver option

(** {2 Error handling} *)

(** [raise_error] is a shorthand for raising [Location.Error] with the result
of [Location.errorf]. *)
val raise_errorf : ?sub:Location.error list -> ?if_highlight:string ->
val raise_errorf : ?sub:Location.error list ->
?loc:Location.t -> ('a, unit, string, 'b) format4 -> 'a

(** [string_of_core_type typ] unparses [typ], omitting any attributes. *)
Expand Down
10 changes: 10 additions & 0 deletions src/runtime/dune
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@
(wrapped false)
(synopsis "Type-driven code generation")
(libraries result))

(rule
(deps ppx_deriving_runtime.cppo.ml)
(targets ppx_deriving_runtime.ml)
(action (run %{bin:cppo} -V OCAML:%{ocaml_version} %{deps} -o %{targets})))

(rule
(deps ppx_deriving_runtime.cppo.mli)
(targets ppx_deriving_runtime.mli)
(action (run %{bin:cppo} -V OCAML:%{ocaml_version} %{deps} -o %{targets})))
65 changes: 65 additions & 0 deletions src/runtime/ppx_deriving_runtime.cppo.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
type nonrec int = int
type nonrec char = char
type nonrec string = string
type nonrec float = float
type nonrec bool = bool
type nonrec unit = unit
type nonrec exn = exn
type nonrec 'a array = 'a array
type nonrec 'a list = 'a list
type nonrec 'a option = 'a option
type nonrec nativeint = nativeint
type nonrec int32 = int32
type nonrec int64 = int64
type nonrec 'a lazy_t = 'a lazy_t
type nonrec bytes = bytes

#if OCAML_VERSION >= (4, 08, 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this is merged and published, so I don't know if this is the best place to raise this — but using cppo in the runtime, specifically, is going to cause me … a whole, whole lot of problems over in bs-deriving land (not in the least, because #if OCAML_VERSION actually already means something over there — with a different semantic!). )=

Not y'all's problem, of course; but before I go try and make my rickety, arcane build-system about ten times as rickety and arcane as it already is … is there any chance there's an easy, alternative way to do this upstream? Since this is the only occurrence in the runtime?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to try to make the bs-deriving life easier, but I'm worried that there may not be a very easy way to avoid having two different logics here -- it's solving several delicate problems (deprecation of Pervasives, semantics of include, etc.) and it is actually scoping over most of the runtime definition, not just a small part of it.

What would be your recommendation for a conditional-compilation approach that works with both cppo and bspp? Would you avoid all in-file logic and use two different .ml, selected by the build system?

Maybe it is possible to use both build systems to define one variable (OCAML_VERSION_GTE_408), and use a test on that variable in a syntax that is compatible with both preprocessors?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmmmmmmmm. Well, I'd like to avoid "partially upstreaming" bs-deriving (in the sense of making weird changes like adding OCAML_VERSION_but_not_because_some_other_thing_not_related_to_this_codebase_is_weird, that you've then gotta document somewhere, test, avoid breaking in the future …)

If OCAML_VERSION is idiomatic in the way it's used here, I'm not sure what to do; I'm not exactly a high-wizard of OCaml, unfortunately, just a dude gluing some stuff together. Poorly. 🤣

Two files might work, but is still teetering on just … upstreaming the maintenance burden. I was mostly, vaguely hoping that “hey! wow! there just happen to be two equally-good ways to do this upstream, and the other one won't be as big a problem for Elliott! Yay!”, but doesn't look like it.

I'm gonna keep this in mind and ruminate on it for a while; maybe I can figure something simple out in the coming weeks. (Thanks for the quick replies, by the way!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't personally mind having some changes in the upstream repo that are solely justified by bs-deriving, especially if they only touch the implementation (so we have to document the code but the end-users will not see a difference). I would keep the extra-maintenance-burden in mind, but maybe there is a way that works and isn't actually that burdensome.

(Yes, OCAML_VERSION is used in the idiomatic way here. I think it would have made sense for the Bucklescript people to use the de-facto standard syntax of cppo in their preprocessor syntax, but well...)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Note that I've opened rescript-lang/rescript#3820 about this.)

(* We require 4.08 while 4.07 already has a Stdlib module.
In 4.07, the type equalities on Stdlib.Pervasives
are not strong enough for the 'include Stdlib'
below to satisfy the signature constraints on
Ppx_deriving_runtime.Pervasives. *)
module Stdlib = Stdlib

include Stdlib
#else
module Pervasives = Pervasives
module Stdlib = Pervasives

module Char = Char
module String = String
module Printexc = Printexc
module Array = Array
module List = List
module Nativeint = Nativeint
module Int32 = Int32
module Int64 = Int64
module Lazy = Lazy
module Bytes = Bytes

module Hashtbl = Hashtbl
module Queue = Queue
module Stack = Stack
module Set = Set
module Map = Map
module Weak = Weak

module Printf = Printf
module Format = Format
module Buffer = Buffer
module Result = struct
(* the "result" compatibility module defines Result.result,
not Result.t as the 4.08 stdlib *)
type ('a, 'b) t = ('a, 'b) Result.result =
| Ok of 'a
| Error of 'b

(* ... and we also expose Result.result for backward-compatibility *)
type ('a, 'b) result = ('a, 'b) Result.result =
| Ok of 'a
| Error of 'b
end

include Pervasives
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,37 @@
in a well-defined environment. *)

(** {2 Predefined types} *)

(** The {!Predef} module is necessary in absence of a [type nonrec]
construct. *)
module Predef : sig
type _int = int
type _char = char
type _string = string
type _float = float
type _bool = bool (* = false | true *) (* see PR5936, GPR76, GPR234 *)
type _unit = unit (* = () *)
type _exn = exn
type 'a _array = 'a array
type 'a _list = 'a list (* = [] | 'a :: 'a list *)
type 'a _option = 'a option = None | Some of 'a
type _nativeint = nativeint
type _int32 = int32
type _int64 = int64
type 'a _lazy_t = 'a lazy_t
type _bytes = bytes
end

type int = Predef._int
type char = Predef._char
type string = Predef._string
type float = Predef._float
type bool = Predef._bool
type unit = Predef._unit
type exn = Predef._exn
type 'a array = 'a Predef._array
type 'a list = 'a Predef._list
type 'a option = 'a Predef._option = None | Some of 'a
type nativeint = Predef._nativeint
type int32 = Predef._int32
type int64 = Predef._int64
type 'a lazy_t = 'a Predef._lazy_t
type bytes = Predef._bytes
type nonrec int = int
type nonrec char = char
type nonrec string = string
type nonrec float = float
type nonrec bool = bool
type nonrec unit = unit
type nonrec exn = exn
type nonrec 'a array = 'a array
type nonrec 'a list = 'a list
type nonrec 'a option = 'a option
type nonrec nativeint = nativeint
type nonrec int32 = int32
type nonrec int64 = int64
type nonrec 'a lazy_t = 'a lazy_t
type nonrec bytes = bytes

(** {2 Predefined modules}
{3 Operations on predefined types} *)

#if OCAML_VERSION >= (4, 08, 0)
include (module type of Stdlib with
type fpclass = Stdlib.fpclass and
type in_channel = Stdlib.in_channel and
type out_channel = Stdlib.out_channel and
type open_flag = Stdlib.open_flag and
type 'a ref = 'a Stdlib.ref and
type ('a, 'b, 'c, 'd, 'e, 'f) format6 = ('a, 'b, 'c, 'd, 'e, 'f) Stdlib.format6 and
type ('a, 'b, 'c, 'd) format4 = ('a, 'b, 'c, 'd) Stdlib.format4 and
type ('a, 'b, 'c) format = ('a, 'b, 'c) Stdlib.format
)
#else
module Pervasives : (module type of Pervasives with
type fpclass = Pervasives.fpclass and
type in_channel = Pervasives.in_channel and
Expand All @@ -52,6 +43,9 @@ module Pervasives : (module type of Pervasives with
type ('a, 'b, 'c, 'd, 'e, 'f) format6 = ('a, 'b, 'c, 'd, 'e, 'f) Pervasives.format6 and
type ('a, 'b, 'c, 'd) format4 = ('a, 'b, 'c, 'd) Pervasives.format4 and
type ('a, 'b, 'c) format = ('a, 'b, 'c) Pervasives.format)

module Stdlib = Pervasives

include (module type of Pervasives with
type fpclass = Pervasives.fpclass and
type in_channel = Pervasives.in_channel and
Expand Down Expand Up @@ -92,7 +86,7 @@ module Weak : (module type of Weak with
module Buffer : (module type of Buffer with
type t = Buffer.t)
module Result : sig
type ('a, 'b) result = ('a, 'b) Result.result =
type ('a, 'b) t = ('a, 'b) Result.result =
| Ok of 'a
| Error of 'b
end
Expand All @@ -104,3 +98,4 @@ module Format : (module type of Format with
type formatter_out_functions = Format.formatter_out_functions and
type formatter_tag_functions = Format.formatter_tag_functions and
type formatter = Format.formatter)
#endif
Loading