2 * Copyright (c) Facebook, Inc. and its affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the "hack" directory of this source tree.
17 open Convert_longident
29 (Some
"ocamlrep_derive", "OcamlRep");
30 (Some
"serde", "Serialize");
31 (Some
"serde", "Deserialize");
34 let derive_blacklists =
36 (* A custom implementation of Ord for Error_ matches the sorting behavior of
38 ("errors::Error_", ["Ord"; "PartialOrd"]);
39 (* GlobalOptions contains a couple floats, which only implement PartialEq
40 and PartialOrd, and do not implement Hash. *)
41 ("global_options::GlobalOptions", ["Eq"; "Hash"; "Ord"]);
42 (* And GlobalOptions is used in Genv which is used in Env. We
43 * don't care about comparison or hashing on environments *)
44 ("typing_env_types::Env", ["Eq"; "Hash"; "Ord"]);
45 ("typing_env_types::Genv", ["Eq"; "Hash"; "Ord"]);
47 |> List.fold ~init
:SMap.empty ~f
:(fun map
(ty
, bl
) -> SMap.add ty bl map
)
49 let derived_traits ty
=
50 let ty = sprintf
"%s::%s" (curr_module_name
()) ty in
51 match SMap.find_opt
ty derive_blacklists with
52 | None
-> default_derives
54 List.filter
default_derives ~f
:(fun (_
, derive
) ->
55 not
(List.mem blacklist derive ~equal
:( = )))
57 let blacklisted_types =
59 ("decl_defs", "Linearization");
60 ("typing_defs", "ExpandEnv");
61 ("typing_defs", "PhaseTy");
64 (* HACK: ignore anything beginning with the "decl" or "locl" prefix, since the
65 oxidized version of Ty does not have a phase. *)
66 let blacklisted_type_prefixes =
68 ("typing_defs", "Decl");
69 ("typing_defs_core", "Decl");
70 ("typing_defs", "Locl");
71 ("typing_defs_core", "Locl");
74 (* HACK: Typing_reason is usually aliased to Reason, so we have lots of
75 instances of Reason.t. Since we usually convert an identifier like Reason.t
76 to reason::Reason, the actual type needs to be renamed to the common alias.
77 This looks nicer anyway. *)
78 let renamed_types = [(("typing_reason", "TypingReason"), "Reason")]
80 (* By default, when we see an alias to a tuple type, we will assume the alias
81 adds some meaning, and generate a new tuple struct type named after the
82 alias. In some cases, the alias adds no meaning and we should also use an
84 let tuple_aliases = [("ast_defs", "Pstring"); ("errors", "Message")]
87 A list of (<module>, <ty1>) where ty1 is enum and all non-empty variant fields should
88 be wrapped by Box to keep ty1 size down.
90 let box_variant = [("aast", "Expr_"); ("aast", "Stmt_"); ("aast", "Def")]
92 let should_box_variant ty =
93 List.mem
box_variant (curr_module_name
(), ty) ~equal
:( = )
95 let add_rcoc = [("aast", "Nsenv")]
97 let should_add_rcoc ty =
98 List.mem
add_rcoc (curr_module_name
(), ty) ~equal
:( = )
100 let override_field_type =
103 ("Fun_", SMap.of_list
[("doc_comment", "Option<doc_comment::DocComment>")]);
105 SMap.of_list
[("doc_comment", "Option<doc_comment::DocComment>")] );
107 SMap.of_list
[("doc_comment", "Option<doc_comment::DocComment>")] );
109 SMap.of_list
[("doc_comment", "Option<doc_comment::DocComment>")] );
111 SMap.of_list
[("doc_comment", "Option<doc_comment::DocComment>")] );
113 SMap.of_list
[("doc_comment", "Option<doc_comment::DocComment>")] );
115 SMap.of_list
[("doc_comment", "Option<doc_comment::DocComment>")] );
118 let get_overrides name
=
119 match SMap.find_opt name
override_field_type with
123 let blacklisted ty_name
=
124 let ty = (curr_module_name
(), ty_name
) in
125 List.mem
blacklisted_types ty ~equal
:( = )
126 || List.exists
blacklisted_type_prefixes ~f
:(fun (mod_name
, prefix
) ->
127 mod_name
= curr_module_name
() && String.is_prefix ty_name ~prefix
)
130 List.find
renamed_types ~f
:(fun (x
, _
) -> x
= (curr_module_name
(), ty_name
))
131 |> Option.value_map ~f
:snd ~default
:ty_name
133 let should_use_alias_instead_of_tuple_struct ty_name
=
134 List.mem
tuple_aliases (curr_module_name
(), ty_name
) ~equal
:( = )
136 let doc_comment_of_attribute attr
=
138 | ({ txt
= "ocaml.doc"; _
}, PStr structure_items
) ->
139 List.find_map structure_items
(fun structure_item
->
140 match structure_item
.pstr_desc
with
142 ({ pexp_desc
= Pexp_constant
(Pconst_string
(doc
, _
)); _
}, _
) ->
147 let convert_doc_comment doc
=
149 |> String.strip ~drop
:(function
156 |> String.split ~on
:'
\n'
159 ~f
:(fun (was_in_code_block
, lines
) original_line
->
160 (* Remove leading whitespace *)
161 let lstripped = String.lstrip original_line
in
162 let maybe_chop_prefix prefix s
=
163 String.chop_prefix s ~prefix
|> Option.value ~default
:s
165 (* Remove leading asterisk and one space after, if present *)
167 lstripped |> maybe_chop_prefix "*" |> maybe_chop_prefix " "
169 let now_in_code_block =
170 if String.is_prefix ~prefix
:"```" (String.lstrip
no_asterisk) then
171 not was_in_code_block
176 if now_in_code_block && was_in_code_block
&& lstripped = no_asterisk
178 sprintf
"///%s\n" original_line
180 sprintf
"/// %s\n" no_asterisk
182 (now_in_code_block, line :: lines
))
183 |> (fun (_
, l
) -> List.rev l
)
186 let doc_comment_of_attribute_list attrs
=
188 |> List.find_map ~f
:doc_comment_of_attribute
189 |> Option.map ~f
:convert_doc_comment
190 |> Option.value ~default
:""
192 let type_param (ct
, _
) = core_type ct
194 let type_params params
=
195 if List.is_empty params
then
198 params
|> map_and_concat ~f
:type_param ~sep
:", " |> sprintf
"<%s>"
200 let record_label_declaration ?
(pub
= false) ?
(prefix
= "") overrides ld
=
201 let doc = doc_comment_of_attribute_list ld
.pld_attributes
in
209 ld
.pld_name
.txt
|> String.chop_prefix_exn ~prefix
|> convert_field_name
212 match SMap.find_opt
name overrides
with
213 | None
-> core_type ld
.pld_type
216 sprintf
"%s%s%s: %s,\n" doc pub name ty
218 let record_declaration ?
(pub = false) overrides labels
=
220 labels
|> List.map ~f
:(fun ld
-> ld
.pld_name
.txt
) |> common_prefix_of_list
222 (* Only remove a common prefix up to the last underscore (if a record has
223 fields x_bar and x_baz, we want to remove x_, not x_ba). *)
225 let idx = ref (String.length
prefix) in
226 while !idx > 1 && prefix.[!idx - 1] <> '_'
do
229 String.sub
prefix 0 !idx
232 |> map_and_concat ~f
:(record_label_declaration ~
pub ~
prefix overrides
)
235 let constructor_arguments ?
(box_fields
= false) = function
236 | Pcstr_tuple types
->
237 if not box_fields
then
243 let ty = core_type
ty in
246 || String.is_prefix
ty ~
prefix:"Vec<"
247 || String.is_prefix
ty ~
prefix:"Block<"
251 sprintf
"(Box<%s>)" ty
252 | _
-> sprintf
"(Box<%s>)" (tuple types
)
254 | Pcstr_record labels
-> record_declaration SMap.empty labels
256 let variant_constructor_declaration ?
(box_fields
= false) cd
=
257 let doc = doc_comment_of_attribute_list cd
.pcd_attributes
in
258 let name = convert_type_name cd
.pcd_name
.txt
in
259 let args = constructor_arguments ~box_fields cd
.pcd_args
in
261 (* If we see the [@value 42] attribute, assume it's for ppx_deriving enum,
262 and that all the variants are zero-argument (i.e., assume this is a
263 C-like enum and provide custom discriminant values). *)
264 List.find_map cd
.pcd_attributes
(fun attr
->
266 | ( { txt
= "value"; _
},
274 Pexp_constant
(Pconst_integer
(discriminant, None
));
281 Some
(" = " ^
discriminant)
283 |> Option.value ~default
:""
285 sprintf
"%s%s%s%s,\n" doc name args discriminant
287 let ctor_arg_len (ctor_args
: constructor_arguments) : int =
289 | Pcstr_tuple x
-> List.length x
290 | Pcstr_record x
-> List.length x
292 let type_declaration name td
=
293 let doc = doc_comment_of_attribute_list td
.ptype_attributes
in
294 let attrs_and_vis additional_derives
=
296 derived_traits name @ additional_derives
297 |> List.sort ~compare
:(fun (_
, t1
) (_
, t2
) -> String.compare t1 t2
)
298 |> List.map ~f
:(fun (m
, trait
) ->
299 Option.iter m ~f
:(fun m
-> add_extern_use
(m ^
"::" ^ trait
));
301 |> String.concat ~sep
:", "
302 |> sprintf
"#[derive(%s)]"
304 doc ^
derive_attr ^
"\npub"
307 match (td
.ptype_params
, td
.ptype_name
.txt
) with
308 (* HACK: eliminate tparam from `type _ ty_` and phase-parameterized types *)
309 | ([({ ptyp_desc
= Ptyp_any
; _
}, _
)], "ty_")
310 | ([({ ptyp_desc
= Ptyp_var
"phase"; _
}, _
)], _
)
311 | ([({ ptyp_desc
= Ptyp_var
"ty"; _
}, _
)], _
)
312 when curr_module_name
() = "typing_defs_core"
313 || curr_module_name
() = "typing_defs" ->
315 | (tparams, _
) -> type_params tparams
317 match (td
.ptype_kind
, td
.ptype_manifest
) with
319 (* The manifest represents a `= <some_type>` clause. When td.ptype_kind is
320 Ptype_abstract, this is a simple type alias:
322 type foo = Other_module.bar
324 In this case, the manifest contains the type Other_module.bar.
326 The ptype_kind can also be a full type definition. It is Ptype_variant in
327 a declaration like this:
329 type foo = Other_module.foo =
333 For these declarations, the OCaml compiler verifies that the variants in
334 Other_module.foo are equivalent to the ones we define in this
337 I don't think there's a direct equivalent to this in Rust, or any reason
338 to try to reproduce it. If we see a manifest, we can ignore the
339 ptype_kind and just alias, re-export, or define a newtype for
341 (match ty.ptyp_desc
with
342 (* Polymorphic variants. *)
344 raise
(Skip_type_decl
"polymorphic variants not supported")
345 | Ptyp_constr
({ txt
= Lident
"t"; _
}, []) ->
346 (* In the case of `type t = prefix * string ;; type relative_path = t`, we
347 have already defined a RelativePath type because we renamed t in the
348 first declaration to the name of the module. We can just skip the second
349 declaration introducing the alias. *)
350 let mod_name_as_type = convert_type_name
(curr_module_name
()) in
351 if name = mod_name_as_type then
354 ( "it is an alias to type t, which was already renamed to "
355 ^
mod_name_as_type ))
357 sprintf
"%spub type %s%s = %s;" doc name tparams mod_name_as_type
358 | Ptyp_constr
({ txt
= id
; _
}, targs
) ->
359 let id = longident_to_string
id in
360 let ty_name = id |> String.split ~on
:'
:'
|> List.last_exn
in
361 if List.length td
.ptype_params
= List.length targs
&& self
() = ty_name
364 raise
(Skip_type_decl
("it is a re-export of " ^
id))
366 let ty = core_type
ty in
367 if should_add_rcoc name then
369 "%spub type %s%s = ocamlrep::rc::RcOc<%s>;"
375 sprintf
"%spub type %s%s = %s;" doc name tparams ty
377 if should_use_alias_instead_of_tuple_struct name then
378 sprintf
"%spub type %s%s = %s;" doc name tparams (core_type
ty)
381 match ty.ptyp_desc
with
382 | Ptyp_tuple tys
-> tuple tys ~
pub:true
383 | _
-> sprintf
"(pub %s)" @@ core_type
ty
385 sprintf
"%s struct %s%s %s;" (attrs_and_vis []) name tparams ty)
386 (* Variant types, including GADTs. *)
387 | (Ptype_variant ctors
, None
) ->
389 List.for_all ctors
(fun c
-> 0 = ctor_arg_len c
.pcd_args
)
397 let should_box_variant = should_box_variant name in
401 (variant_constructor_declaration ~box_fields
:should_box_variant)
403 sprintf
"%s enum %s%s {\n%s}" (attrs_and_vis derives) name tparams ctors
405 | (Ptype_record labels
, None
) ->
406 let labels = record_declaration (get_overrides name) labels ~
pub:true in
407 sprintf
"%s struct %s%s %s" (attrs_and_vis []) name tparams labels
408 (* `type foo`; an abstract type with no specified implementation. This doesn't
409 mean much outside of an .mli, I don't think. *)
410 | (Ptype_abstract
, None
) ->
411 raise
(Skip_type_decl
"Abstract types without manifest not supported")
412 (* type foo += A, e.g. the exn type. *)
413 | (Ptype_open
, None
) -> raise
(Skip_type_decl
"Open types not supported")
415 let type_declaration td
=
416 let name = td
.ptype_name
.txt
in
423 let name = convert_type_name
name in
424 let name = rename name in
425 let mod_name = curr_module_name
() in
426 if blacklisted name then
427 log
"Not converting type %s::%s: it was blacklisted" mod_name name
429 try with_self
name (fun () -> add_decl
name (type_declaration name td
))
430 with Skip_type_decl reason
->
431 log
"Not converting type %s::%s: %s" mod_name name reason