diff --git a/bin/ocaml_merlin.ml b/bin/ocaml_merlin.ml index 8e424e93ca97..7296ddeac53b 100644 --- a/bin/ocaml_merlin.ml +++ b/bin/ocaml_merlin.ml @@ -78,14 +78,13 @@ end = struct let load_merlin_file file = (* We search for an appropriate merlin configuration in the current directory and its parents *) - let filename = String.lowercase_ascii (Path.Build.basename file) in let rec find_closest path = match get_merlin_files_paths path |> List.find_map ~f:(fun file_path -> match Merlin.Processed.load_file file_path with | Error msg -> Some (Merlin_conf.make_error msg) - | Ok config -> Merlin.Processed.get config ~filename) + | Ok config -> Merlin.Processed.get config ~file) with | Some p -> Some p | None -> ( diff --git a/bin/top.ml b/bin/top.ml index db24d96216ac..056466a2d4c4 100644 --- a/bin/top.ml +++ b/bin/top.ml @@ -189,9 +189,7 @@ module Module = struct let+ (pp, ppx), files_to_load = Memo.fork_and_join pps files_to_load in let code = let modules = Dune_rules.Compilation_context.modules cctx in - let opens_ = - Dune_rules.Module_compilation.open_modules modules module_ - in + let opens_ = Dune_rules.Modules.local_open modules module_ in List.map opens_ ~f:(fun name -> sprintf "open %s" (Dune_rules.Module_name.to_string name)) in diff --git a/src/dune_rules/dune_file.ml b/src/dune_rules/dune_file.ml index bc838e3fdba9..bd61b556cb17 100644 --- a/src/dune_rules/dune_file.ml +++ b/src/dune_rules/dune_file.ml @@ -2143,11 +2143,16 @@ module Include_subdirs = struct | Include of qualification let decode ~enable_qualified = - let opts_list = - [ ("no", No); ("unqualified", Include Unqualified) ] - @ if enable_qualified then [ ("qualified", Include Qualified) ] else [] - in - enum opts_list + sum + [ ("no", return No) + ; ("unqualified", return (Include Unqualified)) + ; ( "qualified" + , let+ () = + if enable_qualified then return () + else Syntax.since Stanza.syntax (3, 7) + in + Include Qualified ) + ] end module Library_redirect = struct diff --git a/src/dune_rules/link_time_code_gen.ml b/src/dune_rules/link_time_code_gen.ml index 2cffe9a089bb..d15858df28ae 100644 --- a/src/dune_rules/link_time_code_gen.ml +++ b/src/dune_rules/link_time_code_gen.ml @@ -28,7 +28,7 @@ let generate_and_compile_module cctx ~precompiled_cmi ~name ~lib ~code ~requires let main_module_name = Option.value_exn main_module_name in (* XXX this is fishy. We shouldn't be introducing a toplevel module into a wrapped library with a single module *) - Module.with_wrapper gen_module ~main_module_name + Module.with_wrapper gen_module ~main_module_name ~path:[] in let open Memo.O in let* () = diff --git a/src/dune_rules/merlin.ml b/src/dune_rules/merlin.ml index c9f3f7858e15..8e3879becaae 100644 --- a/src/dune_rules/merlin.ml +++ b/src/dune_rules/merlin.ml @@ -1,5 +1,15 @@ open Import +let remove_extension file = + let dir = Path.Build.parent_exn file in + let basename = + match Path.Build.basename file |> Filename.chop_extension with + | s -> s + | exception Code_error.E _ -> + Code_error.raise "opens" [ ("file", Path.Build.to_dyn file) ] + in + Path.Build.relative dir basename + module Processed = struct (* The actual content of the merlin file as built by the [Unprocessed.process] function from the unprocessed info gathered through [gen_rules]. The first @@ -40,6 +50,7 @@ module Processed = struct { config : config ; modules : Module_name.t list ; pp_config : pp_flag option Module_name.Per_item.t + ; per_module_opens : Module_name.t list Path.Build.Map.t } module D = struct @@ -47,7 +58,7 @@ module Processed = struct let name = "merlin-conf" - let version = 3 + let version = 4 let to_dyn _ = Dyn.String "Use [dune ocaml dump-dot-merlin] instead" end @@ -68,7 +79,7 @@ module Processed = struct let serialize_path = Path.to_absolute_filename - let to_sexp ~pp { stdlib_dir; obj_dirs; src_dirs; flags; extensions } = + let to_sexp ~opens ~pp { stdlib_dir; obj_dirs; src_dirs; flags; extensions } = let make_directive tag value = Sexp.List [ Atom tag; value ] in let make_directive_of_path tag path = make_directive tag (Sexp.Atom (serialize_path path)) @@ -94,6 +105,16 @@ module Processed = struct (Sexp.List (List.map ~f:(fun s -> Sexp.Atom s) flags)) ] in + let flags = + match opens with + | [] -> flags + | flags -> + [ make_directive "FLG" + (Sexp.List + (List.concat_map flags ~f:(fun name -> + [ Sexp.Atom "-open"; Atom (Module_name.to_string name) ]))) + ] + in match pp with | None -> flags | Some { flag; args } -> @@ -147,29 +168,36 @@ module Processed = struct print "\n"); Buffer.contents b - let get { modules; pp_config; config } ~filename = + let opens per_module_opens file = + let file = remove_extension file in + Path.Build.Map.find per_module_opens file + + let get { per_module_opens; modules; pp_config; config } ~file = (* We only match the first part of the filename : foo.ml -> foo foo.cppo.ml -> foo *) let fname = + let filename = Path.Build.basename file in String.lsplit2 filename ~on:'.' |> Option.map ~f:fst |> Option.value ~default:filename |> String.lowercase in + let opens = opens per_module_opens file in List.find_opt modules ~f:(fun name -> let fname' = Module_name.to_string name |> String.lowercase in String.equal fname fname') |> Option.map ~f:(fun name -> let pp = Module_name.Per_item.get pp_config name in - to_sexp ~pp config) + let opens = Option.value_exn opens in + to_sexp ~opens ~pp config) let print_file path = match load_file path with | Error msg -> Printf.eprintf "%s\n" msg - | Ok { modules; pp_config; config } -> + | Ok { per_module_opens = _; modules; pp_config; config } -> let pp_one module_ = let pp = Module_name.Per_item.get pp_config module_ in - let sexp = to_sexp ~pp config in + let sexp = to_sexp ~opens:[] ~pp config in let open Pp.O in Pp.vbox (Pp.text (Module_name.to_string module_)) ++ Pp.newline @@ -196,6 +224,7 @@ module Processed = struct (acc_pp, acc_obj, acc_src, acc_flags, acc_ext) { modules = _ ; pp_config + ; per_module_opens = _ ; config = { stdlib_dir = _; obj_dirs; src_dirs; flags; extensions } } @@ -264,16 +293,7 @@ module Unprocessed = struct Path.Set.singleton @@ obj_dir_of_lib `Private mode (Obj_dir.of_local obj_dir) in - let flags = - Ocaml_flags.common - @@ - match Modules.alias_module modules with - | None -> flags - | Some m -> - Ocaml_flags.prepend_common - [ "-open"; Module_name.to_string (Module.name m) ] - flags - in + let flags = Ocaml_flags.common flags in let extensions = Dialect.DB.extensions_for_merlin dialects in let config = { stdlib_dir @@ -420,12 +440,22 @@ module Unprocessed = struct in { Processed.stdlib_dir; src_dirs; obj_dirs; flags; extensions } and+ pp_config = pp_config t sctx ~expander in + let per_module_opens = + Modules.fold_no_vlib modules ~init:Path.Build.Map.empty ~f:(fun m init -> + Module.sources m + |> List.fold_left ~init ~f:(fun acc file -> + let file = Path.as_in_build_dir_exn file |> remove_extension in + let opens = + Modules.alias_for modules m |> List.map ~f:Module.name + in + Path.Build.Map.set acc file opens)) + in let modules = (* And copy for each module the resulting pp flags *) Modules.fold_no_vlib modules ~init:[] ~f:(fun m acc -> Module.name m :: acc) in - { Processed.modules; pp_config; config } + { Processed.modules; pp_config; config; per_module_opens } end let dot_merlin sctx ~dir ~more_src_dirs ~expander (t : Unprocessed.t) = diff --git a/src/dune_rules/merlin.mli b/src/dune_rules/merlin.mli index 16c1857580f2..eba7b85311f3 100644 --- a/src/dune_rules/merlin.mli +++ b/src/dune_rules/merlin.mli @@ -37,7 +37,7 @@ module Processed : sig print the resulting configuration in dot-merlin syntax. *) val print_generic_dot_merlin : Path.t list -> unit - val get : t -> filename:string -> Sexp.t option + val get : t -> file:Path.Build.t -> Sexp.t option end val make : diff --git a/src/dune_rules/ml_sources.ml b/src/dune_rules/ml_sources.ml index 237d2c51783f..5651c68f921b 100644 --- a/src/dune_rules/ml_sources.ml +++ b/src/dune_rules/ml_sources.ml @@ -361,7 +361,9 @@ let modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules = let project = Scope.project scope in if Dune_project.wrapped_executables project then Modules_group.make_wrapped ~src_dir:dir ~modules `Exe - else Modules_group.exe_unwrapped modules + else + let modules = Module_trie.to_map modules in + Modules_group.exe_unwrapped modules in let obj_dir = Dune_file.Executables.obj_dir ~dir exes in let modules = @@ -398,30 +400,34 @@ let modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules = | _ -> Memo.return `Skip) >>| filter_partition_map -let check_no_qualified (loc, include_subdirs) = - if include_subdirs = Dune_file.Include_subdirs.Include Qualified then - User_error.raise ~loc - [ Pp.text "(include_subdirs qualified) is not supported yet" ] - -let make dune_file ~dir ~scope ~lib_config ~loc ~lookup_vlib ~include_subdirs +let make dune_file ~dir ~scope ~lib_config ~loc ~lookup_vlib + ~include_subdirs:(_loc, (include_subdirs : Dune_file.Include_subdirs.t)) ~dirs = let+ modules_of_stanzas = - check_no_qualified include_subdirs; let modules = let dialects = Dune_project.dialects (Scope.project scope) in - List.fold_left dirs ~init:Module_name.Map.empty - ~f:(fun acc ((dir : Path.Build.t), _local, files) -> - let modules = modules_of_files ~dialects ~dir ~files in - Module_name.Map.union acc modules ~f:(fun name x y -> - User_error.raise ~loc - [ Pp.textf "Module %S appears in several directories:" - (Module_name.to_string name) - ; Pp.textf "- %s" - (Path.to_string_maybe_quoted (Module.Source.src_dir x)) - ; Pp.textf "- %s" - (Path.to_string_maybe_quoted (Module.Source.src_dir y)) - ; Pp.text "This is not allowed, please rename one of them." - ])) + match include_subdirs with + | Include Qualified -> + List.fold_left dirs ~init:Module_trie.empty + ~f:(fun acc ((dir : Path.Build.t), local, files) -> + let modules = modules_of_files ~dialects ~dir ~files in + let path = List.map local ~f:Module_name.of_string in + Module_trie.set_map acc path modules) + | No | Include Unqualified -> + List.fold_left dirs ~init:Module_name.Map.empty + ~f:(fun acc ((dir : Path.Build.t), _local, files) -> + let modules = modules_of_files ~dialects ~dir ~files in + Module_name.Map.union acc modules ~f:(fun name x y -> + User_error.raise ~loc + [ Pp.textf "Module %S appears in several directories:" + (Module_name.to_string name) + ; Pp.textf "- %s" + (Path.to_string_maybe_quoted (Module.Source.src_dir x)) + ; Pp.textf "- %s" + (Path.to_string_maybe_quoted (Module.Source.src_dir y)) + ; Pp.text "This is not allowed, please rename one of them." + ])) + |> Module_trie.of_map in modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules in diff --git a/src/dune_rules/ml_sources.mli b/src/dune_rules/ml_sources.mli index 865882754adc..d7b649dcf022 100644 --- a/src/dune_rules/ml_sources.mli +++ b/src/dune_rules/ml_sources.mli @@ -60,5 +60,5 @@ val make : -> loc:Loc.t -> lookup_vlib:(loc:Loc.t -> dir:Path.Build.t -> t Memo.t) -> include_subdirs:Loc.t * Dune_file.Include_subdirs.t - -> dirs:(Path.Build.t * 'a list * String.Set.t) list + -> dirs:(Path.Build.t * string list * String.Set.t) list -> t Memo.t diff --git a/src/dune_rules/module.ml b/src/dune_rules/module.ml index cda8f6f95dcb..ba8ab7a806c1 100644 --- a/src/dune_rules/module.ml +++ b/src/dune_rules/module.ml @@ -116,10 +116,13 @@ type t = ; pp : (string list Action_builder.t * Sandbox_config.t) option ; visibility : Visibility.t ; kind : Kind.t + ; path : Module_name.Path.t } let name t = t.source.name +let path t = t.path + let kind t = t.kind let pp_flags t = t.pp @@ -158,7 +161,7 @@ let of_source ?obj_name ~visibility ~(kind : Kind.t) (source : Source.t) = Module_name.Unique.of_path_assuming_needs_no_mangling_allow_invalid file.path in - { source; obj_name; pp = None; visibility; kind } + { source; obj_name; pp = None; visibility; kind; path = [] } let has t ~ml_kind = match (ml_kind : Ml_kind.t) with @@ -175,8 +178,13 @@ let iter t ~f = Memo.parallel_iter Ml_kind.all ~f:(fun kind -> Memo.Option.iter (Ml_kind.Dict.get t.source.files kind) ~f:(f kind)) -let with_wrapper t ~main_module_name = - { t with obj_name = Module_name.wrap t.source.name ~with_:main_module_name } +let with_wrapper t ~main_module_name ~path = + let with_ = main_module_name :: path in + { t with obj_name = Module_name.wrap t.source.name ~with_ } + +let set_path t ~main_module_name ~path = + assert (Module_name.equal (Option.value_exn (List.last path)) (name t)); + { t with obj_name = Module_name.Path.wrap (main_module_name :: path); path } let add_file t kind file = let source = Source.add_file t.source kind file in @@ -196,13 +204,14 @@ let src_dir t = Source.src_dir t.source let set_pp t pp = { t with pp } -let to_dyn { source; obj_name; pp; visibility; kind } = +let to_dyn { source; obj_name; pp; visibility; kind; path } = Dyn.record [ ("source", Source.to_dyn source) ; ("obj_name", Module_name.Unique.to_dyn obj_name) ; ("pp", Dyn.(option string) (Option.map ~f:(fun _ -> "has pp") pp)) ; ("visibility", Visibility.to_dyn visibility) ; ("kind", Kind.to_dyn kind) + ; ("path", Module_name.Path.to_dyn path) ] let ml_gen = ".ml-gen" @@ -248,8 +257,8 @@ end module Obj_map_traversals = Memo.Make_map_traversals (Obj_map) let encode - ({ source = { name; files = _ }; obj_name; pp = _; visibility; kind } as t) - = + ({ path; source = { name; files = _ }; obj_name; pp = _; visibility; kind } + as t) = let open Dune_lang.Encoder in let has_impl = has t ~ml_kind:Impl in let kind = @@ -262,6 +271,7 @@ let encode record_fields [ field "name" Module_name.encode name ; field "obj_name" Module_name.Unique.encode obj_name + ; field "path" Module_name.Path.encode path ; field "visibility" Visibility.encode visibility ; field_o "kind" Kind.encode kind ; field_b "impl" has_impl @@ -277,6 +287,7 @@ let decode ~src_dir = fields (let+ name = field "name" Module_name.decode and+ obj_name = field "obj_name" Module_name.Unique.decode + and+ path = field "path" Module_name.Path.decode and+ visibility = field "visibility" Visibility.decode and+ kind = field_o "kind" Kind.decode and+ impl = field_b "impl" @@ -296,7 +307,8 @@ let decode ~src_dir = let intf = file intf Intf in let impl = file impl Impl in let source = Source.make ?impl ?intf name in - of_source ~obj_name ~visibility ~kind source) + let t = of_source ~obj_name ~visibility ~kind source in + { t with path }) let pped = map_files ~f:(fun _kind (file : File.t) -> diff --git a/src/dune_rules/module.mli b/src/dune_rules/module.mli index 4fe921f0d2a0..955aec997b9b 100644 --- a/src/dune_rules/module.mli +++ b/src/dune_rules/module.mli @@ -51,6 +51,8 @@ val of_source : visibility:Visibility.t -> kind:Kind.t -> Source.t -> t val name : t -> Module_name.t +val path : t -> Module_name.Path.t + val source : t -> ml_kind:Ml_kind.t -> File.t option val pp_flags : t -> (string list Action_builder.t * Sandbox_config.t) option @@ -63,8 +65,13 @@ val iter : t -> f:(Ml_kind.t -> File.t -> unit Memo.t) -> unit Memo.t val has : t -> ml_kind:Ml_kind.t -> bool -(** Prefix the object name with the library name. *) -val with_wrapper : t -> main_module_name:Module_name.t -> t +(** Prefix the object name with the library name and the module path for + qualified includes. *) +val with_wrapper : + t -> main_module_name:Module_name.t -> path:Module_name.Path.t -> t + +val set_path : + t -> main_module_name:Module_name.t -> path:Module_name.Path.t -> t val add_file : t -> Ml_kind.t -> File.t -> t diff --git a/src/dune_rules/module_compilation.ml b/src/dune_rules/module_compilation.ml index 304b6a08f605..ee24c367eb2d 100644 --- a/src/dune_rules/module_compilation.ml +++ b/src/dune_rules/module_compilation.ml @@ -14,13 +14,8 @@ let force_read_cmi source_file = [ "-intf-suffix"; Path.extension source_file ] the mli is not present it is added as additional target to the .cmo generation *) -let open_modules modules m = - match Modules.alias_for modules m with - | None -> [] - | Some (m : Module.t) -> [ Module.name m ] - let opens modules m = - match open_modules modules m with + match Modules.local_open modules m with | [] -> Command.Args.empty | modules -> Command.Args.S @@ -402,7 +397,7 @@ module Alias_module = struct let of_modules project modules ~alias_module = let main_module = Modules.main_module_name modules |> Option.value_exn in let aliases = - Modules.for_alias modules + Modules.for_alias modules alias_module |> Module_name.Map.to_list_map ~f:(fun local_name m -> let obj_name = Module.obj_name m in { local_name; obj_name }) diff --git a/src/dune_rules/module_compilation.mli b/src/dune_rules/module_compilation.mli index 9f592d28b81a..5f412b52989b 100644 --- a/src/dune_rules/module_compilation.mli +++ b/src/dune_rules/module_compilation.mli @@ -10,8 +10,6 @@ val build_module : -> Module.t -> unit Memo.t -val open_modules : Modules.t -> Module.t -> Module_name.t list - val ocamlc_i : deps:Module.t list Action_builder.t Ml_kind.Dict.t -> Compilation_context.t diff --git a/src/dune_rules/module_name.ml b/src/dune_rules/module_name.ml index 2cf8deb799d5..adf39899c960 100644 --- a/src/dune_rules/module_name.ml +++ b/src/dune_rules/module_name.ml @@ -117,5 +117,30 @@ module Unique = struct module Set = Set end -let wrap t ~with_ = - sprintf "%s__%s" (Unique.of_name_assuming_needs_no_mangling with_) t +module Path = struct + module T = struct + type nonrec t = t list + + let to_dyn = Dyn.list to_dyn + + let compare = List.compare ~compare + + let to_string t = List.map ~f:to_string t |> String.concat ~sep:"." + end + + include T + + let uncapitalize s = to_string s |> String.uncapitalize + + module C = Comparable.Make (T) + module Set = C.Set + module Map = C.Map + + let wrap = String.concat ~sep:"__" + + let encode (t : t) = Dune_lang.List (List.map t ~f:encode) + + let decode = Dune_lang.Decoder.(repeat decode) +end + +let wrap t ~with_ = Path.wrap (t :: with_) diff --git a/src/dune_rules/module_name.mli b/src/dune_rules/module_name.mli index 1217f7aa23ef..44db0237c98f 100644 --- a/src/dune_rules/module_name.mli +++ b/src/dune_rules/module_name.mli @@ -66,7 +66,29 @@ module Unique : sig include Comparable_intf.S with type key := t end -val wrap : t -> with_:t -> Unique.t +module Path : sig + type nonrec t = t list + + val compare : t -> t -> Ordering.t + + val to_dyn : t -> Dyn.t + + val to_string : t -> string + + val uncapitalize : t -> string + + module Map : Stdune.Map.S with type key = t + + module Set : Stdune.Set.S with type elt = t and type 'a map = 'a Map.t + + val wrap : t -> Unique.t + + val encode : t -> Dune_lang.t + + val decode : t Dune_lang.Decoder.t +end + +val wrap : t -> with_:Path.t -> Unique.t include Comparable_intf.S with type key := t diff --git a/src/dune_rules/module_trie.ml b/src/dune_rules/module_trie.ml new file mode 100644 index 000000000000..4e3e73d00a23 --- /dev/null +++ b/src/dune_rules/module_trie.ml @@ -0,0 +1,193 @@ +open! Import +module Map = Module_name.Map + +type key = Map.key list + +type 'a t = 'a node Map.t + +and 'a node = + | Leaf of 'a + | Map of 'a t + +let empty = Map.empty + +let mapi = + let rec loop t f acc = + Map.mapi t ~f:(fun name node -> + let path = name :: acc in + match node with + | Leaf a -> Leaf (f (List.rev path) a) + | Map m -> Map (loop m f path)) + in + fun t ~f -> loop t f [] + +let map t ~f = mapi t ~f:(fun _key m -> f m) + +let of_map t : _ t = Map.map t ~f:(fun v -> Leaf v) + +let rec find t = function + | [] -> None + | p :: ps -> ( + match Map.find t p with + | None -> None + | Some (Leaf a) -> Option.some_if (List.is_empty ps) a + | Some (Map t) -> find t ps) + +let rec gen_set t ps v = + match ps with + | [] -> t + | p :: ps -> + Map.update t p ~f:(fun x -> + if List.is_empty ps then Some v + else + match x with + | None -> None + | Some (Leaf _ as leaf) -> Some leaf + | Some (Map m) -> Some (Map (gen_set m ps v))) + +let set t k v = gen_set t k (Leaf v) + +let set_map t k v = gen_set t k (Map (of_map v)) + +let non_empty_map m = if Map.is_empty m then None else Some (Map m) + +let rec filter_map t ~f = + Map.filter_map t ~f:(function + | Map m -> non_empty_map (filter_map m ~f) + | Leaf a -> ( + match f a with + | None -> None + | Some a -> Some (Leaf a))) + +let rec remove t = function + | [] -> t + | p :: ps -> + Map.update t p ~f:(fun x -> + if List.is_empty ps then None + else + match x with + | None -> None + | Some (Leaf _ as leaf) -> Some leaf + | Some (Map m) -> non_empty_map (remove m ps)) + +let mem t p = Option.is_some (find t p) + +let foldi t ~init ~f = + let rec loop acc path t = + Map.foldi ~init:acc t ~f:(fun k v acc -> + match v with + | Leaf s -> f (List.rev (k :: path)) s acc + | Map t -> loop acc (k :: path) t) + in + loop init [] t + +let fold t ~init ~f = foldi t ~init ~f:(fun _key -> f) + +let to_list_map t ~f = foldi t ~init:[] ~f:(fun key x acc -> f key x :: acc) + +let is_empty t = + match fold t ~init:() ~f:(fun _ _ -> raise_notrace Exit) with + | () -> false + | exception Exit -> true + +let exists t ~f = + match + fold t ~init:() ~f:(fun v acc -> if f v then raise_notrace Exit else acc) + with + | () -> false + | exception Exit -> true + +let rec to_dyn f t = + Map.to_dyn + (function + | Leaf a -> f a + | Map a -> to_dyn f a) + t + +let values = fold ~init:[] ~f:(fun v acc -> v :: acc) + +let merge x y ~f = + let base _path _ = assert false in + let rec loop path x y = + match (x, y) with + | None, None -> assert false + | Some x, None -> base path x + | None, Some x -> base path x + | Some x, Some y -> + Map.merge x y ~f:(fun name x y -> + let path = name :: path in + let rev_path = List.rev path in + let leaf l r = + match f rev_path l r with + | None -> None + | Some x -> Some (Leaf x) + in + match (x, y) with + | None, None -> assert false + | None, Some (Leaf y) -> leaf None (Some y) + | None, Some (Map v) -> non_empty_map (base path v) + | Some (Map v), None -> non_empty_map (base path v) + | Some (Leaf x), None -> leaf (Some x) None + | Some (Map x), Some (Map y) -> + non_empty_map (loop path (Some x) (Some y)) + | Some (Leaf x), Some (Leaf y) -> leaf (Some x) (Some y) + | Some (Leaf _), Some (Map y) -> + non_empty_map (loop path None (Some y)) + | Some (Map x), Some (Leaf _) -> + non_empty_map (loop path (Some x) None)) + in + loop [] (Some x) (Some y) + +let singleton path v = set empty path v + +let as_singleton t = + match + fold t ~init:None ~f:(fun v acc -> + match acc with + | None -> Some v + | Some _ -> raise_notrace Exit) + with + | None | (exception Exit) -> None + | Some v -> Some v + +let rec encode t : Dune_lang.t list = + Module_name.Map.to_list_map t ~f:(fun _ t -> + Dune_lang.List + (match t with + | Leaf m -> Dune_lang.atom "module" :: Module.encode m + | Map m -> Dune_lang.atom "map" :: encode m)) + +let decode ~src_dir = + let open Dune_lang.Decoder in + let old = + (* TODO remove before committing *) + let+ map = Module.Name_map.decode ~src_dir in + of_map map + in + let rec t = + lazy + (let+ modules = either (repeat (sum node)) old in + match modules with + | Right map -> map + | Left modules -> Module_name.Map.of_list_exn modules) + and node = + [ ( "module" + , let+ m = Module.decode ~src_dir in + (Module.name m, Leaf m) ) + ; ( "map" + , let* p = Module_name.decode in + let+ m = Lazy.force t in + (p, Map m) ) + ] + in + Lazy.force t + +let to_map t = + Module_name.Map.map t ~f:(function + | Leaf v -> v + | Map _ -> assert false) + +let toplevel_only (t : _ t) = + Module_name.Map.filter_map t ~f:(function + | Leaf v -> Some v + | Map _ -> None) diff --git a/src/dune_rules/module_trie.mli b/src/dune_rules/module_trie.mli new file mode 100644 index 000000000000..df456cf301e9 --- /dev/null +++ b/src/dune_rules/module_trie.mli @@ -0,0 +1,58 @@ +open Import + +type 'a t = 'a node Module_name.Map.t + +and 'a node = + | Leaf of 'a + | Map of 'a t + +type key = Module_name.Path.t + +val empty : 'a t + +val map : 'a t -> f:('a -> 'b) -> 'b t + +val mapi : 'a t -> f:(key -> 'a -> 'b) -> 'b t + +val of_map : 'a Module_name.Map.t -> 'a t + +val find : 'a t -> key -> 'a option + +val set : 'a t -> key -> 'a -> 'a t + +val set_map : 'a t -> key -> 'a Module_name.Map.t -> 'a t + +val remove : 'a t -> key -> 'a t + +val mem : 'a t -> key -> bool + +val is_empty : _ t -> bool + +val fold : 'a t -> init:'acc -> f:('a -> 'acc -> 'acc) -> 'acc + +val to_list_map : 'a t -> f:(key -> 'a -> 'b) -> 'b list + +val foldi : 'a t -> init:'acc -> f:(key -> 'a -> 'acc -> 'acc) -> 'acc + +val to_dyn : ('a -> Dyn.t) -> 'a t -> Dyn.t + +val to_map : 'a t -> 'a Module_name.Map.t + +val values : 'a t -> 'a list + +val exists : 'a t -> f:('a -> bool) -> bool + +val singleton : key -> 'a -> 'a t + +val merge : + 'a t -> 'b t -> f:(key -> 'a option -> 'b option -> 'c option) -> 'c t + +val as_singleton : 'a t -> 'a option + +val filter_map : 'a t -> f:('a -> 'b option) -> 'b t + +val decode : src_dir:Path.t -> Module.t t Dune_lang.Decoder.t + +val encode : Module.t t -> Dune_lang.t list + +val toplevel_only : 'a t -> 'a Module_name.Map.t diff --git a/src/dune_rules/modules.ml b/src/dune_rules/modules.ml index 9901a226790a..baa6f78015dc 100644 --- a/src/dune_rules/modules.ml +++ b/src/dune_rules/modules.ml @@ -100,7 +100,7 @@ module Stdlib = struct if Module.name m = main_module_name || special_compiler_module stdlib m then m - else Module.with_wrapper m ~main_module_name) + else Module.with_wrapper m ~main_module_name ~path:[]) in let unwrapped = stdlib.modules_before_stdlib in let exit_module = stdlib.exit_module in @@ -154,7 +154,7 @@ module Mangle = struct let of_lib ~lib_name ~implements ~main_module_name ~modules = let kind : Lib.kind = if implements then Implementation lib_name - else if Module_name.Map.mem modules main_module_name then + else if Module_trie.mem modules [ main_module_name ] then Has_lib_interface else Neither in @@ -192,17 +192,19 @@ module Mangle = struct let wrap_modules t modules = let prefix = prefix t in let f = + (* TODO wrap all lib interfaces properly *) match t with - | Exe | Melange -> Module.with_wrapper ~main_module_name:prefix.public + | Exe | Melange -> + fun path m -> Module.set_path m ~main_module_name:prefix.public ~path | Lib { main_module_name; kind = _ } -> - fun m -> + fun path m -> if Module.name m = main_module_name then m else let visibility = Module.visibility m in let prefix = Visibility.Map.find prefix visibility in - Module.with_wrapper m ~main_module_name:prefix + Module.set_path m ~main_module_name:prefix ~path in - Module_name.Map.map modules ~f + Module_trie.mapi modules ~f end let impl_only_of_map m = @@ -210,151 +212,223 @@ let impl_only_of_map m = if Module.has m ~ml_kind:Impl then m :: acc else acc) module Wrapped = struct + module Group = struct + type t = + { alias : Module.t + ; modules : node Module_name.Map.t + ; (* TODO rename *) path : Module_name.t + } + + and node = + | Group of t + | Module of Module.t + + let of_trie (trie : Module.t Module_trie.t) ~mangle ~src_dir : t = + let rec loop path trie = + { alias = Mangle.make_alias_module mangle ~src_dir + ; path + ; modules = + Module_name.Map.mapi trie ~f:(fun path (m : 'a Module_trie.node) -> + match m with + | Leaf m -> Module m + | Map m -> Group (loop path m)) + } + in + loop (Mangle.prefix mangle).public trie + + let rec relocate_alias_module t ~src_dir = + { t with + alias = Module.set_src_dir t.alias ~src_dir + ; modules = + Module_name.Map.map t.modules ~f:(function + | Module m -> Module m + | Group g -> Group (relocate_alias_module g ~src_dir)) + } + + let rec fold { alias; modules; path = _ } ~f ~init = + let init = f alias init in + Module_name.Map.fold modules ~init ~f:(fun node init -> + match node with + | Module m -> f m init + | Group t -> fold t ~f ~init) + + let rec exists { alias; modules; path = _ } ~f = + f alias + || Module_name.Map.exists modules ~f:(function + | Module m -> f m + | Group p -> exists p ~f) + + let rec to_dyn { alias; modules; path } = + let open Dyn in + record + [ ("alias", Module.to_dyn alias) + ; ("path", Module_name.to_dyn path) + ; ( "modules" + , Module_name.Map.to_dyn + (function + | Module m -> variant "module" [ Module.to_dyn m ] + | Group g -> variant "group" [ to_dyn g ]) + modules ) + ] + + let rec map ({ alias; modules; path = _ } as t) ~f = + let alias = f alias in + let modules = + Module_name.Map.map modules ~f:(function + | Module m -> Module (f m) + | Group g -> Group (map g ~f)) + in + { t with alias; modules } + + let lib_interface t = + match Module_name.Map.find t.modules t.path with + | None | Some (Group _) -> t.alias + | Some (Module m) -> m + + let decode ~src_dir:_ = assert false + + let rec encode { alias; modules; path } = + let open Dune_lang.Encoder in + record_fields + [ field_l "alias" sexp (Module.encode alias) + ; field "path" Module_name.encode path + ; field_l "modules" + (fun x -> x) + (Module_name.Map.to_list_map modules ~f:(fun _ t -> + Dune_lang.List + (match t with + | Group g -> Dune_lang.atom "group" :: encode g + | Module m -> Dune_lang.atom "module" :: Module.encode m))) + ] + + let parents t m = + let rec loop acc t = function + | [] -> acc + | p :: ps -> ( + match Module_name.Map.find t.modules p with + | None -> assert false + | Some (Module _) -> + assert (ps = []); + acc + | Some (Group g) -> loop (g :: acc) g ps) + in + loop [ t ] t (List.tl (Module.path m)) + + module Memo_traversals = struct + let rec parallel_map ({ alias; modules; path = _ } as t) ~f = + let open Memo.O in + let+ alias, modules = + Memo.fork_and_join + (fun () -> f alias) + (fun () -> + Module_name.Map_traversals.parallel_map modules ~f:(fun _ n -> + match n with + | Module m -> + let+ m = f m in + Module m + | Group g -> + let+ g = parallel_map g ~f in + Group g)) + in + { t with alias; modules } + end + end + type t = - { modules : Module.Name_map.t + { group : Group.t ; wrapped_compat : Module.Name_map.t - ; alias_module : Module.t - ; main_module_name : Module_name.t ; wrapped : Mode.t } - let encode - { modules; wrapped_compat; alias_module; main_module_name; wrapped } = + let lib_interface t = Group.lib_interface t.group + + let for_alias t m = + assert (Module.kind m = Alias); + let neighbours = List.hd (Group.parents t.group m) in + Module_name.Map.remove neighbours.modules neighbours.path + |> Module_name.Map.map ~f:(fun (g : Group.node) -> + match g with + | Module m -> m + | Group g -> Group.lib_interface g) + + let encode { group; wrapped_compat; wrapped } = let open Dune_lang.Encoder in let module E = Common.Encode in record_fields - [ E.main_module_name main_module_name - ; E.modules modules - ; field_l "alias_module" sexp (Module.encode alias_module) - ; field "wrapped" Wrapped.encode wrapped + [ field_l "group" (fun x -> x) (Group.encode group) ; E.modules ~name:"wrapped_compat" wrapped_compat + ; field "wrapped" Wrapped.encode wrapped ] let decode ~src_dir = let open Dune_lang.Decoder in let open Common.Decode in fields - (let+ main_module_name = main_module_name - and+ modules = modules ~src_dir () + (let+ group = field "group" (Group.decode ~src_dir) and+ wrapped_compat = modules ~name:"wrapped_compat" ~src_dir () - and+ alias_module = field "alias_module" (Module.decode ~src_dir) and+ wrapped = field "wrapped" Mode.decode in - { main_module_name; modules; wrapped_compat; alias_module; wrapped }) - - let map - ({ modules - ; wrapped_compat - ; alias_module - ; main_module_name = _ - ; wrapped = _ - } as t) ~f = + { group; wrapped_compat; wrapped }) + + let map ({ group; wrapped_compat; wrapped = _ } as t) ~f = { t with - modules = Module_name.Map.map modules ~f + group = Group.map group ~f ; wrapped_compat = Module_name.Map.map wrapped_compat ~f - ; alias_module = f alias_module } let make ~src_dir ~lib_name ~implements ~modules ~main_module_name ~wrapped = let mangle = Mangle.of_lib ~main_module_name ~lib_name ~implements ~modules in - let modules, wrapped_compat = - let wrapped_modules = Mangle.wrap_modules mangle modules in + let modules = Mangle.wrap_modules mangle modules in + let wrapped_compat = match (wrapped : Mode.t) with | Simple false -> assert false - | Simple true -> (wrapped_modules, Module_name.Map.empty) + | Simple true -> Module_name.Map.empty | Yes_with_transition _ -> - ( wrapped_modules - , Module_name.Map.remove modules main_module_name - |> Module_name.Map.filter_map ~f:(fun m -> - match Module.visibility m with - | Public -> Some (Module.wrapped_compat m) - | Private -> None) ) + let toplevel = Module_trie.toplevel_only modules in + Module_name.Map.remove toplevel main_module_name + |> Module_name.Map.filter_map ~f:(fun m -> + match Module.visibility m with + | Private -> None + | Public -> Some (Module.wrapped_compat m)) in - let alias_module = Mangle.make_alias_module ~src_dir mangle in - { modules; alias_module; wrapped_compat; main_module_name; wrapped } + let group = Group.of_trie modules ~mangle ~src_dir in + { group; wrapped_compat; wrapped } let make_exe_or_melange ~src_dir ~modules mangle = - let alias_module = Mangle.make_alias_module mangle ~src_dir in let modules = Mangle.wrap_modules mangle modules in - { modules - ; wrapped_compat = Module_name.Map.empty - ; alias_module - (* XXX exe's don't have a main module, but this is harmless *) - ; main_module_name = Module.name alias_module - ; wrapped = Simple true - } + let group = Group.of_trie modules ~mangle ~src_dir in + { group; wrapped_compat = Module_name.Map.empty; wrapped = Simple true } - let obj_map - { modules - ; wrapped_compat - ; alias_module - ; main_module_name = _ - ; wrapped = _ - } ~f = - let init = Module.Obj_map.singleton alias_module (f alias_module) in - let acc = - Module_name.Map.fold ~f:(fun m acc -> Module.Obj_map.add_exn acc m (f m)) - in - acc modules ~init:(acc wrapped_compat ~init) + let obj_map { group; wrapped_compat; wrapped = _ } ~f = + let add_module m acc = Module.Obj_map.add_exn acc m (f m) in + let init = Group.fold group ~init:Module.Obj_map.empty ~f:add_module in + Module_name.Map.fold ~init wrapped_compat ~f:add_module - let to_dyn - { modules; wrapped_compat; alias_module; main_module_name; wrapped } = + let to_dyn { group; wrapped_compat; wrapped } = let open Dyn in record - [ ("modules", Module.Name_map.to_dyn modules) - ; ("wrapped_compat", Module.Name_map.to_dyn wrapped_compat) - ; ("alias_module", Module.to_dyn alias_module) - ; ("main_module_name", Module_name.to_dyn main_module_name) - ; ("wrapped", Wrapped.to_dyn wrapped) + [ ("group", Group.to_dyn group) + ; ("wrapped_compat", Module_name.Map.to_dyn Module.to_dyn wrapped_compat) + ; ("wrapped", Mode.to_dyn wrapped) ] - let is_alias_name t name = Module.name t.alias_module = name + let impl_only { group; wrapped_compat; wrapped = _ } = + let init = Module_name.Map.values wrapped_compat in + Group.fold group ~init ~f:(fun v acc -> + if Module.has v ~ml_kind:Impl then v :: acc else acc) - let impl_only - { modules - ; wrapped_compat - ; alias_module - ; main_module_name = _ - ; wrapped = _ - } = - let modules = - impl_only_of_map modules @ Module_name.Map.values wrapped_compat - in - alias_module :: modules - - let fold - { modules - ; wrapped_compat - ; alias_module - ; main_module_name = _ - ; wrapped = _ - } ~init ~f = - let init = f alias_module init in - let init = Module_name.Map.fold modules ~f ~init in + let fold { group; wrapped_compat; wrapped = _ } ~init ~f = + let init = Group.fold group ~f ~init in Module_name.Map.fold wrapped_compat ~f ~init - let exists - { modules - ; wrapped_compat - ; alias_module - ; main_module_name = _ - ; wrapped = _ - } ~f = - f alias_module - || Module_name.Map.exists modules ~f - || Module_name.Map.exists wrapped_compat ~f - - let lib_interface t = - Module_name.Map.find t.modules t.main_module_name - |> Option.value ~default:t.alias_module + let exists { group; wrapped_compat; wrapped = _ } ~f = + Group.exists group ~f || Module_name.Map.exists wrapped_compat ~f let find t name = - if is_alias_name t name then Some t.alias_module - else - match Module_name.Map.find t.modules name with - | Some _ as m -> m - | None -> Module_name.Map.find t.wrapped_compat name + match Module_name.Map.find t.group.modules name with + | Some (Module m) -> Some m + | Some (Group _) | None -> None let find_dep t ~of_ name = match Module.kind of_ with @@ -363,17 +437,32 @@ module Wrapped = struct let li = lib_interface t in Option.some_if (name = Module.name li) li | _ -> - if is_alias_name t name then Some t.alias_module - else Module_name.Map.find t.modules name + (* TODO don't recompute this *) + let parents = + match Group.parents t.group of_ with + | [] -> assert false + | top :: rest as parents -> + if + Module_name.Unique.equal + (Module.obj_name @@ Group.lib_interface top) + (Module.obj_name of_) + then rest + else parents + in + List.find_map parents ~f:(fun parent -> + match Module_name.Map.find parent.modules name with + | None -> None + | Some (Module m) -> Some m + | Some (Group g) -> Some (Group.lib_interface g)) let alias_for t m = match Module.kind m with - | Alias | Wrapped_compat -> None - | _ -> Some t.alias_module + | Alias | Wrapped_compat -> [] + | _ -> Group.parents t.group m |> List.map ~f:(fun (s : Group.t) -> s.alias) let relocate_alias_module t ~src_dir = - let alias_module = Module.set_src_dir t.alias_module ~src_dir in - { t with alias_module } + let group = Group.relocate_alias_module t.group ~src_dir in + { t with group } end type t = @@ -399,10 +488,6 @@ let rec encode t = | Stdlib m -> List (atom "stdlib" :: Stdlib.encode m) | Impl { impl; _ } -> encode impl -let as_singleton m = - if Module_name.Map.cardinal m <> 1 then None - else Module_name.Map.choose m |> Option.map ~f:snd - let singleton m = Singleton m let decode ~src_dir = @@ -445,7 +530,7 @@ let rec lib_interface = function let rec main_module_name = function | Singleton m -> Some (Module.name m) | Unwrapped _ -> None - | Wrapped w -> Some w.main_module_name + | Wrapped w -> Some w.group.path | Stdlib w -> Some w.main_module_name | Impl { vlib; impl = _ } -> main_module_name vlib @@ -459,11 +544,14 @@ let lib ~src_dir ~main_module_name ~wrapped ~stdlib ~lib_name ~implements match stdlib with | Some stdlib -> let main_module_name = Option.value_exn main_module_name in + let modules = Module_trie.to_map modules in Stdlib (Stdlib.make ~stdlib ~modules ~main_module_name) | None -> ( - match (wrapped, main_module_name, as_singleton modules) with + match (wrapped, main_module_name, Module_trie.as_singleton modules) with | Simple false, _, Some m -> Singleton m - | Simple false, _, None -> Unwrapped modules + | Simple false, _, None -> + (* TODO allow unwrapped modules to use [(include_subdirs qualified)] *) + Unwrapped (Module_trie.to_map modules) | (Yes_with_transition _ | Simple true), Some main_module_name, Some m -> if Module.name m = main_module_name && not implements then Singleton m else make_wrapped main_module_name @@ -520,7 +608,7 @@ let rec find_dep t ~of_ name = let make_singleton m mangle = Singleton (let main_module_name = (Mangle.prefix mangle).public in - Module.with_wrapper m ~main_module_name) + Module.with_wrapper m ~main_module_name ~path:[]) let singleton_exe m = make_singleton m Exe @@ -532,7 +620,7 @@ let make_wrapped ~src_dir ~modules kind = | `Exe -> Exe | `Melange -> Melange in - match as_singleton modules with + match Module_trie.as_singleton modules with | Some m -> make_singleton m mangle | None -> Wrapped (Wrapped.make_exe_or_melange ~src_dir ~modules mangle) @@ -589,22 +677,20 @@ let split_by_lib t = let compat_for_exn t m = match t with | Singleton _ | Stdlib _ | Unwrapped _ -> assert false - | Wrapped { modules; _ } -> - Module_name.Map.find modules (Module.name m) |> Option.value_exn | Impl _ -> Code_error.raise "wrapped compat not supported for vlib" [] + | Wrapped { group; _ } -> ( + match Module_name.Map.find group.modules (Module.name m) with + | None -> assert false + | Some (Module m) -> m + | Some (Group g) -> Wrapped.Group.lib_interface g) -let rec for_alias = function +let rec for_alias t m = + match t with | Stdlib _ | Singleton _ | Unwrapped _ -> Module_name.Map.empty - | Wrapped - { modules - ; main_module_name - ; alias_module = _ - ; wrapped_compat = _ - ; wrapped = _ - } -> Module_name.Map.remove modules main_module_name + | Wrapped w -> Wrapped.for_alias w m | Impl { vlib; impl } -> - let impl = for_alias impl in - let vlib = for_alias vlib in + let impl = for_alias impl m in + let vlib = for_alias vlib m in Module_name.Map.merge impl vlib ~f:(fun _ impl vlib -> match (impl, vlib) with | None, None -> assert false @@ -619,19 +705,15 @@ let rec fold_user_available t ~f ~init = match t with | Stdlib w -> Stdlib.fold w ~init ~f | Singleton m -> f m init - | Wrapped { modules; _ } | Unwrapped modules -> - Module_name.Map.fold modules ~init ~f + | Unwrapped modules -> Module_name.Map.fold modules ~init ~f + | Wrapped { group; _ } -> Wrapped.Group.fold group ~init ~f | Impl { impl; vlib = _ } -> (* XXX shouldn't we folding over [vlib] as well? *) fold_user_available impl ~f ~init let is_user_written m = match Module.kind m with - | Root -> false - | Wrapped_compat | Alias -> - (* Logically, this should be [acc]. But this is unreachable these are stored - separately *) - assert false + | Root | Wrapped_compat | Alias -> false | _ -> true let rec fold_user_written t ~f ~init = @@ -639,8 +721,8 @@ let rec fold_user_written t ~f ~init = match t with | Stdlib w -> Stdlib.fold w ~init ~f | Singleton m -> f m init - | Wrapped { modules; _ } | Unwrapped modules -> - Module_name.Map.fold modules ~init ~f + | Unwrapped modules -> Module_name.Map.fold modules ~init ~f + | Wrapped { group; _ } -> Wrapped.Group.fold group ~init ~f | Impl { impl; vlib = _ } -> fold_user_written impl ~f ~init let rec map_user_written t ~f = @@ -656,17 +738,9 @@ let rec map_user_written t ~f = | Stdlib w -> let+ res = Stdlib.traverse w ~f in Stdlib res - | Wrapped - ({ modules - ; alias_module = _ - ; main_module_name = _ - ; wrapped_compat = _ - ; wrapped = _ - } as w) -> - let+ modules = - Module_name.Map_traversals.parallel_map modules ~f:(fun _ -> f) - in - Wrapped { w with modules } + | Wrapped ({ group; wrapped_compat = _; wrapped = _ } as w) -> + let+ group = Wrapped.Group.Memo_traversals.parallel_map group ~f in + Wrapped { w with group } | Impl t -> let+ vlib = map_user_written t.vlib ~f in Impl { t with vlib } @@ -726,23 +800,19 @@ let entry_modules t = | Singleton m -> [ m ] | Unwrapped m -> Module_name.Map.values m | Wrapped m -> - (* we assume this is never called for implementations *) - [ Wrapped.lib_interface m ] + [ (* we assume this is never called for implementations *) + Wrapped.lib_interface m + ] | Impl i -> Code_error.raise "entry_modules: not defined for implementations" [ ("impl", dyn_of_impl i) ]) let virtual_module_names = - fold_no_vlib ~init:Module_name.Set.empty ~f:(fun m acc -> + fold_no_vlib ~init:Module_name.Path.Set.empty ~f:(fun m acc -> match Module.kind m with - | Virtual -> Module_name.Set.add acc (Module.name m) + | Virtual -> Module_name.Path.Set.add acc [ Module.name m ] | _ -> acc) -let rec alias_module = function - | Stdlib _ | Singleton _ | Unwrapped _ -> None - | Wrapped w -> Some w.alias_module - | Impl { impl; vlib = _ } -> alias_module impl - let rec wrapped = function | Wrapped w -> w.wrapped | Singleton _ | Unwrapped _ -> Simple false @@ -751,14 +821,16 @@ let rec wrapped = function let rec alias_for t m = match Module.kind m with - | Root -> None + | Root -> [] | _ -> ( match t with - | Singleton _ | Unwrapped _ -> None + | Singleton _ | Unwrapped _ -> [] | Wrapped w -> Wrapped.alias_for w m - | Stdlib w -> Stdlib.alias_for w m + | Stdlib w -> Stdlib.alias_for w m |> Option.to_list | Impl { impl; vlib = _ } -> alias_for impl m) +let local_open t m = alias_for t m |> List.map ~f:Module.name + let is_stdlib_alias t m = match t with | Stdlib w -> w.main_module_name = Module.name m diff --git a/src/dune_rules/modules.mli b/src/dune_rules/modules.mli index c658d2df32c5..11b36923f5e9 100644 --- a/src/dune_rules/modules.mli +++ b/src/dune_rules/modules.mli @@ -15,7 +15,7 @@ val lib : -> stdlib:Ocaml_stdlib.t option -> lib_name:Lib_name.Local.t -> implements:bool - -> modules:Module.Name_map.t + -> modules:Module.t Module_trie.t -> t val encode : t -> Dune_lang.t @@ -44,14 +44,17 @@ val fold_no_vlib : t -> init:'acc -> f:(Module.t -> 'acc -> 'acc) -> 'acc val exe_unwrapped : Module.Name_map.t -> t val make_wrapped : - src_dir:Path.Build.t -> modules:Module.Name_map.t -> [ `Exe | `Melange ] -> t + src_dir:Path.Build.t + -> modules:Module.t Module_trie.t + -> [ `Exe | `Melange ] + -> t (** For wrapped libraries, this is the user written entry module for the library. For single module libraries, it's the sole module in the library *) val lib_interface : t -> Module.t option (** Returns the modules that need to be aliased in the alias module *) -val for_alias : t -> Module.Name_map.t +val for_alias : t -> Module.t -> Module.t Module_name.Map.t val fold_user_written : t -> f:(Module.t -> 'acc -> 'acc) -> init:'acc -> 'acc @@ -86,17 +89,15 @@ val entry_modules : t -> Module.t list val main_module_name : t -> Module_name.t option (** Returns only the virtual module names in the library *) -val virtual_module_names : t -> Module_name.Set.t - -(** Returns the alias module if it exists. This module only exists for - [(wrapped true)] and when there is more than 1 module. *) -val alias_module : t -> Module.t option +val virtual_module_names : t -> Module_name.Path.Set.t val wrapped : t -> Wrapped.t val version_installed : t -> install_dir:Path.t -> t -val alias_for : t -> Module.t -> Module.t option +val alias_for : t -> Module.t -> Module.t list + +val local_open : t -> Module.t -> Module_name.t list val is_stdlib_alias : t -> Module.t -> bool diff --git a/src/dune_rules/modules_field_evaluator.ml b/src/dune_rules/modules_field_evaluator.ml index 65394aa65a1a..c4b67bf21781 100644 --- a/src/dune_rules/modules_field_evaluator.ml +++ b/src/dune_rules/modules_field_evaluator.ml @@ -7,7 +7,7 @@ end module Implementation = struct type t = - { existing_virtual_modules : Module_name.Set.t + { existing_virtual_modules : Module_name.Path.Set.t ; allow_new_public_modules : bool } end @@ -19,13 +19,20 @@ type kind = let eval = let key = function - | Error s -> s - | Ok m -> Module.Source.name m + | Error s -> [ s ] + | Ok m -> [ Module.Source.name m ] in - let module Unordered = Ordered_set_lang.Unordered (Module_name) in + let module Key = struct + type t = Module_name.Path.t + + let compare = Module_name.Path.compare + + module Map = Module_trie + end in + let module Unordered = Ordered_set_lang.Unordered (Key) in let parse ~all_modules ~fake_modules ~loc s = let name = Module_name.of_string_allow_invalid (loc, s) in - match Module_name.Map.find all_modules name with + match Module_trie.find all_modules [ name ] with | Some m -> Ok m | None -> fake_modules := Module_name.Map.set !fake_modules name loc; @@ -33,9 +40,9 @@ let eval = in fun ~loc ~fake_modules ~all_modules ~standard osl -> let parse = parse ~fake_modules ~all_modules in - let standard = Module_name.Map.map standard ~f:(fun m -> (loc, Ok m)) in + let standard = Module_trie.map standard ~f:(fun m -> (loc, Ok m)) in let modules = Unordered.eval_loc ~parse ~standard ~key osl in - Module_name.Map.filter_map modules ~f:(fun (loc, m) -> + Module_trie.filter_map modules ~f:(fun (loc, m) -> match m with | Ok m -> Some (loc, m) | Error s -> @@ -55,8 +62,8 @@ type single_module_error = | Vmodule_impls_with_own_intf type errors = - { errors : (single_module_error * Loc.t * Module_name.t) list - ; unimplemented_virt_modules : Module_name.Set.t + { errors : (single_module_error * Loc.t * Module_name.Path.t) list + ; unimplemented_virt_modules : Module_name.Path.Set.t } let find_errors ~modules ~intf_only ~virtual_modules ~private_modules @@ -65,21 +72,21 @@ let find_errors ~modules ~intf_only ~virtual_modules ~private_modules (* We expect that [modules] is big and all the other ones are small, that's why the code is implemented this way. *) List.fold_left [ intf_only; virtual_modules; private_modules ] - ~init:(Module_name.Map.map modules ~f:snd) ~f:(fun acc map -> - Module_name.Map.foldi map ~init:acc ~f:(fun name (_loc, m) acc -> - Module_name.Map.set acc name m)) + ~init:(Module_trie.map modules ~f:snd) ~f:(fun acc map -> + Module_trie.foldi map ~init:acc ~f:(fun name (_loc, m) acc -> + Module_trie.set acc name m)) in let errors = - Module_name.Map.foldi all ~init:[] ~f:(fun module_name module_ acc -> + Module_trie.foldi all ~init:[] ~f:(fun module_name module_ acc -> let has_impl = Module.Source.has module_ ~ml_kind:Impl in let has_intf = Module.Source.has module_ ~ml_kind:Intf in let impl_vmodule = - Module_name.Set.mem existing_virtual_modules module_name + Module_name.Path.Set.mem existing_virtual_modules module_name in - let modules = Module_name.Map.find modules module_name in - let private_ = Module_name.Map.find private_modules module_name in - let virtual_ = Module_name.Map.find virtual_modules module_name in - let intf_only = Module_name.Map.find intf_only module_name in + let modules = Module_trie.find modules module_name in + let private_ = Module_trie.find private_modules module_name in + let virtual_ = Module_trie.find virtual_modules module_name in + let intf_only = Module_trie.find intf_only module_name in let with_property prop f acc = match prop with | None -> acc @@ -112,8 +119,8 @@ let find_errors ~modules ~intf_only ~virtual_modules ~private_modules @@ acc) in let unimplemented_virt_modules = - Module_name.Set.filter existing_virtual_modules ~f:(fun module_name -> - match Module_name.Map.find all module_name with + Module_name.Path.Set.filter existing_virtual_modules ~f:(fun module_name -> + match Module_trie.find all module_name with | None -> true | Some m -> not (Module.Source.has m ~ml_kind:Impl)) in @@ -128,12 +135,12 @@ let check_invalid_module_listing ~stanza_loc ~modules_without_implementation in if List.is_non_empty errors - || not (Module_name.Set.is_empty unimplemented_virt_modules) + || not (Module_name.Path.Set.is_empty unimplemented_virt_modules) then ( let get kind = List.filter_map errors ~f:(fun (k, loc, m) -> Option.some_if (kind = k) (loc, m)) - |> List.sort ~compare:(fun (_, a) (_, b) -> Module_name.compare a b) + |> List.sort ~compare:(fun (_, a) (_, b) -> Module_name.Path.compare a b) in let vmodule_impls_with_own_intf = get Vmodule_impls_with_own_intf in let forbidden_new_public_modules = get Forbidden_new_public_module in @@ -148,11 +155,11 @@ let check_invalid_module_listing ~stanza_loc ~modules_without_implementation let spurious_modules_intf = get Spurious_module_intf in let spurious_modules_virtual = get Spurious_module_virtual in let uncapitalized = - List.map ~f:(fun (_, m) -> Module_name.uncapitalize m) + List.map ~f:(fun (_, m) -> Module_name.Path.uncapitalize m) in let line_list modules = Pp.enumerate modules ~f:(fun (_, m) -> - Pp.verbatim (Module_name.to_string m)) + Pp.verbatim (Module_name.Path.to_string m)) in let print before l after = match l with @@ -203,7 +210,7 @@ let check_invalid_module_listing ~stanza_loc ~modules_without_implementation [ Pp.text "This is not possible." ]; print [ Pp.text "These modules are declared virtual, but are missing." ] - (unimplemented_virt_modules |> Module_name.Set.to_list + (unimplemented_virt_modules |> Module_name.Path.Set.to_list |> List.map ~f:(fun name -> (stanza_loc, name))) [ Pp.text "You must provide an implementation for all of these modules." ]; (if missing_intf_only <> [] then @@ -241,16 +248,16 @@ let check_invalid_module_listing ~stanza_loc ~modules_without_implementation ] spurious_modules_virtual []) -let eval ~modules:all_modules ~stanza_loc ~modules_field - ~modules_without_implementation ~root_module ~private_modules ~kind ~src_dir - = +let eval ~modules:(all_modules : Module.Source.t Module_trie.t) ~stanza_loc + ~modules_field ~modules_without_implementation ~root_module ~private_modules + ~kind ~src_dir = (* Fake modules are modules that do not exist but it doesn't matter because they are only removed from a set (for jbuild file compatibility) *) let fake_modules = ref Module_name.Map.empty in let eval = eval ~loc:stanza_loc ~fake_modules ~all_modules in let modules = eval ~standard:all_modules modules_field in let intf_only = - eval ~standard:Module_name.Map.empty modules_without_implementation + eval ~standard:Module_trie.empty modules_without_implementation in let allow_new_public_modules = match kind with @@ -259,16 +266,16 @@ let eval ~modules:all_modules ~stanza_loc ~modules_field in let existing_virtual_modules = match kind with - | Exe_or_normal_lib | Virtual _ -> Module_name.Set.empty + | Exe_or_normal_lib | Virtual _ -> Module_name.Path.Set.empty | Implementation { existing_virtual_modules; _ } -> existing_virtual_modules in let virtual_modules = match kind with - | Exe_or_normal_lib | Implementation _ -> Module_name.Map.empty + | Exe_or_normal_lib | Implementation _ -> Module_trie.empty | Virtual { virtual_modules } -> - eval ~standard:Module_name.Map.empty virtual_modules + eval ~standard:Module_trie.empty virtual_modules in - let private_modules = eval ~standard:Module_name.Map.empty private_modules in + let private_modules = eval ~standard:Module_trie.empty private_modules in Module_name.Map.iteri !fake_modules ~f:(fun m loc -> User_error.raise ~loc [ Pp.textf "Module %s is excluded but it doesn't exist." @@ -278,17 +285,17 @@ let eval ~modules:all_modules ~stanza_loc ~modules_field ~intf_only ~modules ~virtual_modules ~private_modules ~existing_virtual_modules ~allow_new_public_modules; let all_modules = - Module_name.Map.map modules ~f:(fun (_, m) -> - let name = Module.Source.name m in + Module_trie.map modules ~f:(fun (_, m) -> + let name = [ Module.Source.name m ] in let visibility = - if Module_name.Map.mem private_modules name then Visibility.Private + if Module_trie.mem private_modules name then Visibility.Private else Public in let kind = - if Module_name.Map.mem virtual_modules name then Module.Kind.Virtual + if Module_trie.mem virtual_modules name then Module.Kind.Virtual else if Module.Source.has m ~ml_kind:Impl then let name = Module.Source.name m in - if Module_name.Set.mem existing_virtual_modules name then + if Module_name.Path.Set.mem existing_virtual_modules [ name ] then Impl_vmodule else Impl else Intf_only @@ -299,4 +306,4 @@ let eval ~modules:all_modules ~stanza_loc ~modules_field | None -> all_modules | Some (_, name) -> let module_ = Module.generated ~kind:Root ~src_dir name in - Module_name.Map.set all_modules name module_ + Module_trie.set all_modules [ name ] module_ diff --git a/src/dune_rules/modules_field_evaluator.mli b/src/dune_rules/modules_field_evaluator.mli index b6665a97aaa3..617b727a2c67 100644 --- a/src/dune_rules/modules_field_evaluator.mli +++ b/src/dune_rules/modules_field_evaluator.mli @@ -9,7 +9,7 @@ end module Implementation : sig type t = - { existing_virtual_modules : Module_name.Set.t + { existing_virtual_modules : Module_name.Path.Set.t ; allow_new_public_modules : bool } end @@ -20,7 +20,7 @@ type kind = | Exe_or_normal_lib val eval : - modules:Module.Source.t Module_name.Map.t + modules:Module.Source.t Module_trie.t -> stanza_loc:Loc.t -> modules_field:Ordered_set_lang.t -> modules_without_implementation:Ordered_set_lang.t @@ -28,4 +28,4 @@ val eval : -> private_modules:Ordered_set_lang.t -> kind:kind -> src_dir:Path.Build.t - -> Module.t Module_name.Map.t + -> Module.t Module_trie.t diff --git a/src/dune_rules/ocamldep.ml b/src/dune_rules/ocamldep.ml index c131a1d6cfbd..aa359ce4feee 100644 --- a/src/dune_rules/ocamldep.ml +++ b/src/dune_rules/ocamldep.ml @@ -66,8 +66,8 @@ let interpret_deps md ~unit deps = (Module_name.to_string main_module_name) ]); match Modules.alias_for modules unit with - | None -> deps - | Some m -> m :: deps + | [] -> deps + | m -> m @ deps let deps_of ({ sandbox; modules; sctx; dir; obj_dir; vimpl = _; stdlib = _ } as md) diff --git a/test/blackbox-tests/test-cases/include-qualified/basic.t/dune-project b/test/blackbox-tests/test-cases/include-qualified/basic.t/dune-project index 1863cf146487..3c48133ad585 100644 --- a/test/blackbox-tests/test-cases/include-qualified/basic.t/dune-project +++ b/test/blackbox-tests/test-cases/include-qualified/basic.t/dune-project @@ -1 +1 @@ -(lang dune 3.5) +(lang dune 3.7) diff --git a/test/blackbox-tests/test-cases/include-qualified/basic.t/run.t b/test/blackbox-tests/test-cases/include-qualified/basic.t/run.t index cfc667daf76e..6479cb0a6668 100644 --- a/test/blackbox-tests/test-cases/include-qualified/basic.t/run.t +++ b/test/blackbox-tests/test-cases/include-qualified/basic.t/run.t @@ -1,8 +1,19 @@ Basic test showcasing the feature. Every directory creates a new level of aliasing. $ dune build - File "lib/dune", line 1, characters 17-26: - 1 | (include_subdirs qualified) - ^^^^^^^^^ - Error: Unknown value qualified - Hint: did you mean unqualified? + File "lib/foolib.ml-gen", line 4, characters 10-11: + 4 | module Foo.Bar = Foolib__Bar + ^ + Error: Syntax error [1] + + $ cat _build/default/lib/foolib.ml-gen + (* generated by dune *) + + (** @canonical Foolib.Foo.Bar *) + module Foo.Bar = Foolib__Bar + + (** @canonical Foolib.Foo.A.B *) + module Foo.A.B = Foolib__B + + module Foolib = struct end + [@@deprecated "this module is shadowed"] diff --git a/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/dune-project b/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/dune-project index 1863cf146487..3c48133ad585 100644 --- a/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/dune-project +++ b/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/dune-project @@ -1 +1 @@ -(lang dune 3.5) +(lang dune 3.7) diff --git a/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/run.t b/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/run.t index bc6f4d790cad..b8435be0213d 100644 --- a/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/run.t +++ b/test/blackbox-tests/test-cases/include-qualified/nested-lib-interface.t/run.t @@ -1,8 +1,5 @@ We are also allowed to write lib interface files at each level. $ dune build - File "lib/dune", line 1, characters 17-26: - 1 | (include_subdirs qualified) - ^^^^^^^^^ - Error: Unknown value qualified - Hint: did you mean unqualified? + Error: No rule found for exe/test.exe + -> required by alias default in dune:1 [1] diff --git a/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/dune-project b/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/dune-project index 1863cf146487..3c48133ad585 100644 --- a/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/dune-project +++ b/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/dune-project @@ -1 +1 @@ -(lang dune 3.5) +(lang dune 3.7) diff --git a/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/run.t b/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/run.t index 8afc3a625d98..a86861acb9ec 100644 --- a/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/run.t +++ b/test/blackbox-tests/test-cases/include-qualified/nested-virtual.t/run.t @@ -1,13 +1,7 @@ We can nested modules virtual $ dune build @all - File "impl/dune", line 1, characters 17-26: - 1 | (include_subdirs qualified) - ^^^^^^^^^ - Error: Unknown value qualified - Hint: did you mean unqualified? - File "vlib/dune", line 1, characters 17-26: - 1 | (include_subdirs qualified) - ^^^^^^^^^ - Error: Unknown value qualified - Hint: did you mean unqualified? + File "vlib/dune", line 5, characters 18-26: + 5 | (virtual_modules bar/virt)) + ^^^^^^^^ + Error: Module Bar/virt doesn't exist. [1] diff --git a/test/blackbox-tests/test-cases/include-qualified/pp.t/dune-project b/test/blackbox-tests/test-cases/include-qualified/pp.t/dune-project index 1863cf146487..3c48133ad585 100644 --- a/test/blackbox-tests/test-cases/include-qualified/pp.t/dune-project +++ b/test/blackbox-tests/test-cases/include-qualified/pp.t/dune-project @@ -1 +1 @@ -(lang dune 3.5) +(lang dune 3.7) diff --git a/test/blackbox-tests/test-cases/include-qualified/pp.t/run.t b/test/blackbox-tests/test-cases/include-qualified/pp.t/run.t index dc4e4e2d1681..2c417822460d 100644 --- a/test/blackbox-tests/test-cases/include-qualified/pp.t/run.t +++ b/test/blackbox-tests/test-cases/include-qualified/pp.t/run.t @@ -1,8 +1,10 @@ We can set preprocessing options for nested modules $ dune build @all - File "dune", line 1, characters 17-26: - 1 | (include_subdirs qualified) - ^^^^^^^^^ - Error: Unknown value qualified - Hint: did you mean unqualified? + File "dune", line 8, characters 30-38: + 8 | (run cat %{input-file})) bar/ppme)))) + ^^^^^^^^ + Error: "bar/ppme" is an invalid module name. + Module names must be non-empty and composed only of the following characters: + 'A'..'Z', 'a'..'z', '_', ''' or '0'..'9'. + Hint: barppme would be a correct module name [1]