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