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.
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.
11 //! Rust AST Visitor. Extracts useful information and massages it into a form
21 use rustc
::hir
::map
as hir_map
;
22 use rustc
::hir
::def
::Def
;
23 use rustc
::hir
::def_id
::{DefId, LOCAL_CRATE}
;
24 use rustc
::middle
::cstore
::LoadedMacro
;
25 use rustc
::middle
::privacy
::AccessLevel
;
26 use rustc
::util
::nodemap
::FxHashSet
;
31 use clean
::{self, AttributesExt, NestedAttributesExt}
;
34 // looks to me like the first two of these are actually
35 // output parameters, maybe only mutated once; perhaps
36 // better simply to have the visit method return a tuple
39 // also, is there some reason that this doesn't use the 'visit'
40 // framework from syntax?
42 pub struct RustdocVisitor
<'a
, 'tcx
: 'a
> {
44 pub attrs
: hir
::HirVec
<ast
::Attribute
>,
45 pub cx
: &'a core
::DocContext
<'a
, 'tcx
>,
46 view_item_stack
: FxHashSet
<ast
::NodeId
>,
48 /// Is the current module and all of its parents public?
49 inside_public_path
: bool
,
50 reexported_macros
: FxHashSet
<DefId
>,
53 impl<'a
, 'tcx
> RustdocVisitor
<'a
, 'tcx
> {
54 pub fn new(cx
: &'a core
::DocContext
<'a
, 'tcx
>) -> RustdocVisitor
<'a
, 'tcx
> {
55 // If the root is reexported, terminate all recursion.
56 let mut stack
= FxHashSet();
57 stack
.insert(ast
::CRATE_NODE_ID
);
59 module
: Module
::new(None
),
60 attrs
: hir
::HirVec
::new(),
62 view_item_stack
: stack
,
64 inside_public_path
: true,
65 reexported_macros
: FxHashSet(),
69 fn stability(&self, id
: ast
::NodeId
) -> Option
<attr
::Stability
> {
70 self.cx
.tcx
.hir
.opt_local_def_id(id
)
71 .and_then(|def_id
| self.cx
.tcx
.lookup_stability(def_id
)).cloned()
74 fn deprecation(&self, id
: ast
::NodeId
) -> Option
<attr
::Deprecation
> {
75 self.cx
.tcx
.hir
.opt_local_def_id(id
)
76 .and_then(|def_id
| self.cx
.tcx
.lookup_deprecation(def_id
))
79 pub fn visit(&mut self, krate
: &hir
::Crate
) {
80 self.attrs
= krate
.attrs
.clone();
82 self.module
= self.visit_mod_contents(krate
.span
,
88 // attach the crate's exported macros to the top-level module:
89 let macro_exports
: Vec
<_
> =
90 krate
.exported_macros
.iter().map(|def
| self.visit_local_macro(def
)).collect();
91 self.module
.macros
.extend(macro_exports
);
92 self.module
.is_crate
= true;
95 pub fn visit_variant_data(&mut self, item
: &hir
::Item
,
96 name
: ast
::Name
, sd
: &hir
::VariantData
,
97 generics
: &hir
::Generics
) -> Struct
{
98 debug
!("Visiting struct");
99 let struct_type
= struct_type_from_def(&*sd
);
102 struct_type
: struct_type
,
104 vis
: item
.vis
.clone(),
105 stab
: self.stability(item
.id
),
106 depr
: self.deprecation(item
.id
),
107 attrs
: item
.attrs
.clone(),
108 generics
: generics
.clone(),
109 fields
: sd
.fields().iter().cloned().collect(),
114 pub fn visit_union_data(&mut self, item
: &hir
::Item
,
115 name
: ast
::Name
, sd
: &hir
::VariantData
,
116 generics
: &hir
::Generics
) -> Union
{
117 debug
!("Visiting union");
118 let struct_type
= struct_type_from_def(&*sd
);
121 struct_type
: struct_type
,
123 vis
: item
.vis
.clone(),
124 stab
: self.stability(item
.id
),
125 depr
: self.deprecation(item
.id
),
126 attrs
: item
.attrs
.clone(),
127 generics
: generics
.clone(),
128 fields
: sd
.fields().iter().cloned().collect(),
133 pub fn visit_enum_def(&mut self, it
: &hir
::Item
,
134 name
: ast
::Name
, def
: &hir
::EnumDef
,
135 params
: &hir
::Generics
) -> Enum
{
136 debug
!("Visiting enum");
139 variants
: def
.variants
.iter().map(|v
| Variant
{
141 attrs
: v
.node
.attrs
.clone(),
142 stab
: self.stability(v
.node
.data
.id()),
143 depr
: self.deprecation(v
.node
.data
.id()),
144 def
: v
.node
.data
.clone(),
148 stab
: self.stability(it
.id
),
149 depr
: self.deprecation(it
.id
),
150 generics
: params
.clone(),
151 attrs
: it
.attrs
.clone(),
157 pub fn visit_fn(&mut self, item
: &hir
::Item
,
158 name
: ast
::Name
, fd
: &hir
::FnDecl
,
159 unsafety
: &hir
::Unsafety
,
160 constness
: hir
::Constness
,
163 body
: hir
::BodyId
) -> Function
{
164 debug
!("Visiting fn");
167 vis
: item
.vis
.clone(),
168 stab
: self.stability(item
.id
),
169 depr
: self.deprecation(item
.id
),
170 attrs
: item
.attrs
.clone(),
174 generics
: gen
.clone(),
176 constness
: constness
,
182 pub fn visit_mod_contents(&mut self, span
: Span
, attrs
: hir
::HirVec
<ast
::Attribute
>,
183 vis
: hir
::Visibility
, id
: ast
::NodeId
,
185 name
: Option
<ast
::Name
>) -> Module
{
186 let mut om
= Module
::new(name
);
187 om
.where_outer
= span
;
188 om
.where_inner
= m
.inner
;
190 om
.vis
= vis
.clone();
191 om
.stab
= self.stability(id
);
192 om
.depr
= self.deprecation(id
);
194 // Keep track of if there were any private modules in the path.
195 let orig_inside_public_path
= self.inside_public_path
;
196 self.inside_public_path
&= vis
== hir
::Public
;
197 for i
in &m
.item_ids
{
198 let item
= self.cx
.tcx
.hir
.expect_item(i
.id
);
199 self.visit_item(item
, None
, &mut om
);
201 self.inside_public_path
= orig_inside_public_path
;
202 if let Some(exports
) = self.cx
.tcx
.export_map
.get(&id
) {
203 for export
in exports
{
204 if let Def
::Macro(def_id
, ..) = export
.def
{
205 if def_id
.krate
== LOCAL_CRATE
|| self.reexported_macros
.contains(&def_id
) {
206 continue // These are `krate.exported_macros`, handled in `self.visit()`.
209 let imported_from
= self.cx
.sess().cstore
.original_crate_name(def_id
.krate
);
210 let def
= match self.cx
.sess().cstore
.load_macro(def_id
, self.cx
.sess()) {
211 LoadedMacro
::MacroDef(macro_def
) => macro_def
,
212 // FIXME(jseyfried): document proc macro reexports
213 LoadedMacro
::ProcMacro(..) => continue,
216 let matchers
= if let ast
::ItemKind
::MacroDef(ref def
) = def
.node
{
217 let tts
: Vec
<_
> = def
.stream().into_trees().collect();
218 tts
.chunks(4).map(|arm
| arm
[0].span()).collect()
223 om
.macros
.push(Macro
{
225 attrs
: def
.attrs
.clone().into(),
226 name
: def
.ident
.name
,
229 stab
: self.stability(def
.id
),
230 depr
: self.deprecation(def
.id
),
231 imported_from
: Some(imported_from
),
239 /// Tries to resolve the target of a `pub use` statement and inlines the
240 /// target if it is defined locally and would not be documented otherwise,
241 /// or when it is specifically requested with `please_inline`.
242 /// (the latter is the case when the import is marked `doc(inline)`)
244 /// Cross-crate inlining occurs later on during crate cleaning
245 /// and follows different rules.
247 /// Returns true if the target has been inlined.
248 fn maybe_inline_local(&mut self,
251 renamed
: Option
<ast
::Name
>,
254 please_inline
: bool
) -> bool
{
256 fn inherits_doc_hidden(cx
: &core
::DocContext
, mut node
: ast
::NodeId
) -> bool
{
257 while let Some(id
) = cx
.tcx
.hir
.get_enclosing_scope(node
) {
259 if cx
.tcx
.hir
.attrs(node
).lists("doc").has_word("hidden") {
262 if node
== ast
::CRATE_NODE_ID
{
269 debug
!("maybe_inline_local def: {:?}", def
);
271 let tcx
= self.cx
.tcx
;
275 let def_did
= def
.def_id();
277 let use_attrs
= tcx
.hir
.attrs(id
);
278 // Don't inline doc(hidden) imports so they can be stripped at a later stage.
279 let is_no_inline
= use_attrs
.lists("doc").has_word("no_inline") ||
280 use_attrs
.lists("doc").has_word("hidden");
282 // Memoize the non-inlined `pub use`'d macros so we don't push an extra
283 // declaration in `visit_mod_contents()`
284 if !def_did
.is_local() {
285 if let Def
::Macro(did
, _
) = def
{
286 if please_inline { return true }
287 debug
!("memoizing non-inlined macro export: {:?}", def
);
288 self.reexported_macros
.insert(did
);
293 // For cross-crate impl inlining we need to know whether items are
294 // reachable in documentation - a previously nonreachable item can be
295 // made reachable by cross-crate inlining which we're checking here.
296 // (this is done here because we need to know this upfront)
297 if !def_did
.is_local() && !is_no_inline
{
298 let attrs
= clean
::inline
::load_attrs(self.cx
, def_did
);
299 let self_is_hidden
= attrs
.lists("doc").has_word("hidden");
305 Def
::TyAlias(did
) if !self_is_hidden
=> {
306 self.cx
.access_levels
.borrow_mut().map
.insert(did
, AccessLevel
::Public
);
308 Def
::Mod(did
) => if !self_is_hidden
{
309 ::visit_lib
::LibEmbargoVisitor
::new(self.cx
).visit_mod(did
);
317 let def_node_id
= match tcx
.hir
.as_local_node_id(def_did
) {
318 Some(n
) => n
, None
=> return false
321 let is_private
= !self.cx
.access_levels
.borrow().is_public(def_did
);
322 let is_hidden
= inherits_doc_hidden(self.cx
, def_node_id
);
324 // Only inline if requested or if the item would otherwise be stripped
325 if (!please_inline
&& !is_private
&& !is_hidden
) || is_no_inline
{
329 if !self.view_item_stack
.insert(def_node_id
) { return false }
331 let ret
= match tcx
.hir
.get(def_node_id
) {
332 hir_map
::NodeItem(it
) => {
333 let prev
= mem
::replace(&mut self.inlining
, true);
336 hir
::ItemMod(ref m
) => {
337 for i
in &m
.item_ids
{
338 let i
= self.cx
.tcx
.hir
.expect_item(i
.id
);
339 self.visit_item(i
, None
, om
);
342 hir
::ItemEnum(..) => {}
343 _
=> { panic!("glob not mapped to a module or enum"); }
346 self.visit_item(it
, renamed
, om
);
348 self.inlining
= prev
;
353 self.view_item_stack
.remove(&def_node_id
);
357 pub fn visit_item(&mut self, item
: &hir
::Item
,
358 renamed
: Option
<ast
::Name
>, om
: &mut Module
) {
359 debug
!("Visiting item {:?}", item
);
360 let name
= renamed
.unwrap_or(item
.name
);
362 hir
::ItemForeignMod(ref fm
) => {
363 // If inlining we only want to include public functions.
364 om
.foreigns
.push(if self.inlining
{
367 items
: fm
.items
.iter().filter(|i
| i
.vis
== hir
::Public
).cloned().collect(),
373 // If we're inlining, skip private items.
374 _
if self.inlining
&& item
.vis
!= hir
::Public
=> {}
375 hir
::ItemGlobalAsm(..) => {}
376 hir
::ItemExternCrate(ref p
) => {
377 let cstore
= &self.cx
.sess().cstore
;
378 om
.extern_crates
.push(ExternCrate
{
379 cnum
: cstore
.extern_mod_stmt_cnum(item
.id
)
380 .unwrap_or(LOCAL_CRATE
),
382 path
: p
.map(|x
|x
.to_string()),
383 vis
: item
.vis
.clone(),
384 attrs
: item
.attrs
.clone(),
388 hir
::ItemUse(_
, hir
::UseKind
::ListStem
) => {}
389 hir
::ItemUse(ref path
, kind
) => {
390 let is_glob
= kind
== hir
::UseKind
::Glob
;
392 // If there was a private module in the current path then don't bother inlining
393 // anything as it will probably be stripped anyway.
394 if item
.vis
== hir
::Public
&& self.inside_public_path
{
395 let please_inline
= item
.attrs
.iter().any(|item
| {
396 match item
.meta_item_list() {
397 Some(ref list
) if item
.check_name("doc") => {
398 list
.iter().any(|i
| i
.check_name("inline"))
403 let name
= if is_glob { None }
else { Some(name) }
;
404 if self.maybe_inline_local(item
.id
,
414 om
.imports
.push(Import
{
417 vis
: item
.vis
.clone(),
418 attrs
: item
.attrs
.clone(),
419 path
: (**path
).clone(),
424 hir
::ItemMod(ref m
) => {
425 om
.mods
.push(self.visit_mod_contents(item
.span
,
432 hir
::ItemEnum(ref ed
, ref gen
) =>
433 om
.enums
.push(self.visit_enum_def(item
, name
, ed
, gen
)),
434 hir
::ItemStruct(ref sd
, ref gen
) =>
435 om
.structs
.push(self.visit_variant_data(item
, name
, sd
, gen
)),
436 hir
::ItemUnion(ref sd
, ref gen
) =>
437 om
.unions
.push(self.visit_union_data(item
, name
, sd
, gen
)),
438 hir
::ItemFn(ref fd
, ref unsafety
, constness
, ref abi
, ref gen
, body
) =>
439 om
.fns
.push(self.visit_fn(item
, name
, &**fd
, unsafety
,
440 constness
, abi
, gen
, body
)),
441 hir
::ItemTy(ref ty
, ref gen
) => {
447 attrs
: item
.attrs
.clone(),
449 vis
: item
.vis
.clone(),
450 stab
: self.stability(item
.id
),
451 depr
: self.deprecation(item
.id
),
455 hir
::ItemStatic(ref ty
, ref mut_
, ref exp
) => {
458 mutability
: mut_
.clone(),
462 attrs
: item
.attrs
.clone(),
464 vis
: item
.vis
.clone(),
465 stab
: self.stability(item
.id
),
466 depr
: self.deprecation(item
.id
),
470 hir
::ItemConst(ref ty
, ref exp
) => {
476 attrs
: item
.attrs
.clone(),
478 vis
: item
.vis
.clone(),
479 stab
: self.stability(item
.id
),
480 depr
: self.deprecation(item
.id
),
482 om
.constants
.push(s
);
484 hir
::ItemTrait(unsafety
, ref gen
, ref b
, ref item_ids
) => {
485 let items
= item_ids
.iter()
486 .map(|ti
| self.cx
.tcx
.hir
.trait_item(ti
.id
).clone())
492 generics
: gen
.clone(),
493 bounds
: b
.iter().cloned().collect(),
495 attrs
: item
.attrs
.clone(),
497 vis
: item
.vis
.clone(),
498 stab
: self.stability(item
.id
),
499 depr
: self.deprecation(item
.id
),
504 hir
::ItemImpl(unsafety
,
511 // Don't duplicate impls when inlining, we'll pick them up
512 // regardless of where they're located.
514 let items
= item_ids
.iter()
515 .map(|ii
| self.cx
.tcx
.hir
.impl_item(ii
.id
).clone())
520 defaultness
: defaultness
,
521 generics
: gen
.clone(),
525 attrs
: item
.attrs
.clone(),
528 vis
: item
.vis
.clone(),
529 stab
: self.stability(item
.id
),
530 depr
: self.deprecation(item
.id
),
535 hir
::ItemDefaultImpl(unsafety
, ref trait_ref
) => {
536 // See comment above about ItemImpl.
538 let i
= DefaultImpl
{
540 trait_
: trait_ref
.clone(),
542 attrs
: item
.attrs
.clone(),
545 om
.def_traits
.push(i
);
551 // convert each exported_macro into a doc item
552 fn visit_local_macro(&self, def
: &hir
::MacroDef
) -> Macro
{
553 let tts
= def
.body
.trees().collect
::<Vec
<_
>>();
554 // Extract the spans of all matchers. They represent the "interface" of the macro.
555 let matchers
= tts
.chunks(4).map(|arm
| arm
[0].span()).collect();
558 def_id
: self.cx
.tcx
.hir
.local_def_id(def
.id
),
559 attrs
: def
.attrs
.clone(),
563 stab
: self.stability(def
.id
),
564 depr
: self.deprecation(def
.id
),