(* * Copyright (c) 2014 Leo White * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) open Asttypes open Types module OCamlPath = Path open Odoc_model.Paths open Odoc_model.Lang open Odoc_model.Names module Env = Ident_env module Paths = Odoc_model.Paths type env = { ident_env : Env.t; warnings_tag : string option; (** used to suppress warnings *) } let empty_doc env = { Odoc_model.Comment.elements = []; warnings_tag = env.warnings_tag } module Compat = struct #if OCAML_VERSION >= (4, 14, 0) #if OCAML_VERSION >= (5, 3, 0) let newty2 = Btype.newty2 #endif (** this is the type on which physical equality is meaningful *) type repr_type_node = Types.transient_expr (** repr has morally type [type_expr -> repr_type_node] in all OCaml versions *) let repr x = Transient_expr.repr x let get_desc = Types.get_desc let get_row_name = Types.row_name let row_field_repr = Types.row_field_repr let field_kind_repr = Types.field_kind_repr let static_row_repr = Btype.static_row let row_closed = Types.row_closed let row_fields = Types.row_fields let field_public = Types.Fpublic let self_type = Btype.self_type let csig_self x = x.Types.csig_self let row_repr x = x let concr_mem = Types.Meths.mem let csig_concr x = x.Types.csig_meths let eq_type = Types.eq_type #if OCAML_VERSION >= (5,4,0) || defined OXCAML let invisible_wrap ty = newty2 ~level:Btype.generic_level (Ttuple [None,ty]) #else let invisible_wrap ty = newty2 ~level:Btype.generic_level (Ttuple [ty]) #endif #else type repr_type_node = Types.type_expr let repr = Btype.repr let get_desc x = (repr x).Types.desc let get_row_name x = x.Types.row_name let row_field_repr = Btype.row_field_repr let field_kind_repr = Btype.field_kind_repr let static_row_repr x = Btype.static_row (Btype.row_repr x) let row_closed x = x.Types.row_closed let row_fields x = x.Types.row_fields let field_public = Types.Fpresent let self_type = Ctype.self_type let csig_self x = Btype.repr x.Types.csig_self let row_repr = Btype.row_repr let concr_mem = Types.Concr.mem let csig_concr x = x.Types.csig_concr let eq_type x y = x == y || repr x == repr y (** Create a new node pointing to [ty] that is printed in the same way as [ty]*) let invisible_wrap ty = Btype.(newty2 generic_level (Ttuple [None, ty])) #endif end let proxy ty = Compat.(repr (Btype.proxy ty)) let opt_map f = function | None -> None | Some x -> Some (f x) let opt_iter f = function | None -> () | Some x -> f x let read_label lbl = let open TypeExpr in #if OCAML_VERSION < (4,3,0) (* NOTE(@ostera): 4.02 does not have an Asttypes variant for whether the * label exists, and is an optional label or not, so I went back to string * manipulation *) if String.length lbl == 0 then None else match String.get lbl 0 with | '?' -> Some (Optional (String.sub lbl 1 (String.length lbl - 1))) | _ -> Some (Label lbl) #elif defined OXCAML match lbl with | Types.Nolabel -> None | Types.Labelled s -> Some (Label s) | Types.Optional s -> Some (Optional s) | Types.Position s -> (* FIXME: do better? *) Some (Label s) #else match lbl with | Asttypes.Nolabel -> None | Asttypes.Labelled s -> Some (Label s) | Asttypes.Optional s -> Some (Optional s) #endif (* Handle type variable names *) (** To identify equal type node for type variables, we need a map from the representative type node to names. Otherwise, equivalent variables would end up with distinct names *) let used_names : (Compat.repr_type_node * string) list ref = ref [] let name_counter = ref 0 let reserved_names = ref [] let reset_names () = used_names := []; name_counter := 0; reserved_names := [] let reserve_name = function | Some name -> if not (List.mem name !reserved_names) then reserved_names := name :: !reserved_names | None -> () let rec next_name () = let name = if !name_counter < 26 then String.make 1 (Char.chr(97 + !name_counter)) else String.make 1 (Char.chr(97 + !name_counter mod 26)) ^ string_of_int(!name_counter / 26) in incr name_counter; if List.mem name !reserved_names then next_name () else name let fresh_name base = let current_name = ref base in let i = ref 0 in while List.exists (fun (_, name') -> !current_name = name') !used_names do current_name := base ^ (string_of_int !i); i := !i + 1; done; !current_name let name_of_type_repr (ty : Compat.repr_type_node) = try List.assq ty !used_names with Not_found -> let base = match ty.desc with #if defined OXCAML | Tvar { name = Some name; _ } | Tunivar { name = Some name; _ } -> name #else | Tvar (Some name) | Tunivar (Some name) -> name #endif | _ -> next_name () in let name = fresh_name base in if name <> "_" then used_names := (ty, name) :: !used_names; name let name_of_type ty = name_of_type_repr (Compat.repr ty) let remove_names tyl = used_names := List.filter (fun (ty,_) -> not (List.memq ty tyl)) !used_names (* Handle recursive types and shared row variables *) let aliased: Compat.repr_type_node list ref = ref [] let used_aliases = ref [] let reset_aliased () = aliased := []; used_aliases := [] let is_aliased px = List.memq px !aliased let aliasable (ty : Types.type_expr) = match Compat.get_desc ty with | Tvar _ | Tunivar _ | Tpoly _ -> false | _ -> true let add_alias_proxy px = if not (List.memq px !aliased) then begin aliased := px :: !aliased; match px.desc with #if defined OXCAML | Tvar { name; _ } | Tunivar { name; _ } -> #else | Tvar name | Tunivar name -> #endif reserve_name name | _ -> () end let add_alias ty = add_alias_proxy (proxy ty) let used_alias (px : Compat.repr_type_node) = List.memq px !used_aliases let use_alias (px : Compat.repr_type_node) = used_aliases := px :: !used_aliases let visited_rows: Compat.repr_type_node list ref = ref [] let reset_visited_rows () = visited_rows := [] let is_row_visited px = List.memq px !visited_rows let visit_row px = visited_rows := px :: !visited_rows let visit_object ty px = if Ctype.opened_object ty then visited_rows := px :: !visited_rows let namable_row row = Compat.get_row_name row <> None && List.for_all (fun (_, f) -> match Compat.row_field_repr f with #if OCAML_VERSION >= (4, 14, 0) | Reither(c, l, _) -> #else | Reither(c, l, _, _) -> #endif Compat.row_closed row && if c then l = [] else List.length l = 1 | _ -> true) (Compat.row_fields row) let mark_type ty = let rec loop visited ty = let px = proxy ty in if List.memq px visited && aliasable ty then add_alias_proxy px else let visited = px :: visited in match Compat.get_desc ty with #if defined OXCAML | Tvar { name; _ } | Tunivar { name; _ } -> #else | Tvar name | Tunivar name -> #endif reserve_name name | Tarrow(_, ty1, ty2, _) -> loop visited ty1; loop visited ty2 #if OCAML_VERSION >= (5,4,0) || defined OXCAML | Ttuple tyl -> List.iter (fun (_lbl,x) -> loop visited x) tyl #else | Ttuple tyl -> List.iter (loop visited) tyl #endif #if defined OXCAML | Tunboxed_tuple tyl -> List.iter (fun (_, ty) -> loop visited ty) tyl #endif | Tconstr(_, tyl, _) -> List.iter (loop visited) tyl | Tvariant row -> if is_row_visited px then add_alias_proxy px else begin if not (Compat.static_row_repr row) then visit_row px; match Compat.get_row_name row with | Some(_, tyl) when namable_row row -> List.iter (loop visited) tyl | _ -> Btype.iter_row (loop visited) row end | Tobject (fi, nm) -> if is_row_visited px then add_alias_proxy px else begin visit_object ty px; match !nm with | None -> let fields, _ = Ctype.flatten_fields fi in List.iter (fun (_, kind, ty) -> if Compat.field_kind_repr kind = Compat.field_public then loop visited ty) fields | Some (_, l) -> List.iter (loop visited) (List.tl l) end | Tfield(_, kind, ty1, ty2) when Compat.field_kind_repr kind = Compat.field_public -> loop visited ty1; loop visited ty2 | Tfield(_, _, _, ty2) -> loop visited ty2 | Tnil -> () | Tpoly (ty, tyl) -> List.iter (fun t -> add_alias t) tyl; loop visited ty #if OCAML_VERSION>=(5,4,0) | Tpackage p -> List.iter (fun (_,x) -> loop visited x) p.pack_cstrs #elif OCAML_VERSION>=(4,13,0) | Tpackage(_,tyl) -> List.iter (fun (_,x) -> loop visited x) tyl #else | Tpackage(_, _, tyl) -> List.iter (loop visited) tyl #endif #if OCAML_VERSION<(4,13,0) | Tsubst ty -> loop visited ty #else | Tsubst (ty,_) -> loop visited ty #endif #if defined OXCAML | Tquote typ -> loop visited typ | Tsplice typ -> loop visited typ | Tof_kind _ -> () #endif | Tlink _ -> assert false in loop [] ty let reset_context () = reset_names (); reset_aliased (); reset_visited_rows () let mark_type_expr t = reset_context (); mark_type t let mark_value_description vd = reset_context (); mark_type vd.val_type let mark_type_parameter param = let px = proxy param in add_alias_proxy px; mark_type param; if aliasable param then use_alias px #if OCAML_VERSION<(4,13,0) let tvar_none ty = ty.desc <- Tvar None #elif OCAML_VERSION < (4,14,0) let tvar_none ty = Types.Private_type_expr.set_desc ty (Tvar None) #elif defined OXCAML let tvar_none ty jkind = Types.Transient_expr.(set_desc (coerce ty) (Tvar { name = None; jkind })) #else let tvar_none ty = Types.Transient_expr.(set_desc (coerce ty) (Tvar None)) #endif let wrap_constrained_params tyl = let params = List.fold_left (fun tyl ty -> if List.exists (Compat.eq_type ty) tyl then Compat.invisible_wrap ty :: tyl else ty :: tyl) (* Two parameters might be identical due to a constraint but we need to print them differently in order to make the output syntactically valid. We use [Ttuple [ty]] because it is printed as [ty]. *) [] tyl in List.rev params let prepare_type_parameters params manifest = let params = wrap_constrained_params params in begin match manifest with | Some ty -> let vars = Ctype.free_variables ty in List.iter (fun ty -> match Compat.get_desc ty with #if defined OXCAML | Tvar { name = Some "_"; jkind } -> if List.memq ty vars then tvar_none ty jkind #else | Tvar (Some "_") -> if List.memq ty vars then tvar_none ty #endif | _ -> ()) params | None -> () end; params (* NOTE(@ostera): constructor with inlined records were introduced post 4.02 *) let mark_constructor_args = #if OCAML_VERSION < (4,3,0) List.iter mark_type #else function #if defined OXCAML | Cstr_tuple args -> List.iter (fun carg -> mark_type carg.ca_type) args #else | Cstr_tuple args -> List.iter mark_type args #endif | Cstr_record lds -> List.iter (fun ld -> mark_type ld.ld_type) lds #endif let mark_type_kind = function #if OCAML_VERSION >= (5,2,0) | Type_abstract _ -> () #else | Type_abstract -> () #endif #if defined OXCAML | Type_variant (cds,_,_) -> #elif OCAML_VERSION >= (4,13,0) | Type_variant (cds,_) -> #else | Type_variant cds -> #endif List.iter (fun cd -> mark_constructor_args cd.cd_args; opt_iter mark_type cd.cd_res) cds #if defined OXCAML | Type_record_unboxed_product(lds, _, _) -> List.iter (fun ld -> mark_type ld.ld_type) lds | Type_record(lds, _, _) -> #else | Type_record(lds, _) -> #endif List.iter (fun ld -> mark_type ld.ld_type) lds | Type_open -> () let mark_type_declaration decl = let params = prepare_type_parameters decl.type_params decl.type_manifest in reset_context (); List.iter mark_type_parameter params; opt_iter mark_type decl.type_manifest; mark_type_kind decl.type_kind; params let mark_extension_constructor ext = mark_constructor_args ext.ext_args; opt_iter mark_type ext.ext_ret_type let mark_type_extension type_params exts = let type_params = prepare_type_parameters type_params None in reset_context (); List.iter mark_type_parameter type_params; List.iter mark_extension_constructor exts; type_params let mark_type_extension' ext rest = let type_params = ext.ext_type_params in let exts = ext :: (List.map snd rest) in mark_type_extension type_params exts let mark_exception ext = reset_context (); mark_extension_constructor ext let rec mark_class_type params = function | Cty_constr (_, tyl, cty) -> let sty = Compat.self_type cty in if is_row_visited (proxy sty) || List.exists aliasable params || List.exists (Ctype.deep_occur sty) tyl then mark_class_type params cty else List.iter mark_type tyl | Cty_signature sign -> let sty = Compat.csig_self sign in let px = proxy sty in if is_row_visited px then add_alias_proxy px else visit_row px; let (fields, _) = Ctype.flatten_fields (Ctype.object_fields sign.csig_self) in List.iter (fun (_, _, ty) -> mark_type ty) fields; Vars.iter (fun _ (_, _, ty) -> mark_type ty) sign.csig_vars; if is_aliased px && aliasable sty then use_alias px | Cty_arrow (_, ty, cty) -> mark_type ty; mark_class_type params cty let mark_class_type_declaration cltd = reset_context (); List.iter mark_type_parameter cltd.clty_params; mark_class_type cltd.clty_params cltd.clty_type let mark_class_declaration cld = reset_context (); List.iter mark_type_parameter cld.cty_params; mark_class_type cld.cty_params cld.cty_type #if defined OXCAML (** Extract non-default mode strings from an OxCaml argument mode. Replicates the logic from [Printtyp.tree_of_modes]. *) let extract_arg_modes marg = let modes = Mode.Alloc.zap_to_legacy marg in let diff = Mode.Alloc.Const.diff modes Mode.Alloc.Const.legacy in (* Apply implied-default elision rules from Printtyp *) let forkable = match modes.areality, modes.forkable with | Local, Unforkable | Global, Forkable -> None | _, _ -> diff.forkable in let yielding = match modes.areality, modes.yielding with | Local, Yielding | Global, Unyielding -> None | _, _ -> diff.yielding in let contention = match modes.visibility, modes.contention with | Immutable, Contended | Read, Shared | Read_write, Uncontended -> None | _, _ -> diff.contention in let portability = match modes.statefulness, modes.portability with | Stateless, Portable | Observing, Shareable | Stateful, Nonportable -> None | _, _ -> diff.portability in let print_opt print a = Option.map (fun v -> Format.asprintf "%a" print v) a in List.filter_map Fun.id [ print_opt Mode.Locality.Const.print diff.areality ; print_opt Mode.Uniqueness.Const.print diff.uniqueness ; print_opt Mode.Linearity.Const.print diff.linearity ; print_opt Mode.Portability.Const.print portability ; print_opt Mode.Contention.Const.print contention ; print_opt Mode.Forkable.Const.print forkable ; print_opt Mode.Yielding.Const.print yielding ; print_opt Mode.Statefulness.Const.print diff.statefulness ; print_opt Mode.Visibility.Const.print diff.visibility ] (** Extract jkind/layout string from an OxCaml type variable's jkind. Returns [None] for the default [value] layout or unknown layouts. *) let extract_jkind_of_tvar jkind = let desc = Jkind.get jkind in match desc.layout with | Sort (Base Value) -> None (* default — don't annotate *) | Sort (Base b) -> Some (Jkind_types.Sort.to_string_base b) | Sort (Var _) -> None (* sort variable — not determined *) | Product _ -> None (* product layout — complex, skip for now *) | Any -> None (** Extract non-default modality strings from a value's modalities. Replicates the implied-modality filtering from [Typemode.least_modalities]. *) let extract_modalities modalities = let m = Mode.Modality.zap_to_floor modalities in if Mode.Modality.Const.is_id m then [] else begin let atoms = Mode.Modality.Const.diff Mode.Modality.Const.id m in (* Compute implied atoms from each annotated atom *) let implied_of_atom (Mode.Modality.Atom (ax, v)) = match ax, v with | Comonadic Areality, Meet_with Global -> [ Mode.Modality.Atom (Comonadic Forkable, Meet_with Mode.Forkable.Const.Forkable) ; Mode.Modality.Atom (Comonadic Yielding, Meet_with Mode.Yielding.Const.Unyielding) ; Mode.Modality.Atom (Monadic Uniqueness, Join_with Mode.Uniqueness.Const.Aliased) ] | Comonadic Areality, Meet_with Local -> [ Mode.Modality.Atom (Comonadic Forkable, Meet_with Mode.Forkable.Const.Unforkable) ; Mode.Modality.Atom (Comonadic Yielding, Meet_with Mode.Yielding.Const.Yielding) ] | Monadic Visibility, Join_with Immutable -> [ Mode.Modality.Atom (Monadic Contention, Join_with Mode.Contention.Const.Contended) ] | Monadic Visibility, Join_with Read -> [ Mode.Modality.Atom (Monadic Contention, Join_with Mode.Contention.Const.Shared) ] | Monadic Visibility, Join_with Read_write -> [ Mode.Modality.Atom (Monadic Contention, Join_with Mode.Contention.Const.Uncontended) ] | Comonadic Statefulness, Meet_with Stateless -> [ Mode.Modality.Atom (Comonadic Portability, Meet_with Mode.Portability.Const.Portable) ] | Comonadic Statefulness, Meet_with Observing -> [ Mode.Modality.Atom (Comonadic Portability, Meet_with Mode.Portability.Const.Shareable) ] | Comonadic Statefulness, Meet_with Stateful -> [ Mode.Modality.Atom (Comonadic Portability, Meet_with Mode.Portability.Const.Nonportable) ] | _ -> [] in let implied = List.concat_map implied_of_atom atoms in (* Filter out atoms that are exactly implied by other atoms *) let filtered = List.filter (fun a -> not (List.mem a implied)) atoms in (* Add back atoms on implied axes with overridden (non-implied) values *) let overridden = List.filter_map (fun imp_atom -> let (Mode.Modality.Atom (ax, _v_implied)) = imp_atom in let v_actual = Mode.Modality.Const.proj ax m in let actual_atom = Mode.Modality.Atom (ax, v_actual) in if actual_atom <> imp_atom then Some actual_atom else None ) implied in let final_atoms = filtered @ overridden in List.filter_map (fun (Mode.Modality.Atom (ax, v)) -> let s = Format.asprintf "%a" (Printtyp.modality ax) v in if s = "" then None else Some s ) final_atoms end #endif let rec read_type_expr env typ = let open TypeExpr in let px = proxy typ in if used_alias px then Var (name_of_type typ, None) else begin let alias = if not (is_aliased px && aliasable typ) then None else begin use_alias px; Some (name_of_type typ) end in let typ = match Compat.get_desc typ with #if defined OXCAML | Tvar { name; _ } -> (* Tvar is a use site — don't annotate with jkind here. Jkinds are extracted at the Tunivar binding site in Tpoly, matching Printtyp's convention of stating the jkind once at the universal quantifier. *) let nm = match name with Some n -> n | None -> name_of_type typ in if nm = "_" then Any else Var (nm, None) | Tarrow((lbl, marg, mret), arg, res, _) -> let arg_modes = extract_arg_modes marg in (* Suppress return modes when the return type is itself a function. A closure capturing a local argument is necessarily local, so the return mode is always implied. Showing it is redundant. This matches the elision logic in Printtyp.tree_of_modes. *) let ret_modes = match Compat.get_desc res with | Tarrow _ -> [] | _ -> extract_arg_modes mret in #else | Tvar _ -> let name = name_of_type typ in if name = "_" then Any else Var (name, None) | Tarrow(lbl, arg, res, _) -> let arg_modes = [] in let ret_modes = [] in #endif let lbl = read_label lbl in let lbl,arg = match lbl with | Some (Optional s) -> ( let read_as_wrapped () = (Some (RawOptional s), read_type_expr env arg) in match Compat.get_desc arg with | Tpoly(arg, []) -> begin match Compat.get_desc arg with | Tconstr(_option, [arg], _) -> lbl, read_type_expr env arg (* Unwrap option if possible *) | _ -> read_as_wrapped () end | _ -> read_as_wrapped ()) (* If not, mark is as wrapped *) | _ -> lbl, read_type_expr env arg in let res = read_type_expr env res in Arrow(lbl, arg, res, arg_modes, ret_modes) | Ttuple typs -> #if OCAML_VERSION >= (5,4,0) || defined OXCAML let typs = List.map (fun (lbl,x) -> lbl, read_type_expr env x) typs in #else let typs = List.map (fun x -> None, read_type_expr env x) typs in #endif Tuple typs #if defined OXCAML | Tunboxed_tuple typs -> let typs = List.map (fun (l,t) -> l, read_type_expr env t) typs in Unboxed_tuple typs #endif | Tconstr(p, params, _) -> let p = Env.Path.read_type env.ident_env p in let params = List.map (read_type_expr env) params in Constr(p, params) | Tvariant row -> read_row env px row | Tobject (fi, nm) -> read_object env fi !nm | Tnil | Tfield _ -> read_object env typ None | Tpoly (typ, []) -> read_type_expr env typ | Tpoly (typ, tyl) -> let reprs = List.map Compat.repr tyl in let vars = List.map2 (fun orig repr -> let name = name_of_type_repr repr in (* Extract jkind from the Tunivar binding site. This is the only place jkinds are recorded — use sites (Tvar) intentionally return None to avoid redundancy. *) let jkind = #if defined OXCAML match Compat.get_desc orig with | Tunivar { jkind; _ } -> extract_jkind_of_tvar jkind | _ -> None #else None #endif in (name, jkind)) tyl reprs in let typ = read_type_expr env typ in remove_names reprs; Poly(vars, typ) #if defined OXCAML | Tunivar { jkind; _ } -> Var (name_of_type typ, extract_jkind_of_tvar jkind) #else | Tunivar _ -> Var (name_of_type typ, None) #endif #if OCAML_VERSION>=(5,4,0) | Tpackage {pack_path=p; pack_cstrs } -> let eqs = List.filter_map (fun (l,ty) -> Option.map (fun x -> x, ty) (Longident.unflatten l)) pack_cstrs in #elif OCAML_VERSION>=(4,13,0) | Tpackage(p,eqs) -> #else | Tpackage(p, frags, tyl) -> let eqs = List.combine frags tyl in #endif let open TypeExpr.Package in let path = Env.Path.read_module_type env.ident_env p in let substitutions = List.map (fun (frag,typ) -> let frag = Env.Fragment.read_type frag in let typ = read_type_expr env typ in (frag, typ)) eqs in Package {path; substitutions} #if OCAML_VERSION<(4,13,0) | Tsubst typ -> read_type_expr env typ #else | Tsubst (typ,_) -> read_type_expr env typ #endif #if defined OXCAML | Tquote typ -> Quote (read_type_expr env typ) | Tsplice typ -> Splice (read_type_expr env typ) | Tof_kind _ -> assert false #endif | Tlink _ -> assert false in match alias with | None -> typ | Some name -> Alias(typ, name) end and read_row env _px row = let open TypeExpr in let open TypeExpr.Polymorphic_variant in let row = Compat.row_repr row in let fields = if Compat.row_closed row then List.filter (fun (_, f) -> Compat.row_field_repr f <> Rabsent) (Compat.row_fields row) else Compat.row_fields row in let sorted_fields = List.sort (fun (p,_) (q,_) -> compare p q) fields in let present = List.filter (fun (_, f) -> match Compat.row_field_repr f with | Rpresent _ -> true | _ -> false) sorted_fields in let all_present = List.length present = List.length sorted_fields in match Compat.get_row_name row with | Some(p, params) when namable_row row -> let p = Env.Path.read_type env.ident_env p in let params = List.map (read_type_expr env) params in if Compat.row_closed row && all_present then Constr (p, params) else let kind = if all_present then Open else Closed (List.map fst present) in Polymorphic_variant {kind; elements = [Type (Constr (p, params))]} | _ -> let elements = List.map (fun (name, f) -> let doc = empty_doc env in match Compat.row_field_repr f with | Rpresent None -> Constructor {name; constant = true; arguments = []; doc} | Rpresent (Some typ) -> Constructor { name; constant = false; arguments = [read_type_expr env typ]; doc; } #if OCAML_VERSION >= (4, 14, 0) | Reither(constant, typs, _) -> #else | Reither(constant, typs, _, _) -> #endif let arguments = List.map (read_type_expr env) typs in Constructor {name; constant; arguments; doc} | Rabsent -> assert false) sorted_fields in let kind = if all_present then if Compat.row_closed row then Fixed else Open else Closed (List.map fst present) in Polymorphic_variant {kind; elements} and read_object env fi nm = let open TypeExpr in let open TypeExpr.Object in let px = proxy fi in if used_alias px then Var (name_of_type fi, None) else begin use_alias px; match nm with | None -> let (fields, rest) = Ctype.flatten_fields fi in let present_fields = List.fold_right (fun (n, k, t) l -> match Compat.field_kind_repr k with | f when f = Compat.field_public -> (n, t) :: l | _ -> l) fields [] in let sorted_fields = List.sort (fun (n, _) (n', _) -> compare n n') present_fields in let methods = List.map (fun (name, typ) -> Method {name; type_ = read_type_expr env typ}) sorted_fields in let open_ = match Compat.get_desc rest with | Tvar _ | Tunivar _ -> true | Tconstr _ -> true | Tnil -> false | _ -> assert false in Object {fields = methods; open_} | Some (p, _ :: params) -> let p = Env.Path.read_class_type env.ident_env p in let params = List.map (read_type_expr env) params in Class (p, params) | _ -> assert false end let read_value_description ({ident_env ; warnings_tag} as env) parent id vd = let open Signature in let id = Env.find_value_identifier ident_env id in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc = Doc_attr.attached_no_tag ~warnings_tag container vd.val_attributes in mark_value_description vd; let type_ = read_type_expr env vd.val_type in let value = match vd.val_kind with #if defined OXCAML | Val_reg _ -> Value.Abstract #else | Val_reg -> Value.Abstract #endif | Val_prim desc -> let primitives = let open Primitive in desc.prim_name :: (match desc.prim_native_name with "" -> [] | name -> [ name ]) in External primitives | _ -> assert false in (* Source location is not trustworthy since it's a cmi so left as None *) let source_loc_jane = None in #if defined OXCAML let modalities = extract_modalities vd.val_modalities in #else let modalities = [] in #endif Value { Value.id; source_loc; doc; type_; value ; source_loc_jane; modalities } #if defined OXCAML let is_mutable = Types.is_mutable #else let is_mutable ld = ld = Mutable #endif let read_label_declaration env parent ld = let open TypeDecl.Field in let name = Ident.name ld.ld_id in let id = Identifier.Mk.field (parent, Odoc_model.Names.FieldName.make_std name) in let doc = Doc_attr.attached_no_tag ~warnings_tag:env.warnings_tag (parent :> Identifier.LabelParent.t) ld.ld_attributes in let mutable_ = is_mutable ld.ld_mutable in let type_ = read_type_expr env ld.ld_type in {id; doc; mutable_; type_} let read_constructor_declaration_arguments env parent arg = #if OCAML_VERSION < (4,3,0) (* NOTE(@ostera): constructor with inlined records were introduced post 4.02 so it's safe to use Tuple here *) ignore parent; TypeDecl.Constructor.Tuple(List.map (read_type_expr env) arg) #else let open TypeDecl.Constructor in match arg with #if defined OXCAML | Cstr_tuple args -> Tuple (List.map (fun arg -> read_type_expr env arg.ca_type) args) #else | Cstr_tuple args -> Tuple (List.map (read_type_expr env) args) #endif | Cstr_record lds -> Record (List.map (read_label_declaration env parent) lds) #endif let read_constructor_declaration env parent cd = let open TypeDecl.Constructor in let id = Ident_env.find_constructor_identifier env.ident_env cd.cd_id in let container = (parent :> Identifier.LabelParent.t) in let doc = Doc_attr.attached_no_tag ~warnings_tag:env.warnings_tag container cd.cd_attributes in let args = read_constructor_declaration_arguments env (parent :> Identifier.FieldParent.t) cd.cd_args in let res = opt_map (read_type_expr env) cd.cd_res in {id; doc; args; res} let read_type_kind env parent = let open TypeDecl.Representation in function #if OCAML_VERSION >= (5,2,0) | Type_abstract _ -> #else | Type_abstract -> #endif None #if defined OXCAML | Type_variant (cstrs,_,_) -> #elif OCAML_VERSION >= (4,13,0) | Type_variant (cstrs,_) -> #else | Type_variant cstrs -> #endif let cstrs = List.map (read_constructor_declaration env parent) cstrs in Some (Variant cstrs) #if defined OXCAML | Type_record_unboxed_product(lbls, _, _) -> let lbls = List.map (read_label_declaration env (parent :> Identifier.FieldParent.t)) lbls in Some (Record lbls) | Type_record(lbls, _, _) -> #else | Type_record(lbls, _) -> #endif let lbls = List.map (read_label_declaration env (parent :> Identifier.FieldParent.t)) lbls in Some (Record lbls) | Type_open -> Some Extensible let read_injectivity var = #if OCAML_VERSION < (5, 1, 0) let _, _, _, inj = Variance.get_lower var in #else let _, _, inj = Variance.get_lower var in #endif inj let read_type_parameter abstr var param = let open TypeDecl in let name = name_of_type param in let desc = if name = "_" then Any else #if defined OXCAML let jkind_opt = match Compat.get_desc param with | Tvar { jkind; _ } -> extract_jkind_of_tvar jkind | _ -> None in Var (name, jkind_opt) #else Var (name, None) #endif in let variance = if not (abstr || aliasable param) then None else begin let co, cn = Variance.get_upper var in if not cn then Some Pos else if not co then Some Neg else None end in let injectivity = read_injectivity var in {desc; variance; injectivity} let read_type_constraints env params = List.fold_right (fun typ1 acc -> let typ2 = Ctype.unalias typ1 in if Btype.proxy typ1 != Btype.proxy typ2 then let typ1 = read_type_expr env typ1 in let typ2 = read_type_expr env typ2 in (typ1, typ2) :: acc else acc) params [] let read_class_constraints env params = let open ClassSignature in read_type_constraints env params |> List.map (fun (left, right) -> Constraint { Constraint.left; right; doc = empty_doc env }) let read_type_declaration env parent id decl = let open TypeDecl in let id = Env.find_type_identifier env.ident_env id in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc, canonical = Doc_attr.attached ~warnings_tag:env.warnings_tag Odoc_model.Semantics.Expect_canonical container decl.type_attributes in let canonical = match canonical with | None -> None | Some s -> Doc_attr.conv_canonical_type s in let params = mark_type_declaration decl in let manifest = opt_map (read_type_expr env) decl.type_manifest in let constraints = read_type_constraints env params in let representation = read_type_kind env (id :> Identifier.DataType.t) decl.type_kind in let abstr = match decl.type_kind with #if OCAML_VERSION >= (5,2,0) | Type_abstract _ -> #else | Type_abstract -> #endif decl.type_manifest = None || decl.type_private = Private | Type_record _ -> decl.type_private = Private #if defined OXCAML | Type_record_unboxed_product _ -> decl.type_private = Private #endif #if defined OXCAML | Type_variant (tll,_,_) -> #elif OCAML_VERSION >= (4,13,0) | Type_variant (tll,_) -> #else | Type_variant tll -> #endif decl.type_private = Private || List.exists (fun cd -> cd.cd_res <> None) tll | Type_open -> decl.type_manifest = None in let params = List.map2 (read_type_parameter abstr) decl.type_variance params in let private_ = (decl.type_private = Private) in let equation = Equation.{params; manifest; constraints; private_} in (* Source location is not trustworthy since it's a cmi so left as None *) let source_loc_jane = None in {id; source_loc; doc; canonical; equation; representation; source_loc_jane } let read_extension_constructor env parent id ext = let open Extension.Constructor in let id = Env.find_extension_identifier env.ident_env id in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc = Doc_attr.attached_no_tag ~warnings_tag:env.warnings_tag container ext.ext_attributes in let args = read_constructor_declaration_arguments env (parent : Identifier.Signature.t :> Identifier.FieldParent.t) ext.ext_args in let res = opt_map (read_type_expr env) ext.ext_ret_type in {id; source_loc; doc; args; res} let read_type_extension env parent id ext rest = let open Extension in let type_path = Env.Path.read_type env.ident_env ext.ext_type_path in let doc = Doc_attr.empty env.warnings_tag in let type_params = mark_type_extension' ext rest in let first = read_extension_constructor env parent id ext in let rest = List.map (fun (id, ext) -> read_extension_constructor env parent id ext) rest in let constructors = first :: rest in let type_params = List.map (read_type_parameter false Variance.null) type_params in let private_ = (ext.ext_private = Private) in { parent; type_path; type_params; doc; private_; constructors; } let read_exception env parent id ext = let open Exception in let id = Env.find_exception_identifier env.ident_env id in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc = Doc_attr.attached_no_tag ~warnings_tag:env.warnings_tag container ext.ext_attributes in mark_exception ext; let args = read_constructor_declaration_arguments env (parent : Identifier.Signature.t :> Identifier.FieldParent.t) ext.ext_args in let res = opt_map (read_type_expr env) ext.ext_ret_type in (* Source location is not trustworthy since it's a cmi so left as None *) let source_loc_jane = None in {id; source_loc; doc; args; res; source_loc_jane} let read_method env parent concrete (name, kind, typ) = let open Method in let id = Identifier.Mk.method_(parent, Odoc_model.Names.MethodName.make_std name) in let doc = Doc_attr.empty env.warnings_tag in let private_ = (Compat.field_kind_repr kind) <> Compat.field_public in let virtual_ = not (Compat.concr_mem name concrete) in let type_ = read_type_expr env typ in ClassSignature.Method {id; doc; private_; virtual_; type_} let read_instance_variable env parent (name, mutable_, virtual_, typ) = let open InstanceVariable in let id = Identifier.Mk.instance_variable(parent, Odoc_model.Names.InstanceVariableName.make_std name) in let doc = Doc_attr.empty env.warnings_tag in let mutable_ = (mutable_ = Asttypes.Mutable) in let virtual_ = (virtual_ = Virtual) in let type_ = read_type_expr env typ in ClassSignature.InstanceVariable {id; doc; mutable_; virtual_; type_} let read_self_type sty = let px = proxy sty in if not (is_aliased px) then None else Some (TypeExpr.Var (name_of_type_repr px, None)) let rec read_class_signature env parent params = let open ClassType in function | Cty_constr(p, _, cty) -> if is_row_visited (proxy (Compat.self_type cty)) || List.exists aliasable params then read_class_signature env parent params cty else begin let p = Env.Path.read_class_type env.ident_env p in let params = List.map (read_type_expr env) params in Constr(p, params) end | Cty_signature csig -> let open ClassSignature in let self = read_self_type csig.csig_self in let constraints = read_class_constraints env params in let instance_variables = Vars.fold (fun name (mutable_, virtual_, typ) acc -> (name, mutable_, virtual_, typ) :: acc) csig.csig_vars [] in let methods, _ = Ctype.flatten_fields (Ctype.object_fields csig.csig_self) in let methods = List.filter (fun (name, _, _) -> name <> Btype.dummy_method) methods in let instance_variables = List.map (read_instance_variable env parent) instance_variables in let methods = List.map (read_method env parent (Compat.csig_concr csig)) methods in let items = constraints @ instance_variables @ methods in Signature {self; items; doc = empty_doc env} | Cty_arrow _ -> assert false let rec read_virtual = function | Cty_constr(_, _, cty) | Cty_arrow(_, _, cty) -> read_virtual cty | Cty_signature csig -> let methods, _ = Ctype.flatten_fields (Ctype.object_fields csig.csig_self) in let virtual_method = List.exists (fun (name, _, _) -> not (name = Btype.dummy_method || Compat.concr_mem name (Compat.csig_concr csig))) methods in let virtual_instance_variable = Vars.exists (fun _ (_, virtual_, _) -> virtual_ = Virtual) csig.csig_vars in virtual_method || virtual_instance_variable let read_class_type_declaration env parent id cltd = let open ClassType in let id = Env.find_class_type_identifier env.ident_env id in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc = Doc_attr.attached_no_tag ~warnings_tag:env.warnings_tag container cltd.clty_attributes in mark_class_type_declaration cltd; let params = List.map2 (read_type_parameter false) cltd.clty_variance cltd.clty_params in let expr = read_class_signature env (id :> Identifier.ClassSignature.t) cltd.clty_params cltd.clty_type in let virtual_ = read_virtual cltd.clty_type in (* Source location is not trustworthy since it's a cmi so left as None *) let source_loc_jane = None in { id; source_loc; doc; virtual_; params; expr; expansion = None ; source_loc_jane} let rec read_class_type env parent params = let open Class in function | Cty_constr _ | Cty_signature _ as cty -> ClassType (read_class_signature env parent params cty) | Cty_arrow(lbl, arg, cty) -> let lbl = read_label lbl in let lbl, arg = match lbl with | Some (Optional s) -> ( match Compat.get_desc arg with | Tconstr(_option, [arg], _) -> lbl, read_type_expr env arg (* Unwrap option if possible *) | _ -> (Some (RawOptional s), read_type_expr env arg)) (* If not, mark is as wrapped *) | _ -> lbl, read_type_expr env arg in let cty = read_class_type env parent params cty in Arrow(lbl, arg, cty) let read_class_declaration env parent id cld = let open Class in let id = Env.find_class_identifier env.ident_env id in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc = Doc_attr.attached_no_tag ~warnings_tag:env.warnings_tag container cld.cty_attributes in mark_class_declaration cld; let params = List.map2 (read_type_parameter false) cld.cty_variance cld.cty_params in let type_ = read_class_type env (id :> Identifier.ClassSignature.t) cld.cty_params cld.cty_type in let virtual_ = cld.cty_new = None in (* Source location is not trustworthy since it's a cmi so left as None *) let source_loc_jane = None in { id; source_loc; doc; virtual_; params; type_; expansion = None ; source_loc_jane} let rec read_module_type env parent (mty : Odoc_model.Compat.module_type) = let open ModuleType in match mty with | Mty_ident p -> Path {p_path = Env.Path.read_module_type env.ident_env p; p_expansion=None } | Mty_signature sg -> Signature (read_signature env parent sg) | Mty_functor(parameter, res) -> let f_parameter, env = match parameter with | Unit -> Odoc_model.Lang.FunctorParameter.Unit, env | Named (id_opt, arg) -> let id, env = match id_opt with | None -> Identifier.Mk.parameter(parent, Odoc_model.Names.ModuleName.make_std "_"), env | Some id -> let e' = Env.add_parameter parent id (ModuleName.of_ident id) env.ident_env in Ident_env.find_parameter_identifier e' id, {env with ident_env = e'} in let arg = read_module_type env (id :> Identifier.Signature.t) arg in Odoc_model.Lang.FunctorParameter.Named ({ FunctorParameter. id; expr = arg }), env in let res = read_module_type env (Identifier.Mk.result parent) res in Functor( f_parameter, res) | Mty_alias p -> let t_original_path = Env.Path.read_module env.ident_env p in let t_desc = ModPath t_original_path in TypeOf { t_desc; t_expansion = None; t_original_path } | Mty_strengthen (mty, p, a) -> let mty = read_module_type env parent mty in let s_path = Env.Path.read_module env.ident_env p in let s_aliasable = match a with | Aliasable -> true | Not_aliasable -> false in match Odoc_model.Lang.umty_of_mty mty with | Some s_expr -> Strengthen {s_expr; s_path; s_aliasable; s_expansion = None} | None -> failwith "invalid Mty_strengthen" and read_module_type_declaration env parent id (mtd : Odoc_model.Compat.modtype_declaration) = let open ModuleType in let id = Env.find_module_type env.ident_env id in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc, canonical = Doc_attr.attached ~warnings_tag:env.warnings_tag Odoc_model.Semantics.Expect_canonical container mtd.mtd_attributes in let canonical = match canonical with | None -> None | Some s -> Doc_attr.conv_canonical_module_type s in let expr = opt_map (read_module_type env (id :> Identifier.Signature.t)) mtd.mtd_type in (* Source location is not trustworthy since it's a cmi so left as None *) let source_loc_jane = None in {id; source_loc; doc; canonical; expr ; source_loc_jane} and read_module_declaration env parent ident (md : Odoc_model.Compat.module_declaration) = let open Module in let id = (Env.find_module_identifier env.ident_env ident :> Identifier.Module.t) in let source_loc = None in let container = (parent : Identifier.Signature.t :> Identifier.LabelParent.t) in let doc, canonical = Doc_attr.attached ~warnings_tag:env.warnings_tag Odoc_model.Semantics.Expect_canonical container md.md_attributes in let canonical = match canonical with | None -> None | Some s -> Some (Doc_attr.conv_canonical_module s) in let type_ = match md.md_type with | Mty_alias p -> Alias (Env.Path.read_module env.ident_env p, None) | _ -> ModuleType (read_module_type env (id :> Identifier.Signature.t) md.md_type) in let hidden = match canonical with | Some _ -> false | None -> Odoc_model.Names.contains_double_underscore (Ident.name ident) in (* Source location is not trustworthy since it's a cmi so left as None *) let source_loc_jane = None in {id; source_loc; doc; type_; canonical; hidden ; source_loc_jane} and read_type_rec_status rec_status = let open Signature in match rec_status with | Trec_first -> Ordinary | Trec_next -> And | Trec_not -> Nonrec and read_module_rec_status rec_status = let open Signature in match rec_status with | Trec_not -> Ordinary | Trec_first -> Rec | Trec_next -> And and read_signature_noenv env parent (items : Odoc_model.Compat.signature) = let rec loop (acc,shadowed) items = let open Signature in let open Odoc_model.Compat in let open Include in match items with | Sig_value(id, v, _) :: rest -> let vd = read_value_description env parent id v in let shadowed = if Env.is_shadowed env.ident_env id then let identifier = Env.find_value_identifier env.ident_env id in match identifier.iv with | `Value (_, n) -> { shadowed with s_values = (Odoc_model.Names.parenthesise (Ident.name id), n) :: shadowed.s_values } else shadowed in loop (vd :: acc, shadowed) rest | Sig_type(id, _, _, _) :: rest when Btype.is_row_name (Ident.name id) -> loop (acc, shadowed) rest | Sig_type(id, decl, rec_status, _)::rest -> let decl = read_type_declaration env parent id decl in let shadowed = if Env.is_shadowed env.ident_env id then let identifier = Env.find_type_identifier env.ident_env id in let `Type (_, name) = identifier.iv in { shadowed with s_types = (Ident.name id, name) :: shadowed.s_types } else shadowed in loop (Type (read_type_rec_status rec_status, decl)::acc, shadowed) rest | Sig_typext (id, ext, Text_first, _) :: rest -> let rec inner_loop inner_acc = function | Sig_typext(id, ext, Text_next, _) :: rest -> inner_loop ((id, ext) :: inner_acc) rest | rest -> let ext = read_type_extension env parent id ext (List.rev inner_acc) in loop (TypExt ext :: acc, shadowed) rest in inner_loop [] rest | Sig_typext (id, ext, Text_next, _) :: rest -> let ext = read_type_extension env parent id ext [] in loop (TypExt ext :: acc, shadowed) rest | Sig_typext (id, ext, Text_exception, _) :: rest -> let exn = read_exception env parent id ext in loop (Exception exn :: acc, shadowed) rest | Sig_module (id, _, md, rec_status, _)::rest -> let md = read_module_declaration env parent id md in let shadowed = if Env.is_shadowed env.ident_env id then let identifier = Env.find_module_identifier env.ident_env id in let name = match identifier.iv with | `Module (_, n) -> n | `Parameter (_, n) -> n | `Root (_, n) -> n in { shadowed with s_modules = (Ident.name id, name) :: shadowed.s_modules } else shadowed in loop (Module (read_module_rec_status rec_status, md)::acc, shadowed) rest | Sig_modtype(id, mtd, _) :: rest -> let mtd = read_module_type_declaration env parent id mtd in let shadowed = if Env.is_shadowed env.ident_env id then let identifier = Env.find_module_type env.ident_env id in let name = match identifier.iv with | `ModuleType (_, n) -> n in { shadowed with s_module_types = (Ident.name id, name) :: shadowed.s_module_types } else shadowed in loop (ModuleType mtd :: acc, shadowed) rest #if OCAML_VERSION < (5,1,0) | Sig_class(id, cl, rec_status, _) :: Sig_class_type _ :: Sig_type _ :: Sig_type _ :: rest -> #else | Sig_class(id, cl, rec_status, _) :: Sig_class_type _ :: Sig_type _ :: rest -> #endif let cl = read_class_declaration env parent id cl in let shadowed = if Env.is_shadowed env.ident_env id then let identifier = Env.find_class_identifier env.ident_env id in let name = match identifier.iv with | `Class (_, n) -> n in { shadowed with s_classes = (Ident.name id, name) :: shadowed.s_classes } else shadowed in loop (Class (read_type_rec_status rec_status, cl)::acc, shadowed) rest #if OCAML_VERSION < (5,1,0) | Sig_class_type(id, cltyp, rec_status, _)::Sig_type _::Sig_type _::rest -> #else | Sig_class_type(id, cltyp, rec_status, _)::Sig_type _::rest -> #endif let cltyp = read_class_type_declaration env parent id cltyp in let shadowed = if Env.is_shadowed env.ident_env id then let identifier = Env.find_class_type_identifier env.ident_env id in let name = match identifier.iv with | `ClassType (_, n) -> n in { shadowed with s_class_types = (Ident.name id, name) :: shadowed.s_class_types } else shadowed in loop (ClassType (read_type_rec_status rec_status, cltyp)::acc, shadowed) rest (* Skip all of the hidden sig items *) (* Bad - we expect Sig_class and Sig_class_type to be matched above with subsequent Sig_type items *) | Sig_class_type _ :: _ | Sig_class _ :: _ -> assert false | [] -> ({items = List.rev acc; compiled=false; removed = []; doc = empty_doc env }, shadowed) in loop ([],{s_modules=[]; s_module_types=[]; s_values=[];s_types=[]; s_classes=[]; s_class_types=[]}) items and read_signature env parent (items : Odoc_model.Compat.signature) = let e' = Env.handle_signature_type_items parent items env.ident_env in let env = { env with ident_env = e' } in fst @@ read_signature_noenv env parent items let read_interface root name ~warnings_tag intf = let id = Identifier.Mk.root (root, Odoc_model.Names.ModuleName.make_std name) in let items = read_signature { ident_env = Env.empty (); warnings_tag } id intf in (id, items)