diff --git a/src/rustdoc/reexport_pass.rs b/src/rustdoc/reexport_pass.rs new file mode 100644 index 00000000000..21074cc46f8 --- /dev/null +++ b/src/rustdoc/reexport_pass.rs @@ -0,0 +1,299 @@ +#[doc = "Finds docs for reexported items and duplicates them"]; + +import std::map; +import rustc::syntax::ast; +import rustc::syntax::ast_util; +import rustc::util::common; + +export mk_pass; + +fn mk_pass() -> pass { + run +} + +type def_set = map::set; +type def_map = map::hashmap; +type path_map = map::hashmap; + +fn run(srv: astsrv::srv, doc: doc::cratedoc) -> doc::cratedoc { + + // First gather the set of defs that are used as reexports + let def_set = build_reexport_def_set(srv); + + // Now find the docs that go with those defs + let def_map = build_reexport_def_map(srv, doc, def_set); + + // Now create a map that tells us where to insert the duplicated + // docs into the existing doc tree + let path_map = build_reexport_path_map(srv, def_map); + + // Finally update the doc tree + merge_reexports(doc, path_map) +} + +fn build_reexport_def_set(srv: astsrv::srv) -> def_set { + astsrv::exec(srv) {|ctxt| + let def_set = common::new_def_hash(); + ctxt.exp_map.items {|_path, defs| + for def in *defs { + let def_id = ast_util::def_id_of_def(def); + def_set.insert(def_id, ()); + } + } + def_set + } +} + +fn build_reexport_def_map( + srv: astsrv::srv, + doc: doc::cratedoc, + def_set: def_set +) -> def_map { + + type ctxt = { + srv: astsrv::srv, + def_set: def_set, + def_map: def_map + }; + + let ctxt = { + srv: srv, + def_set: def_set, + def_map: common::new_def_hash() + }; + + let fold = fold::fold({ + fold_mod: fold_mod + with *fold::default_seq_fold(ctxt) + }); + + fold.fold_crate(fold, doc); + + ret ctxt.def_map; + + fn fold_mod(fold: fold::fold, doc: doc::moddoc) -> doc::moddoc { + let doc = fold::default_seq_fold_mod(fold, doc); + + for item in *doc.items { + let def_id = ast_util::local_def(item.id()); + if fold.ctxt.def_set.contains_key(def_id) { + fold.ctxt.def_map.insert(def_id, item); + } + } + + ret doc; + } +} + +fn to_assoc_list( + map: map::hashmap +) -> [(ast::def_id, V)] { + + let vec = []; + map.items {|k, v| + vec += [(k, v)]; + } + ret vec; +} + +fn from_assoc_list( + list: [(ast::def_id, V)] +) -> map::hashmap { + + let map = common::new_def_hash(); + vec::iter(list) {|elt| + let (k, v) = elt; + map.insert(k, v); + } + ret map; +} + +fn build_reexport_path_map(srv: astsrv::srv, -def_map: def_map) -> path_map { + + // This is real unfortunate. Lots of copying going on here + let def_assoc_list = to_assoc_list(def_map); + #debug("def_map: %?", def_assoc_list); + + astsrv::exec(srv) {|ctxt| + + let def_map = from_assoc_list(def_assoc_list); + let path_map = map::new_str_hash(); + + ctxt.exp_map.items {|path, defs| + + let path = str::split_str(path, "::"); + let modpath = str::connect(vec::init(path), "::"); + let name = option::get(vec::last(path)); + + let reexportdocs = []; + + for def in *defs { + let def_id = ast_util::def_id_of_def(def); + alt def_map.find(def_id) { + some(itemtag) { + reexportdocs += [(name, itemtag)]; + } + none { } + } + } + + if vec::is_not_empty(reexportdocs) { + let prevdocs = alt path_map.find(modpath) { + some(docs) { docs } + none { [] } + }; + let reexportdocs = prevdocs + reexportdocs; + path_map.insert(modpath, reexportdocs); + #debug("path_map entry: %? - %?", + modpath, (name, reexportdocs)); + } + } + + path_map + } +} + +fn merge_reexports( + doc: doc::cratedoc, + path_map: path_map +) -> doc::cratedoc { + + let fold = fold::fold({ + fold_mod: fold_mod + with *fold::default_seq_fold(path_map) + }); + + ret fold.fold_crate(fold, doc); + + fn fold_mod(fold: fold::fold, doc: doc::moddoc) -> doc::moddoc { + let doc = fold::default_seq_fold_mod(fold, doc); + + let path = doc.path() + [doc.name()]; + let new_items = get_new_items(path, fold.ctxt); + #debug("merging into %?: %?", path, new_items); + + { + items: ~(*doc.items + new_items) + with doc + } + } + + fn get_new_items(path: [str], path_map: path_map) -> [doc::itemtag] { + #debug("looking for reexports in path %?", path); + alt path_map.find(str::connect(path, "::")) { + some(name_docs) { + vec::foldl([], name_docs) {|v, name_doc| + let (name, doc) = name_doc; + v + [rename_doc(doc, name)] + } + } + none { [] } + } + } + + fn rename_doc(doc: doc::itemtag, name: str) -> doc::itemtag { + alt doc { + doc::modtag(doc @ {item, _}) { + doc::modtag({ + item: rename(item, name) + with doc + }) + } + doc::consttag(doc @ {item, _}) { + doc::consttag({ + item: rename(item, name) + with doc + }) + } + doc::fntag(doc @ {item, _}) { + doc::fntag({ + item: rename(item, name) + with doc + }) + } + doc::enumtag(doc @ {item, _}) { + doc::enumtag({ + item: rename(item, name) + with doc + }) + } + doc::restag(doc @ {item, _}) { + doc::restag({ + item: rename(item, name) + with doc + }) + } + doc::ifacetag(doc @ {item, _}) { + doc::ifacetag({ + item: rename(item, name) + with doc + }) + } + doc::impltag(doc @ {item, _}) { + doc::impltag({ + item: rename(item, name) + with doc + }) + } + doc::tytag(doc @ {item, _}) { + doc::tytag({ + item: rename(item, name) + with doc + }) + } + } + } + + fn rename(doc: doc::itemdoc, name: str) -> doc::itemdoc { + { + name: name + with doc + } + } +} + +#[test] +fn should_duplicate_reexported_items() { + let source = "mod a { export b; fn b() { } } \ + mod c { import a::b; export b; }"; + let doc = test::mk_doc(source); + assert doc.topmod.mods()[1].fns()[0].name() == "b"; +} + +#[test] +fn should_duplicate_multiple_reexported_items() { + let source = "mod a { \ + export b; export c; \ + fn b() { } fn c() { } \ + } \ + mod d { \ + import a::b; import a::c; \ + export b; export c; \ + }"; + let srv = astsrv::mk_srv_from_str(source); + let doc = extract::from_srv(srv, ""); + let doc = path_pass::mk_pass()(srv, doc); + let doc = run(srv, doc); + // Reexports may not be in any specific order + let doc = sort_item_name_pass::mk_pass()(srv, doc); + assert doc.topmod.mods()[1].fns()[0].name() == "b"; + assert doc.topmod.mods()[1].fns()[1].name() == "c"; +} + +#[test] +fn should_rename_items_reexported_with_different_names() { + let source = "mod a { export b; fn b() { } } \ + mod c { import x = a::b; export x; }"; + let doc = test::mk_doc(source); + assert doc.topmod.mods()[1].fns()[0].name() == "x"; +} + +#[cfg(test)] +mod test { + fn mk_doc(source: str) -> doc::cratedoc { + let srv = astsrv::mk_srv_from_str(source); + let doc = extract::from_srv(srv, ""); + let doc = path_pass::mk_pass()(srv, doc); + run(srv, doc) + } +} diff --git a/src/rustdoc/rustdoc.rc b/src/rustdoc/rustdoc.rc index 036a2190657..0e1e5e68452 100644 --- a/src/rustdoc/rustdoc.rc +++ b/src/rustdoc/rustdoc.rc @@ -32,4 +32,5 @@ mod astsrv; mod demo; mod sort_pass; mod sort_item_name_pass; -mod sort_item_type_pass; \ No newline at end of file +mod sort_item_type_pass; +mod reexport_pass; diff --git a/src/rustdoc/rustdoc.rs b/src/rustdoc/rustdoc.rs index 9aa9bb24e04..f9a63b87389 100755 --- a/src/rustdoc/rustdoc.rs +++ b/src/rustdoc/rustdoc.rs @@ -106,6 +106,7 @@ fn run(source_file: str) { desc_to_brief_pass::mk_pass(), trim_pass::mk_pass(), unindent_pass::mk_pass(), + reexport_pass::mk_pass(), sort_item_name_pass::mk_pass(), sort_item_type_pass::mk_pass(), markdown_pass::mk_pass {|f| f(std::io:: stdout()) }