]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
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 | //! Rust AST Visitor. Extracts useful information and massages it into a form | |
12 | //! usable for clean | |
13 | ||
14 | use std::collections::HashSet; | |
9346a6ac | 15 | use std::mem; |
1a4d82fc JJ |
16 | |
17 | use syntax::abi; | |
18 | use syntax::ast; | |
b039eaaf SL |
19 | use syntax::attr; |
20 | use syntax::attr::AttrMetaMethods; | |
1a4d82fc | 21 | use syntax::codemap::Span; |
1a4d82fc | 22 | |
54a0048b | 23 | use rustc::hir::map as hir_map; |
1a4d82fc JJ |
24 | use rustc::middle::stability; |
25 | ||
54a0048b | 26 | use rustc::hir; |
e9174d1e | 27 | |
1a4d82fc | 28 | use core; |
54a0048b | 29 | use clean::{Clean, Attributes}; |
1a4d82fc JJ |
30 | use doctree::*; |
31 | ||
32 | // looks to me like the first two of these are actually | |
33 | // output parameters, maybe only mutated once; perhaps | |
34 | // better simply to have the visit method return a tuple | |
35 | // containing them? | |
36 | ||
37 | // also, is there some reason that this doesn't use the 'visit' | |
38 | // framework from syntax? | |
39 | ||
40 | pub struct RustdocVisitor<'a, 'tcx: 'a> { | |
41 | pub module: Module, | |
9cc50fc6 | 42 | pub attrs: hir::HirVec<ast::Attribute>, |
62682a34 | 43 | pub cx: &'a core::DocContext<'a, 'tcx>, |
1a4d82fc JJ |
44 | pub analysis: Option<&'a core::CrateAnalysis>, |
45 | view_item_stack: HashSet<ast::NodeId>, | |
9346a6ac | 46 | inlining_from_glob: bool, |
1a4d82fc JJ |
47 | } |
48 | ||
49 | impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { | |
62682a34 | 50 | pub fn new(cx: &'a core::DocContext<'a, 'tcx>, |
1a4d82fc JJ |
51 | analysis: Option<&'a core::CrateAnalysis>) -> RustdocVisitor<'a, 'tcx> { |
52 | // If the root is reexported, terminate all recursion. | |
53 | let mut stack = HashSet::new(); | |
54 | stack.insert(ast::CRATE_NODE_ID); | |
55 | RustdocVisitor { | |
56 | module: Module::new(None), | |
9cc50fc6 | 57 | attrs: hir::HirVec::new(), |
1a4d82fc JJ |
58 | cx: cx, |
59 | analysis: analysis, | |
60 | view_item_stack: stack, | |
9346a6ac | 61 | inlining_from_glob: false, |
1a4d82fc JJ |
62 | } |
63 | } | |
64 | ||
65 | fn stability(&self, id: ast::NodeId) -> Option<attr::Stability> { | |
b039eaaf SL |
66 | self.cx.tcx_opt().and_then(|tcx| { |
67 | self.cx.map.opt_local_def_id(id) | |
9cc50fc6 | 68 | .and_then(|def_id| stability::lookup_stability(tcx, def_id)) |
b039eaaf SL |
69 | .cloned() |
70 | }) | |
1a4d82fc JJ |
71 | } |
72 | ||
9cc50fc6 SL |
73 | fn deprecation(&self, id: ast::NodeId) -> Option<attr::Deprecation> { |
74 | self.cx.tcx_opt().and_then(|tcx| { | |
75 | self.cx.map.opt_local_def_id(id) | |
76 | .and_then(|def_id| stability::lookup_deprecation(tcx, def_id)) | |
77 | }) | |
78 | } | |
79 | ||
e9174d1e | 80 | pub fn visit(&mut self, krate: &hir::Crate) { |
1a4d82fc JJ |
81 | self.attrs = krate.attrs.clone(); |
82 | ||
83 | self.module = self.visit_mod_contents(krate.span, | |
84 | krate.attrs.clone(), | |
e9174d1e | 85 | hir::Public, |
1a4d82fc JJ |
86 | ast::CRATE_NODE_ID, |
87 | &krate.module, | |
88 | None); | |
89 | // attach the crate's exported macros to the top-level module: | |
90 | self.module.macros = krate.exported_macros.iter() | |
91 | .map(|def| self.visit_macro(def)).collect(); | |
92 | self.module.is_crate = true; | |
93 | } | |
94 | ||
b039eaaf SL |
95 | pub fn visit_variant_data(&mut self, item: &hir::Item, |
96 | name: ast::Name, sd: &hir::VariantData, | |
e9174d1e | 97 | generics: &hir::Generics) -> Struct { |
1a4d82fc JJ |
98 | debug!("Visiting struct"); |
99 | let struct_type = struct_type_from_def(&*sd); | |
100 | Struct { | |
101 | id: item.id, | |
102 | struct_type: struct_type, | |
103 | name: name, | |
54a0048b | 104 | vis: item.vis.clone(), |
1a4d82fc | 105 | stab: self.stability(item.id), |
9cc50fc6 | 106 | depr: self.deprecation(item.id), |
1a4d82fc JJ |
107 | attrs: item.attrs.clone(), |
108 | generics: generics.clone(), | |
b039eaaf | 109 | fields: sd.fields().iter().cloned().collect(), |
1a4d82fc JJ |
110 | whence: item.span |
111 | } | |
112 | } | |
113 | ||
e9174d1e | 114 | pub fn visit_enum_def(&mut self, it: &hir::Item, |
b039eaaf | 115 | name: ast::Name, def: &hir::EnumDef, |
e9174d1e | 116 | params: &hir::Generics) -> Enum { |
1a4d82fc JJ |
117 | debug!("Visiting enum"); |
118 | Enum { | |
119 | name: name, | |
120 | variants: def.variants.iter().map(|v| Variant { | |
121 | name: v.node.name, | |
122 | attrs: v.node.attrs.clone(), | |
b039eaaf | 123 | stab: self.stability(v.node.data.id()), |
9cc50fc6 | 124 | depr: self.deprecation(v.node.data.id()), |
b039eaaf | 125 | def: v.node.data.clone(), |
1a4d82fc JJ |
126 | whence: v.span, |
127 | }).collect(), | |
54a0048b | 128 | vis: it.vis.clone(), |
1a4d82fc | 129 | stab: self.stability(it.id), |
9cc50fc6 | 130 | depr: self.deprecation(it.id), |
1a4d82fc JJ |
131 | generics: params.clone(), |
132 | attrs: it.attrs.clone(), | |
133 | id: it.id, | |
134 | whence: it.span, | |
135 | } | |
136 | } | |
137 | ||
e9174d1e | 138 | pub fn visit_fn(&mut self, item: &hir::Item, |
b039eaaf | 139 | name: ast::Name, fd: &hir::FnDecl, |
e9174d1e SL |
140 | unsafety: &hir::Unsafety, |
141 | constness: hir::Constness, | |
62682a34 | 142 | abi: &abi::Abi, |
e9174d1e | 143 | gen: &hir::Generics) -> Function { |
1a4d82fc JJ |
144 | debug!("Visiting fn"); |
145 | Function { | |
146 | id: item.id, | |
54a0048b | 147 | vis: item.vis.clone(), |
1a4d82fc | 148 | stab: self.stability(item.id), |
9cc50fc6 | 149 | depr: self.deprecation(item.id), |
1a4d82fc JJ |
150 | attrs: item.attrs.clone(), |
151 | decl: fd.clone(), | |
152 | name: name, | |
153 | whence: item.span, | |
154 | generics: gen.clone(), | |
155 | unsafety: *unsafety, | |
62682a34 | 156 | constness: constness, |
9346a6ac | 157 | abi: *abi, |
1a4d82fc JJ |
158 | } |
159 | } | |
160 | ||
9cc50fc6 | 161 | pub fn visit_mod_contents(&mut self, span: Span, attrs: hir::HirVec<ast::Attribute>, |
e9174d1e SL |
162 | vis: hir::Visibility, id: ast::NodeId, |
163 | m: &hir::Mod, | |
b039eaaf | 164 | name: Option<ast::Name>) -> Module { |
1a4d82fc | 165 | let mut om = Module::new(name); |
1a4d82fc JJ |
166 | om.where_outer = span; |
167 | om.where_inner = m.inner; | |
168 | om.attrs = attrs; | |
54a0048b | 169 | om.vis = vis.clone(); |
1a4d82fc | 170 | om.stab = self.stability(id); |
9cc50fc6 | 171 | om.depr = self.deprecation(id); |
1a4d82fc | 172 | om.id = id; |
92a42be0 SL |
173 | for i in &m.item_ids { |
174 | let item = self.cx.map.expect_item(i.id); | |
175 | self.visit_item(item, None, &mut om); | |
1a4d82fc JJ |
176 | } |
177 | om | |
178 | } | |
179 | ||
e9174d1e | 180 | fn visit_view_path(&mut self, path: hir::ViewPath_, |
1a4d82fc | 181 | om: &mut Module, |
85aaf69f | 182 | id: ast::NodeId, |
e9174d1e | 183 | please_inline: bool) -> Option<hir::ViewPath_> { |
85aaf69f | 184 | match path { |
e9174d1e | 185 | hir::ViewPathSimple(dst, base) => { |
54a0048b | 186 | if self.maybe_inline_local(id, Some(dst), false, om, please_inline) { |
85aaf69f SL |
187 | None |
188 | } else { | |
e9174d1e | 189 | Some(hir::ViewPathSimple(dst, base)) |
1a4d82fc JJ |
190 | } |
191 | } | |
e9174d1e | 192 | hir::ViewPathList(p, paths) => { |
85aaf69f | 193 | let mine = paths.into_iter().filter(|path| { |
54a0048b | 194 | !self.maybe_inline_local(path.node.id(), None, false, om, |
85aaf69f | 195 | please_inline) |
9cc50fc6 | 196 | }).collect::<hir::HirVec<hir::PathListItem>>(); |
1a4d82fc | 197 | |
9346a6ac | 198 | if mine.is_empty() { |
85aaf69f SL |
199 | None |
200 | } else { | |
e9174d1e | 201 | Some(hir::ViewPathList(p, mine)) |
85aaf69f | 202 | } |
1a4d82fc JJ |
203 | } |
204 | ||
e9174d1e | 205 | hir::ViewPathGlob(base) => { |
54a0048b | 206 | if self.maybe_inline_local(id, None, true, om, please_inline) { |
85aaf69f SL |
207 | None |
208 | } else { | |
e9174d1e | 209 | Some(hir::ViewPathGlob(base)) |
1a4d82fc JJ |
210 | } |
211 | } | |
212 | } | |
85aaf69f | 213 | |
1a4d82fc JJ |
214 | } |
215 | ||
54a0048b SL |
216 | /// Tries to resolve the target of a `pub use` statement and inlines the |
217 | /// target if it is defined locally and would not be documented otherwise, | |
218 | /// or when it is specifically requested with `please_inline`. | |
219 | /// (the latter is the case when the import is marked `doc(inline)`) | |
220 | /// | |
221 | /// Cross-crate inlining occurs later on during crate cleaning | |
222 | /// and follows different rules. | |
223 | /// | |
224 | /// Returns true if the target has been inlined. | |
225 | fn maybe_inline_local(&mut self, id: ast::NodeId, renamed: Option<ast::Name>, | |
1a4d82fc | 226 | glob: bool, om: &mut Module, please_inline: bool) -> bool { |
54a0048b SL |
227 | |
228 | fn inherits_doc_hidden(cx: &core::DocContext, mut node: ast::NodeId) -> bool { | |
229 | while let Some(id) = cx.map.get_enclosing_scope(node) { | |
230 | node = id; | |
231 | let attrs = cx.map.attrs(node).clean(cx); | |
232 | if attrs.list("doc").has_word("hidden") { | |
233 | return true; | |
234 | } | |
235 | if node == ast::CRATE_NODE_ID { | |
236 | break; | |
237 | } | |
238 | } | |
239 | false | |
240 | } | |
241 | ||
1a4d82fc JJ |
242 | let tcx = match self.cx.tcx_opt() { |
243 | Some(tcx) => tcx, | |
244 | None => return false | |
245 | }; | |
c34b1796 | 246 | let def = tcx.def_map.borrow()[&id].def_id(); |
b039eaaf SL |
247 | let def_node_id = match tcx.map.as_local_node_id(def) { |
248 | Some(n) => n, None => return false | |
249 | }; | |
1a4d82fc JJ |
250 | let analysis = match self.analysis { |
251 | Some(analysis) => analysis, None => return false | |
252 | }; | |
54a0048b SL |
253 | |
254 | let use_attrs = tcx.map.attrs(id).clean(self.cx); | |
255 | ||
256 | let is_private = !analysis.access_levels.is_public(def); | |
257 | let is_hidden = inherits_doc_hidden(self.cx, def_node_id); | |
258 | let is_no_inline = use_attrs.list("doc").has_word("no_inline"); | |
259 | ||
260 | // Only inline if requested or if the item would otherwise be stripped | |
261 | if (!please_inline && !is_private && !is_hidden) || is_no_inline { | |
1a4d82fc JJ |
262 | return false |
263 | } | |
54a0048b | 264 | |
b039eaaf | 265 | if !self.view_item_stack.insert(def_node_id) { return false } |
1a4d82fc | 266 | |
b039eaaf | 267 | let ret = match tcx.map.get(def_node_id) { |
e9174d1e | 268 | hir_map::NodeItem(it) => { |
1a4d82fc | 269 | if glob { |
9346a6ac | 270 | let prev = mem::replace(&mut self.inlining_from_glob, true); |
1a4d82fc | 271 | match it.node { |
e9174d1e | 272 | hir::ItemMod(ref m) => { |
92a42be0 SL |
273 | for i in &m.item_ids { |
274 | let i = self.cx.map.expect_item(i.id); | |
275 | self.visit_item(i, None, om); | |
1a4d82fc JJ |
276 | } |
277 | } | |
e9174d1e | 278 | hir::ItemEnum(..) => {} |
1a4d82fc JJ |
279 | _ => { panic!("glob not mapped to a module or enum"); } |
280 | } | |
9346a6ac | 281 | self.inlining_from_glob = prev; |
1a4d82fc JJ |
282 | } else { |
283 | self.visit_item(it, renamed, om); | |
284 | } | |
285 | true | |
286 | } | |
287 | _ => false, | |
288 | }; | |
b039eaaf | 289 | self.view_item_stack.remove(&def_node_id); |
1a4d82fc JJ |
290 | return ret; |
291 | } | |
292 | ||
e9174d1e | 293 | pub fn visit_item(&mut self, item: &hir::Item, |
b039eaaf | 294 | renamed: Option<ast::Name>, om: &mut Module) { |
1a4d82fc | 295 | debug!("Visiting item {:?}", item); |
b039eaaf | 296 | let name = renamed.unwrap_or(item.name); |
1a4d82fc | 297 | match item.node { |
e9174d1e | 298 | hir::ItemExternCrate(ref p) => { |
85aaf69f SL |
299 | om.extern_crates.push(ExternCrate { |
300 | name: name, | |
54a0048b SL |
301 | path: p.map(|x|x.to_string()), |
302 | vis: item.vis.clone(), | |
85aaf69f SL |
303 | attrs: item.attrs.clone(), |
304 | whence: item.span, | |
305 | }) | |
306 | } | |
e9174d1e | 307 | hir::ItemUse(ref vpath) => { |
85aaf69f | 308 | let node = vpath.node.clone(); |
e9174d1e | 309 | let node = if item.vis == hir::Public { |
85aaf69f SL |
310 | let please_inline = item.attrs.iter().any(|item| { |
311 | match item.meta_item_list() { | |
54a0048b | 312 | Some(list) if &item.name()[..] == "doc" => { |
c34b1796 | 313 | list.iter().any(|i| &i.name()[..] == "inline") |
85aaf69f | 314 | } |
54a0048b | 315 | _ => false, |
85aaf69f SL |
316 | } |
317 | }); | |
318 | match self.visit_view_path(node, om, item.id, please_inline) { | |
319 | None => return, | |
320 | Some(p) => p | |
321 | } | |
322 | } else { | |
323 | node | |
324 | }; | |
325 | om.imports.push(Import { | |
326 | id: item.id, | |
54a0048b | 327 | vis: item.vis.clone(), |
85aaf69f SL |
328 | attrs: item.attrs.clone(), |
329 | node: node, | |
330 | whence: item.span, | |
331 | }); | |
332 | } | |
e9174d1e | 333 | hir::ItemMod(ref m) => { |
1a4d82fc JJ |
334 | om.mods.push(self.visit_mod_contents(item.span, |
335 | item.attrs.clone(), | |
54a0048b | 336 | item.vis.clone(), |
1a4d82fc JJ |
337 | item.id, |
338 | m, | |
339 | Some(name))); | |
340 | }, | |
e9174d1e | 341 | hir::ItemEnum(ref ed, ref gen) => |
1a4d82fc | 342 | om.enums.push(self.visit_enum_def(item, name, ed, gen)), |
e9174d1e | 343 | hir::ItemStruct(ref sd, ref gen) => |
b039eaaf | 344 | om.structs.push(self.visit_variant_data(item, name, sd, gen)), |
e9174d1e | 345 | hir::ItemFn(ref fd, ref unsafety, constness, ref abi, ref gen, _) => |
62682a34 SL |
346 | om.fns.push(self.visit_fn(item, name, &**fd, unsafety, |
347 | constness, abi, gen)), | |
e9174d1e | 348 | hir::ItemTy(ref ty, ref gen) => { |
1a4d82fc JJ |
349 | let t = Typedef { |
350 | ty: ty.clone(), | |
351 | gen: gen.clone(), | |
352 | name: name, | |
353 | id: item.id, | |
354 | attrs: item.attrs.clone(), | |
355 | whence: item.span, | |
54a0048b | 356 | vis: item.vis.clone(), |
1a4d82fc | 357 | stab: self.stability(item.id), |
9cc50fc6 | 358 | depr: self.deprecation(item.id), |
1a4d82fc JJ |
359 | }; |
360 | om.typedefs.push(t); | |
361 | }, | |
e9174d1e | 362 | hir::ItemStatic(ref ty, ref mut_, ref exp) => { |
1a4d82fc JJ |
363 | let s = Static { |
364 | type_: ty.clone(), | |
365 | mutability: mut_.clone(), | |
366 | expr: exp.clone(), | |
367 | id: item.id, | |
368 | name: name, | |
369 | attrs: item.attrs.clone(), | |
370 | whence: item.span, | |
54a0048b | 371 | vis: item.vis.clone(), |
1a4d82fc | 372 | stab: self.stability(item.id), |
9cc50fc6 | 373 | depr: self.deprecation(item.id), |
1a4d82fc JJ |
374 | }; |
375 | om.statics.push(s); | |
376 | }, | |
e9174d1e | 377 | hir::ItemConst(ref ty, ref exp) => { |
1a4d82fc JJ |
378 | let s = Constant { |
379 | type_: ty.clone(), | |
380 | expr: exp.clone(), | |
381 | id: item.id, | |
382 | name: name, | |
383 | attrs: item.attrs.clone(), | |
384 | whence: item.span, | |
54a0048b | 385 | vis: item.vis.clone(), |
1a4d82fc | 386 | stab: self.stability(item.id), |
9cc50fc6 | 387 | depr: self.deprecation(item.id), |
1a4d82fc JJ |
388 | }; |
389 | om.constants.push(s); | |
390 | }, | |
e9174d1e | 391 | hir::ItemTrait(unsafety, ref gen, ref b, ref items) => { |
1a4d82fc JJ |
392 | let t = Trait { |
393 | unsafety: unsafety, | |
394 | name: name, | |
395 | items: items.clone(), | |
396 | generics: gen.clone(), | |
85aaf69f | 397 | bounds: b.iter().cloned().collect(), |
1a4d82fc JJ |
398 | id: item.id, |
399 | attrs: item.attrs.clone(), | |
400 | whence: item.span, | |
54a0048b | 401 | vis: item.vis.clone(), |
1a4d82fc | 402 | stab: self.stability(item.id), |
9cc50fc6 | 403 | depr: self.deprecation(item.id), |
1a4d82fc JJ |
404 | }; |
405 | om.traits.push(t); | |
406 | }, | |
e9174d1e | 407 | hir::ItemImpl(unsafety, polarity, ref gen, ref tr, ref ty, ref items) => { |
1a4d82fc JJ |
408 | let i = Impl { |
409 | unsafety: unsafety, | |
410 | polarity: polarity, | |
411 | generics: gen.clone(), | |
412 | trait_: tr.clone(), | |
413 | for_: ty.clone(), | |
414 | items: items.clone(), | |
415 | attrs: item.attrs.clone(), | |
416 | id: item.id, | |
417 | whence: item.span, | |
54a0048b | 418 | vis: item.vis.clone(), |
1a4d82fc | 419 | stab: self.stability(item.id), |
9cc50fc6 | 420 | depr: self.deprecation(item.id), |
1a4d82fc | 421 | }; |
9346a6ac AL |
422 | // Don't duplicate impls when inlining glob imports, we'll pick |
423 | // them up regardless of where they're located. | |
424 | if !self.inlining_from_glob { | |
425 | om.impls.push(i); | |
426 | } | |
1a4d82fc | 427 | }, |
e9174d1e | 428 | hir::ItemDefaultImpl(unsafety, ref trait_ref) => { |
c34b1796 AL |
429 | let i = DefaultImpl { |
430 | unsafety: unsafety, | |
431 | trait_: trait_ref.clone(), | |
432 | id: item.id, | |
433 | attrs: item.attrs.clone(), | |
434 | whence: item.span, | |
435 | }; | |
9346a6ac AL |
436 | // see comment above about ItemImpl |
437 | if !self.inlining_from_glob { | |
438 | om.def_traits.push(i); | |
439 | } | |
c34b1796 | 440 | } |
e9174d1e | 441 | hir::ItemForeignMod(ref fm) => { |
1a4d82fc JJ |
442 | om.foreigns.push(fm.clone()); |
443 | } | |
1a4d82fc JJ |
444 | } |
445 | } | |
446 | ||
447 | // convert each exported_macro into a doc item | |
e9174d1e | 448 | fn visit_macro(&self, def: &hir::MacroDef) -> Macro { |
92a42be0 SL |
449 | // Extract the spans of all matchers. They represent the "interface" of the macro. |
450 | let matchers = def.body.chunks(4).map(|arm| arm[0].get_span()).collect(); | |
451 | ||
1a4d82fc JJ |
452 | Macro { |
453 | id: def.id, | |
454 | attrs: def.attrs.clone(), | |
b039eaaf | 455 | name: def.name, |
1a4d82fc | 456 | whence: def.span, |
92a42be0 | 457 | matchers: matchers, |
1a4d82fc | 458 | stab: self.stability(def.id), |
9cc50fc6 | 459 | depr: self.deprecation(def.id), |
d9579d0f | 460 | imported_from: def.imported_from, |
1a4d82fc JJ |
461 | } |
462 | } | |
463 | } |