]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/clean/inline.rs
rollup merge of #16721 : tshepang/convenience
[rust.git] / src / librustdoc / clean / inline.rs
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::def;
20 use rustc::middle::ty;
21 use rustc::middle::subst;
22 use rustc::middle::stability;
23
24 use core;
25 use doctree;
26 use clean;
27
28 use super::Clean;
29
30 /// Attempt to inline the definition of a local node id into this AST.
31 ///
32 /// This function will fetch the definition of the id specified, and if it is
33 /// from another crate it will attempt to inline the documentation from the
34 /// other crate into this crate.
35 ///
36 /// This is primarily used for `pub use` statements which are, in general,
37 /// implementation details. Inlining the documentation should help provide a
38 /// better experience when reading the documentation in this use case.
39 ///
40 /// The returned value is `None` if the `id` could not be inlined, and `Some`
41 /// of a vector of items if it was successfully expanded.
42 pub fn try_inline(id: ast::NodeId, into: Option<ast::Ident>)
43                   -> Option<Vec<clean::Item>> {
44     let cx = ::ctxtkey.get().unwrap();
45     let tcx = match cx.maybe_typed {
46         core::Typed(ref tycx) => tycx,
47         core::NotTyped(_) => return None,
48     };
49     let def = match tcx.def_map.borrow().find(&id) {
50         Some(def) => *def,
51         None => return None,
52     };
53     let did = def.def_id();
54     if ast_util::is_local(did) { return None }
55     try_inline_def(&**cx, tcx, def).map(|vec| {
56         vec.move_iter().map(|mut item| {
57             match into {
58                 Some(into) if item.name.is_some() => {
59                     item.name = Some(into.clean());
60                 }
61                 _ => {}
62             }
63             item
64         }).collect()
65     })
66 }
67
68 fn try_inline_def(cx: &core::DocContext,
69                   tcx: &ty::ctxt,
70                   def: def::Def) -> Option<Vec<clean::Item>> {
71     let mut ret = Vec::new();
72     let did = def.def_id();
73     let inner = match def {
74         def::DefTrait(did) => {
75             record_extern_fqn(cx, did, clean::TypeTrait);
76             clean::TraitItem(build_external_trait(tcx, did))
77         }
78         def::DefFn(did, style) => {
79             // If this function is a tuple struct constructor, we just skip it
80             if csearch::get_tuple_struct_definition_if_ctor(&tcx.sess.cstore,
81                                                             did).is_some() {
82                 return None
83             }
84             record_extern_fqn(cx, did, clean::TypeFunction);
85             clean::FunctionItem(build_external_function(tcx, did, style))
86         }
87         def::DefStruct(did) => {
88             record_extern_fqn(cx, did, clean::TypeStruct);
89             ret.extend(build_impls(cx, tcx, did).move_iter());
90             clean::StructItem(build_struct(tcx, did))
91         }
92         def::DefTy(did) => {
93             record_extern_fqn(cx, did, clean::TypeEnum);
94             ret.extend(build_impls(cx, tcx, did).move_iter());
95             build_type(tcx, did)
96         }
97         // Assume that the enum type is reexported next to the variant, and
98         // variants don't show up in documentation specially.
99         def::DefVariant(..) => return Some(Vec::new()),
100         def::DefMod(did) => {
101             record_extern_fqn(cx, did, clean::TypeModule);
102             clean::ModuleItem(build_module(cx, tcx, did))
103         }
104         def::DefStatic(did, mtbl) => {
105             record_extern_fqn(cx, did, clean::TypeStatic);
106             clean::StaticItem(build_static(tcx, did, mtbl))
107         }
108         _ => return None,
109     };
110     let fqn = csearch::get_item_path(tcx, did);
111     cx.inlined.borrow_mut().as_mut().unwrap().insert(did);
112     ret.push(clean::Item {
113         source: clean::Span::empty(),
114         name: Some(fqn.last().unwrap().to_string()),
115         attrs: load_attrs(tcx, did),
116         inner: inner,
117         visibility: Some(ast::Public),
118         stability: stability::lookup(tcx, did).clean(),
119         def_id: did,
120     });
121     Some(ret)
122 }
123
124 pub fn load_attrs(tcx: &ty::ctxt, did: ast::DefId) -> Vec<clean::Attribute> {
125     let mut attrs = Vec::new();
126     csearch::get_item_attrs(&tcx.sess.cstore, did, |v| {
127         attrs.extend(v.move_iter().map(|a| {
128             a.clean()
129         }));
130     });
131     attrs
132 }
133
134 /// Record an external fully qualified name in the external_paths cache.
135 ///
136 /// These names are used later on by HTML rendering to generate things like
137 /// source links back to the original item.
138 pub fn record_extern_fqn(cx: &core::DocContext,
139                          did: ast::DefId,
140                          kind: clean::TypeKind) {
141     match cx.maybe_typed {
142         core::Typed(ref tcx) => {
143             let fqn = csearch::get_item_path(tcx, did);
144             let fqn = fqn.move_iter().map(|i| i.to_string()).collect();
145             cx.external_paths.borrow_mut().as_mut().unwrap().insert(did, (fqn, kind));
146         }
147         core::NotTyped(..) => {}
148     }
149 }
150
151 pub fn build_external_trait(tcx: &ty::ctxt, did: ast::DefId) -> clean::Trait {
152     let def = ty::lookup_trait_def(tcx, did);
153     let trait_items = ty::trait_items(tcx, did).clean();
154     let provided = ty::provided_trait_methods(tcx, did);
155     let mut items = trait_items.move_iter().map(|trait_item| {
156         if provided.iter().any(|a| a.def_id == trait_item.def_id) {
157             clean::ProvidedMethod(trait_item)
158         } else {
159             clean::RequiredMethod(trait_item)
160         }
161     });
162     let trait_def = ty::lookup_trait_def(tcx, did);
163     let bounds = trait_def.bounds.clean();
164     clean::Trait {
165         generics: (&def.generics, subst::TypeSpace).clean(),
166         items: items.collect(),
167         bounds: bounds,
168     }
169 }
170
171 fn build_external_function(tcx: &ty::ctxt,
172                            did: ast::DefId,
173                            style: ast::FnStyle) -> clean::Function {
174     let t = ty::lookup_item_type(tcx, did);
175     clean::Function {
176         decl: match ty::get(t.ty).sty {
177             ty::ty_bare_fn(ref f) => (did, &f.sig).clean(),
178             _ => fail!("bad function"),
179         },
180         generics: (&t.generics, subst::FnSpace).clean(),
181         fn_style: style,
182     }
183 }
184
185 fn build_struct(tcx: &ty::ctxt, did: ast::DefId) -> clean::Struct {
186     use syntax::parse::token::special_idents::unnamed_field;
187
188     let t = ty::lookup_item_type(tcx, did);
189     let fields = ty::lookup_struct_fields(tcx, did);
190
191     clean::Struct {
192         struct_type: match fields.as_slice() {
193             [] => doctree::Unit,
194             [ref f] if f.name == unnamed_field.name => doctree::Newtype,
195             [ref f, ..] if f.name == unnamed_field.name => doctree::Tuple,
196             _ => doctree::Plain,
197         },
198         generics: (&t.generics, subst::TypeSpace).clean(),
199         fields: fields.clean(),
200         fields_stripped: false,
201     }
202 }
203
204 fn build_type(tcx: &ty::ctxt, did: ast::DefId) -> clean::ItemEnum {
205     let t = ty::lookup_item_type(tcx, did);
206     match ty::get(t.ty).sty {
207         ty::ty_enum(edid, _) if !csearch::is_typedef(&tcx.sess.cstore, did) => {
208             return clean::EnumItem(clean::Enum {
209                 generics: (&t.generics, subst::TypeSpace).clean(),
210                 variants_stripped: false,
211                 variants: ty::enum_variants(tcx, edid).clean(),
212             })
213         }
214         _ => {}
215     }
216
217     clean::TypedefItem(clean::Typedef {
218         type_: t.ty.clean(),
219         generics: (&t.generics, subst::TypeSpace).clean(),
220     })
221 }
222
223 fn build_impls(cx: &core::DocContext,
224                tcx: &ty::ctxt,
225                did: ast::DefId) -> Vec<clean::Item> {
226     ty::populate_implementations_for_type_if_necessary(tcx, did);
227     let mut impls = Vec::new();
228
229     match tcx.inherent_impls.borrow().find(&did) {
230         None => {}
231         Some(i) => {
232             impls.extend(i.borrow().iter().map(|&did| { build_impl(cx, tcx, did) }));
233         }
234     }
235
236     // If this is the first time we've inlined something from this crate, then
237     // we inline *all* impls from the crate into this crate. Note that there's
238     // currently no way for us to filter this based on type, and we likely need
239     // many impls for a variety of reasons.
240     //
241     // Primarily, the impls will be used to populate the documentation for this
242     // type being inlined, but impls can also be used when generating
243     // documentation for primitives (no way to find those specifically).
244     if cx.populated_crate_impls.borrow_mut().insert(did.krate) {
245         csearch::each_top_level_item_of_crate(&tcx.sess.cstore,
246                                               did.krate,
247                                               |def, _, _| {
248             populate_impls(cx, tcx, def, &mut impls)
249         });
250
251         fn populate_impls(cx: &core::DocContext,
252                           tcx: &ty::ctxt,
253                           def: decoder::DefLike,
254                           impls: &mut Vec<Option<clean::Item>>) {
255             match def {
256                 decoder::DlImpl(did) => impls.push(build_impl(cx, tcx, did)),
257                 decoder::DlDef(def::DefMod(did)) => {
258                     csearch::each_child_of_item(&tcx.sess.cstore,
259                                                 did,
260                                                 |def, _, _| {
261                         populate_impls(cx, tcx, def, impls)
262                     })
263                 }
264                 _ => {}
265             }
266         }
267     }
268
269     impls.move_iter().filter_map(|a| a).collect()
270 }
271
272 fn build_impl(cx: &core::DocContext,
273               tcx: &ty::ctxt,
274               did: ast::DefId) -> Option<clean::Item> {
275     if !cx.inlined.borrow_mut().as_mut().unwrap().insert(did) {
276         return None
277     }
278
279     let associated_trait = csearch::get_impl_trait(tcx, did);
280     // If this is an impl for a #[doc(hidden)] trait, be sure to not inline it.
281     match associated_trait {
282         Some(ref t) => {
283             let trait_attrs = load_attrs(tcx, t.def_id);
284             if trait_attrs.iter().any(|a| is_doc_hidden(a)) {
285                 return None
286             }
287         }
288         None => {}
289     }
290
291     let attrs = load_attrs(tcx, did);
292     let ty = ty::lookup_item_type(tcx, did);
293     let trait_items = csearch::get_impl_items(&tcx.sess.cstore, did)
294             .iter()
295             .filter_map(|did| {
296         let did = did.def_id();
297         let impl_item = ty::impl_or_trait_item(tcx, did);
298         match impl_item {
299             ty::MethodTraitItem(method) => {
300                 if method.vis != ast::Public && associated_trait.is_none() {
301                     return None
302                 }
303                 let mut item = method.clean();
304                 item.inner = match item.inner.clone() {
305                     clean::TyMethodItem(clean::TyMethod {
306                         fn_style, decl, self_, generics
307                     }) => {
308                         clean::MethodItem(clean::Method {
309                             fn_style: fn_style,
310                             decl: decl,
311                             self_: self_,
312                             generics: generics,
313                         })
314                     }
315                     _ => fail!("not a tymethod"),
316                 };
317                 Some(item)
318             }
319         }
320     }).collect();
321     return Some(clean::Item {
322         inner: clean::ImplItem(clean::Impl {
323             derived: clean::detect_derived(attrs.as_slice()),
324             trait_: associated_trait.clean().map(|bound| {
325                 match bound {
326                     clean::TraitBound(ty) => ty,
327                     clean::RegionBound => unreachable!(),
328                 }
329             }),
330             for_: ty.ty.clean(),
331             generics: (&ty.generics, subst::TypeSpace).clean(),
332             items: trait_items,
333         }),
334         source: clean::Span::empty(),
335         name: None,
336         attrs: attrs,
337         visibility: Some(ast::Inherited),
338         stability: stability::lookup(tcx, did).clean(),
339         def_id: did,
340     });
341
342     fn is_doc_hidden(a: &clean::Attribute) -> bool {
343         match *a {
344             clean::List(ref name, ref inner) if name.as_slice() == "doc" => {
345                 inner.iter().any(|a| {
346                     match *a {
347                         clean::Word(ref s) => s.as_slice() == "hidden",
348                         _ => false,
349                     }
350                 })
351             }
352             _ => false
353         }
354     }
355 }
356
357 fn build_module(cx: &core::DocContext, tcx: &ty::ctxt,
358                 did: ast::DefId) -> clean::Module {
359     let mut items = Vec::new();
360     fill_in(cx, tcx, did, &mut items);
361     return clean::Module {
362         items: items,
363         is_crate: false,
364     };
365
366     // FIXME: this doesn't handle reexports inside the module itself.
367     //        Should they be handled?
368     fn fill_in(cx: &core::DocContext, tcx: &ty::ctxt, did: ast::DefId,
369                items: &mut Vec<clean::Item>) {
370         csearch::each_child_of_item(&tcx.sess.cstore, did, |def, _, vis| {
371             match def {
372                 decoder::DlDef(def::DefForeignMod(did)) => {
373                     fill_in(cx, tcx, did, items);
374                 }
375                 decoder::DlDef(def) if vis == ast::Public => {
376                     match try_inline_def(cx, tcx, def) {
377                         Some(i) => items.extend(i.move_iter()),
378                         None => {}
379                     }
380                 }
381                 decoder::DlDef(..) => {}
382                 // All impls were inlined above
383                 decoder::DlImpl(..) => {}
384                 decoder::DlField => fail!("unimplemented field"),
385             }
386         });
387     }
388 }
389
390 fn build_static(tcx: &ty::ctxt,
391                 did: ast::DefId,
392                 mutable: bool) -> clean::Static {
393     clean::Static {
394         type_: ty::lookup_item_type(tcx, did).ty.clean(),
395         mutability: if mutable {clean::Mutable} else {clean::Immutable},
396         expr: "\n\n\n".to_string(), // trigger the "[definition]" links
397     }
398 }