|
| 1 | +// Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT |
| 2 | +// file at the top-level directory of this distribution and at |
| 3 | +// http://rust-lang.org/COPYRIGHT. |
| 4 | +// |
| 5 | +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 7 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 8 | +// option. This file may not be copied, modified, or distributed |
| 9 | +// except according to those terms. |
| 10 | + |
| 11 | +//! Support for inlining external documentation into the current AST. |
| 12 | +
|
| 13 | +use syntax::ast; |
| 14 | +use syntax::ast_util; |
| 15 | +use syntax::attr::AttrMetaMethods; |
| 16 | + |
| 17 | +use rustc::metadata::csearch; |
| 18 | +use rustc::metadata::decoder; |
| 19 | +use rustc::middle::ty; |
| 20 | + |
| 21 | +use core; |
| 22 | +use doctree; |
| 23 | +use clean; |
| 24 | + |
| 25 | +use super::Clean; |
| 26 | + |
| 27 | +/// Attempt to inline the definition of a local node id into this AST. |
| 28 | +/// |
| 29 | +/// This function will fetch the definition of the id specified, and if it is |
| 30 | +/// from another crate it will attempt to inline the documentation from the |
| 31 | +/// other crate into this crate. |
| 32 | +/// |
| 33 | +/// This is primarily used for `pub use` statements which are, in general, |
| 34 | +/// implementation details. Inlining the documentation should help provide a |
| 35 | +/// better experience when reading the documentation in this use case. |
| 36 | +/// |
| 37 | +/// The returned value is `None` if the `id` could not be inlined, and `Some` |
| 38 | +/// of a vector of items if it was successfully expanded. |
| 39 | +pub fn try_inline(id: ast::NodeId) -> Option<Vec<clean::Item>> { |
| 40 | + let cx = ::ctxtkey.get().unwrap(); |
| 41 | + let tcx = match cx.maybe_typed { |
| 42 | + core::Typed(ref tycx) => tycx, |
| 43 | + core::NotTyped(_) => return None, |
| 44 | + }; |
| 45 | + let def = match tcx.def_map.borrow().find(&id) { |
| 46 | + Some(def) => *def, |
| 47 | + None => return None, |
| 48 | + }; |
| 49 | + let did = ast_util::def_id_of_def(def); |
| 50 | + if ast_util::is_local(did) { return None } |
| 51 | + try_inline_def(&**cx, tcx, def) |
| 52 | +} |
| 53 | + |
| 54 | +fn try_inline_def(cx: &core::DocContext, |
| 55 | + tcx: &ty::ctxt, |
| 56 | + def: ast::Def) -> Option<Vec<clean::Item>> { |
| 57 | + let mut ret = Vec::new(); |
| 58 | + let did = ast_util::def_id_of_def(def); |
| 59 | + let inner = match def { |
| 60 | + ast::DefTrait(did) => { |
| 61 | + record_extern_fqn(cx, did, clean::TypeTrait); |
| 62 | + clean::TraitItem(build_external_trait(tcx, did)) |
| 63 | + } |
| 64 | + ast::DefFn(did, style) => { |
| 65 | + record_extern_fqn(cx, did, clean::TypeFunction); |
| 66 | + clean::FunctionItem(build_external_function(tcx, did, style)) |
| 67 | + } |
| 68 | + ast::DefStruct(did) => { |
| 69 | + record_extern_fqn(cx, did, clean::TypeStruct); |
| 70 | + ret.extend(build_impls(tcx, did).move_iter()); |
| 71 | + clean::StructItem(build_struct(tcx, did)) |
| 72 | + } |
| 73 | + ast::DefTy(did) => { |
| 74 | + record_extern_fqn(cx, did, clean::TypeEnum); |
| 75 | + ret.extend(build_impls(tcx, did).move_iter()); |
| 76 | + build_type(tcx, did) |
| 77 | + } |
| 78 | + // Assume that the enum type is reexported next to the variant, and |
| 79 | + // variants don't show up in documentation specially. |
| 80 | + ast::DefVariant(..) => return Some(Vec::new()), |
| 81 | + ast::DefMod(did) => { |
| 82 | + record_extern_fqn(cx, did, clean::TypeModule); |
| 83 | + clean::ModuleItem(build_module(cx, tcx, did)) |
| 84 | + } |
| 85 | + _ => return None, |
| 86 | + }; |
| 87 | + let fqn = csearch::get_item_path(tcx, did); |
| 88 | + ret.push(clean::Item { |
| 89 | + source: clean::Span::empty(), |
| 90 | + name: Some(fqn.last().unwrap().to_str().to_strbuf()), |
| 91 | + attrs: load_attrs(tcx, did), |
| 92 | + inner: inner, |
| 93 | + visibility: Some(ast::Public), |
| 94 | + def_id: did, |
| 95 | + }); |
| 96 | + Some(ret) |
| 97 | +} |
| 98 | + |
| 99 | +pub fn load_attrs(tcx: &ty::ctxt, did: ast::DefId) -> Vec<clean::Attribute> { |
| 100 | + let mut attrs = Vec::new(); |
| 101 | + csearch::get_item_attrs(&tcx.sess.cstore, did, |v| { |
| 102 | + attrs.extend(v.move_iter().map(|mut a| { |
| 103 | + // FIXME this isn't quite always true, it's just true about 99% of |
| 104 | + // the time when dealing with documentation. For example, |
| 105 | + // this would treat doc comments of the form `#[doc = "foo"]` |
| 106 | + // incorrectly. |
| 107 | + if a.name().get() == "doc" && a.value_str().is_some() { |
| 108 | + a.node.is_sugared_doc = true; |
| 109 | + } |
| 110 | + a.clean() |
| 111 | + })); |
| 112 | + }); |
| 113 | + attrs |
| 114 | +} |
| 115 | + |
| 116 | +/// Record an external fully qualified name in the external_paths cache. |
| 117 | +/// |
| 118 | +/// These names are used later on by HTML rendering to generate things like |
| 119 | +/// source links back to the original item. |
| 120 | +pub fn record_extern_fqn(cx: &core::DocContext, |
| 121 | + did: ast::DefId, |
| 122 | + kind: clean::TypeKind) { |
| 123 | + match cx.maybe_typed { |
| 124 | + core::Typed(ref tcx) => { |
| 125 | + let fqn = csearch::get_item_path(tcx, did); |
| 126 | + let fqn = fqn.move_iter().map(|i| i.to_str().to_strbuf()).collect(); |
| 127 | + cx.external_paths.borrow_mut().get_mut_ref().insert(did, (fqn, kind)); |
| 128 | + } |
| 129 | + core::NotTyped(..) => {} |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +pub fn build_external_trait(tcx: &ty::ctxt, did: ast::DefId) -> clean::Trait { |
| 134 | + let def = ty::lookup_trait_def(tcx, did); |
| 135 | + let methods = ty::trait_methods(tcx, did); |
| 136 | + clean::Trait { |
| 137 | + generics: def.generics.clean(), |
| 138 | + methods: methods.iter().map(|i| i.clean()).collect(), |
| 139 | + parents: Vec::new(), // FIXME: this is likely wrong |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +fn build_external_function(tcx: &ty::ctxt, |
| 144 | + did: ast::DefId, |
| 145 | + style: ast::FnStyle) -> clean::Function { |
| 146 | + let t = ty::lookup_item_type(tcx, did); |
| 147 | + clean::Function { |
| 148 | + decl: match ty::get(t.ty).sty { |
| 149 | + ty::ty_bare_fn(ref f) => (did, &f.sig).clean(), |
| 150 | + _ => fail!("bad function"), |
| 151 | + }, |
| 152 | + generics: t.generics.clean(), |
| 153 | + fn_style: style, |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +fn build_struct(tcx: &ty::ctxt, did: ast::DefId) -> clean::Struct { |
| 158 | + use syntax::parse::token::special_idents::unnamed_field; |
| 159 | + |
| 160 | + let t = ty::lookup_item_type(tcx, did); |
| 161 | + let fields = ty::lookup_struct_fields(tcx, did); |
| 162 | + |
| 163 | + clean::Struct { |
| 164 | + struct_type: match fields.as_slice() { |
| 165 | + [] => doctree::Unit, |
| 166 | + [ref f] if f.name == unnamed_field.name => doctree::Newtype, |
| 167 | + [ref f, ..] if f.name == unnamed_field.name => doctree::Tuple, |
| 168 | + _ => doctree::Plain, |
| 169 | + }, |
| 170 | + generics: t.generics.clean(), |
| 171 | + fields: fields.iter().map(|f| f.clean()).collect(), |
| 172 | + fields_stripped: false, |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +fn build_type(tcx: &ty::ctxt, did: ast::DefId) -> clean::ItemEnum { |
| 177 | + let t = ty::lookup_item_type(tcx, did); |
| 178 | + match ty::get(t.ty).sty { |
| 179 | + ty::ty_enum(edid, _) => { |
| 180 | + return clean::EnumItem(clean::Enum { |
| 181 | + generics: t.generics.clean(), |
| 182 | + variants_stripped: false, |
| 183 | + variants: ty::enum_variants(tcx, edid).clean(), |
| 184 | + }) |
| 185 | + } |
| 186 | + _ => {} |
| 187 | + } |
| 188 | + |
| 189 | + clean::TypedefItem(clean::Typedef { |
| 190 | + type_: t.ty.clean(), |
| 191 | + generics: t.generics.clean(), |
| 192 | + }) |
| 193 | +} |
| 194 | + |
| 195 | +fn build_impls(tcx: &ty::ctxt, |
| 196 | + did: ast::DefId) -> Vec<clean::Item> { |
| 197 | + ty::populate_implementations_for_type_if_necessary(tcx, did); |
| 198 | + let mut impls = Vec::new(); |
| 199 | + |
| 200 | + match tcx.inherent_impls.borrow().find(&did) { |
| 201 | + None => {} |
| 202 | + Some(i) => { |
| 203 | + impls.extend(i.borrow().iter().map(|&did| { build_impl(tcx, did) })); |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + impls |
| 208 | +} |
| 209 | + |
| 210 | +fn build_impl(tcx: &ty::ctxt, did: ast::DefId) -> clean::Item { |
| 211 | + let associated_trait = csearch::get_impl_trait(tcx, did); |
| 212 | + let attrs = load_attrs(tcx, did); |
| 213 | + let ty = ty::lookup_item_type(tcx, did); |
| 214 | + let methods = csearch::get_impl_methods(&tcx.sess.cstore, did).iter().map(|did| { |
| 215 | + let mut item = match ty::method(tcx, *did).clean() { |
| 216 | + clean::Provided(item) => item, |
| 217 | + clean::Required(item) => item, |
| 218 | + }; |
| 219 | + item.inner = match item.inner.clone() { |
| 220 | + clean::TyMethodItem(clean::TyMethod { |
| 221 | + fn_style, decl, self_, generics |
| 222 | + }) => { |
| 223 | + clean::MethodItem(clean::Method { |
| 224 | + fn_style: fn_style, |
| 225 | + decl: decl, |
| 226 | + self_: self_, |
| 227 | + generics: generics, |
| 228 | + }) |
| 229 | + } |
| 230 | + _ => fail!("not a tymethod"), |
| 231 | + }; |
| 232 | + item |
| 233 | + }).collect(); |
| 234 | + clean::Item { |
| 235 | + inner: clean::ImplItem(clean::Impl { |
| 236 | + derived: clean::detect_derived(attrs.as_slice()), |
| 237 | + trait_: associated_trait.clean().map(|bound| { |
| 238 | + match bound { |
| 239 | + clean::TraitBound(ty) => ty, |
| 240 | + clean::RegionBound => unreachable!(), |
| 241 | + } |
| 242 | + }), |
| 243 | + for_: ty.ty.clean(), |
| 244 | + generics: ty.generics.clean(), |
| 245 | + methods: methods, |
| 246 | + }), |
| 247 | + source: clean::Span::empty(), |
| 248 | + name: None, |
| 249 | + attrs: attrs, |
| 250 | + visibility: Some(ast::Inherited), |
| 251 | + def_id: did, |
| 252 | + } |
| 253 | +} |
| 254 | + |
| 255 | +fn build_module(cx: &core::DocContext, tcx: &ty::ctxt, |
| 256 | + did: ast::DefId) -> clean::Module { |
| 257 | + let mut items = Vec::new(); |
| 258 | + |
| 259 | + // FIXME: this doesn't handle reexports inside the module itself. |
| 260 | + // Should they be handled? |
| 261 | + csearch::each_child_of_item(&tcx.sess.cstore, did, |def, _, _| { |
| 262 | + match def { |
| 263 | + decoder::DlDef(def) => { |
| 264 | + match try_inline_def(cx, tcx, def) { |
| 265 | + Some(i) => items.extend(i.move_iter()), |
| 266 | + None => {} |
| 267 | + } |
| 268 | + } |
| 269 | + decoder::DlImpl(did) => items.push(build_impl(tcx, did)), |
| 270 | + decoder::DlField => fail!("unimplemented field"), |
| 271 | + } |
| 272 | + }); |
| 273 | + |
| 274 | + clean::Module { |
| 275 | + items: items, |
| 276 | + is_crate: false, |
| 277 | + } |
| 278 | +} |
0 commit comments