]> git.lizzy.rs Git - rust.git/blob - src/librustc/middle/reachable.rs
Rollup merge of #41135 - japaric:unstable-docs, r=steveklabnik
[rust.git] / src / librustc / middle / reachable.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 // Finds items that are externally reachable, to determine which items
12 // need to have their metadata (and possibly their AST) serialized.
13 // All items that can be referred to through an exported name are
14 // reachable, and when a reachable thing is inline or generic, it
15 // makes all other generics or inline functions that it references
16 // reachable as well.
17
18 use hir::map as hir_map;
19 use hir::def::Def;
20 use hir::def_id::{DefId, CrateNum};
21 use ty::{self, TyCtxt};
22 use ty::maps::Providers;
23 use middle::privacy;
24 use session::config;
25 use util::nodemap::{NodeSet, FxHashSet};
26
27 use syntax::abi::Abi;
28 use syntax::ast;
29 use syntax::attr;
30 use syntax::codemap::DUMMY_SP;
31 use hir;
32 use hir::def_id::LOCAL_CRATE;
33 use hir::intravisit::{Visitor, NestedVisitorMap};
34 use hir::itemlikevisit::ItemLikeVisitor;
35 use hir::intravisit;
36
37 // Returns true if the given set of generics implies that the item it's
38 // associated with must be inlined.
39 fn generics_require_inlining(generics: &hir::Generics) -> bool {
40     !generics.ty_params.is_empty()
41 }
42
43 // Returns true if the given item must be inlined because it may be
44 // monomorphized or it was marked with `#[inline]`. This will only return
45 // true for functions.
46 fn item_might_be_inlined(item: &hir::Item) -> bool {
47     if attr::requests_inline(&item.attrs) {
48         return true
49     }
50
51     match item.node {
52         hir::ItemImpl(_, _, ref generics, ..) |
53         hir::ItemFn(.., ref generics, _) => {
54             generics_require_inlining(generics)
55         }
56         _ => false,
57     }
58 }
59
60 fn method_might_be_inlined<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
61                                      sig: &hir::MethodSig,
62                                      impl_item: &hir::ImplItem,
63                                      impl_src: DefId) -> bool {
64     if attr::requests_inline(&impl_item.attrs) ||
65         generics_require_inlining(&sig.generics) {
66         return true
67     }
68     if let Some(impl_node_id) = tcx.hir.as_local_node_id(impl_src) {
69         match tcx.hir.find(impl_node_id) {
70             Some(hir_map::NodeItem(item)) =>
71                 item_might_be_inlined(&item),
72             Some(..) | None =>
73                 span_bug!(impl_item.span, "impl did is not an item")
74         }
75     } else {
76         span_bug!(impl_item.span, "found a foreign impl as a parent of a local method")
77     }
78 }
79
80 // Information needed while computing reachability.
81 struct ReachableContext<'a, 'tcx: 'a> {
82     // The type context.
83     tcx: TyCtxt<'a, 'tcx, 'tcx>,
84     tables: &'a ty::TypeckTables<'tcx>,
85     // The set of items which must be exported in the linkage sense.
86     reachable_symbols: NodeSet,
87     // A worklist of item IDs. Each item ID in this worklist will be inlined
88     // and will be scanned for further references.
89     worklist: Vec<ast::NodeId>,
90     // Whether any output of this compilation is a library
91     any_library: bool,
92 }
93
94 impl<'a, 'tcx> Visitor<'tcx> for ReachableContext<'a, 'tcx> {
95     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
96         NestedVisitorMap::None
97     }
98
99     fn visit_nested_body(&mut self, body: hir::BodyId) {
100         let old_tables = self.tables;
101         self.tables = self.tcx.body_tables(body);
102         let body = self.tcx.hir.body(body);
103         self.visit_body(body);
104         self.tables = old_tables;
105     }
106
107     fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
108         let def = match expr.node {
109             hir::ExprPath(ref qpath) => {
110                 Some(self.tables.qpath_def(qpath, expr.id))
111             }
112             hir::ExprMethodCall(..) => {
113                 let method_call = ty::MethodCall::expr(expr.id);
114                 let def_id = self.tables.method_map[&method_call].def_id;
115                 Some(Def::Method(def_id))
116             }
117             _ => None
118         };
119
120         if let Some(def) = def {
121             let def_id = def.def_id();
122             if let Some(node_id) = self.tcx.hir.as_local_node_id(def_id) {
123                 if self.def_id_represents_local_inlined_item(def_id) {
124                     self.worklist.push(node_id);
125                 } else {
126                     match def {
127                         // If this path leads to a constant, then we need to
128                         // recurse into the constant to continue finding
129                         // items that are reachable.
130                         Def::Const(..) | Def::AssociatedConst(..) => {
131                             self.worklist.push(node_id);
132                         }
133
134                         // If this wasn't a static, then the destination is
135                         // surely reachable.
136                         _ => {
137                             self.reachable_symbols.insert(node_id);
138                         }
139                     }
140                 }
141             }
142         }
143
144         intravisit::walk_expr(self, expr)
145     }
146 }
147
148 impl<'a, 'tcx> ReachableContext<'a, 'tcx> {
149     // Returns true if the given def ID represents a local item that is
150     // eligible for inlining and false otherwise.
151     fn def_id_represents_local_inlined_item(&self, def_id: DefId) -> bool {
152         let node_id = match self.tcx.hir.as_local_node_id(def_id) {
153             Some(node_id) => node_id,
154             None => { return false; }
155         };
156
157         match self.tcx.hir.find(node_id) {
158             Some(hir_map::NodeItem(item)) => {
159                 match item.node {
160                     hir::ItemFn(..) => item_might_be_inlined(&item),
161                     _ => false,
162                 }
163             }
164             Some(hir_map::NodeTraitItem(trait_method)) => {
165                 match trait_method.node {
166                     hir::TraitItemKind::Const(_, ref default) => default.is_some(),
167                     hir::TraitItemKind::Method(_, hir::TraitMethod::Provided(_)) => true,
168                     hir::TraitItemKind::Method(_, hir::TraitMethod::Required(_)) |
169                     hir::TraitItemKind::Type(..) => false,
170                 }
171             }
172             Some(hir_map::NodeImplItem(impl_item)) => {
173                 match impl_item.node {
174                     hir::ImplItemKind::Const(..) => true,
175                     hir::ImplItemKind::Method(ref sig, _) => {
176                         if generics_require_inlining(&sig.generics) ||
177                                 attr::requests_inline(&impl_item.attrs) {
178                             true
179                         } else {
180                             let impl_did = self.tcx
181                                                .hir
182                                                .get_parent_did(node_id);
183                             // Check the impl. If the generics on the self
184                             // type of the impl require inlining, this method
185                             // does too.
186                             let impl_node_id = self.tcx.hir.as_local_node_id(impl_did).unwrap();
187                             match self.tcx.hir.expect_item(impl_node_id).node {
188                                 hir::ItemImpl(_, _, ref generics, ..) => {
189                                     generics_require_inlining(generics)
190                                 }
191                                 _ => false
192                             }
193                         }
194                     }
195                     hir::ImplItemKind::Type(_) => false,
196                 }
197             }
198             Some(_) => false,
199             None => false   // This will happen for default methods.
200         }
201     }
202
203     // Step 2: Mark all symbols that the symbols on the worklist touch.
204     fn propagate(&mut self) {
205         let mut scanned = FxHashSet();
206         loop {
207             let search_item = match self.worklist.pop() {
208                 Some(item) => item,
209                 None => break,
210             };
211             if !scanned.insert(search_item) {
212                 continue
213             }
214
215             if let Some(ref item) = self.tcx.hir.find(search_item) {
216                 self.propagate_node(item, search_item);
217             }
218         }
219     }
220
221     fn propagate_node(&mut self, node: &hir_map::Node<'tcx>,
222                       search_item: ast::NodeId) {
223         if !self.any_library {
224             // If we are building an executable, only explicitly extern
225             // types need to be exported.
226             if let hir_map::NodeItem(item) = *node {
227                 let reachable = if let hir::ItemFn(.., abi, _, _) = item.node {
228                     abi != Abi::Rust
229                 } else {
230                     false
231                 };
232                 let is_extern = attr::contains_extern_indicator(&self.tcx.sess.diagnostic(),
233                                                                 &item.attrs);
234                 if reachable || is_extern {
235                     self.reachable_symbols.insert(search_item);
236                 }
237             }
238         } else {
239             // If we are building a library, then reachable symbols will
240             // continue to participate in linkage after this product is
241             // produced. In this case, we traverse the ast node, recursing on
242             // all reachable nodes from this one.
243             self.reachable_symbols.insert(search_item);
244         }
245
246         match *node {
247             hir_map::NodeItem(item) => {
248                 match item.node {
249                     hir::ItemFn(.., body) => {
250                         if item_might_be_inlined(&item) {
251                             self.visit_nested_body(body);
252                         }
253                     }
254
255                     // Reachable constants will be inlined into other crates
256                     // unconditionally, so we need to make sure that their
257                     // contents are also reachable.
258                     hir::ItemConst(_, init) => {
259                         self.visit_nested_body(init);
260                     }
261
262                     // These are normal, nothing reachable about these
263                     // inherently and their children are already in the
264                     // worklist, as determined by the privacy pass
265                     hir::ItemExternCrate(_) | hir::ItemUse(..) |
266                     hir::ItemTy(..) | hir::ItemStatic(..) |
267                     hir::ItemMod(..) | hir::ItemForeignMod(..) |
268                     hir::ItemImpl(..) | hir::ItemTrait(..) |
269                     hir::ItemStruct(..) | hir::ItemEnum(..) |
270                     hir::ItemUnion(..) | hir::ItemDefaultImpl(..) => {}
271                 }
272             }
273             hir_map::NodeTraitItem(trait_method) => {
274                 match trait_method.node {
275                     hir::TraitItemKind::Const(_, None) |
276                     hir::TraitItemKind::Method(_, hir::TraitMethod::Required(_)) => {
277                         // Keep going, nothing to get exported
278                     }
279                     hir::TraitItemKind::Const(_, Some(body_id)) |
280                     hir::TraitItemKind::Method(_, hir::TraitMethod::Provided(body_id)) => {
281                         self.visit_nested_body(body_id);
282                     }
283                     hir::TraitItemKind::Type(..) => {}
284                 }
285             }
286             hir_map::NodeImplItem(impl_item) => {
287                 match impl_item.node {
288                     hir::ImplItemKind::Const(_, body) => {
289                         self.visit_nested_body(body);
290                     }
291                     hir::ImplItemKind::Method(ref sig, body) => {
292                         let did = self.tcx.hir.get_parent_did(search_item);
293                         if method_might_be_inlined(self.tcx, sig, impl_item, did) {
294                             self.visit_nested_body(body)
295                         }
296                     }
297                     hir::ImplItemKind::Type(_) => {}
298                 }
299             }
300             // Nothing to recurse on for these
301             hir_map::NodeForeignItem(_) |
302             hir_map::NodeVariant(_) |
303             hir_map::NodeStructCtor(_) |
304             hir_map::NodeField(_) |
305             hir_map::NodeTy(_) => {}
306             _ => {
307                 bug!("found unexpected thingy in worklist: {}",
308                      self.tcx.hir.node_to_string(search_item))
309             }
310         }
311     }
312 }
313
314 // Some methods from non-exported (completely private) trait impls still have to be
315 // reachable if they are called from inlinable code. Generally, it's not known until
316 // monomorphization if a specific trait impl item can be reachable or not. So, we
317 // conservatively mark all of them as reachable.
318 // FIXME: One possible strategy for pruning the reachable set is to avoid marking impl
319 // items of non-exported traits (or maybe all local traits?) unless their respective
320 // trait items are used from inlinable code through method call syntax or UFCS, or their
321 // trait is a lang item.
322 struct CollectPrivateImplItemsVisitor<'a, 'tcx: 'a> {
323     tcx: TyCtxt<'a, 'tcx, 'tcx>,
324     access_levels: &'a privacy::AccessLevels,
325     worklist: &'a mut Vec<ast::NodeId>,
326 }
327
328 impl<'a, 'tcx: 'a> ItemLikeVisitor<'tcx> for CollectPrivateImplItemsVisitor<'a, 'tcx> {
329     fn visit_item(&mut self, item: &hir::Item) {
330         // We need only trait impls here, not inherent impls, and only non-exported ones
331         if let hir::ItemImpl(.., Some(ref trait_ref), _, ref impl_item_refs) = item.node {
332             if !self.access_levels.is_reachable(item.id) {
333                 for impl_item_ref in impl_item_refs {
334                     self.worklist.push(impl_item_ref.id.node_id);
335                 }
336
337                 let trait_def_id = match trait_ref.path.def {
338                     Def::Trait(def_id) => def_id,
339                     _ => unreachable!()
340                 };
341
342                 if !trait_def_id.is_local() {
343                     return
344                 }
345
346                 for default_method in self.tcx.provided_trait_methods(trait_def_id) {
347                     let node_id = self.tcx
348                                       .hir
349                                       .as_local_node_id(default_method.def_id)
350                                       .unwrap();
351                     self.worklist.push(node_id);
352                 }
353             }
354         }
355     }
356
357     fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem) {}
358
359     fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem) {
360         // processed in visit_item above
361     }
362 }
363
364 pub fn find_reachable<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> NodeSet {
365     ty::queries::reachable_set::get(tcx, DUMMY_SP, LOCAL_CRATE)
366 }
367
368 fn reachable_set<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, crate_num: CrateNum) -> NodeSet {
369     debug_assert!(crate_num == LOCAL_CRATE);
370
371     let access_levels = &ty::queries::privacy_access_levels::get(tcx, DUMMY_SP, LOCAL_CRATE);
372
373     let any_library = tcx.sess.crate_types.borrow().iter().any(|ty| {
374         *ty == config::CrateTypeRlib || *ty == config::CrateTypeDylib ||
375         *ty == config::CrateTypeProcMacro
376     });
377     let mut reachable_context = ReachableContext {
378         tcx: tcx,
379         tables: &ty::TypeckTables::empty(),
380         reachable_symbols: NodeSet(),
381         worklist: Vec::new(),
382         any_library: any_library,
383     };
384
385     // Step 1: Seed the worklist with all nodes which were found to be public as
386     //         a result of the privacy pass along with all local lang items and impl items.
387     //         If other crates link to us, they're going to expect to be able to
388     //         use the lang items, so we need to be sure to mark them as
389     //         exported.
390     for (id, _) in &access_levels.map {
391         reachable_context.worklist.push(*id);
392     }
393     for item in tcx.lang_items.items().iter() {
394         if let Some(did) = *item {
395             if let Some(node_id) = tcx.hir.as_local_node_id(did) {
396                 reachable_context.worklist.push(node_id);
397             }
398         }
399     }
400     {
401         let mut collect_private_impl_items = CollectPrivateImplItemsVisitor {
402             tcx: tcx,
403             access_levels: access_levels,
404             worklist: &mut reachable_context.worklist,
405         };
406         tcx.hir.krate().visit_all_item_likes(&mut collect_private_impl_items);
407     }
408
409     // Step 2: Mark all symbols that the symbols on the worklist touch.
410     reachable_context.propagate();
411
412     // Return the set of reachable symbols.
413     reachable_context.reachable_symbols
414 }
415
416 pub fn provide(providers: &mut Providers) {
417     *providers = Providers {
418         reachable_set,
419         ..*providers
420     };
421 }