mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-28 20:16:58 +03:00
Merge branch 'master' into format-temporaries
This commit is contained in:
@@ -69,6 +69,7 @@ David Manescu <david.manescu@gmail.com> <dman2626@uni.sydney.edu.au>
|
||||
David Ross <daboross@daboross.net>
|
||||
Derek Chiang <derekchiang93@gmail.com> Derek Chiang (Enchi Jiang) <derekchiang93@gmail.com>
|
||||
Diggory Hardy <diggory.hardy@gmail.com> Diggory Hardy <github@dhardy.name>
|
||||
Dustin Bensing <dustin.bensing@googlemail.com>
|
||||
Dylan Braithwaite <dylanbraithwaite1@gmail.com> <mail@dylanb.me>
|
||||
Dzmitry Malyshau <kvarkus@gmail.com>
|
||||
E. Dunham <edunham@mozilla.com> edunham <edunham@mozilla.com>
|
||||
|
||||
+2
-15
@@ -534,7 +534,7 @@ name = "compiletest"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"env_logger 0.6.2",
|
||||
"env_logger 0.7.0",
|
||||
"getopts",
|
||||
"lazy_static 1.3.0",
|
||||
"libc",
|
||||
@@ -3337,17 +3337,6 @@ dependencies = [
|
||||
"core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_ast_borrowck"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"graphviz",
|
||||
"log",
|
||||
"rustc",
|
||||
"rustc_data_structures",
|
||||
"syntax_pos",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_codegen_llvm"
|
||||
version = "0.0.0"
|
||||
@@ -3420,12 +3409,11 @@ dependencies = [
|
||||
name = "rustc_driver"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"env_logger 0.6.2",
|
||||
"env_logger 0.7.0",
|
||||
"graphviz",
|
||||
"lazy_static 1.3.0",
|
||||
"log",
|
||||
"rustc",
|
||||
"rustc_ast_borrowck",
|
||||
"rustc_codegen_utils",
|
||||
"rustc_data_structures",
|
||||
"rustc_errors",
|
||||
@@ -3483,7 +3471,6 @@ dependencies = [
|
||||
"once_cell",
|
||||
"rustc",
|
||||
"rustc-rayon",
|
||||
"rustc_ast_borrowck",
|
||||
"rustc_codegen_ssa",
|
||||
"rustc_codegen_utils",
|
||||
"rustc_data_structures",
|
||||
|
||||
@@ -47,8 +47,6 @@ Stabilized APIs
|
||||
- [`<*mut T>::cast`]
|
||||
- [`Duration::as_secs_f32`]
|
||||
- [`Duration::as_secs_f64`]
|
||||
- [`Duration::div_duration_f32`]
|
||||
- [`Duration::div_duration_f64`]
|
||||
- [`Duration::div_f32`]
|
||||
- [`Duration::div_f64`]
|
||||
- [`Duration::from_secs_f32`]
|
||||
@@ -100,8 +98,6 @@ Compatibility Notes
|
||||
[`<*mut T>::cast`]: https://doc.rust-lang.org/std/primitive.pointer.html#method.cast
|
||||
[`Duration::as_secs_f32`]: https://doc.rust-lang.org/std/time/struct.Duration.html#method.as_secs_f32
|
||||
[`Duration::as_secs_f64`]: https://doc.rust-lang.org/std/time/struct.Duration.html#method.as_secs_f64
|
||||
[`Duration::div_duration_f32`]: https://doc.rust-lang.org/std/time/struct.Duration.html#method.div_duration_f32
|
||||
[`Duration::div_duration_f64`]: https://doc.rust-lang.org/std/time/struct.Duration.html#method.div_duration_f64
|
||||
[`Duration::div_f32`]: https://doc.rust-lang.org/std/time/struct.Duration.html#method.div_f32
|
||||
[`Duration::div_f64`]: https://doc.rust-lang.org/std/time/struct.Duration.html#method.div_f64
|
||||
[`Duration::from_secs_f32`]: https://doc.rust-lang.org/std/time/struct.Duration.html#method.from_secs_f32
|
||||
|
||||
@@ -580,7 +580,6 @@ pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(map_get_key_value)]
|
||||
/// use std::collections::BTreeMap;
|
||||
///
|
||||
/// let mut map = BTreeMap::new();
|
||||
@@ -588,7 +587,7 @@ pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
|
||||
/// assert_eq!(map.get_key_value(&1), Some((&1, &"a")));
|
||||
/// assert_eq!(map.get_key_value(&2), None);
|
||||
/// ```
|
||||
#[unstable(feature = "map_get_key_value", issue = "49347")]
|
||||
#[stable(feature = "map_get_key_value", since = "1.40.0")]
|
||||
pub fn get_key_value<Q: ?Sized>(&self, k: &Q) -> Option<(&K, &V)>
|
||||
where K: Borrow<Q>,
|
||||
Q: Ord
|
||||
|
||||
@@ -369,6 +369,8 @@
|
||||
//! [drop-guarantee]: #drop-guarantee
|
||||
//! [`poll`]: ../../std/future/trait.Future.html#tymethod.poll
|
||||
//! [`Pin::get_unchecked_mut`]: struct.Pin.html#method.get_unchecked_mut
|
||||
//! [`bool`]: ../../std/primitive.bool.html
|
||||
//! [`i32`]: ../../std/primitive.i32.html
|
||||
|
||||
#![stable(feature = "pin", since = "1.33.0")]
|
||||
|
||||
|
||||
@@ -86,7 +86,6 @@ macro_rules! arena_types {
|
||||
rustc::infer::canonical::QueryResponse<'tcx, rustc::ty::Ty<'tcx>>
|
||||
>,
|
||||
[few] crate_inherent_impls: rustc::ty::CrateInherentImpls,
|
||||
[decode] borrowck: rustc::middle::borrowck::BorrowCheckResult,
|
||||
[few] upstream_monomorphizations:
|
||||
rustc::util::nodemap::DefIdMap<
|
||||
rustc_data_structures::fx::FxHashMap<
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use std::cell::RefCell;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use crate::util::common::MemoizationMap;
|
||||
|
||||
use super::{DepKind, DepNodeIndex, DepGraph};
|
||||
|
||||
/// A DepTrackingMap offers a subset of the `Map` API and ensures that
|
||||
/// we make calls to `read` and `write` as appropriate. We key the
|
||||
/// maps with a unique type for brevity.
|
||||
pub struct DepTrackingMap<M: DepTrackingMapConfig> {
|
||||
phantom: PhantomData<M>,
|
||||
graph: DepGraph,
|
||||
map: FxHashMap<M::Key, (M::Value, DepNodeIndex)>,
|
||||
}
|
||||
|
||||
pub trait DepTrackingMapConfig {
|
||||
type Key: Eq + Hash + Clone;
|
||||
type Value: Clone;
|
||||
fn to_dep_kind() -> DepKind;
|
||||
}
|
||||
|
||||
impl<M: DepTrackingMapConfig> DepTrackingMap<M> {
|
||||
pub fn new(graph: DepGraph) -> DepTrackingMap<M> {
|
||||
DepTrackingMap {
|
||||
phantom: PhantomData,
|
||||
graph,
|
||||
map: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: DepTrackingMapConfig> MemoizationMap for RefCell<DepTrackingMap<M>> {
|
||||
type Key = M::Key;
|
||||
type Value = M::Value;
|
||||
|
||||
/// Memoizes an entry in the dep-tracking-map. If the entry is not
|
||||
/// already present, then `op` will be executed to compute its value.
|
||||
/// The resulting dependency graph looks like this:
|
||||
///
|
||||
/// [op] -> Map(key) -> CurrentTask
|
||||
///
|
||||
/// Here, `[op]` represents whatever nodes `op` reads in the
|
||||
/// course of execution; `Map(key)` represents the node for this
|
||||
/// map, and `CurrentTask` represents the current task when
|
||||
/// `memoize` is invoked.
|
||||
///
|
||||
/// **Important:** when `op` is invoked, the current task will be
|
||||
/// switched to `Map(key)`. Therefore, if `op` makes use of any
|
||||
/// HIR nodes or shared state accessed through its closure
|
||||
/// environment, it must explicitly register a read of that
|
||||
/// state. As an example, see `type_of_item` in `collect`,
|
||||
/// which looks something like this:
|
||||
///
|
||||
/// ```
|
||||
/// fn type_of_item(..., item: &hir::Item) -> Ty<'tcx> {
|
||||
/// let item_def_id = ccx.tcx.hir().local_def_id(it.hir_id);
|
||||
/// ccx.tcx.item_types.memoized(item_def_id, || {
|
||||
/// ccx.tcx.dep_graph.read(DepNode::Hir(item_def_id)); // (*)
|
||||
/// compute_type_of_item(ccx, item)
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The key is the line marked `(*)`: the closure implicitly
|
||||
/// accesses the body of the item `item`, so we register a read
|
||||
/// from `Hir(item_def_id)`.
|
||||
fn memoize<OP>(&self, key: M::Key, op: OP) -> M::Value
|
||||
where OP: FnOnce() -> M::Value
|
||||
{
|
||||
let graph;
|
||||
{
|
||||
let this = self.borrow();
|
||||
if let Some(&(ref result, dep_node)) = this.map.get(&key) {
|
||||
this.graph.read_index(dep_node);
|
||||
return result.clone();
|
||||
}
|
||||
graph = this.graph.clone();
|
||||
}
|
||||
|
||||
let (result, dep_node) = graph.with_anon_task(M::to_dep_kind(), op);
|
||||
self.borrow_mut().map.insert(key, (result.clone(), dep_node));
|
||||
graph.read_index(dep_node);
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -590,7 +590,7 @@ pub fn try_mark_green(
|
||||
// mark it as green by recursively marking all of its
|
||||
// dependencies green.
|
||||
self.try_mark_previous_green(
|
||||
tcx.global_tcx(),
|
||||
tcx,
|
||||
data,
|
||||
prev_index,
|
||||
&dep_node
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
pub mod debug;
|
||||
mod dep_node;
|
||||
mod dep_tracking_map;
|
||||
mod graph;
|
||||
mod prev;
|
||||
mod query;
|
||||
@@ -8,7 +7,6 @@
|
||||
mod serialized;
|
||||
pub mod cgu_reuse_tracker;
|
||||
|
||||
pub use self::dep_tracking_map::{DepTrackingMap, DepTrackingMapConfig};
|
||||
pub use self::dep_node::{DepNode, DepKind, DepConstructor, WorkProductId, RecoverKey, label_strs};
|
||||
pub use self::graph::{DepGraph, WorkProduct, DepNodeIndex, DepNodeColor, TaskDeps, hash_result};
|
||||
pub use self::graph::WorkProductFileKind;
|
||||
|
||||
@@ -93,30 +93,35 @@ struct CheckAttrVisitor<'tcx> {
|
||||
impl CheckAttrVisitor<'tcx> {
|
||||
/// Checks any attribute.
|
||||
fn check_attributes(&self, item: &hir::Item, target: Target) {
|
||||
if target == Target::Fn || target == Target::Const {
|
||||
self.tcx.codegen_fn_attrs(self.tcx.hir().local_def_id(item.hir_id));
|
||||
} else if let Some(a) = item.attrs.iter().find(|a| a.check_name(sym::target_feature)) {
|
||||
self.tcx.sess.struct_span_err(a.span, "attribute should be applied to a function")
|
||||
.span_label(item.span, "not a function")
|
||||
.emit();
|
||||
}
|
||||
|
||||
let mut is_valid = true;
|
||||
for attr in &item.attrs {
|
||||
if attr.check_name(sym::inline) {
|
||||
is_valid &= if attr.check_name(sym::inline) {
|
||||
self.check_inline(attr, &item.span, target)
|
||||
} else if attr.check_name(sym::non_exhaustive) {
|
||||
self.check_non_exhaustive(attr, item, target)
|
||||
} else if attr.check_name(sym::marker) {
|
||||
self.check_marker(attr, item, target)
|
||||
}
|
||||
} else if attr.check_name(sym::target_feature) {
|
||||
self.check_target_feature(attr, item, target)
|
||||
} else {
|
||||
true
|
||||
};
|
||||
}
|
||||
|
||||
if !is_valid {
|
||||
return;
|
||||
}
|
||||
|
||||
if target == Target::Fn {
|
||||
self.tcx.codegen_fn_attrs(self.tcx.hir().local_def_id(item.hir_id));
|
||||
}
|
||||
|
||||
self.check_repr(item, target);
|
||||
self.check_used(item, target);
|
||||
}
|
||||
|
||||
/// Checks if an `#[inline]` is applied to a function or a closure.
|
||||
fn check_inline(&self, attr: &hir::Attribute, span: &Span, target: Target) {
|
||||
/// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid.
|
||||
fn check_inline(&self, attr: &hir::Attribute, span: &Span, target: Target) -> bool {
|
||||
if target != Target::Fn && target != Target::Closure {
|
||||
struct_span_err!(self.tcx.sess,
|
||||
attr.span,
|
||||
@@ -124,13 +129,21 @@ fn check_inline(&self, attr: &hir::Attribute, span: &Span, target: Target) {
|
||||
"attribute should be applied to function or closure")
|
||||
.span_label(*span, "not a function or closure")
|
||||
.emit();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the `#[non_exhaustive]` attribute on an `item` is valid.
|
||||
fn check_non_exhaustive(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
|
||||
/// Checks if the `#[non_exhaustive]` attribute on an `item` is valid. Returns `true` if valid.
|
||||
fn check_non_exhaustive(
|
||||
&self,
|
||||
attr: &hir::Attribute,
|
||||
item: &hir::Item,
|
||||
target: Target,
|
||||
) -> bool {
|
||||
match target {
|
||||
Target::Struct | Target::Enum => { /* Valid */ },
|
||||
Target::Struct | Target::Enum => true,
|
||||
_ => {
|
||||
struct_span_err!(self.tcx.sess,
|
||||
attr.span,
|
||||
@@ -138,25 +151,44 @@ fn check_non_exhaustive(&self, attr: &hir::Attribute, item: &hir::Item, target:
|
||||
"attribute can only be applied to a struct or enum")
|
||||
.span_label(item.span, "not a struct or enum")
|
||||
.emit();
|
||||
return;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the `#[marker]` attribute on an `item` is valid.
|
||||
fn check_marker(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
|
||||
/// Checks if the `#[marker]` attribute on an `item` is valid. Returns `true` if valid.
|
||||
fn check_marker(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) -> bool {
|
||||
match target {
|
||||
Target::Trait => { /* Valid */ },
|
||||
Target::Trait => true,
|
||||
_ => {
|
||||
self.tcx.sess
|
||||
.struct_span_err(attr.span, "attribute can only be applied to a trait")
|
||||
.span_label(item.span, "not a trait")
|
||||
.emit();
|
||||
return;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid.
|
||||
fn check_target_feature(
|
||||
&self,
|
||||
attr: &hir::Attribute,
|
||||
item: &hir::Item,
|
||||
target: Target,
|
||||
) -> bool {
|
||||
match target {
|
||||
Target::Fn => true,
|
||||
_ => {
|
||||
self.tcx.sess
|
||||
.struct_span_err(attr.span, "attribute should be applied to a function")
|
||||
.span_label(item.span, "not a function")
|
||||
.emit();
|
||||
false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the `#[repr]` attributes on `item` are valid.
|
||||
fn check_repr(&self, item: &hir::Item, target: Target) {
|
||||
// Extract the names of all repr hints, e.g., [foo, bar, align] for:
|
||||
|
||||
@@ -705,7 +705,6 @@ fn generator_movability_for_fn(
|
||||
E0628,
|
||||
"generators cannot have explicit parameters"
|
||||
);
|
||||
self.sess.abort_if_errors();
|
||||
}
|
||||
Some(match movability {
|
||||
Movability::Movable => hir::GeneratorMovability::Movable,
|
||||
@@ -998,7 +997,7 @@ fn lower_expr_yield(&mut self, span: Span, opt_expr: Option<&Expr>) -> hir::Expr
|
||||
E0727,
|
||||
"`async` generators are not yet supported",
|
||||
);
|
||||
self.sess.abort_if_errors();
|
||||
return hir::ExprKind::Err;
|
||||
},
|
||||
None => self.generator_kind = Some(hir::GeneratorKind::Gen),
|
||||
}
|
||||
|
||||
+135
-108
@@ -23,8 +23,6 @@
|
||||
use syntax::ext::base::MacroKind;
|
||||
use syntax_pos::{Span, DUMMY_SP};
|
||||
|
||||
use std::result::Result::Err;
|
||||
|
||||
pub mod blocks;
|
||||
mod collector;
|
||||
mod def_collector;
|
||||
@@ -183,6 +181,44 @@ pub struct Map<'hir> {
|
||||
hir_to_node_id: FxHashMap<HirId, NodeId>,
|
||||
}
|
||||
|
||||
struct ParentHirIterator<'map> {
|
||||
current_id: HirId,
|
||||
map: &'map Map<'map>,
|
||||
}
|
||||
|
||||
impl<'map> ParentHirIterator<'map> {
|
||||
fn new(current_id: HirId, map: &'map Map<'map>) -> ParentHirIterator<'map> {
|
||||
ParentHirIterator {
|
||||
current_id,
|
||||
map,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map> Iterator for ParentHirIterator<'map> {
|
||||
type Item = (HirId, Node<'map>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.current_id == CRATE_HIR_ID {
|
||||
return None;
|
||||
}
|
||||
loop { // There are nodes that do not have entries, so we need to skip them.
|
||||
let parent_id = self.map.get_parent_node(self.current_id);
|
||||
|
||||
if parent_id == self.current_id {
|
||||
self.current_id = CRATE_HIR_ID;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.current_id = parent_id;
|
||||
if let Some(entry) = self.map.find_entry(parent_id) {
|
||||
return Some((parent_id, entry.node));
|
||||
}
|
||||
// If this `HirId` doesn't have an `Entry`, skip it and look for its `parent_id`.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'hir> Map<'hir> {
|
||||
#[inline]
|
||||
fn lookup(&self, id: HirId) -> Option<&Entry<'hir>> {
|
||||
@@ -682,45 +718,6 @@ pub fn is_hir_id_module(&self, hir_id: HirId) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// If there is some error when walking the parents (e.g., a node does not
|
||||
/// have a parent in the map or a node can't be found), then we return the
|
||||
/// last good `HirId` we found. Note that reaching the crate root (`id == 0`),
|
||||
/// is not an error, since items in the crate module have the crate root as
|
||||
/// parent.
|
||||
fn walk_parent_nodes<F, F2>(&self,
|
||||
start_id: HirId,
|
||||
found: F,
|
||||
bail_early: F2)
|
||||
-> Result<HirId, HirId>
|
||||
where F: Fn(&Node<'hir>) -> bool, F2: Fn(&Node<'hir>) -> bool
|
||||
{
|
||||
let mut id = start_id;
|
||||
loop {
|
||||
let parent_id = self.get_parent_node(id);
|
||||
if parent_id == CRATE_HIR_ID {
|
||||
return Ok(CRATE_HIR_ID);
|
||||
}
|
||||
if parent_id == id {
|
||||
return Err(id);
|
||||
}
|
||||
|
||||
if let Some(entry) = self.find_entry(parent_id) {
|
||||
if let Node::Crate = entry.node {
|
||||
return Err(id);
|
||||
}
|
||||
if found(&entry.node) {
|
||||
return Ok(parent_id);
|
||||
} else if bail_early(&entry.node) {
|
||||
return Err(parent_id);
|
||||
}
|
||||
id = parent_id;
|
||||
} else {
|
||||
return Err(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the `HirId` for `id`'s enclosing method, unless there's a
|
||||
/// `while` or `loop` before reaching it, as block tail returns are not
|
||||
/// available in them.
|
||||
@@ -744,29 +741,46 @@ fn walk_parent_nodes<F, F2>(&self,
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_return_block(&self, id: HirId) -> Option<HirId> {
|
||||
let match_fn = |node: &Node<'_>| {
|
||||
match *node {
|
||||
let mut iter = ParentHirIterator::new(id, &self).peekable();
|
||||
let mut ignore_tail = false;
|
||||
if let Some(entry) = self.find_entry(id) {
|
||||
if let Node::Expr(Expr { kind: ExprKind::Ret(_), .. }) = entry.node {
|
||||
// When dealing with `return` statements, we don't care about climbing only tail
|
||||
// expressions.
|
||||
ignore_tail = true;
|
||||
}
|
||||
}
|
||||
while let Some((hir_id, node)) = iter.next() {
|
||||
if let (Some((_, next_node)), false) = (iter.peek(), ignore_tail) {
|
||||
match next_node {
|
||||
Node::Block(Block { expr: None, .. }) => return None,
|
||||
Node::Block(Block { expr: Some(expr), .. }) => {
|
||||
if hir_id != expr.hir_id {
|
||||
// The current node is not the tail expression of its parent.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
match node {
|
||||
Node::Item(_) |
|
||||
Node::ForeignItem(_) |
|
||||
Node::TraitItem(_) |
|
||||
Node::Expr(Expr { kind: ExprKind::Closure(..), ..}) |
|
||||
Node::ImplItem(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
let match_non_returning_block = |node: &Node<'_>| {
|
||||
match *node {
|
||||
Node::ImplItem(_) => return Some(hir_id),
|
||||
Node::Expr(ref expr) => {
|
||||
match expr.kind {
|
||||
ExprKind::Loop(..) | ExprKind::Ret(..) => true,
|
||||
_ => false,
|
||||
// Ignore `return`s on the first iteration
|
||||
ExprKind::Loop(..) | ExprKind::Ret(..) => return None,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
Node::Local(_) => return None,
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
self.walk_parent_nodes(id, match_fn, match_non_returning_block).ok()
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Retrieves the `HirId` for `id`'s parent item, or `id` itself if no
|
||||
@@ -774,16 +788,17 @@ pub fn get_return_block(&self, id: HirId) -> Option<HirId> {
|
||||
/// in the HIR which is recorded by the map and is an item, either an item
|
||||
/// in a module, trait, or impl.
|
||||
pub fn get_parent_item(&self, hir_id: HirId) -> HirId {
|
||||
match self.walk_parent_nodes(hir_id, |node| match *node {
|
||||
Node::Item(_) |
|
||||
Node::ForeignItem(_) |
|
||||
Node::TraitItem(_) |
|
||||
Node::ImplItem(_) => true,
|
||||
_ => false,
|
||||
}, |_| false) {
|
||||
Ok(id) => id,
|
||||
Err(id) => id,
|
||||
for (hir_id, node) in ParentHirIterator::new(hir_id, &self) {
|
||||
match node {
|
||||
Node::Crate |
|
||||
Node::Item(_) |
|
||||
Node::ForeignItem(_) |
|
||||
Node::TraitItem(_) |
|
||||
Node::ImplItem(_) => return hir_id,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
hir_id
|
||||
}
|
||||
|
||||
/// Returns the `DefId` of `id`'s nearest module parent, or `id` itself if no
|
||||
@@ -795,60 +810,64 @@ pub fn get_module_parent(&self, id: HirId) -> DefId {
|
||||
/// Returns the `HirId` of `id`'s nearest module parent, or `id` itself if no
|
||||
/// module parent is in this map.
|
||||
pub fn get_module_parent_node(&self, hir_id: HirId) -> HirId {
|
||||
match self.walk_parent_nodes(hir_id, |node| match *node {
|
||||
Node::Item(&Item { kind: ItemKind::Mod(_), .. }) => true,
|
||||
_ => false,
|
||||
}, |_| false) {
|
||||
Ok(id) => id,
|
||||
Err(id) => id,
|
||||
for (hir_id, node) in ParentHirIterator::new(hir_id, &self) {
|
||||
if let Node::Item(&Item { kind: ItemKind::Mod(_), .. }) = node {
|
||||
return hir_id;
|
||||
}
|
||||
}
|
||||
CRATE_HIR_ID
|
||||
}
|
||||
|
||||
/// Returns the nearest enclosing scope. A scope is roughly an item or block.
|
||||
pub fn get_enclosing_scope(&self, hir_id: HirId) -> Option<HirId> {
|
||||
self.walk_parent_nodes(hir_id, |node| match *node {
|
||||
Node::Item(i) => {
|
||||
match i.kind {
|
||||
ItemKind::Fn(..)
|
||||
| ItemKind::Mod(..)
|
||||
| ItemKind::Enum(..)
|
||||
| ItemKind::Struct(..)
|
||||
| ItemKind::Union(..)
|
||||
| ItemKind::Trait(..)
|
||||
| ItemKind::Impl(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::ForeignItem(fi) => {
|
||||
match fi.kind {
|
||||
ForeignItemKind::Fn(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::TraitItem(ti) => {
|
||||
match ti.kind {
|
||||
TraitItemKind::Method(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::ImplItem(ii) => {
|
||||
match ii.kind {
|
||||
ImplItemKind::Method(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::Block(_) => true,
|
||||
_ => false,
|
||||
}, |_| false).ok()
|
||||
for (hir_id, node) in ParentHirIterator::new(hir_id, &self) {
|
||||
if match node {
|
||||
Node::Item(i) => {
|
||||
match i.kind {
|
||||
ItemKind::Fn(..)
|
||||
| ItemKind::Mod(..)
|
||||
| ItemKind::Enum(..)
|
||||
| ItemKind::Struct(..)
|
||||
| ItemKind::Union(..)
|
||||
| ItemKind::Trait(..)
|
||||
| ItemKind::Impl(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::ForeignItem(fi) => {
|
||||
match fi.kind {
|
||||
ForeignItemKind::Fn(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::TraitItem(ti) => {
|
||||
match ti.kind {
|
||||
TraitItemKind::Method(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::ImplItem(ii) => {
|
||||
match ii.kind {
|
||||
ImplItemKind::Method(..) => true,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
Node::Block(_) => true,
|
||||
_ => false,
|
||||
} {
|
||||
return Some(hir_id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the defining scope for an opaque type definition.
|
||||
pub fn get_defining_scope(&self, id: HirId) -> Option<HirId> {
|
||||
pub fn get_defining_scope(&self, id: HirId) -> HirId {
|
||||
let mut scope = id;
|
||||
loop {
|
||||
scope = self.get_enclosing_scope(scope)?;
|
||||
scope = self.get_enclosing_scope(scope).unwrap_or(CRATE_HIR_ID);
|
||||
if scope == CRATE_HIR_ID {
|
||||
return Some(CRATE_HIR_ID);
|
||||
return CRATE_HIR_ID;
|
||||
}
|
||||
match self.get(scope) {
|
||||
Node::Item(i) => {
|
||||
@@ -861,7 +880,7 @@ pub fn get_defining_scope(&self, id: HirId) -> Option<HirId> {
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Some(scope)
|
||||
scope
|
||||
}
|
||||
|
||||
pub fn get_parent_did(&self, id: HirId) -> DefId {
|
||||
@@ -1064,6 +1083,14 @@ pub fn span_if_local(&self, id: DefId) -> Option<Span> {
|
||||
self.as_local_hir_id(id).map(|id| self.span(id))
|
||||
}
|
||||
|
||||
pub fn res_span(&self, res: Res) -> Option<Span> {
|
||||
match res {
|
||||
Res::Err => None,
|
||||
Res::Local(id) => Some(self.span(id)),
|
||||
res => self.span_if_local(res.opt_def_id()?),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node_to_string(&self, id: HirId) -> String {
|
||||
hir_id_to_string(self, id, true)
|
||||
}
|
||||
|
||||
+1
-12
@@ -1291,17 +1291,6 @@ pub struct Arm {
|
||||
pub body: P<Expr>,
|
||||
}
|
||||
|
||||
impl Arm {
|
||||
// HACK(or_patterns; Centril | dlrobertson): Remove this and
|
||||
// correctly handle each case in which this method is used.
|
||||
pub fn top_pats_hack(&self) -> &[P<Pat>] {
|
||||
match &self.pat.kind {
|
||||
PatKind::Or(pats) => pats,
|
||||
_ => std::slice::from_ref(&self.pat),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RustcEncodable, RustcDecodable, Debug, HashStable)]
|
||||
pub enum Guard {
|
||||
If(P<Expr>),
|
||||
@@ -1563,7 +1552,7 @@ pub enum ExprKind {
|
||||
/// Thus, `x.foo::<Bar, Baz>(a, b, c, d)` is represented as
|
||||
/// `ExprKind::MethodCall(PathSegment { foo, [Bar, Baz] }, [x, a, b, c, d])`.
|
||||
MethodCall(P<PathSegment>, Span, HirVec<Expr>),
|
||||
/// A tuple (e.g., `(a, b, c ,d)`).
|
||||
/// A tuple (e.g., `(a, b, c, d)`).
|
||||
Tup(HirVec<Expr>),
|
||||
/// A binary operation (e.g., `a + b`, `a * b`).
|
||||
Binary(BinOp, P<Expr>, P<Expr>),
|
||||
|
||||
@@ -93,6 +93,8 @@ pub fn suppressed(self) -> bool {
|
||||
/// checks, so we should ignore errors if NLL is (unconditionally)
|
||||
/// enabled.
|
||||
pub fn when_nll_is_enabled(tcx: TyCtxt<'_>) -> Self {
|
||||
// FIXME(Centril): Once we actually remove `::Migrate` also make
|
||||
// this always `true` and then proceed to eliminate the dead code.
|
||||
match tcx.borrowck_mode() {
|
||||
// If we're on Migrate mode, report AST region errors
|
||||
BorrowckMode::Migrate => SuppressRegionErrors { suppressed: false },
|
||||
@@ -1460,7 +1462,7 @@ pub fn type_is_copy_modulo_regions(
|
||||
// type-checking closure types are in local tables only.
|
||||
if !self.in_progress_tables.is_some() || !ty.has_closure_types() {
|
||||
if !(param_env, ty).has_local_value() {
|
||||
return ty.is_copy_modulo_regions(self.tcx.global_tcx(), param_env, span);
|
||||
return ty.is_copy_modulo_regions(self.tcx, param_env, span);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -561,15 +561,13 @@ pub fn infer_opaque_definition_from_instantiation(
|
||||
def_id, instantiated_ty
|
||||
);
|
||||
|
||||
let gcx = self.tcx.global_tcx();
|
||||
|
||||
// Use substs to build up a reverse map from regions to their
|
||||
// identity mappings. This is necessary because of `impl
|
||||
// Trait` lifetimes are computed by replacing existing
|
||||
// lifetimes with 'static and remapping only those used in the
|
||||
// `impl Trait` return type, resulting in the parameters
|
||||
// shifting.
|
||||
let id_substs = InternalSubsts::identity_for_item(gcx, def_id);
|
||||
let id_substs = InternalSubsts::identity_for_item(self.tcx, def_id);
|
||||
let map: FxHashMap<GenericArg<'tcx>, GenericArg<'tcx>> = opaque_defn
|
||||
.substs
|
||||
.iter()
|
||||
@@ -854,7 +852,7 @@ fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
|
||||
)
|
||||
.emit();
|
||||
|
||||
self.tcx().global_tcx().mk_region(ty::ReStatic)
|
||||
self.tcx().mk_region(ty::ReStatic)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1215,7 +1213,7 @@ pub fn may_define_opaque_type(
|
||||
let mut hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
|
||||
|
||||
// Named opaque types can be defined by any siblings or children of siblings.
|
||||
let scope = tcx.hir().get_defining_scope(opaque_hir_id).expect("could not get defining scope");
|
||||
let scope = tcx.hir().get_defining_scope(opaque_hir_id);
|
||||
// We walk up the node tree until we hit the root or the scope of the opaque type.
|
||||
while hir_id != scope && hir_id != hir::CRATE_HIR_ID {
|
||||
hir_id = tcx.hir().get_parent_item(hir_id);
|
||||
|
||||
@@ -100,7 +100,6 @@
|
||||
pub mod lint;
|
||||
|
||||
pub mod middle {
|
||||
pub mod borrowck;
|
||||
pub mod expr_use_visitor;
|
||||
pub mod cstore;
|
||||
pub mod dead;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
use crate::ich::StableHashingContext;
|
||||
|
||||
use rustc_data_structures::stable_hasher::{HashStable, StableHasher,
|
||||
StableHasherResult};
|
||||
|
||||
#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable)]
|
||||
pub enum SignalledError { SawSomeError, NoErrorsSeen }
|
||||
|
||||
impl Default for SignalledError {
|
||||
fn default() -> SignalledError {
|
||||
SignalledError::NoErrorsSeen
|
||||
}
|
||||
}
|
||||
|
||||
impl_stable_hash_for!(enum self::SignalledError { SawSomeError, NoErrorsSeen });
|
||||
|
||||
#[derive(Debug, Default, RustcEncodable, RustcDecodable)]
|
||||
pub struct BorrowCheckResult {
|
||||
pub signalled_any_error: SignalledError,
|
||||
}
|
||||
|
||||
impl<'a> HashStable<StableHashingContext<'a>> for BorrowCheckResult {
|
||||
fn hash_stable<W: StableHasherResult>(&self,
|
||||
hcx: &mut StableHashingContext<'a>,
|
||||
hasher: &mut StableHasher<W>) {
|
||||
let BorrowCheckResult {
|
||||
ref signalled_any_error,
|
||||
} = *self;
|
||||
signalled_any_error.hash_stable(hcx, hasher);
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ fn check_transmute(&self, span: Span, from: Ty<'tcx>, to: Ty<'tcx>) {
|
||||
|
||||
// Special-case transmutting from `typeof(function)` and
|
||||
// `Option<typeof(function)>` to present a clearer error.
|
||||
let from = unpack_option_like(self.tcx.global_tcx(), from);
|
||||
let from = unpack_option_like(self.tcx, from);
|
||||
if let (&ty::FnDef(..), SizeSkeleton::Known(size_to)) = (&from.kind, sk_to) {
|
||||
if size_to == Pointer.size(&self.tcx) {
|
||||
struct_span_err!(self.tcx.sess, span, E0591,
|
||||
|
||||
@@ -749,7 +749,7 @@ fn cat_upvar(
|
||||
.unwrap_or(ty::ClosureKind::LATTICE_BOTTOM),
|
||||
|
||||
None =>
|
||||
closure_substs.closure_kind(closure_def_id, self.tcx.global_tcx()),
|
||||
closure_substs.closure_kind(closure_def_id, self.tcx),
|
||||
}
|
||||
}
|
||||
_ => span_bug!(span, "unexpected type for fn in mem_categorization: {:?}", ty),
|
||||
|
||||
@@ -1504,7 +1504,7 @@ pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
|
||||
Goto { .. } => vec!["".into()],
|
||||
SwitchInt { ref values, switch_ty, .. } => ty::tls::with(|tcx| {
|
||||
let param_env = ty::ParamEnv::empty();
|
||||
let switch_ty = tcx.lift_to_global(&switch_ty).unwrap();
|
||||
let switch_ty = tcx.lift(&switch_ty).unwrap();
|
||||
let size = tcx.layout_of(param_env.and(switch_ty)).unwrap().size;
|
||||
values
|
||||
.iter()
|
||||
|
||||
@@ -397,10 +397,6 @@
|
||||
}
|
||||
|
||||
BorrowChecking {
|
||||
query borrowck(key: DefId) -> &'tcx BorrowCheckResult {
|
||||
cache_on_disk_if { key.is_local() }
|
||||
}
|
||||
|
||||
/// Borrow-checks the function body. If this is a closure, returns
|
||||
/// additional requirements that the closure's creator must verify.
|
||||
query mir_borrowck(key: DefId) -> mir::BorrowCheckResult<'tcx> {
|
||||
@@ -469,7 +465,7 @@
|
||||
}
|
||||
|
||||
TypeChecking {
|
||||
query check_match(key: DefId) -> SignalledError {
|
||||
query check_match(key: DefId) {
|
||||
cache_on_disk_if { key.is_local() }
|
||||
}
|
||||
|
||||
|
||||
@@ -478,14 +478,6 @@ pub fn migrate(self) -> bool {
|
||||
BorrowckMode::Migrate => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether we should emit the AST-based borrow checker errors.
|
||||
pub fn use_ast(self) -> bool {
|
||||
match self {
|
||||
BorrowckMode::Mir => false,
|
||||
BorrowckMode::Migrate => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Input {
|
||||
@@ -1268,14 +1260,6 @@ fn parse_symbol_mangling_version(
|
||||
save_analysis: bool = (false, parse_bool, [UNTRACKED],
|
||||
"write syntax and type analysis (in JSON format) information, in \
|
||||
addition to normal output"),
|
||||
flowgraph_print_loans: bool = (false, parse_bool, [UNTRACKED],
|
||||
"include loan analysis data in -Z unpretty flowgraph output"),
|
||||
flowgraph_print_moves: bool = (false, parse_bool, [UNTRACKED],
|
||||
"include move analysis data in -Z unpretty flowgraph output"),
|
||||
flowgraph_print_assigns: bool = (false, parse_bool, [UNTRACKED],
|
||||
"include assignment analysis data in -Z unpretty flowgraph output"),
|
||||
flowgraph_print_all: bool = (false, parse_bool, [UNTRACKED],
|
||||
"include all dataflow analysis data in -Z unpretty flowgraph output"),
|
||||
print_region_graph: bool = (false, parse_bool, [UNTRACKED],
|
||||
"prints region inference graph. \
|
||||
Use with RUST_REGION_GRAPH=help for more info"),
|
||||
@@ -1424,8 +1408,6 @@ fn parse_symbol_mangling_version(
|
||||
valid types are any of the types for `--pretty`, as well as:
|
||||
`expanded`, `expanded,identified`,
|
||||
`expanded,hygiene` (with internal representations),
|
||||
`flowgraph=<nodeid>` (graphviz formatted flowgraph for node),
|
||||
`flowgraph,unlabelled=<nodeid>` (unlabelled graphviz formatted flowgraph for node),
|
||||
`everybody_loops` (all function bodies replaced with `loop {}`),
|
||||
`hir` (the HIR), `hir,identified`,
|
||||
`hir,typed` (HIR with types for each node),
|
||||
|
||||
@@ -589,14 +589,6 @@ fn test_debugging_options_tracking_hash() {
|
||||
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
|
||||
opts.debugging_opts.save_analysis = true;
|
||||
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
|
||||
opts.debugging_opts.flowgraph_print_loans = true;
|
||||
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
|
||||
opts.debugging_opts.flowgraph_print_moves = true;
|
||||
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
|
||||
opts.debugging_opts.flowgraph_print_assigns = true;
|
||||
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
|
||||
opts.debugging_opts.flowgraph_print_all = true;
|
||||
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
|
||||
opts.debugging_opts.print_region_graph = true;
|
||||
assert_eq!(reference.dep_tracking_hash(), opts.dep_tracking_hash());
|
||||
opts.debugging_opts.parse_only = true;
|
||||
|
||||
@@ -108,7 +108,7 @@ fn select_where_possible(
|
||||
goal: obligation.goal.predicate,
|
||||
}, &mut orig_values);
|
||||
|
||||
match infcx.tcx.global_tcx().evaluate_goal(canonical_goal) {
|
||||
match infcx.tcx.evaluate_goal(canonical_goal) {
|
||||
Ok(response) => {
|
||||
if response.is_proven() {
|
||||
making_progress = true;
|
||||
|
||||
@@ -3,12 +3,10 @@
|
||||
// seems likely that they should eventually be merged into more
|
||||
// general routines.
|
||||
|
||||
use crate::dep_graph::{DepKind, DepTrackingMapConfig};
|
||||
use std::marker::PhantomData;
|
||||
use crate::infer::InferCtxt;
|
||||
use crate::traits::{FulfillmentContext, Obligation, ObligationCause, SelectionContext,
|
||||
TraitEngine, Vtable};
|
||||
use crate::ty::{self, Ty, TyCtxt};
|
||||
use crate::ty::{self, TyCtxt};
|
||||
use crate::ty::subst::{Subst, SubstsRef};
|
||||
use crate::ty::fold::TypeFoldable;
|
||||
|
||||
@@ -100,33 +98,8 @@ pub fn subst_and_normalize_erasing_regions<T>(
|
||||
}
|
||||
}
|
||||
|
||||
// Implement DepTrackingMapConfig for `trait_cache`
|
||||
pub struct TraitSelectionCache<'tcx> {
|
||||
data: PhantomData<&'tcx ()>
|
||||
}
|
||||
|
||||
impl<'tcx> DepTrackingMapConfig for TraitSelectionCache<'tcx> {
|
||||
type Key = (ty::ParamEnv<'tcx>, ty::PolyTraitRef<'tcx>);
|
||||
type Value = Vtable<'tcx, ()>;
|
||||
fn to_dep_kind() -> DepKind {
|
||||
DepKind::TraitSelect
|
||||
}
|
||||
}
|
||||
|
||||
// # Global Cache
|
||||
|
||||
pub struct ProjectionCache<'tcx> {
|
||||
data: PhantomData<&'tcx ()>,
|
||||
}
|
||||
|
||||
impl<'tcx> DepTrackingMapConfig for ProjectionCache<'tcx> {
|
||||
type Key = Ty<'tcx>;
|
||||
type Value = Ty<'tcx>;
|
||||
fn to_dep_kind() -> DepKind {
|
||||
DepKind::TraitSelect
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
|
||||
/// Finishes processes any obligations that remain in the
|
||||
/// fulfillment context, and then returns the result with all type
|
||||
|
||||
@@ -497,7 +497,7 @@ fn report_similar_impl_candidates(&self,
|
||||
4
|
||||
};
|
||||
|
||||
let normalize = |candidate| self.tcx.global_tcx().infer_ctxt().enter(|ref infcx| {
|
||||
let normalize = |candidate| self.tcx.infer_ctxt().enter(|ref infcx| {
|
||||
let normalized = infcx
|
||||
.at(&ObligationCause::dummy(), ty::ParamEnv::empty())
|
||||
.normalize(candidate)
|
||||
@@ -783,8 +783,7 @@ pub fn report_selection_error(
|
||||
}
|
||||
|
||||
ty::Predicate::ObjectSafe(trait_def_id) => {
|
||||
let violations = self.tcx.global_tcx()
|
||||
.object_safety_violations(trait_def_id);
|
||||
let violations = self.tcx.object_safety_violations(trait_def_id);
|
||||
if let Some(err) = self.tcx.report_object_safety_error(
|
||||
span,
|
||||
trait_def_id,
|
||||
@@ -920,7 +919,7 @@ pub fn report_selection_error(
|
||||
}
|
||||
|
||||
TraitNotObjectSafe(did) => {
|
||||
let violations = self.tcx.global_tcx().object_safety_violations(did);
|
||||
let violations = self.tcx.object_safety_violations(did);
|
||||
if let Some(err) = self.tcx.report_object_safety_error(span, did, violations) {
|
||||
err
|
||||
} else {
|
||||
|
||||
@@ -495,7 +495,7 @@ fn process_obligation(
|
||||
} else {
|
||||
if !substs.has_local_value() {
|
||||
let instance = ty::Instance::resolve(
|
||||
self.selcx.tcx().global_tcx(),
|
||||
self.selcx.tcx(),
|
||||
obligation.param_env,
|
||||
def_id,
|
||||
substs,
|
||||
|
||||
@@ -40,12 +40,11 @@ pub fn dropck_outlives(&self, ty: Ty<'tcx>) -> InferOk<'tcx, Vec<GenericArg<'tcx
|
||||
};
|
||||
}
|
||||
|
||||
let gcx = tcx.global_tcx();
|
||||
let mut orig_values = OriginalQueryValues::default();
|
||||
let c_ty = self.infcx.canonicalize_query(&self.param_env.and(ty), &mut orig_values);
|
||||
let span = self.cause.span;
|
||||
debug!("c_ty = {:?}", c_ty);
|
||||
if let Ok(result) = &gcx.dropck_outlives(c_ty) {
|
||||
if let Ok(result) = &tcx.dropck_outlives(c_ty) {
|
||||
if result.is_proven() {
|
||||
if let Ok(InferOk { value, obligations }) =
|
||||
self.infcx.instantiate_query_response_and_region_obligations(
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn evaluate_obligation(
|
||||
// Run canonical query. If overflow occurs, rerun from scratch but this time
|
||||
// in standard trait query mode so that overflow is handled appropriately
|
||||
// within `SelectionContext`.
|
||||
self.tcx.global_tcx().evaluate_obligation(c_pred)
|
||||
self.tcx.evaluate_obligation(c_pred)
|
||||
}
|
||||
|
||||
// Helper function that canonicalizes and runs the query. If an
|
||||
|
||||
@@ -141,7 +141,7 @@ fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
// binder). It would be better to normalize in a
|
||||
// binding-aware fashion.
|
||||
|
||||
let gcx = self.infcx.tcx.global_tcx();
|
||||
let tcx = self.infcx.tcx;
|
||||
|
||||
let mut orig_values = OriginalQueryValues::default();
|
||||
// HACK(matthewjasper) `'static` is special-cased in selection,
|
||||
@@ -150,7 +150,7 @@ fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
|
||||
&self.param_env.and(*data), &mut orig_values);
|
||||
debug!("QueryNormalizer: c_data = {:#?}", c_data);
|
||||
debug!("QueryNormalizer: orig_values = {:#?}", orig_values);
|
||||
match gcx.normalize_projection_ty(c_data) {
|
||||
match tcx.normalize_projection_ty(c_data) {
|
||||
Ok(result) => {
|
||||
// We don't expect ambiguity.
|
||||
if result.is_ambiguous() {
|
||||
|
||||
@@ -97,7 +97,7 @@ pub fn implied_outlives_bounds(
|
||||
|
||||
let mut orig_values = OriginalQueryValues::default();
|
||||
let key = self.canonicalize_query(¶m_env.and(ty), &mut orig_values);
|
||||
let result = match self.tcx.global_tcx().implied_outlives_bounds(key) {
|
||||
let result = match self.tcx.implied_outlives_bounds(key) {
|
||||
Ok(r) => r,
|
||||
Err(NoSolution) => {
|
||||
self.tcx.sess.delay_span_bug(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infer::canonical::{Canonical, Canonicalized, CanonicalizedQueryResponse, QueryResponse};
|
||||
use crate::infer::canonical::{Canonicalized, CanonicalizedQueryResponse};
|
||||
use crate::traits::query::Fallible;
|
||||
use crate::hir::def_id::DefId;
|
||||
use crate::ty::{ParamEnvAnd, Ty, TyCtxt};
|
||||
@@ -37,12 +37,6 @@ fn perform_query(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, ()>> {
|
||||
tcx.type_op_ascribe_user_type(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, ()>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, ()>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infer::canonical::{Canonical, Canonicalized, CanonicalizedQueryResponse, QueryResponse};
|
||||
use crate::infer::canonical::{Canonicalized, CanonicalizedQueryResponse};
|
||||
use crate::traits::query::Fallible;
|
||||
use crate::ty::{ParamEnvAnd, Ty, TyCtxt};
|
||||
|
||||
@@ -34,12 +34,6 @@ fn perform_query(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, ()>> {
|
||||
tcx.type_op_eq(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, ()>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, ()>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infer::canonical::{Canonical, Canonicalized, CanonicalizedQueryResponse, QueryResponse};
|
||||
use crate::infer::canonical::{Canonicalized, CanonicalizedQueryResponse};
|
||||
use crate::traits::query::outlives_bounds::OutlivesBound;
|
||||
use crate::traits::query::Fallible;
|
||||
use crate::ty::{ParamEnvAnd, Ty, TyCtxt};
|
||||
@@ -38,12 +38,6 @@ fn perform_query(
|
||||
|
||||
tcx.implied_outlives_bounds(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, Self::QueryResponse>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self::QueryResponse>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::infer::canonical::{
|
||||
Canonical, Canonicalized, CanonicalizedQueryResponse, OriginalQueryValues,
|
||||
QueryRegionConstraints, QueryResponse,
|
||||
Canonicalized, CanonicalizedQueryResponse, OriginalQueryValues,
|
||||
QueryRegionConstraints,
|
||||
};
|
||||
use crate::infer::{InferCtxt, InferOk};
|
||||
use std::fmt;
|
||||
@@ -66,22 +66,6 @@ fn perform_query(
|
||||
canonicalized: Canonicalized<'tcx, ParamEnvAnd<'tcx, Self>>,
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, Self::QueryResponse>>;
|
||||
|
||||
/// Casts a lifted query result (which is in the gcx lifetime)
|
||||
/// into the tcx lifetime. This is always just an identity cast,
|
||||
/// but the generic code doesn't realize it -- put another way, in
|
||||
/// the generic code, we have a `Lifted<'tcx, Self::QueryResponse>`
|
||||
/// and we want to convert that to a `Self::QueryResponse`. This is
|
||||
/// not a priori valid, so we can't do it -- but in practice, it
|
||||
/// is always a no-op (e.g., the lifted form of a type,
|
||||
/// `Ty<'tcx>`, is a subtype of `Ty<'tcx>`). So we have to push
|
||||
/// the operation into the impls that know more specifically what
|
||||
/// `QueryResponse` is. This operation would (maybe) be nicer with
|
||||
/// something like HKTs or GATs, since then we could make
|
||||
/// `QueryResponse` parametric and `'tcx` and `'tcx` etc.
|
||||
fn shrink_to_tcx_lifetime(
|
||||
lifted_query_result: &'a CanonicalizedQueryResponse<'tcx, Self::QueryResponse>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self::QueryResponse>>;
|
||||
|
||||
fn fully_perform_into(
|
||||
query_key: ParamEnvAnd<'tcx, Self>,
|
||||
infcx: &InferCtxt<'_, 'tcx>,
|
||||
@@ -99,7 +83,6 @@ fn fully_perform_into(
|
||||
let canonical_self =
|
||||
infcx.canonicalize_hr_query_hack(&query_key, &mut canonical_var_values);
|
||||
let canonical_result = Self::perform_query(infcx.tcx, canonical_self)?;
|
||||
let canonical_result = Self::shrink_to_tcx_lifetime(&canonical_result);
|
||||
|
||||
let param_env = query_key.param_env;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infer::canonical::{Canonical, Canonicalized, CanonicalizedQueryResponse, QueryResponse};
|
||||
use crate::infer::canonical::{Canonicalized, CanonicalizedQueryResponse};
|
||||
use std::fmt;
|
||||
use crate::traits::query::Fallible;
|
||||
use crate::ty::fold::TypeFoldable;
|
||||
@@ -38,12 +38,6 @@ fn perform_query(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, Self::QueryResponse>> {
|
||||
T::type_op_method(tcx, canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, T>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, T>> {
|
||||
T::shrink_to_tcx_lifetime(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Normalizable<'tcx>: fmt::Debug + TypeFoldable<'tcx> + Lift<'tcx> + Copy {
|
||||
@@ -51,12 +45,6 @@ fn type_op_method(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
canonicalized: Canonicalized<'tcx, ParamEnvAnd<'tcx, Normalize<Self>>>,
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, Self>>;
|
||||
|
||||
/// Converts from the `'tcx` (lifted) form of `Self` into the `tcx`
|
||||
/// form of `Self`.
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, Self>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self>>;
|
||||
}
|
||||
|
||||
impl Normalizable<'tcx> for Ty<'tcx> {
|
||||
@@ -66,12 +54,6 @@ fn type_op_method(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, Self>> {
|
||||
tcx.type_op_normalize_ty(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, Self>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl Normalizable<'tcx> for ty::Predicate<'tcx> {
|
||||
@@ -81,12 +63,6 @@ fn type_op_method(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, Self>> {
|
||||
tcx.type_op_normalize_predicate(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, Self>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl Normalizable<'tcx> for ty::PolyFnSig<'tcx> {
|
||||
@@ -96,12 +72,6 @@ fn type_op_method(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, Self>> {
|
||||
tcx.type_op_normalize_poly_fn_sig(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, Self>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl Normalizable<'tcx> for ty::FnSig<'tcx> {
|
||||
@@ -111,12 +81,6 @@ fn type_op_method(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, Self>> {
|
||||
tcx.type_op_normalize_fn_sig(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, Self>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infer::canonical::{Canonical, Canonicalized, CanonicalizedQueryResponse, QueryResponse};
|
||||
use crate::infer::canonical::{Canonicalized, CanonicalizedQueryResponse};
|
||||
use crate::traits::query::dropck_outlives::trivial_dropck_outlives;
|
||||
use crate::traits::query::dropck_outlives::DropckOutlivesResult;
|
||||
use crate::traits::query::Fallible;
|
||||
@@ -53,12 +53,6 @@ fn perform_query(
|
||||
|
||||
tcx.dropck_outlives(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
lifted_query_result: &'a CanonicalizedQueryResponse<'tcx, Self::QueryResponse>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, Self::QueryResponse>> {
|
||||
lifted_query_result
|
||||
}
|
||||
}
|
||||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infer::canonical::{Canonical, Canonicalized, CanonicalizedQueryResponse, QueryResponse};
|
||||
use crate::infer::canonical::{Canonicalized, CanonicalizedQueryResponse};
|
||||
use crate::traits::query::Fallible;
|
||||
use crate::ty::{ParamEnvAnd, Predicate, TyCtxt};
|
||||
|
||||
@@ -43,12 +43,6 @@ fn perform_query(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, ()>> {
|
||||
tcx.type_op_prove_predicate(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, ()>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, ()>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infer::canonical::{Canonical, Canonicalized, CanonicalizedQueryResponse, QueryResponse};
|
||||
use crate::infer::canonical::{Canonicalized, CanonicalizedQueryResponse};
|
||||
use crate::traits::query::Fallible;
|
||||
use crate::ty::{ParamEnvAnd, Ty, TyCtxt};
|
||||
|
||||
@@ -34,12 +34,6 @@ fn perform_query(
|
||||
) -> Fallible<CanonicalizedQueryResponse<'tcx, ()>> {
|
||||
tcx.type_op_subtype(canonicalized)
|
||||
}
|
||||
|
||||
fn shrink_to_tcx_lifetime(
|
||||
v: &'a CanonicalizedQueryResponse<'tcx, ()>,
|
||||
) -> &'a Canonical<'tcx, QueryResponse<'tcx, ()>> {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
|
||||
@@ -2491,7 +2491,7 @@ fn candidate_should_be_dropped_in_favor_of(
|
||||
if other.evaluation.must_apply_modulo_regions() {
|
||||
match victim.candidate {
|
||||
ImplCandidate(victim_def) => {
|
||||
let tcx = self.tcx().global_tcx();
|
||||
let tcx = self.tcx();
|
||||
return tcx.specializes((other_def, victim_def))
|
||||
|| tcx.impls_are_allowed_to_overlap(
|
||||
other_def, victim_def).is_some();
|
||||
|
||||
@@ -162,7 +162,6 @@ fn insert(
|
||||
}
|
||||
};
|
||||
|
||||
let tcx = tcx.global_tcx();
|
||||
let (le, ge) = traits::overlapping_impls(
|
||||
tcx,
|
||||
possible_sibling,
|
||||
|
||||
@@ -661,8 +661,7 @@ pub fn impl_is_default(self, node_item_def_id: DefId) -> bool {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.global_tcx()
|
||||
.impl_defaultness(node_item_def_id)
|
||||
self.impl_defaultness(node_item_def_id)
|
||||
.is_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1067,14 +1067,6 @@ pub struct GlobalCtxt<'tcx> {
|
||||
}
|
||||
|
||||
impl<'tcx> TyCtxt<'tcx> {
|
||||
/// Gets the global `TyCtxt`.
|
||||
#[inline]
|
||||
pub fn global_tcx(self) -> TyCtxt<'tcx> {
|
||||
TyCtxt {
|
||||
gcx: self.gcx,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn hir(self) -> &'tcx hir_map::Map<'tcx> {
|
||||
&self.hir_map
|
||||
@@ -1156,11 +1148,6 @@ pub fn lift<T: ?Sized + Lift<'tcx>>(self, value: &T) -> Option<T::Lifted> {
|
||||
value.lift_to_tcx(self)
|
||||
}
|
||||
|
||||
/// Like lift, but only tries in the global tcx.
|
||||
pub fn lift_to_global<T: ?Sized + Lift<'tcx>>(self, value: &T) -> Option<T::Lifted> {
|
||||
value.lift_to_tcx(self.global_tcx())
|
||||
}
|
||||
|
||||
/// Creates a type context and call the closure with a `TyCtxt` reference
|
||||
/// to the context. The closure enforces that the type context and any interned
|
||||
/// value (types, substs, etc.) can only be used while `ty::tls` has a valid
|
||||
@@ -1432,13 +1419,7 @@ pub fn serialize_query_result_cache<E>(self,
|
||||
-> Result<(), E::Error>
|
||||
where E: ty::codec::TyEncoder
|
||||
{
|
||||
self.queries.on_disk_cache.serialize(self.global_tcx(), encoder)
|
||||
}
|
||||
|
||||
/// If `true`, we should use the AST-based borrowck (we may *also* use
|
||||
/// the MIR-based borrowck).
|
||||
pub fn use_ast_borrowck(self) -> bool {
|
||||
self.borrowck_mode().use_ast()
|
||||
self.queries.on_disk_cache.serialize(self, encoder)
|
||||
}
|
||||
|
||||
/// If `true`, we should use the MIR-based borrowck, but also
|
||||
@@ -1606,7 +1587,7 @@ pub fn enter_local<F, R>(&'tcx self, f: F) -> R
|
||||
let tcx = TyCtxt {
|
||||
gcx: self,
|
||||
};
|
||||
ty::tls::with_related_context(tcx.global_tcx(), |icx| {
|
||||
ty::tls::with_related_context(tcx, |icx| {
|
||||
let new_icx = ty::tls::ImplicitCtxt {
|
||||
tcx,
|
||||
query: icx.query.clone(),
|
||||
@@ -2431,7 +2412,7 @@ pub fn mk_nil_ptr(self) -> Ty<'tcx> {
|
||||
|
||||
#[inline]
|
||||
pub fn mk_array(self, ty: Ty<'tcx>, n: u64) -> Ty<'tcx> {
|
||||
self.mk_ty(Array(ty, ty::Const::from_usize(self.global_tcx(), n)))
|
||||
self.mk_ty(Array(ty, ty::Const::from_usize(self, n)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -2646,7 +2627,7 @@ pub fn intern_canonical_var_infos(self, ts: &[CanonicalVarInfo]) -> CanonicalVar
|
||||
if ts.len() == 0 {
|
||||
List::empty()
|
||||
} else {
|
||||
self.global_tcx()._intern_canonical_var_infos(ts)
|
||||
self._intern_canonical_var_infos(ts)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ pub fn sort_string(&self, tcx: TyCtxt<'_>) -> Cow<'static, str> {
|
||||
ty::Adt(def, _) => format!("{} `{}`", def.descr(), tcx.def_path_str(def.did)).into(),
|
||||
ty::Foreign(def_id) => format!("extern type `{}`", tcx.def_path_str(def_id)).into(),
|
||||
ty::Array(_, n) => {
|
||||
let n = tcx.lift_to_global(&n).unwrap();
|
||||
let n = tcx.lift(&n).unwrap();
|
||||
match n.try_eval_usize(tcx, ty::ParamEnv::empty()) {
|
||||
Some(n) => {
|
||||
format!("array of {} element{}", n, pluralise!(n)).into()
|
||||
|
||||
@@ -210,7 +210,7 @@ pub fn new(def_id: DefId, substs: SubstsRef<'tcx>)
|
||||
}
|
||||
|
||||
pub fn mono(tcx: TyCtxt<'tcx>, def_id: DefId) -> Instance<'tcx> {
|
||||
Instance::new(def_id, tcx.global_tcx().empty_substs_for_def_id(def_id))
|
||||
Instance::new(def_id, tcx.empty_substs_for_def_id(def_id))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
@@ -1883,7 +1883,7 @@ fn data_layout(&self) -> &TargetDataLayout {
|
||||
|
||||
impl<'tcx> HasTyCtxt<'tcx> for TyCtxt<'tcx> {
|
||||
fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
self.global_tcx()
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2003,7 +2003,7 @@ impl TyCtxt<'tcx> {
|
||||
pub fn layout_of(self, param_env_and_ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>)
|
||||
-> Result<TyLayout<'tcx>, LayoutError<'tcx>> {
|
||||
let cx = LayoutCx {
|
||||
tcx: self.global_tcx(),
|
||||
tcx: self,
|
||||
param_env: param_env_and_ty.param_env
|
||||
};
|
||||
cx.layout_of(param_env_and_ty.value)
|
||||
@@ -2017,7 +2017,7 @@ impl ty::query::TyCtxtAt<'tcx> {
|
||||
pub fn layout_of(self, param_env_and_ty: ty::ParamEnvAnd<'tcx, Ty<'tcx>>)
|
||||
-> Result<TyLayout<'tcx>, LayoutError<'tcx>> {
|
||||
let cx = LayoutCx {
|
||||
tcx: self.global_tcx().at(self.span),
|
||||
tcx: self.at(self.span),
|
||||
param_env: param_env_and_ty.param_env
|
||||
};
|
||||
cx.layout_of(param_env_and_ty.value)
|
||||
|
||||
@@ -2378,7 +2378,7 @@ pub fn variant_of_res(&self, res: Res) -> &VariantDef {
|
||||
pub fn eval_explicit_discr(&self, tcx: TyCtxt<'tcx>, expr_did: DefId) -> Option<Discr<'tcx>> {
|
||||
let param_env = tcx.param_env(expr_did);
|
||||
let repr_type = self.repr.discr_type();
|
||||
let substs = InternalSubsts::identity_for_item(tcx.global_tcx(), expr_did);
|
||||
let substs = InternalSubsts::identity_for_item(tcx, expr_did);
|
||||
let instance = ty::Instance::new(expr_did, substs);
|
||||
let cid = GlobalId {
|
||||
instance,
|
||||
@@ -2387,7 +2387,7 @@ pub fn eval_explicit_discr(&self, tcx: TyCtxt<'tcx>, expr_did: DefId) -> Option<
|
||||
match tcx.const_eval(param_env.and(cid)) {
|
||||
Ok(val) => {
|
||||
// FIXME: Find the right type and use it instead of `val.ty` here
|
||||
if let Some(b) = val.try_eval_bits(tcx.global_tcx(), param_env, val.ty) {
|
||||
if let Some(b) = val.try_eval_bits(tcx, param_env, val.ty) {
|
||||
trace!("discriminants: {} ({:?})", b, repr_type);
|
||||
Some(Discr {
|
||||
val: b,
|
||||
@@ -2423,7 +2423,7 @@ pub fn discriminants(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
) -> impl Iterator<Item = (VariantIdx, Discr<'tcx>)> + Captures<'tcx> {
|
||||
let repr_type = self.repr.discr_type();
|
||||
let initial = repr_type.initial_discriminant(tcx.global_tcx());
|
||||
let initial = repr_type.initial_discriminant(tcx);
|
||||
let mut prev_discr = None::<Discr<'tcx>>;
|
||||
self.variants.iter_enumerated().map(move |(i, v)| {
|
||||
let mut discr = prev_discr.map_or(initial, |d| d.wrap_incr(tcx));
|
||||
@@ -2457,7 +2457,7 @@ pub fn discriminant_for_variant(
|
||||
let (val, offset) = self.discriminant_def_for_variant(variant_index);
|
||||
let explicit_value = val
|
||||
.and_then(|expr_did| self.eval_explicit_discr(tcx, expr_did))
|
||||
.unwrap_or_else(|| self.repr.discr_type().initial_discriminant(tcx.global_tcx()));
|
||||
.unwrap_or_else(|| self.repr.discr_type().initial_discriminant(tcx));
|
||||
explicit_value.checked_add(tcx, offset as u128).0
|
||||
}
|
||||
|
||||
|
||||
@@ -917,7 +917,7 @@ fn pretty_print_const(
|
||||
let min = 1u128 << (bit_size - 1);
|
||||
let max = min - 1;
|
||||
|
||||
let ty = self.tcx().lift_to_global(&ct.ty).unwrap();
|
||||
let ty = self.tcx().lift(&ct.ty).unwrap();
|
||||
let size = self.tcx().layout_of(ty::ParamEnv::empty().and(ty))
|
||||
.unwrap()
|
||||
.size;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
use crate::hir::{self, TraitCandidate, ItemLocalId, CodegenFnAttrs};
|
||||
use crate::infer::canonical::{self, Canonical};
|
||||
use crate::lint;
|
||||
use crate::middle::borrowck::{BorrowCheckResult, SignalledError};
|
||||
use crate::middle::cstore::{ExternCrate, LinkagePreference, NativeLibrary, ForeignModule};
|
||||
use crate::middle::cstore::{NativeLibraryKind, DepKind, CrateSource};
|
||||
use crate::middle::privacy::AccessLevels;
|
||||
|
||||
@@ -265,7 +265,7 @@ pub(super) fn start_query<F, R>(
|
||||
tls::with_related_context(self, move |current_icx| {
|
||||
// Update the `ImplicitCtxt` to point to our new query job.
|
||||
let new_icx = tls::ImplicitCtxt {
|
||||
tcx: self.global_tcx(),
|
||||
tcx: self,
|
||||
query: Some(job),
|
||||
diagnostics,
|
||||
layout_depth: current_icx.layout_depth,
|
||||
@@ -274,7 +274,7 @@ pub(super) fn start_query<F, R>(
|
||||
|
||||
// Use the `ImplicitCtxt` while we execute the query.
|
||||
tls::enter_context(&new_icx, |_| {
|
||||
compute(self.global_tcx())
|
||||
compute(self)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -384,7 +384,7 @@ pub(super) fn get_query<Q: QueryDescription<'tcx>>(self, span: Span, key: Q::Key
|
||||
let ((result, dep_node_index), diagnostics) = with_diagnostics(|diagnostics| {
|
||||
self.start_query(job.job.clone(), diagnostics, |tcx| {
|
||||
tcx.dep_graph.with_anon_task(Q::dep_kind(), || {
|
||||
Q::compute(tcx.global_tcx(), key)
|
||||
Q::compute(tcx, key)
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -445,10 +445,10 @@ fn load_from_disk_and_cache_in_memory<Q: QueryDescription<'tcx>>(
|
||||
debug_assert!(self.dep_graph.is_green(dep_node));
|
||||
|
||||
// First we try to load the result from the on-disk cache.
|
||||
let result = if Q::cache_on_disk(self.global_tcx(), key.clone(), None) &&
|
||||
let result = if Q::cache_on_disk(self, key.clone(), None) &&
|
||||
self.sess.opts.debugging_opts.incremental_queries {
|
||||
self.sess.profiler(|p| p.incremental_load_result_start(Q::NAME));
|
||||
let result = Q::try_load_from_disk(self.global_tcx(), prev_dep_node_index);
|
||||
let result = Q::try_load_from_disk(self, prev_dep_node_index);
|
||||
self.sess.profiler(|p| p.incremental_load_result_end(Q::NAME));
|
||||
|
||||
// We always expect to find a cached result for things that
|
||||
@@ -643,7 +643,7 @@ fn force_query<Q: QueryDescription<'tcx>>(self, key: Q::Key, span: Span, dep_nod
|
||||
macro_rules! handle_cycle_error {
|
||||
([][$tcx: expr, $error:expr]) => {{
|
||||
$tcx.report_cycle($error).emit();
|
||||
Value::from_cycle_error($tcx.global_tcx())
|
||||
Value::from_cycle_error($tcx)
|
||||
}};
|
||||
([fatal_cycle$(, $modifiers:ident)*][$tcx:expr, $error:expr]) => {{
|
||||
$tcx.report_cycle($error).emit();
|
||||
@@ -652,7 +652,7 @@ macro_rules! handle_cycle_error {
|
||||
}};
|
||||
([cycle_delay_bug$(, $modifiers:ident)*][$tcx:expr, $error:expr]) => {{
|
||||
$tcx.report_cycle($error).delay_as_bug();
|
||||
Value::from_cycle_error($tcx.global_tcx())
|
||||
Value::from_cycle_error($tcx)
|
||||
}};
|
||||
([$other:ident$(, $modifiers:ident)*][$($args:tt)*]) => {
|
||||
handle_cycle_error!([$($modifiers),*][$($args)*])
|
||||
@@ -999,7 +999,7 @@ fn compute(tcx: TyCtxt<'tcx>, key: Self::Key) -> Self::Value {
|
||||
// would be missing appropriate entries in `providers`.
|
||||
.unwrap_or(&tcx.queries.fallback_extern_providers)
|
||||
.$name;
|
||||
provider(tcx.global_tcx(), key)
|
||||
provider(tcx, key)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use rustc_data_structures::{fx::FxHashMap, sync::Lock};
|
||||
use rustc_data_structures::sync::Lock;
|
||||
|
||||
use std::cell::{RefCell, Cell};
|
||||
use std::cell::Cell;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use std::sync::mpsc::{Sender};
|
||||
@@ -279,39 +278,3 @@ pub fn indenter() -> Indenter {
|
||||
debug!(">>");
|
||||
Indenter { _cannot_construct_outside_of_this_module: () }
|
||||
}
|
||||
|
||||
pub trait MemoizationMap {
|
||||
type Key: Clone;
|
||||
type Value: Clone;
|
||||
|
||||
/// If `key` is present in the map, return the value,
|
||||
/// otherwise invoke `op` and store the value in the map.
|
||||
///
|
||||
/// N.B., if the receiver is a `DepTrackingMap`, special care is
|
||||
/// needed in the `op` to ensure that the correct edges are
|
||||
/// added into the dep graph. See the `DepTrackingMap` impl for
|
||||
/// more details!
|
||||
fn memoize<OP>(&self, key: Self::Key, op: OP) -> Self::Value
|
||||
where OP: FnOnce() -> Self::Value;
|
||||
}
|
||||
|
||||
impl<K, V> MemoizationMap for RefCell<FxHashMap<K,V>>
|
||||
where K: Hash+Eq+Clone, V: Clone
|
||||
{
|
||||
type Key = K;
|
||||
type Value = V;
|
||||
|
||||
fn memoize<OP>(&self, key: K, op: OP) -> V
|
||||
where OP: FnOnce() -> V
|
||||
{
|
||||
let result = self.borrow().get(&key).cloned();
|
||||
match result {
|
||||
Some(result) => result,
|
||||
None => {
|
||||
let result = op();
|
||||
self.borrow_mut().insert(key, result.clone());
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
authors = ["The Rust Project Developers"]
|
||||
name = "rustc_ast_borrowck"
|
||||
version = "0.0.0"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "rustc_ast_borrowck"
|
||||
path = "lib.rs"
|
||||
test = false
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
syntax_pos = { path = "../libsyntax_pos" }
|
||||
# for "clarity", rename the graphviz crate to dot; graphviz within `borrowck`
|
||||
# refers to the borrowck-specific graphviz adapter traits.
|
||||
dot = { path = "../libgraphviz", package = "graphviz" }
|
||||
rustc = { path = "../librustc" }
|
||||
rustc_data_structures = { path = "../librustc_data_structures" }
|
||||
@@ -1,1167 +0,0 @@
|
||||
% The Borrow Checker
|
||||
|
||||
> WARNING: This README is more or less obsolete, and will be removed
|
||||
> soon! The new system is described in the [rustc guide].
|
||||
|
||||
[rustc guide]: https://rust-lang.github.io/rustc-guide/borrow_check.html
|
||||
|
||||
This pass has the job of enforcing memory safety. This is a subtle
|
||||
topic. This docs aim to explain both the practice and the theory
|
||||
behind the borrow checker. They start with a high-level overview of
|
||||
how it works, and then proceed to dive into the theoretical
|
||||
background. Finally, they go into detail on some of the more subtle
|
||||
aspects.
|
||||
|
||||
# Table of contents
|
||||
|
||||
These docs are long. Search for the section you are interested in.
|
||||
|
||||
- Overview
|
||||
- Formal model
|
||||
- Borrowing and loans
|
||||
- Moves and initialization
|
||||
- Drop flags and structural fragments
|
||||
- Future work
|
||||
|
||||
# Overview
|
||||
|
||||
The borrow checker checks one function at a time. It operates in two
|
||||
passes. The first pass, called `gather_loans`, walks over the function
|
||||
and identifies all of the places where borrows (e.g., `&` expressions
|
||||
and `ref` bindings) and moves (copies or captures of a linear value)
|
||||
occur. It also tracks initialization sites. For each borrow and move,
|
||||
it checks various basic safety conditions at this time (for example,
|
||||
that the lifetime of the borrow doesn't exceed the lifetime of the
|
||||
value being borrowed, or that there is no move out of an `&T`
|
||||
referent).
|
||||
|
||||
It then uses the dataflow module to propagate which of those borrows
|
||||
may be in scope at each point in the procedure. A loan is considered
|
||||
to come into scope at the expression that caused it and to go out of
|
||||
scope when the lifetime of the resulting reference expires.
|
||||
|
||||
Once the in-scope loans are known for each point in the program, the
|
||||
borrow checker walks the IR again in a second pass called
|
||||
`check_loans`. This pass examines each statement and makes sure that
|
||||
it is safe with respect to the in-scope loans.
|
||||
|
||||
# Formal model
|
||||
|
||||
Throughout the docs we'll consider a simple subset of Rust in which
|
||||
you can only borrow from places, defined like so:
|
||||
|
||||
```text
|
||||
P = x | P.f | *P
|
||||
```
|
||||
|
||||
Here `x` represents some variable, `P.f` is a field reference,
|
||||
and `*P` is a pointer dereference. There is no auto-deref or other
|
||||
niceties. This means that if you have a type like:
|
||||
|
||||
```rust
|
||||
struct S { f: i32 }
|
||||
```
|
||||
|
||||
and a variable `a: Box<S>`, then the rust expression `a.f` would correspond
|
||||
to an `P` of `(*a).f`.
|
||||
|
||||
Here is the formal grammar for the types we'll consider:
|
||||
|
||||
```text
|
||||
TY = i32 | bool | S<'LT...> | Box<TY> | & 'LT MQ TY
|
||||
MQ = mut | imm
|
||||
```
|
||||
|
||||
Most of these types should be pretty self explanatory. Here `S` is a
|
||||
struct name and we assume structs are declared like so:
|
||||
|
||||
```text
|
||||
SD = struct S<'LT...> { (f: TY)... }
|
||||
```
|
||||
|
||||
# Borrowing and loans
|
||||
|
||||
## An intuitive explanation
|
||||
|
||||
### Issuing loans
|
||||
|
||||
Now, imagine we had a program like this:
|
||||
|
||||
```rust
|
||||
struct Foo { f: i32, g: i32 }
|
||||
...
|
||||
'a: {
|
||||
let mut x: Box<Foo> = ...;
|
||||
let y = &mut (*x).f;
|
||||
x = ...;
|
||||
}
|
||||
```
|
||||
|
||||
This is of course dangerous because mutating `x` will free the old
|
||||
value and hence invalidate `y`. The borrow checker aims to prevent
|
||||
this sort of thing.
|
||||
|
||||
#### Loans and restrictions
|
||||
|
||||
The way the borrow checker works is that it analyzes each borrow
|
||||
expression (in our simple model, that's stuff like `&P`, though in
|
||||
real life there are a few other cases to consider). For each borrow
|
||||
expression, it computes a `Loan`, which is a data structure that
|
||||
records (1) the value being borrowed, (2) the mutability and scope of
|
||||
the borrow, and (3) a set of restrictions. In the code, `Loan` is a
|
||||
struct defined in `middle::borrowck`. Formally, we define `LOAN` as
|
||||
follows:
|
||||
|
||||
```text
|
||||
LOAN = (P, LT, MQ, RESTRICTION*)
|
||||
RESTRICTION = (P, ACTION*)
|
||||
ACTION = MUTATE | CLAIM | FREEZE
|
||||
```
|
||||
|
||||
Here the `LOAN` tuple defines the place `P` being borrowed; the
|
||||
lifetime `LT` of that borrow; the mutability `MQ` of the borrow; and a
|
||||
list of restrictions. The restrictions indicate actions which, if
|
||||
taken, could invalidate the loan and lead to type safety violations.
|
||||
|
||||
Each `RESTRICTION` is a pair of a restrictive place `P` (which will
|
||||
either be the path that was borrowed or some prefix of the path that
|
||||
was borrowed) and a set of restricted actions. There are three kinds
|
||||
of actions that may be restricted for the path `P`:
|
||||
|
||||
- `MUTATE` means that `P` cannot be assigned to;
|
||||
- `CLAIM` means that the `P` cannot be borrowed mutably;
|
||||
- `FREEZE` means that the `P` cannot be borrowed immutably;
|
||||
|
||||
Finally, it is never possible to move from a place that appears in a
|
||||
restriction. This implies that the "empty restriction" `(P, [])`,
|
||||
which contains an empty set of actions, still has a purpose---it
|
||||
prevents moves from `P`. I chose not to make `MOVE` a fourth kind of
|
||||
action because that would imply that sometimes moves are permitted
|
||||
from restricted values, which is not the case.
|
||||
|
||||
#### Example
|
||||
|
||||
To give you a better feeling for what kind of restrictions derived
|
||||
from a loan, let's look at the loan `L` that would be issued as a
|
||||
result of the borrow `&mut (*x).f` in the example above:
|
||||
|
||||
```text
|
||||
L = ((*x).f, 'a, mut, RS) where
|
||||
RS = [((*x).f, [MUTATE, CLAIM, FREEZE]),
|
||||
(*x, [MUTATE, CLAIM, FREEZE]),
|
||||
(x, [MUTATE, CLAIM, FREEZE])]
|
||||
```
|
||||
|
||||
The loan states that the expression `(*x).f` has been loaned as
|
||||
mutable for the lifetime `'a`. Because the loan is mutable, that means
|
||||
that the value `(*x).f` may be mutated via the newly created reference
|
||||
(and *only* via that pointer). This is reflected in the
|
||||
restrictions `RS` that accompany the loan.
|
||||
|
||||
The first restriction `((*x).f, [MUTATE, CLAIM, FREEZE])` states that
|
||||
the lender may not mutate, freeze, nor alias `(*x).f`. Mutation is
|
||||
illegal because `(*x).f` is only supposed to be mutated via the new
|
||||
reference, not by mutating the original path `(*x).f`. Freezing is
|
||||
illegal because the path now has an `&mut` alias; so even if we the
|
||||
lender were to consider `(*x).f` to be immutable, it might be mutated
|
||||
via this alias. They will be enforced for the lifetime `'a` of the
|
||||
loan. After the loan expires, the restrictions no longer apply.
|
||||
|
||||
The second restriction on `*x` is interesting because it does not
|
||||
apply to the path that was lent (`(*x).f`) but rather to a prefix of
|
||||
the borrowed path. This is due to the rules of inherited mutability:
|
||||
if the user were to assign to (or freeze) `*x`, they would indirectly
|
||||
overwrite (or freeze) `(*x).f`, and thus invalidate the reference
|
||||
that was created. In general it holds that when a path is
|
||||
lent, restrictions are issued for all the owning prefixes of that
|
||||
path. In this case, the path `*x` owns the path `(*x).f` and,
|
||||
because `x` has ownership, the path `x` owns the path `*x`.
|
||||
Therefore, borrowing `(*x).f` yields restrictions on both
|
||||
`*x` and `x`.
|
||||
|
||||
### Checking for illegal assignments, moves, and reborrows
|
||||
|
||||
Once we have computed the loans introduced by each borrow, the borrow
|
||||
checker uses a data flow propagation to compute the full set of loans
|
||||
in scope at each expression and then uses that set to decide whether
|
||||
that expression is legal. Remember that the scope of loan is defined
|
||||
by its lifetime LT. We sometimes say that a loan which is in-scope at
|
||||
a particular point is an "outstanding loan", and the set of
|
||||
restrictions included in those loans as the "outstanding
|
||||
restrictions".
|
||||
|
||||
The kinds of expressions which in-scope loans can render illegal are:
|
||||
- *assignments* (`lv = v`): illegal if there is an in-scope restriction
|
||||
against mutating `lv`;
|
||||
- *moves*: illegal if there is any in-scope restriction on `lv` at all;
|
||||
- *mutable borrows* (`&mut lv`): illegal there is an in-scope restriction
|
||||
against claiming `lv`;
|
||||
- *immutable borrows* (`&lv`): illegal there is an in-scope restriction
|
||||
against freezing `lv`.
|
||||
|
||||
## Formal rules
|
||||
|
||||
Now that we hopefully have some kind of intuitive feeling for how the
|
||||
borrow checker works, let's look a bit more closely now at the precise
|
||||
conditions that it uses.
|
||||
|
||||
I will present the rules in a modified form of standard inference
|
||||
rules, which looks as follows:
|
||||
|
||||
```text
|
||||
PREDICATE(X, Y, Z) // Rule-Name
|
||||
Condition 1
|
||||
Condition 2
|
||||
Condition 3
|
||||
```
|
||||
|
||||
The initial line states the predicate that is to be satisfied. The
|
||||
indented lines indicate the conditions that must be met for the
|
||||
predicate to be satisfied. The right-justified comment states the name
|
||||
of this rule: there are comments in the borrowck source referencing
|
||||
these names, so that you can cross reference to find the actual code
|
||||
that corresponds to the formal rule.
|
||||
|
||||
### Invariants
|
||||
|
||||
I want to collect, at a high-level, the invariants the borrow checker
|
||||
maintains. I will give them names and refer to them throughout the
|
||||
text. Together these invariants are crucial for the overall soundness
|
||||
of the system.
|
||||
|
||||
**Mutability requires uniqueness.** To mutate a path
|
||||
|
||||
**Unique mutability.** There is only one *usable* mutable path to any
|
||||
given memory at any given time. This implies that when claiming memory
|
||||
with an expression like `p = &mut x`, the compiler must guarantee that
|
||||
the borrowed value `x` can no longer be mutated so long as `p` is
|
||||
live. (This is done via restrictions, read on.)
|
||||
|
||||
**.**
|
||||
|
||||
|
||||
### The `gather_loans` pass
|
||||
|
||||
We start with the `gather_loans` pass, which walks the AST looking for
|
||||
borrows. For each borrow, there are three bits of information: the
|
||||
place `P` being borrowed and the mutability `MQ` and lifetime `LT`
|
||||
of the resulting pointer. Given those, `gather_loans` applies four
|
||||
validity tests:
|
||||
|
||||
1. `MUTABILITY(P, MQ)`: The mutability of the reference is
|
||||
compatible with the mutability of `P` (i.e., not borrowing immutable
|
||||
data as mutable).
|
||||
|
||||
2. `ALIASABLE(P, MQ)`: The aliasability of the reference is
|
||||
compatible with the aliasability of `P`. The goal is to prevent
|
||||
`&mut` borrows of aliasability data.
|
||||
|
||||
3. `LIFETIME(P, LT, MQ)`: The lifetime of the borrow does not exceed
|
||||
the lifetime of the value being borrowed.
|
||||
|
||||
4. `RESTRICTIONS(P, LT, ACTIONS) = RS`: This pass checks and computes the
|
||||
restrictions to maintain memory safety. These are the restrictions
|
||||
that will go into the final loan. We'll discuss in more detail below.
|
||||
|
||||
## Checking mutability
|
||||
|
||||
Checking mutability is fairly straightforward. We just want to prevent
|
||||
immutable data from being borrowed as mutable. Note that it is ok to borrow
|
||||
mutable data as immutable, since that is simply a freeze. The judgement
|
||||
`MUTABILITY(P, MQ)` means the mutability of `P` is compatible with a borrow
|
||||
of mutability `MQ`. The Rust code corresponding to this predicate is the
|
||||
function `check_mutability` in `middle::borrowck::gather_loans`.
|
||||
|
||||
### Checking mutability of variables
|
||||
|
||||
*Code pointer:* Function `check_mutability()` in `gather_loans/mod.rs`,
|
||||
but also the code in `mem_categorization`.
|
||||
|
||||
Let's begin with the rules for variables, which state that if a
|
||||
variable is declared as mutable, it may be borrowed any which way, but
|
||||
otherwise the variable must be borrowed as immutable:
|
||||
|
||||
```text
|
||||
MUTABILITY(X, MQ) // M-Var-Mut
|
||||
DECL(X) = mut
|
||||
|
||||
MUTABILITY(X, imm) // M-Var-Imm
|
||||
DECL(X) = imm
|
||||
```
|
||||
|
||||
### Checking mutability of owned content
|
||||
|
||||
Fields and boxes inherit their mutability from
|
||||
their base expressions, so both of their rules basically
|
||||
delegate the check to the base expression `P`:
|
||||
|
||||
```text
|
||||
MUTABILITY(P.f, MQ) // M-Field
|
||||
MUTABILITY(P, MQ)
|
||||
|
||||
MUTABILITY(*P, MQ) // M-Deref-Unique
|
||||
TYPE(P) = Box<Ty>
|
||||
MUTABILITY(P, MQ)
|
||||
```
|
||||
|
||||
### Checking mutability of immutable pointer types
|
||||
|
||||
Immutable pointer types like `&T` can only
|
||||
be borrowed if MQ is immutable:
|
||||
|
||||
```text
|
||||
MUTABILITY(*P, imm) // M-Deref-Borrowed-Imm
|
||||
TYPE(P) = &Ty
|
||||
```
|
||||
|
||||
### Checking mutability of mutable pointer types
|
||||
|
||||
`&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut:
|
||||
|
||||
```text
|
||||
MUTABILITY(*P, MQ) // M-Deref-Borrowed-Mut
|
||||
TYPE(P) = &mut Ty
|
||||
```
|
||||
|
||||
## Checking aliasability
|
||||
|
||||
The goal of the aliasability check is to ensure that we never permit `&mut`
|
||||
borrows of aliasable data. The judgement `ALIASABLE(P, MQ)` means the
|
||||
aliasability of `P` is compatible with a borrow of mutability `MQ`. The Rust
|
||||
code corresponding to this predicate is the function `check_aliasability()` in
|
||||
`middle::borrowck::gather_loans`.
|
||||
|
||||
### Checking aliasability of variables
|
||||
|
||||
Local variables are never aliasable as they are accessible only within
|
||||
the stack frame.
|
||||
|
||||
```text
|
||||
ALIASABLE(X, MQ) // M-Var-Mut
|
||||
```
|
||||
|
||||
### Checking aliasable of owned content
|
||||
|
||||
Owned content is aliasable if it is found in an aliasable location:
|
||||
|
||||
```text
|
||||
ALIASABLE(P.f, MQ) // M-Field
|
||||
ALIASABLE(P, MQ)
|
||||
|
||||
ALIASABLE(*P, MQ) // M-Deref-Unique
|
||||
ALIASABLE(P, MQ)
|
||||
```
|
||||
|
||||
### Checking aliasability of immutable pointer types
|
||||
|
||||
Immutable pointer types like `&T` are aliasable, and hence can only be
|
||||
borrowed immutably:
|
||||
|
||||
```text
|
||||
ALIASABLE(*P, imm) // M-Deref-Borrowed-Imm
|
||||
TYPE(P) = &Ty
|
||||
```
|
||||
|
||||
### Checking aliasability of mutable pointer types
|
||||
|
||||
`&mut T` can be frozen, so it is acceptable to borrow it as either imm or mut:
|
||||
|
||||
```text
|
||||
ALIASABLE(*P, MQ) // M-Deref-Borrowed-Mut
|
||||
TYPE(P) = &mut Ty
|
||||
```
|
||||
|
||||
## Checking lifetime
|
||||
|
||||
These rules aim to ensure that no data is borrowed for a scope that exceeds
|
||||
its lifetime. These two computations wind up being intimately related.
|
||||
Formally, we define a predicate `LIFETIME(P, LT, MQ)`, which states that
|
||||
"the place `P` can be safely borrowed for the lifetime `LT` with mutability
|
||||
`MQ`". The Rust code corresponding to this predicate is the module
|
||||
`middle::borrowck::gather_loans::lifetime`.
|
||||
|
||||
### Checking lifetime of variables
|
||||
|
||||
The rule for variables states that a variable can only be borrowed a
|
||||
lifetime `LT` that is a subregion of the variable's scope:
|
||||
|
||||
```text
|
||||
LIFETIME(X, LT, MQ) // L-Local
|
||||
LT <= block where X is declared
|
||||
```
|
||||
|
||||
### Checking lifetime for owned content
|
||||
|
||||
The lifetime of a field or box is the same as the lifetime
|
||||
of its owner:
|
||||
|
||||
```text
|
||||
LIFETIME(P.f, LT, MQ) // L-Field
|
||||
LIFETIME(P, LT, MQ)
|
||||
|
||||
LIFETIME(*P, LT, MQ) // L-Deref-Send
|
||||
TYPE(P) = Box<Ty>
|
||||
LIFETIME(P, LT, MQ)
|
||||
```
|
||||
|
||||
### Checking lifetime for derefs of references
|
||||
|
||||
References have a lifetime `LT'` associated with them. The
|
||||
data they point at has been guaranteed to be valid for at least this
|
||||
lifetime. Therefore, the borrow is valid so long as the lifetime `LT`
|
||||
of the borrow is shorter than the lifetime `LT'` of the pointer
|
||||
itself:
|
||||
|
||||
```text
|
||||
LIFETIME(*P, LT, MQ) // L-Deref-Borrowed
|
||||
TYPE(P) = <' Ty OR <' mut Ty
|
||||
LT <= LT'
|
||||
```
|
||||
|
||||
## Computing the restrictions
|
||||
|
||||
The final rules govern the computation of *restrictions*, meaning that
|
||||
we compute the set of actions that will be illegal for the life of the
|
||||
loan. The predicate is written `RESTRICTIONS(P, LT, ACTIONS) =
|
||||
RESTRICTION*`, which can be read "in order to prevent `ACTIONS` from
|
||||
occurring on `P`, the restrictions `RESTRICTION*` must be respected
|
||||
for the lifetime of the loan".
|
||||
|
||||
Note that there is an initial set of restrictions: these restrictions
|
||||
are computed based on the kind of borrow:
|
||||
|
||||
```text
|
||||
&mut P => RESTRICTIONS(P, LT, MUTATE|CLAIM|FREEZE)
|
||||
&P => RESTRICTIONS(P, LT, MUTATE|CLAIM)
|
||||
```
|
||||
|
||||
The reasoning here is that a mutable borrow must be the only writer,
|
||||
therefore it prevents other writes (`MUTATE`), mutable borrows
|
||||
(`CLAIM`), and immutable borrows (`FREEZE`). An immutable borrow
|
||||
permits other immutable borrows but forbids writes and mutable borrows.
|
||||
|
||||
### Restrictions for loans of a local variable
|
||||
|
||||
The simplest case is a borrow of a local variable `X`:
|
||||
|
||||
```text
|
||||
RESTRICTIONS(X, LT, ACTIONS) = (X, ACTIONS) // R-Variable
|
||||
```
|
||||
|
||||
In such cases we just record the actions that are not permitted.
|
||||
|
||||
### Restrictions for loans of fields
|
||||
|
||||
Restricting a field is the same as restricting the owner of that
|
||||
field:
|
||||
|
||||
```text
|
||||
RESTRICTIONS(P.f, LT, ACTIONS) = RS, (P.f, ACTIONS) // R-Field
|
||||
RESTRICTIONS(P, LT, ACTIONS) = RS
|
||||
```
|
||||
|
||||
The reasoning here is as follows. If the field must not be mutated,
|
||||
then you must not mutate the owner of the field either, since that
|
||||
would indirectly modify the field. Similarly, if the field cannot be
|
||||
frozen or aliased, we cannot allow the owner to be frozen or aliased,
|
||||
since doing so indirectly freezes/aliases the field. This is the
|
||||
origin of inherited mutability.
|
||||
|
||||
### Restrictions for loans of owned referents
|
||||
|
||||
Because the mutability of owned referents is inherited, restricting an
|
||||
owned referent is similar to restricting a field, in that it implies
|
||||
restrictions on the pointer. However, boxes have an important
|
||||
twist: if the owner `P` is mutated, that causes the owned referent
|
||||
`*P` to be freed! So whenever an owned referent `*P` is borrowed, we
|
||||
must prevent the box `P` from being mutated, which means
|
||||
that we always add `MUTATE` and `CLAIM` to the restriction set imposed
|
||||
on `P`:
|
||||
|
||||
```text
|
||||
RESTRICTIONS(*P, LT, ACTIONS) = RS, (*P, ACTIONS) // R-Deref-Send-Pointer
|
||||
TYPE(P) = Box<Ty>
|
||||
RESTRICTIONS(P, LT, ACTIONS|MUTATE|CLAIM) = RS
|
||||
```
|
||||
|
||||
### Restrictions for loans of immutable borrowed referents
|
||||
|
||||
Immutable borrowed referents are freely aliasable, meaning that
|
||||
the compiler does not prevent you from copying the pointer. This
|
||||
implies that issuing restrictions is useless. We might prevent the
|
||||
user from acting on `*P` itself, but there could be another path
|
||||
`*P1` that refers to the exact same memory, and we would not be
|
||||
restricting that path. Therefore, the rule for `&Ty` pointers
|
||||
always returns an empty set of restrictions, and it only permits
|
||||
restricting `MUTATE` and `CLAIM` actions:
|
||||
|
||||
```text
|
||||
RESTRICTIONS(*P, LT, ACTIONS) = [] // R-Deref-Imm-Borrowed
|
||||
TYPE(P) = <' Ty
|
||||
LT <= LT' // (1)
|
||||
ACTIONS subset of [MUTATE, CLAIM]
|
||||
```
|
||||
|
||||
The reason that we can restrict `MUTATE` and `CLAIM` actions even
|
||||
without a restrictions list is that it is never legal to mutate nor to
|
||||
borrow mutably the contents of a `&Ty` pointer. In other words,
|
||||
those restrictions are already inherent in the type.
|
||||
|
||||
Clause (1) in the rule for `&Ty` deserves mention. Here I
|
||||
specify that the lifetime of the loan must be less than the lifetime
|
||||
of the `&Ty` pointer. In simple cases, this clause is redundant, since
|
||||
the `LIFETIME()` function will already enforce the required rule:
|
||||
|
||||
```rust
|
||||
fn foo(point: &'a Point) -> &'static i32 {
|
||||
&point.x // Error
|
||||
}
|
||||
```
|
||||
|
||||
The above example fails to compile both because of clause (1) above
|
||||
but also by the basic `LIFETIME()` check. However, in more advanced
|
||||
examples involving multiple nested pointers, clause (1) is needed:
|
||||
|
||||
```rust
|
||||
fn foo(point: &'a &'b mut Point) -> &'b i32 {
|
||||
&point.x // Error
|
||||
}
|
||||
```
|
||||
|
||||
The `LIFETIME` rule here would accept `'b` because, in fact, the
|
||||
*memory is* guaranteed to remain valid (i.e., not be freed) for the
|
||||
lifetime `'b`, since the `&mut` pointer is valid for `'b`. However, we
|
||||
are returning an immutable reference, so we need the memory to be both
|
||||
valid and immutable. Even though `point.x` is referenced by an `&mut`
|
||||
pointer, it can still be considered immutable so long as that `&mut`
|
||||
pointer is found in an aliased location. That means the memory is
|
||||
guaranteed to be *immutable* for the lifetime of the `&` pointer,
|
||||
which is only `'a`, not `'b`. Hence this example yields an error.
|
||||
|
||||
As a final twist, consider the case of two nested *immutable*
|
||||
pointers, rather than a mutable pointer within an immutable one:
|
||||
|
||||
```rust
|
||||
fn foo(point: &'a &'b Point) -> &'b i32 {
|
||||
&point.x // OK
|
||||
}
|
||||
```
|
||||
|
||||
This function is legal. The reason for this is that the inner pointer
|
||||
(`*point : &'b Point`) is enough to guarantee the memory is immutable
|
||||
and valid for the lifetime `'b`. This is reflected in
|
||||
`RESTRICTIONS()` by the fact that we do not recurse (i.e., we impose
|
||||
no restrictions on `P`, which in this particular case is the pointer
|
||||
`point : &'a &'b Point`).
|
||||
|
||||
#### Why both `LIFETIME()` and `RESTRICTIONS()`?
|
||||
|
||||
Given the previous text, it might seem that `LIFETIME` and
|
||||
`RESTRICTIONS` should be folded together into one check, but there is
|
||||
a reason that they are separated. They answer separate concerns.
|
||||
The rules pertaining to `LIFETIME` exist to ensure that we don't
|
||||
create a borrowed pointer that outlives the memory it points at. So
|
||||
`LIFETIME` prevents a function like this:
|
||||
|
||||
```rust
|
||||
fn get_1<'a>() -> &'a i32 {
|
||||
let x = 1;
|
||||
&x
|
||||
}
|
||||
```
|
||||
|
||||
Here we would be returning a pointer into the stack. Clearly bad.
|
||||
|
||||
However, the `RESTRICTIONS` rules are more concerned with how memory
|
||||
is used. The example above doesn't generate an error according to
|
||||
`RESTRICTIONS` because, for local variables, we don't require that the
|
||||
loan lifetime be a subset of the local variable lifetime. The idea
|
||||
here is that we *can* guarantee that `x` is not (e.g.) mutated for the
|
||||
lifetime `'a`, even though `'a` exceeds the function body and thus
|
||||
involves unknown code in the caller -- after all, `x` ceases to exist
|
||||
after we return and hence the remaining code in `'a` cannot possibly
|
||||
mutate it. This distinction is important for type checking functions
|
||||
like this one:
|
||||
|
||||
```rust
|
||||
fn inc_and_get<'a>(p: &'a mut Point) -> &'a i32 {
|
||||
p.x += 1;
|
||||
&p.x
|
||||
}
|
||||
```
|
||||
|
||||
In this case, we take in a `&mut` and return a frozen borrowed pointer
|
||||
with the same lifetime. So long as the lifetime of the returned value
|
||||
doesn't exceed the lifetime of the `&mut` we receive as input, this is
|
||||
fine, though it may seem surprising at first (it surprised me when I
|
||||
first worked it through). After all, we're guaranteeing that `*p`
|
||||
won't be mutated for the lifetime `'a`, even though we can't "see" the
|
||||
entirety of the code during that lifetime, since some of it occurs in
|
||||
our caller. But we *do* know that nobody can mutate `*p` except
|
||||
through `p`. So if we don't mutate `*p` and we don't return `p`, then
|
||||
we know that the right to mutate `*p` has been lost to our caller --
|
||||
in terms of capability, the caller passed in the ability to mutate
|
||||
`*p`, and we never gave it back. (Note that we can't return `p` while
|
||||
`*p` is borrowed since that would be a move of `p`, as `&mut` pointers
|
||||
are affine.)
|
||||
|
||||
### Restrictions for loans of mutable borrowed referents
|
||||
|
||||
Mutable borrowed pointers are guaranteed to be the only way to mutate
|
||||
their referent. This permits us to take greater license with them; for
|
||||
example, the referent can be frozen simply be ensuring that we do not
|
||||
use the original pointer to perform mutate. Similarly, we can allow
|
||||
the referent to be claimed, so long as the original pointer is unused
|
||||
while the new claimant is live.
|
||||
|
||||
The rule for mutable borrowed pointers is as follows:
|
||||
|
||||
```text
|
||||
RESTRICTIONS(*P, LT, ACTIONS) = RS, (*P, ACTIONS) // R-Deref-Mut-Borrowed
|
||||
TYPE(P) = <' mut Ty
|
||||
LT <= LT' // (1)
|
||||
RESTRICTIONS(P, LT, ACTIONS) = RS // (2)
|
||||
```
|
||||
|
||||
Let's examine the two numbered clauses:
|
||||
|
||||
Clause (1) specifies that the lifetime of the loan (`LT`) cannot
|
||||
exceed the lifetime of the `&mut` pointer (`LT'`). The reason for this
|
||||
is that the `&mut` pointer is guaranteed to be the only legal way to
|
||||
mutate its referent -- but only for the lifetime `LT'`. After that
|
||||
lifetime, the loan on the referent expires and hence the data may be
|
||||
modified by its owner again. This implies that we are only able to
|
||||
guarantee that the referent will not be modified or aliased for a
|
||||
maximum of `LT'`.
|
||||
|
||||
Here is a concrete example of a bug this rule prevents:
|
||||
|
||||
```rust
|
||||
// Test region-reborrow-from-shorter-mut-ref.rs:
|
||||
fn copy_borrowed_ptr<'a,'b,T>(x: &'a mut &'b mut T) -> &'b mut T {
|
||||
&mut **p // ERROR due to clause (1)
|
||||
}
|
||||
fn main() {
|
||||
let mut x = 1;
|
||||
let mut y = &mut x; // <-'b-----------------------------+
|
||||
// +-'a--------------------+ |
|
||||
// v v |
|
||||
let z = copy_borrowed_ptr(&mut y); // y is lent |
|
||||
*y += 1; // Here y==z, so both should not be usable... |
|
||||
*z += 1; // ...and yet they would be, but for clause 1. |
|
||||
} // <------------------------------------------------------+
|
||||
```
|
||||
|
||||
Clause (2) propagates the restrictions on the referent to the pointer
|
||||
itself. This is the same as with an box, though the
|
||||
reasoning is mildly different. The basic goal in all cases is to
|
||||
prevent the user from establishing another route to the same data. To
|
||||
see what I mean, let's examine various cases of what can go wrong and
|
||||
show how it is prevented.
|
||||
|
||||
**Example danger 1: Moving the base pointer.** One of the simplest
|
||||
ways to violate the rules is to move the base pointer to a new name
|
||||
and access it via that new name, thus bypassing the restrictions on
|
||||
the old name. Here is an example:
|
||||
|
||||
```rust
|
||||
// src/test/compile-fail/borrowck-move-mut-base-ptr.rs
|
||||
fn foo(t0: &mut i32) {
|
||||
let p: &i32 = &*t0; // Freezes `*t0`
|
||||
let t1 = t0; //~ ERROR cannot move out of `t0`
|
||||
*t1 = 22; // OK, not a write through `*t0`
|
||||
}
|
||||
```
|
||||
|
||||
Remember that `&mut` pointers are linear, and hence `let t1 = t0` is a
|
||||
move of `t0` -- or would be, if it were legal. Instead, we get an
|
||||
error, because clause (2) imposes restrictions on `P` (`t0`, here),
|
||||
and any restrictions on a path make it impossible to move from that
|
||||
path.
|
||||
|
||||
**Example danger 2: Claiming the base pointer.** Another possible
|
||||
danger is to mutably borrow the base path. This can lead to two bad
|
||||
scenarios. The most obvious is that the mutable borrow itself becomes
|
||||
another path to access the same data, as shown here:
|
||||
|
||||
```rust
|
||||
// src/test/compile-fail/borrowck-mut-borrow-of-mut-base-ptr.rs
|
||||
fn foo<'a>(mut t0: &'a mut i32,
|
||||
mut t1: &'a mut i32) {
|
||||
let p: &i32 = &*t0; // Freezes `*t0`
|
||||
let mut t2 = &mut t0; //~ ERROR cannot borrow `t0`
|
||||
**t2 += 1; // Mutates `*t0`
|
||||
}
|
||||
```
|
||||
|
||||
In this example, `**t2` is the same memory as `*t0`. Because `t2` is
|
||||
an `&mut` pointer, `**t2` is a unique path and hence it would be
|
||||
possible to mutate `**t2` even though that memory was supposed to be
|
||||
frozen by the creation of `p`. However, an error is reported -- the
|
||||
reason is that the freeze `&*t0` will restrict claims and mutation
|
||||
against `*t0` which, by clause 2, in turn prevents claims and mutation
|
||||
of `t0`. Hence the claim `&mut t0` is illegal.
|
||||
|
||||
Another danger with an `&mut` pointer is that we could swap the `t0`
|
||||
value away to create a new path:
|
||||
|
||||
```rust
|
||||
// src/test/compile-fail/borrowck-swap-mut-base-ptr.rs
|
||||
fn foo<'a>(mut t0: &'a mut i32,
|
||||
mut t1: &'a mut i32) {
|
||||
let p: &i32 = &*t0; // Freezes `*t0`
|
||||
swap(&mut t0, &mut t1); //~ ERROR cannot borrow `t0`
|
||||
*t1 = 22;
|
||||
}
|
||||
```
|
||||
|
||||
This is illegal for the same reason as above. Note that if we added
|
||||
back a swap operator -- as we used to have -- we would want to be very
|
||||
careful to ensure this example is still illegal.
|
||||
|
||||
**Example danger 3: Freeze the base pointer.** In the case where the
|
||||
referent is claimed, even freezing the base pointer can be dangerous,
|
||||
as shown in the following example:
|
||||
|
||||
```rust
|
||||
// src/test/compile-fail/borrowck-borrow-of-mut-base-ptr.rs
|
||||
fn foo<'a>(mut t0: &'a mut i32,
|
||||
mut t1: &'a mut i32) {
|
||||
let p: &mut i32 = &mut *t0; // Claims `*t0`
|
||||
let mut t2 = &t0; //~ ERROR cannot borrow `t0`
|
||||
let q: &i32 = &*t2; // Freezes `*t0` but not through `*p`
|
||||
*p += 1; // violates type of `*q`
|
||||
}
|
||||
```
|
||||
|
||||
Here the problem is that `*t0` is claimed by `p`, and hence `p` wants
|
||||
to be the controlling pointer through which mutation or freezes occur.
|
||||
But `t2` would -- if it were legal -- have the type `& &mut i32`, and
|
||||
hence would be a mutable pointer in an aliasable location, which is
|
||||
considered frozen (since no one can write to `**t2` as it is not a
|
||||
unique path). Therefore, we could reasonably create a frozen `&i32`
|
||||
pointer pointing at `*t0` that coexists with the mutable pointer `p`,
|
||||
which is clearly unsound.
|
||||
|
||||
However, it is not always unsafe to freeze the base pointer. In
|
||||
particular, if the referent is frozen, there is no harm in it:
|
||||
|
||||
```rust
|
||||
// src/test/ui/borrowck-borrow-of-mut-base-ptr-safe.rs
|
||||
fn foo<'a>(mut t0: &'a mut i32,
|
||||
mut t1: &'a mut i32) {
|
||||
let p: &i32 = &*t0; // Freezes `*t0`
|
||||
let mut t2 = &t0;
|
||||
let q: &i32 = &*t2; // Freezes `*t0`, but that's ok...
|
||||
let r: &i32 = &*t0; // ...after all, could do same thing directly.
|
||||
}
|
||||
```
|
||||
|
||||
In this case, creating the alias `t2` of `t0` is safe because the only
|
||||
thing `t2` can be used for is to further freeze `*t0`, which is
|
||||
already frozen. In particular, we cannot assign to `*t0` through the
|
||||
new alias `t2`, as demonstrated in this test case:
|
||||
|
||||
```rust
|
||||
// src/test/ui/borrowck-borrow-mut-base-ptr-in-aliasable-loc.rs
|
||||
fn foo(t0: & &mut i32) {
|
||||
let t1 = t0;
|
||||
let p: &i32 = &**t0;
|
||||
**t1 = 22; //~ ERROR cannot assign
|
||||
}
|
||||
```
|
||||
|
||||
This distinction is reflected in the rules. When doing an `&mut`
|
||||
borrow -- as in the first example -- the set `ACTIONS` will be
|
||||
`CLAIM|MUTATE|FREEZE`, because claiming the referent implies that it
|
||||
cannot be claimed, mutated, or frozen by anyone else. These
|
||||
restrictions are propagated back to the base path and hence the base
|
||||
path is considered unfreezable.
|
||||
|
||||
In contrast, when the referent is merely frozen -- as in the second
|
||||
example -- the set `ACTIONS` will be `CLAIM|MUTATE`, because freezing
|
||||
the referent implies that it cannot be claimed or mutated but permits
|
||||
others to freeze. Hence when these restrictions are propagated back to
|
||||
the base path, it will still be considered freezable.
|
||||
|
||||
|
||||
|
||||
**FIXME [RFC 1751](https://github.com/rust-lang/rfcs/issues/1751)
|
||||
Restrictions against mutating the base pointer.**
|
||||
When an `&mut` pointer is frozen or claimed, we currently pass along the
|
||||
restriction against MUTATE to the base pointer. I do not believe this
|
||||
restriction is needed. It dates from the days when we had a way to
|
||||
mutate that preserved the value being mutated (i.e., swap). Nowadays
|
||||
the only form of mutation is assignment, which destroys the pointer
|
||||
being mutated -- therefore, a mutation cannot create a new path to the
|
||||
same data. Rather, it removes an existing path. This implies that not
|
||||
only can we permit mutation, we can have mutation kill restrictions in
|
||||
the dataflow sense.
|
||||
|
||||
**WARNING:** We do not currently have `const` borrows in the
|
||||
language. If they are added back in, we must ensure that they are
|
||||
consistent with all of these examples. The crucial question will be
|
||||
what sorts of actions are permitted with a `&const &mut` pointer. I
|
||||
would suggest that an `&mut` referent found in an `&const` location be
|
||||
prohibited from both freezes and claims. This would avoid the need to
|
||||
prevent `const` borrows of the base pointer when the referent is
|
||||
borrowed.
|
||||
|
||||
[ Previous revisions of this document discussed `&const` in more detail.
|
||||
See the revision history. ]
|
||||
|
||||
# Moves and initialization
|
||||
|
||||
The borrow checker is also in charge of ensuring that:
|
||||
|
||||
- all memory which is accessed is initialized
|
||||
- immutable local variables are assigned at most once.
|
||||
|
||||
These are two separate dataflow analyses built on the same
|
||||
framework. Let's look at checking that memory is initialized first;
|
||||
the checking of immutable local variable assignments works in a very
|
||||
similar way.
|
||||
|
||||
To track the initialization of memory, we actually track all the
|
||||
points in the program that *create uninitialized memory*, meaning
|
||||
moves and the declaration of uninitialized variables. For each of
|
||||
these points, we create a bit in the dataflow set. Assignments to a
|
||||
variable `x` or path `a.b.c` kill the move/uninitialization bits for
|
||||
those paths and any subpaths (e.g., `x`, `x.y`, `a.b.c`, `*a.b.c`).
|
||||
Bits are unioned when two control-flow paths join. Thus, the
|
||||
presence of a bit indicates that the move may have occurred without an
|
||||
intervening assignment to the same memory. At each use of a variable,
|
||||
we examine the bits in scope, and check that none of them are
|
||||
moves/uninitializations of the variable that is being used.
|
||||
|
||||
Let's look at a simple example:
|
||||
|
||||
```rust
|
||||
fn foo(a: Box<i32>) {
|
||||
let b: Box<i32>; // Gen bit 0.
|
||||
|
||||
if cond { // Bits: 0
|
||||
use(&*a);
|
||||
b = a; // Gen bit 1, kill bit 0.
|
||||
use(&*b);
|
||||
} else {
|
||||
// Bits: 0
|
||||
}
|
||||
// Bits: 0,1
|
||||
use(&*a); // Error.
|
||||
use(&*b); // Error.
|
||||
}
|
||||
|
||||
fn use(a: &i32) { }
|
||||
```
|
||||
|
||||
In this example, the variable `b` is created uninitialized. In one
|
||||
branch of an `if`, we then move the variable `a` into `b`. Once we
|
||||
exit the `if`, therefore, it is an error to use `a` or `b` since both
|
||||
are only conditionally initialized. I have annotated the dataflow
|
||||
state using comments. There are two dataflow bits, with bit 0
|
||||
corresponding to the creation of `b` without an initializer, and bit 1
|
||||
corresponding to the move of `a`. The assignment `b = a` both
|
||||
generates bit 1, because it is a move of `a`, and kills bit 0, because
|
||||
`b` is now initialized. On the else branch, though, `b` is never
|
||||
initialized, and so bit 0 remains untouched. When the two flows of
|
||||
control join, we union the bits from both sides, resulting in both
|
||||
bits 0 and 1 being set. Thus any attempt to use `a` uncovers the bit 1
|
||||
from the "then" branch, showing that `a` may be moved, and any attempt
|
||||
to use `b` uncovers bit 0, from the "else" branch, showing that `b`
|
||||
may not be initialized.
|
||||
|
||||
## Initialization of immutable variables
|
||||
|
||||
Initialization of immutable variables works in a very similar way,
|
||||
except that:
|
||||
|
||||
1. we generate bits for each assignment to a variable;
|
||||
2. the bits are never killed except when the variable goes out of scope.
|
||||
|
||||
Thus the presence of an assignment bit indicates that the assignment
|
||||
may have occurred. Note that assignments are only killed when the
|
||||
variable goes out of scope, as it is not relevant whether or not there
|
||||
has been a move in the meantime. Using these bits, we can declare that
|
||||
an assignment to an immutable variable is legal iff there is no other
|
||||
assignment bit to that same variable in scope.
|
||||
|
||||
## Why is the design made this way?
|
||||
|
||||
It may seem surprising that we assign dataflow bits to *each move*
|
||||
rather than *each path being moved*. This is somewhat less efficient,
|
||||
since on each use, we must iterate through all moves and check whether
|
||||
any of them correspond to the path in question. Similar concerns apply
|
||||
to the analysis for double assignments to immutable variables. The
|
||||
main reason to do it this way is that it allows us to print better
|
||||
error messages, because when a use occurs, we can print out the
|
||||
precise move that may be in scope, rather than simply having to say
|
||||
"the variable may not be initialized".
|
||||
|
||||
## Data structures used in the move analysis
|
||||
|
||||
The move analysis maintains several data structures that enable it to
|
||||
cross-reference moves and assignments to determine when they may be
|
||||
moving/assigning the same memory. These are all collected into the
|
||||
`MoveData` and `FlowedMoveData` structs. The former represents the set
|
||||
of move paths, moves, and assignments, and the latter adds in the
|
||||
results of a dataflow computation.
|
||||
|
||||
### Move paths
|
||||
|
||||
The `MovePath` tree tracks every path that is moved or assigned to.
|
||||
These paths have the same form as the `LoanPath` data structure, which
|
||||
in turn is the "real world version of the places `P` that we
|
||||
introduced earlier. The difference between a `MovePath` and a `LoanPath`
|
||||
is that move paths are:
|
||||
|
||||
1. Canonicalized, so that we have exactly one copy of each, and
|
||||
we can refer to move paths by index;
|
||||
2. Cross-referenced with other paths into a tree, so that given a move
|
||||
path we can efficiently find all parent move paths and all
|
||||
extensions (e.g., given the `a.b` move path, we can easily find the
|
||||
move path `a` and also the move paths `a.b.c`)
|
||||
3. Cross-referenced with moves and assignments, so that we can
|
||||
easily find all moves and assignments to a given path.
|
||||
|
||||
The mechanism that we use is to create a `MovePath` record for each
|
||||
move path. These are arranged in an array and are referenced using
|
||||
`MovePathIndex` values, which are newtype'd indices. The `MovePath`
|
||||
structs are arranged into a tree, representing using the standard
|
||||
Knuth representation where each node has a child 'pointer' and a "next
|
||||
sibling" 'pointer'. In addition, each `MovePath` has a parent
|
||||
'pointer'. In this case, the 'pointers' are just `MovePathIndex`
|
||||
values.
|
||||
|
||||
In this way, if we want to find all base paths of a given move path,
|
||||
we can just iterate up the parent pointers (see `each_base_path()` in
|
||||
the `move_data` module). If we want to find all extensions, we can
|
||||
iterate through the subtree (see `each_extending_path()`).
|
||||
|
||||
### Moves and assignments
|
||||
|
||||
There are structs to represent moves (`Move`) and assignments
|
||||
(`Assignment`), and these are also placed into arrays and referenced
|
||||
by index. All moves of a particular path are arranged into a linked
|
||||
lists, beginning with `MovePath.first_move` and continuing through
|
||||
`Move.next_move`.
|
||||
|
||||
We distinguish between "var" assignments, which are assignments to a
|
||||
variable like `x = foo`, and "path" assignments (`x.f = foo`). This
|
||||
is because we need to assign dataflows to the former, but not the
|
||||
latter, so as to check for double initialization of immutable
|
||||
variables.
|
||||
|
||||
### Gathering and checking moves
|
||||
|
||||
Like loans, we distinguish two phases. The first, gathering, is where
|
||||
we uncover all the moves and assignments. As with loans, we do some
|
||||
basic sanity checking in this phase, so we'll report errors if you
|
||||
attempt to move out of a borrowed pointer etc. Then we do the dataflow
|
||||
(see `FlowedMoveData::new`). Finally, in the `check_loans.rs` code, we
|
||||
walk back over, identify all uses, assignments, and captures, and
|
||||
check that they are legal given the set of dataflow bits we have
|
||||
computed for that program point.
|
||||
|
||||
# Drop flags and structural fragments
|
||||
|
||||
In addition to the job of enforcing memory safety, the borrow checker
|
||||
code is also responsible for identifying the *structural fragments* of
|
||||
data in the function, to support out-of-band dynamic drop flags
|
||||
allocated on the stack. (For background, see [RFC PR #320].)
|
||||
|
||||
[RFC PR #320]: https://github.com/rust-lang/rfcs/pull/320
|
||||
|
||||
Semantically, each piece of data that has a destructor may need a
|
||||
boolean flag to indicate whether or not its destructor has been run
|
||||
yet. However, in many cases there is no need to actually maintain such
|
||||
a flag: It can be apparent from the code itself that a given path is
|
||||
always initialized (or always deinitialized) when control reaches the
|
||||
end of its owner's scope, and thus we can unconditionally emit (or
|
||||
not) the destructor invocation for that path.
|
||||
|
||||
A simple example of this is the following:
|
||||
|
||||
```rust
|
||||
struct D { p: i32 }
|
||||
impl D { fn new(x: i32) -> D { ... }
|
||||
impl Drop for D { ... }
|
||||
|
||||
fn foo(a: D, b: D, t: || -> bool) {
|
||||
let c: D;
|
||||
let d: D;
|
||||
if t() { c = b; }
|
||||
}
|
||||
```
|
||||
|
||||
At the end of the body of `foo`, the compiler knows that `a` is
|
||||
initialized, introducing a drop obligation (deallocating the boxed
|
||||
integer) for the end of `a`'s scope that is run unconditionally.
|
||||
Likewise the compiler knows that `d` is not initialized, and thus it
|
||||
leave out the drop code for `d`.
|
||||
|
||||
The compiler cannot statically know the drop-state of `b` nor `c` at
|
||||
the end of their scope, since that depends on the value of
|
||||
`t`. Therefore, we need to insert boolean flags to track whether we
|
||||
need to drop `b` and `c`.
|
||||
|
||||
However, the matter is not as simple as just mapping local variables
|
||||
to their corresponding drop flags when necessary. In particular, in
|
||||
addition to being able to move data out of local variables, Rust
|
||||
allows one to move values in and out of structured data.
|
||||
|
||||
Consider the following:
|
||||
|
||||
```rust
|
||||
struct S { x: D, y: D, z: D }
|
||||
|
||||
fn foo(a: S, mut b: S, t: || -> bool) {
|
||||
let mut c: S;
|
||||
let d: S;
|
||||
let e: S = a.clone();
|
||||
if t() {
|
||||
c = b;
|
||||
b.x = e.y;
|
||||
}
|
||||
if t() { c.y = D::new(4); }
|
||||
}
|
||||
```
|
||||
|
||||
As before, the drop obligations of `a` and `d` can be statically
|
||||
determined, and again the state of `b` and `c` depend on dynamic
|
||||
state. But additionally, the dynamic drop obligations introduced by
|
||||
`b` and `c` are not just per-local boolean flags. For example, if the
|
||||
first call to `t` returns `false` and the second call `true`, then at
|
||||
the end of their scope, `b` will be completely initialized, but only
|
||||
`c.y` in `c` will be initialized. If both calls to `t` return `true`,
|
||||
then at the end of their scope, `c` will be completely initialized,
|
||||
but only `b.x` will be initialized in `b`, and only `e.x` and `e.z`
|
||||
will be initialized in `e`.
|
||||
|
||||
Note that we need to cover the `z` field in each case in some way,
|
||||
since it may (or may not) need to be dropped, even though `z` is never
|
||||
directly mentioned in the body of the `foo` function. We call a path
|
||||
like `b.z` a *fragment sibling* of `b.x`, since the field `z` comes
|
||||
from the same structure `S` that declared the field `x` in `b.x`.
|
||||
|
||||
In general we need to maintain boolean flags that match the
|
||||
`S`-structure of both `b` and `c`. In addition, we need to consult
|
||||
such a flag when doing an assignment (such as `c.y = D::new(4);`
|
||||
above), in order to know whether or not there is a previous value that
|
||||
needs to be dropped before we do the assignment.
|
||||
|
||||
So for any given function, we need to determine what flags are needed
|
||||
to track its drop obligations. Our strategy for determining the set of
|
||||
flags is to represent the fragmentation of the structure explicitly:
|
||||
by starting initially from the paths that are explicitly mentioned in
|
||||
moves and assignments (such as `b.x` and `c.y` above), and then
|
||||
traversing the structure of the path's type to identify leftover
|
||||
*unmoved fragments*: assigning into `c.y` means that `c.x` and `c.z`
|
||||
are leftover unmoved fragments. Each fragment represents a drop
|
||||
obligation that may need to be tracked. Paths that are only moved or
|
||||
assigned in their entirety (like `a` and `d`) are treated as a single
|
||||
drop obligation.
|
||||
|
||||
The fragment construction process works by piggy-backing on the
|
||||
existing `move_data` module. We already have callbacks that visit each
|
||||
direct move and assignment; these form the basis for the sets of
|
||||
moved_leaf_paths and assigned_leaf_paths. From these leaves, we can
|
||||
walk up their parent chain to identify all of their parent paths.
|
||||
We need to identify the parents because of cases like the following:
|
||||
|
||||
```rust
|
||||
struct Pair<X,Y>{ x: X, y: Y }
|
||||
fn foo(dd_d_d: Pair<Pair<Pair<D, D>, D>, D>) {
|
||||
other_function(dd_d_d.x.y);
|
||||
}
|
||||
```
|
||||
|
||||
In this code, the move of the path `dd_d.x.y` leaves behind not only
|
||||
the fragment drop-obligation `dd_d.x.x` but also `dd_d.y` as well.
|
||||
|
||||
Once we have identified the directly-referenced leaves and their
|
||||
parents, we compute the left-over fragments, in the function
|
||||
`fragments::add_fragment_siblings`. As of this writing this works by
|
||||
looking at each directly-moved or assigned path P, and blindly
|
||||
gathering all sibling fields of P (as well as siblings for the parents
|
||||
of P, etc). After accumulating all such siblings, we filter out the
|
||||
entries added as siblings of P that turned out to be
|
||||
directly-referenced paths (or parents of directly referenced paths)
|
||||
themselves, thus leaving the never-referenced "left-overs" as the only
|
||||
thing left from the gathering step.
|
||||
|
||||
## Array structural fragments
|
||||
|
||||
A special case of the structural fragments discussed above are
|
||||
the elements of an array that has been passed by value, such as
|
||||
the following:
|
||||
|
||||
```rust
|
||||
fn foo(a: [D; 10], i: i32) -> D {
|
||||
a[i]
|
||||
}
|
||||
```
|
||||
|
||||
The above code moves a single element out of the input array `a`.
|
||||
The remainder of the array still needs to be dropped; i.e., it
|
||||
is a structural fragment. Note that after performing such a move,
|
||||
it is not legal to read from the array `a`. There are a number of
|
||||
ways to deal with this, but the important thing to note is that
|
||||
the semantics needs to distinguish in some manner between a
|
||||
fragment that is the *entire* array versus a fragment that represents
|
||||
all-but-one element of the array. A place where that distinction
|
||||
would arise is the following:
|
||||
|
||||
```rust
|
||||
fn foo(a: [D; 10], b: [D; 10], i: i32, t: bool) -> D {
|
||||
if t {
|
||||
a[i]
|
||||
} else {
|
||||
b[i]
|
||||
}
|
||||
|
||||
// When control exits, we will need either to drop all of `a`
|
||||
// and all-but-one of `b`, or to drop all of `b` and all-but-one
|
||||
// of `a`.
|
||||
}
|
||||
```
|
||||
|
||||
There are a number of ways that the codegen backend could choose to
|
||||
compile this (e.g. a `[bool; 10]` array for each such moved array;
|
||||
or an `Option<usize>` for each moved array). From the viewpoint of the
|
||||
borrow-checker, the important thing is to record what kind of fragment
|
||||
is implied by the relevant moves.
|
||||
|
||||
# Future work
|
||||
|
||||
While writing up these docs, I encountered some rules I believe to be
|
||||
stricter than necessary:
|
||||
|
||||
- I think restricting the `&mut` P against moves and `ALIAS` is sufficient,
|
||||
`MUTATE` and `CLAIM` are overkill. `MUTATE` was necessary when swap was
|
||||
a built-in operator, but as it is not, it is implied by `CLAIM`,
|
||||
and `CLAIM` is implied by `ALIAS`. The only net effect of this is an
|
||||
extra error message in some cases, though.
|
||||
- I have not described how closures interact. Current code is unsound.
|
||||
I am working on describing and implementing the fix.
|
||||
- If we wish, we can easily extend the move checking to allow finer-grained
|
||||
tracking of what is initialized and what is not, enabling code like
|
||||
this:
|
||||
|
||||
a = x.f.g; // x.f.g is now uninitialized
|
||||
// here, x and x.f are not usable, but x.f.h *is*
|
||||
x.f.g = b; // x.f.g is not initialized
|
||||
// now x, x.f, x.f.g, x.f.h are all usable
|
||||
|
||||
What needs to change here, most likely, is that the `moves` module
|
||||
should record not only what paths are moved, but what expressions
|
||||
are actual *uses*. For example, the reference to `x` in `x.f.g = b`
|
||||
is not a true *use* in the sense that it requires `x` to be fully
|
||||
initialized. This is in fact why the above code produces an error
|
||||
today: the reference to `x` in `x.f.g = b` is considered illegal
|
||||
because `x` is not fully initialized.
|
||||
|
||||
There are also some possible refactorings:
|
||||
|
||||
- It might be nice to replace all loan paths with the MovePath mechanism,
|
||||
since they allow lightweight comparison using an integer.
|
||||
@@ -1,680 +0,0 @@
|
||||
// ----------------------------------------------------------------------
|
||||
// Checking loans
|
||||
//
|
||||
// Phase 2 of check: we walk down the tree and check that:
|
||||
// 1. assignments are always made to mutable locations;
|
||||
// 2. loans made in overlapping scopes do not conflict
|
||||
// 3. assignments do not affect things loaned out as immutable
|
||||
// 4. moves do not affect things loaned out in any way
|
||||
|
||||
use crate::borrowck::*;
|
||||
use crate::borrowck::InteriorKind::{InteriorElement, InteriorField};
|
||||
use rustc::middle::expr_use_visitor as euv;
|
||||
use rustc::middle::expr_use_visitor::MutateMode;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty::{self, TyCtxt, RegionKind};
|
||||
use syntax_pos::Span;
|
||||
use rustc::hir;
|
||||
use rustc::hir::Node;
|
||||
use log::debug;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
// FIXME (#16118): These functions are intended to allow the borrow checker to
|
||||
// be less precise in its handling of Box while still allowing moves out of a
|
||||
// Box. They should be removed when Unique is removed from LoanPath.
|
||||
|
||||
fn owned_ptr_base_path<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> &'a LoanPath<'tcx> {
|
||||
//! Returns the base of the leftmost dereference of an Unique in
|
||||
//! `loan_path`. If there is no dereference of an Unique in `loan_path`,
|
||||
//! then it just returns `loan_path` itself.
|
||||
|
||||
return match helper(loan_path) {
|
||||
Some(new_loan_path) => new_loan_path,
|
||||
None => loan_path,
|
||||
};
|
||||
|
||||
fn helper<'a, 'tcx>(loan_path: &'a LoanPath<'tcx>) -> Option<&'a LoanPath<'tcx>> {
|
||||
match loan_path.kind {
|
||||
LpVar(_) | LpUpvar(_) => None,
|
||||
LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => {
|
||||
match helper(&lp_base) {
|
||||
v @ Some(_) => v,
|
||||
None => Some(&lp_base)
|
||||
}
|
||||
}
|
||||
LpDowncast(ref lp_base, _) |
|
||||
LpExtend(ref lp_base, ..) => helper(&lp_base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn owned_ptr_base_path_rc<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Rc<LoanPath<'tcx>> {
|
||||
//! The equivalent of `owned_ptr_base_path` for an &Rc<LoanPath> rather than
|
||||
//! a &LoanPath.
|
||||
|
||||
return match helper(loan_path) {
|
||||
Some(new_loan_path) => new_loan_path,
|
||||
None => loan_path.clone()
|
||||
};
|
||||
|
||||
fn helper<'tcx>(loan_path: &Rc<LoanPath<'tcx>>) -> Option<Rc<LoanPath<'tcx>>> {
|
||||
match loan_path.kind {
|
||||
LpVar(_) | LpUpvar(_) => None,
|
||||
LpExtend(ref lp_base, _, LpDeref(mc::Unique)) => {
|
||||
match helper(lp_base) {
|
||||
v @ Some(_) => v,
|
||||
None => Some(lp_base.clone())
|
||||
}
|
||||
}
|
||||
LpDowncast(ref lp_base, _) |
|
||||
LpExtend(ref lp_base, ..) => helper(lp_base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CheckLoanCtxt<'a, 'tcx> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
dfcx_loans: &'a LoanDataFlow<'tcx>,
|
||||
move_data: &'a move_data::FlowedMoveData<'tcx>,
|
||||
all_loans: &'a [Loan<'tcx>],
|
||||
movable_generator: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> euv::Delegate<'tcx> for CheckLoanCtxt<'a, 'tcx> {
|
||||
fn consume(&mut self,
|
||||
consume_id: hir::HirId,
|
||||
_: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume(consume_id={}, cmt={:?})", consume_id, cmt);
|
||||
|
||||
self.consume_common(consume_id.local_id, cmt, mode);
|
||||
}
|
||||
|
||||
fn matched_pat(&mut self,
|
||||
_matched_pat: &hir::Pat,
|
||||
_cmt: &mc::cmt_<'_>,
|
||||
_mode: euv::MatchMode) { }
|
||||
|
||||
fn consume_pat(&mut self,
|
||||
consume_pat: &hir::Pat,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume_pat(consume_pat={:?}, cmt={:?})", consume_pat, cmt);
|
||||
|
||||
self.consume_common(consume_pat.hir_id.local_id, cmt, mode);
|
||||
}
|
||||
|
||||
fn borrow(&mut self,
|
||||
borrow_id: hir::HirId,
|
||||
borrow_span: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
bk: ty::BorrowKind,
|
||||
loan_cause: euv::LoanCause)
|
||||
{
|
||||
debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \
|
||||
bk={:?}, loan_cause={:?})",
|
||||
borrow_id, cmt, loan_region,
|
||||
bk, loan_cause);
|
||||
|
||||
if let Some(lp) = opt_loan_path(cmt) {
|
||||
self.check_if_path_is_moved(borrow_id.local_id, &lp);
|
||||
}
|
||||
|
||||
self.check_for_conflicting_loans(borrow_id.local_id);
|
||||
|
||||
self.check_for_loans_across_yields(cmt, loan_region, borrow_span);
|
||||
}
|
||||
|
||||
fn mutate(&mut self,
|
||||
assignment_id: hir::HirId,
|
||||
_: Span,
|
||||
assignee_cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::MutateMode)
|
||||
{
|
||||
debug!("mutate(assignment_id={}, assignee_cmt={:?})",
|
||||
assignment_id, assignee_cmt);
|
||||
|
||||
if let Some(lp) = opt_loan_path(assignee_cmt) {
|
||||
match mode {
|
||||
MutateMode::Init | MutateMode::JustWrite => {
|
||||
// In a case like `path = 1`, then path does not
|
||||
// have to be *FULLY* initialized, but we still
|
||||
// must be careful lest it contains derefs of
|
||||
// pointers.
|
||||
self.check_if_assigned_path_is_moved(assignee_cmt.hir_id.local_id, &lp);
|
||||
}
|
||||
MutateMode::WriteAndRead => {
|
||||
// In a case like `path += 1`, then path must be
|
||||
// fully initialized, since we will read it before
|
||||
// we write it.
|
||||
self.check_if_path_is_moved(assignee_cmt.hir_id.local_id,
|
||||
&lp);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.check_assignment(assignment_id.local_id, assignee_cmt);
|
||||
}
|
||||
|
||||
fn decl_without_init(&mut self, _id: hir::HirId, _span: Span) { }
|
||||
}
|
||||
|
||||
pub fn check_loans<'a, 'tcx>(
|
||||
bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
dfcx_loans: &LoanDataFlow<'tcx>,
|
||||
move_data: &move_data::FlowedMoveData<'tcx>,
|
||||
all_loans: &[Loan<'tcx>],
|
||||
body: &hir::Body,
|
||||
) {
|
||||
debug!("check_loans(body id={})", body.value.hir_id);
|
||||
|
||||
let def_id = bccx.tcx.hir().body_owner_def_id(body.id());
|
||||
|
||||
let hir_id = bccx.tcx.hir().as_local_hir_id(def_id).unwrap();
|
||||
let movable_generator = !match bccx.tcx.hir().get(hir_id) {
|
||||
Node::Expr(&hir::Expr {
|
||||
kind: hir::ExprKind::Closure(.., Some(hir::GeneratorMovability::Static)),
|
||||
..
|
||||
}) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let param_env = bccx.tcx.param_env(def_id);
|
||||
let mut clcx = CheckLoanCtxt {
|
||||
bccx,
|
||||
dfcx_loans,
|
||||
move_data,
|
||||
all_loans,
|
||||
movable_generator,
|
||||
};
|
||||
let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id);
|
||||
euv::ExprUseVisitor::new(&mut clcx,
|
||||
bccx.tcx,
|
||||
def_id,
|
||||
param_env,
|
||||
&bccx.region_scope_tree,
|
||||
bccx.tables,
|
||||
Some(rvalue_promotable_map))
|
||||
.consume_body(body);
|
||||
}
|
||||
|
||||
fn compatible_borrow_kinds(borrow_kind1: ty::BorrowKind,
|
||||
borrow_kind2: ty::BorrowKind)
|
||||
-> bool {
|
||||
borrow_kind1 == ty::ImmBorrow && borrow_kind2 == ty::ImmBorrow
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> CheckLoanCtxt<'a, 'tcx> {
|
||||
pub fn tcx(&self) -> TyCtxt<'tcx> { self.bccx.tcx }
|
||||
|
||||
pub fn each_issued_loan<F>(&self, node: hir::ItemLocalId, mut op: F) -> bool where
|
||||
F: FnMut(&Loan<'tcx>) -> bool,
|
||||
{
|
||||
//! Iterates over each loan that has been issued
|
||||
//! on entrance to `node`, regardless of whether it is
|
||||
//! actually *in scope* at that point. Sometimes loans
|
||||
//! are issued for future scopes and thus they may have been
|
||||
//! *issued* but not yet be in effect.
|
||||
|
||||
self.dfcx_loans.each_bit_on_entry(node, |loan_index| {
|
||||
let loan = &self.all_loans[loan_index];
|
||||
op(loan)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn each_in_scope_loan<F>(&self, scope: region::Scope, mut op: F) -> bool where
|
||||
F: FnMut(&Loan<'tcx>) -> bool,
|
||||
{
|
||||
//! Like `each_issued_loan()`, but only considers loans that are
|
||||
//! currently in scope.
|
||||
|
||||
self.each_issued_loan(scope.item_local_id(), |loan| {
|
||||
if self.bccx.region_scope_tree.is_subscope_of(scope, loan.kill_scope) {
|
||||
op(loan)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn each_in_scope_loan_affecting_path<F>(&self,
|
||||
scope: region::Scope,
|
||||
loan_path: &LoanPath<'tcx>,
|
||||
mut op: F)
|
||||
-> bool where
|
||||
F: FnMut(&Loan<'tcx>) -> bool,
|
||||
{
|
||||
//! Iterates through all of the in-scope loans affecting `loan_path`,
|
||||
//! calling `op`, and ceasing iteration if `false` is returned.
|
||||
|
||||
// First, we check for a loan restricting the path P being used. This
|
||||
// accounts for borrows of P but also borrows of subpaths, like P.a.b.
|
||||
// Consider the following example:
|
||||
//
|
||||
// let x = &mut a.b.c; // Restricts a, a.b, and a.b.c
|
||||
// let y = a; // Conflicts with restriction
|
||||
|
||||
let loan_path = owned_ptr_base_path(loan_path);
|
||||
let cont = self.each_in_scope_loan(scope, |loan| {
|
||||
let mut ret = true;
|
||||
for restr_path in &loan.restricted_paths {
|
||||
if **restr_path == *loan_path {
|
||||
if !op(loan) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
});
|
||||
|
||||
if !cont {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Next, we must check for *loans* (not restrictions) on the path P or
|
||||
// any base path. This rejects examples like the following:
|
||||
//
|
||||
// let x = &mut a.b;
|
||||
// let y = a.b.c;
|
||||
//
|
||||
// Limiting this search to *loans* and not *restrictions* means that
|
||||
// examples like the following continue to work:
|
||||
//
|
||||
// let x = &mut a.b;
|
||||
// let y = a.c;
|
||||
|
||||
let mut loan_path = loan_path;
|
||||
loop {
|
||||
match loan_path.kind {
|
||||
LpVar(_) | LpUpvar(_) => {
|
||||
break;
|
||||
}
|
||||
LpDowncast(ref lp_base, _) |
|
||||
LpExtend(ref lp_base, ..) => {
|
||||
loan_path = &lp_base;
|
||||
}
|
||||
}
|
||||
|
||||
let cont = self.each_in_scope_loan(scope, |loan| {
|
||||
if *loan.loan_path == *loan_path {
|
||||
op(loan)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
if !cont {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn loans_generated_by(&self, node: hir::ItemLocalId) -> Vec<usize> {
|
||||
//! Returns a vector of the loans that are generated as
|
||||
//! we enter `node`.
|
||||
|
||||
let mut result = Vec::new();
|
||||
self.dfcx_loans.each_gen_bit(node, |loan_index| {
|
||||
result.push(loan_index);
|
||||
true
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn check_for_loans_across_yields(&self,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
borrow_span: Span) {
|
||||
pub fn borrow_of_local_data(cmt: &mc::cmt_<'_>) -> bool {
|
||||
match cmt.cat {
|
||||
// Borrows of static items is allowed
|
||||
Categorization::StaticItem => false,
|
||||
// Reborrow of already borrowed data is ignored
|
||||
// Any errors will be caught on the initial borrow
|
||||
Categorization::Deref(..) => false,
|
||||
|
||||
// By-ref upvars has Derefs so they will get ignored.
|
||||
// Generators counts as FnOnce so this leaves only
|
||||
// by-move upvars, which is local data for generators
|
||||
Categorization::Upvar(..) => true,
|
||||
|
||||
Categorization::ThreadLocal(region) |
|
||||
Categorization::Rvalue(region) => {
|
||||
// Rvalues promoted to 'static are no longer local
|
||||
if let RegionKind::ReStatic = *region {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Borrow of local data must be checked
|
||||
Categorization::Local(..) => true,
|
||||
|
||||
// For interior references and downcasts, find out if the base is local
|
||||
Categorization::Downcast(ref cmt_base, _) |
|
||||
Categorization::Interior(ref cmt_base, _) => borrow_of_local_data(&cmt_base),
|
||||
}
|
||||
}
|
||||
|
||||
if !self.movable_generator {
|
||||
return;
|
||||
}
|
||||
|
||||
if !borrow_of_local_data(cmt) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scope = match *loan_region {
|
||||
// A concrete region in which we will look for a yield expression
|
||||
RegionKind::ReScope(scope) => scope,
|
||||
|
||||
// There cannot be yields inside an empty region
|
||||
RegionKind::ReEmpty => return,
|
||||
|
||||
// Local data cannot have these lifetimes
|
||||
RegionKind::ReEarlyBound(..) |
|
||||
RegionKind::ReLateBound(..) |
|
||||
RegionKind::ReFree(..) |
|
||||
RegionKind::ReStatic => {
|
||||
self.bccx
|
||||
.tcx
|
||||
.sess.delay_span_bug(borrow_span,
|
||||
&format!("unexpected region for local data {:?}",
|
||||
loan_region));
|
||||
return
|
||||
}
|
||||
|
||||
// These cannot exist in borrowck
|
||||
RegionKind::ReVar(..) |
|
||||
RegionKind::RePlaceholder(..) |
|
||||
RegionKind::ReClosureBound(..) |
|
||||
RegionKind::ReErased => span_bug!(borrow_span,
|
||||
"unexpected region in borrowck {:?}",
|
||||
loan_region),
|
||||
};
|
||||
|
||||
let body_id = self.bccx.body.value.hir_id.local_id;
|
||||
|
||||
if self.bccx.region_scope_tree.containing_body(scope) != Some(body_id) {
|
||||
// We are borrowing local data longer than its storage.
|
||||
// This should result in other borrowck errors.
|
||||
self.bccx.tcx.sess.delay_span_bug(borrow_span,
|
||||
"borrowing local data longer than its storage");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(_) = self.bccx.region_scope_tree
|
||||
.yield_in_scope_for_expr(scope, cmt.hir_id, self.bccx.body)
|
||||
{
|
||||
self.bccx.signal_error();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_for_conflicting_loans(&self, node: hir::ItemLocalId) {
|
||||
//! Checks to see whether any of the loans that are issued
|
||||
//! on entrance to `node` conflict with loans that have already been
|
||||
//! issued when we enter `node` (for example, we do not
|
||||
//! permit two `&mut` borrows of the same variable).
|
||||
//!
|
||||
//! (Note that some loans can be *issued* without necessarily
|
||||
//! taking effect yet.)
|
||||
|
||||
debug!("check_for_conflicting_loans(node={:?})", node);
|
||||
|
||||
let new_loan_indices = self.loans_generated_by(node);
|
||||
debug!("new_loan_indices = {:?}", new_loan_indices);
|
||||
|
||||
for &new_loan_index in &new_loan_indices {
|
||||
self.each_issued_loan(node, |issued_loan| {
|
||||
let new_loan = &self.all_loans[new_loan_index];
|
||||
// Only report an error for the first issued loan that conflicts
|
||||
// to avoid O(n^2) errors.
|
||||
self.report_error_if_loans_conflict(issued_loan, new_loan)
|
||||
});
|
||||
}
|
||||
|
||||
for (i, &x) in new_loan_indices.iter().enumerate() {
|
||||
let old_loan = &self.all_loans[x];
|
||||
for &y in &new_loan_indices[(i+1) ..] {
|
||||
let new_loan = &self.all_loans[y];
|
||||
self.report_error_if_loans_conflict(old_loan, new_loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_error_if_loans_conflict(
|
||||
&self,
|
||||
old_loan: &Loan<'tcx>,
|
||||
new_loan: &Loan<'tcx>,
|
||||
) -> bool {
|
||||
//! Checks whether `old_loan` and `new_loan` can safely be issued
|
||||
//! simultaneously.
|
||||
|
||||
debug!("report_error_if_loans_conflict(old_loan={:?}, new_loan={:?})",
|
||||
old_loan,
|
||||
new_loan);
|
||||
|
||||
// Should only be called for loans that are in scope at the same time.
|
||||
assert!(self.bccx.region_scope_tree.scopes_intersect(old_loan.kill_scope,
|
||||
new_loan.kill_scope));
|
||||
|
||||
self.report_error_if_loan_conflicts_with_restriction(
|
||||
old_loan, new_loan)
|
||||
&& self.report_error_if_loan_conflicts_with_restriction(
|
||||
new_loan, old_loan)
|
||||
}
|
||||
|
||||
pub fn report_error_if_loan_conflicts_with_restriction(
|
||||
&self,
|
||||
loan1: &Loan<'tcx>,
|
||||
loan2: &Loan<'tcx>,
|
||||
) -> bool {
|
||||
//! Checks whether the restrictions introduced by `loan1` would
|
||||
//! prohibit `loan2`.
|
||||
debug!("report_error_if_loan_conflicts_with_restriction(\
|
||||
loan1={:?}, loan2={:?})",
|
||||
loan1,
|
||||
loan2);
|
||||
|
||||
if compatible_borrow_kinds(loan1.kind, loan2.kind) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let loan2_base_path = owned_ptr_base_path_rc(&loan2.loan_path);
|
||||
for restr_path in &loan1.restricted_paths {
|
||||
if *restr_path != loan2_base_path { continue; }
|
||||
|
||||
self.bccx.signal_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn consume_common(
|
||||
&self,
|
||||
id: hir::ItemLocalId,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode,
|
||||
) {
|
||||
if let Some(lp) = opt_loan_path(cmt) {
|
||||
match mode {
|
||||
euv::Copy => {
|
||||
self.check_for_copy_of_frozen_path(id, &lp);
|
||||
}
|
||||
euv::Move(_) => {
|
||||
// Sometimes moves aren't from a move path;
|
||||
// this either means that the original move
|
||||
// was from something illegal to move,
|
||||
// or was moved from referent of an unsafe
|
||||
// pointer or something like that.
|
||||
if self.move_data.is_move_path(id, &lp) {
|
||||
self.check_for_move_of_borrowed_path(id, &lp);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.check_if_path_is_moved(id, &lp);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_for_copy_of_frozen_path(&self,
|
||||
id: hir::ItemLocalId,
|
||||
copy_path: &LoanPath<'tcx>) {
|
||||
self.analyze_restrictions_on_use(id, copy_path, ty::ImmBorrow);
|
||||
}
|
||||
|
||||
fn check_for_move_of_borrowed_path(&self,
|
||||
id: hir::ItemLocalId,
|
||||
move_path: &LoanPath<'tcx>) {
|
||||
// We want to detect if there are any loans at all, so we search for
|
||||
// any loans incompatible with MutBorrrow, since all other kinds of
|
||||
// loans are incompatible with that.
|
||||
self.analyze_restrictions_on_use(id, move_path, ty::MutBorrow);
|
||||
}
|
||||
|
||||
fn analyze_restrictions_on_use(&self,
|
||||
expr_id: hir::ItemLocalId,
|
||||
use_path: &LoanPath<'tcx>,
|
||||
borrow_kind: ty::BorrowKind) {
|
||||
debug!("analyze_restrictions_on_use(expr_id={:?}, use_path={:?})",
|
||||
expr_id, use_path);
|
||||
|
||||
let scope = region::Scope {
|
||||
id: expr_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
self.each_in_scope_loan_affecting_path(
|
||||
scope, use_path, |loan| {
|
||||
if !compatible_borrow_kinds(loan.kind, borrow_kind) {
|
||||
self.bccx.signal_error();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Reports an error if `expr` (which should be a path)
|
||||
/// is using a moved/uninitialized value
|
||||
fn check_if_path_is_moved(&self,
|
||||
id: hir::ItemLocalId,
|
||||
lp: &Rc<LoanPath<'tcx>>) {
|
||||
debug!("check_if_path_is_moved(id={:?}, lp={:?})", id, lp);
|
||||
|
||||
// FIXME: if you find yourself tempted to cut and paste
|
||||
// the body below and then specializing the error reporting,
|
||||
// consider refactoring this instead!
|
||||
|
||||
let base_lp = owned_ptr_base_path_rc(lp);
|
||||
self.move_data.each_move_of(id, &base_lp, |_, _| {
|
||||
self.bccx.signal_error();
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
/// Reports an error if assigning to `lp` will use a
|
||||
/// moved/uninitialized value. Mainly this is concerned with
|
||||
/// detecting derefs of uninitialized pointers.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```
|
||||
/// let a: i32;
|
||||
/// a = 10; // ok, even though a is uninitialized
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// struct Point { x: u32, y: u32 }
|
||||
/// let mut p: Point;
|
||||
/// p.x = 22; // ok, even though `p` is uninitialized
|
||||
/// ```
|
||||
///
|
||||
/// ```compile_fail,E0381
|
||||
/// # struct Point { x: u32, y: u32 }
|
||||
/// let mut p: Box<Point>;
|
||||
/// (*p).x = 22; // not ok, p is uninitialized, can't deref
|
||||
/// ```
|
||||
fn check_if_assigned_path_is_moved(&self,
|
||||
id: hir::ItemLocalId,
|
||||
lp: &Rc<LoanPath<'tcx>>)
|
||||
{
|
||||
match lp.kind {
|
||||
LpVar(_) | LpUpvar(_) => {
|
||||
// assigning to `x` does not require that `x` is initialized
|
||||
}
|
||||
LpDowncast(ref lp_base, _) => {
|
||||
// assigning to `(P->Variant).f` is ok if assigning to `P` is ok
|
||||
self.check_if_assigned_path_is_moved(id, lp_base);
|
||||
}
|
||||
LpExtend(ref lp_base, _, LpInterior(_, InteriorField(_))) => {
|
||||
match lp_base.to_type().kind {
|
||||
ty::Adt(def, _) if def.has_dtor(self.tcx()) => {
|
||||
// In the case where the owner implements drop, then
|
||||
// the path must be initialized to prevent a case of
|
||||
// partial reinitialization
|
||||
//
|
||||
// FIXME: could refactor via hypothetical
|
||||
// generalized check_if_path_is_moved
|
||||
let loan_path = owned_ptr_base_path_rc(lp_base);
|
||||
self.move_data.each_move_of(id, &loan_path, |_, _| {
|
||||
self.bccx
|
||||
.signal_error();
|
||||
false
|
||||
});
|
||||
return;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
// assigning to `P.f` is ok if assigning to `P` is ok
|
||||
self.check_if_assigned_path_is_moved(id, lp_base);
|
||||
}
|
||||
LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) |
|
||||
LpExtend(ref lp_base, _, LpDeref(_)) => {
|
||||
// assigning to `P[i]` requires `P` is initialized
|
||||
// assigning to `(*P)` requires `P` is initialized
|
||||
self.check_if_path_is_moved(id, lp_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_assignment(&self,
|
||||
assignment_id: hir::ItemLocalId,
|
||||
assignee_cmt: &mc::cmt_<'tcx>) {
|
||||
debug!("check_assignment(assignee_cmt={:?})", assignee_cmt);
|
||||
|
||||
// Check that we don't invalidate any outstanding loans
|
||||
if let Some(loan_path) = opt_loan_path(assignee_cmt) {
|
||||
let scope = region::Scope {
|
||||
id: assignment_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
self.each_in_scope_loan_affecting_path(scope, &loan_path, |_| {
|
||||
self.bccx.signal_error();
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
// Check for reassignments to (immutable) local variables. This
|
||||
// needs to be done here instead of in check_loans because we
|
||||
// depend on move data.
|
||||
if let Categorization::Local(_) = assignee_cmt.cat {
|
||||
let lp = opt_loan_path(assignee_cmt).unwrap();
|
||||
self.move_data.each_assignment_of(assignment_id, &lp, |_| {
|
||||
if !assignee_cmt.mutbl.is_mutable() {
|
||||
self.bccx.signal_error();
|
||||
}
|
||||
false
|
||||
});
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
//! Computes moves.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use crate::borrowck::move_data::*;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::mem_categorization::InteriorOffsetKind as Kind;
|
||||
use rustc::ty::{self, Ty};
|
||||
|
||||
use std::rc::Rc;
|
||||
use syntax_pos::Span;
|
||||
use log::debug;
|
||||
|
||||
struct GatherMoveInfo<'c, 'tcx> {
|
||||
id: hir::ItemLocalId,
|
||||
cmt: &'c mc::cmt_<'tcx>,
|
||||
}
|
||||
|
||||
pub fn gather_decl<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
var_id: hir::HirId,
|
||||
var_ty: Ty<'tcx>) {
|
||||
let loan_path = Rc::new(LoanPath::new(LpVar(var_id), var_ty));
|
||||
move_data.add_move(bccx.tcx, loan_path, var_id.local_id);
|
||||
}
|
||||
|
||||
pub fn gather_move_from_expr<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_expr_id: hir::ItemLocalId,
|
||||
cmt: &mc::cmt_<'tcx>) {
|
||||
let move_info = GatherMoveInfo {
|
||||
id: move_expr_id,
|
||||
cmt,
|
||||
};
|
||||
gather_move(bccx, move_data, move_info);
|
||||
}
|
||||
|
||||
pub fn gather_move_from_pat<'a, 'c, 'tcx>(
|
||||
bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_pat: &hir::Pat,
|
||||
cmt: &'c mc::cmt_<'tcx>,
|
||||
) {
|
||||
let move_info = GatherMoveInfo {
|
||||
id: move_pat.hir_id.local_id,
|
||||
cmt,
|
||||
};
|
||||
|
||||
debug!("gather_move_from_pat: move_pat={:?}", move_pat);
|
||||
|
||||
gather_move(bccx, move_data, move_info);
|
||||
}
|
||||
|
||||
fn gather_move<'a, 'c, 'tcx>(
|
||||
bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
move_info: GatherMoveInfo<'c, 'tcx>,
|
||||
) {
|
||||
debug!("gather_move(move_id={:?}, cmt={:?})",
|
||||
move_info.id, move_info.cmt);
|
||||
|
||||
let potentially_illegal_move = check_and_get_illegal_move_origin(bccx, move_info.cmt);
|
||||
if let Some(_) = potentially_illegal_move {
|
||||
bccx.signal_error();
|
||||
return;
|
||||
}
|
||||
|
||||
match opt_loan_path(&move_info.cmt) {
|
||||
Some(loan_path) => {
|
||||
move_data.add_move(bccx.tcx, loan_path,
|
||||
move_info.id);
|
||||
}
|
||||
None => {
|
||||
// move from rvalue or raw pointer, hence ok
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gather_assignment<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: &MoveData<'tcx>,
|
||||
assignment_id: hir::ItemLocalId,
|
||||
assignment_span: Span,
|
||||
assignee_loan_path: Rc<LoanPath<'tcx>>) {
|
||||
move_data.add_assignment(bccx.tcx,
|
||||
assignee_loan_path,
|
||||
assignment_id,
|
||||
assignment_span);
|
||||
}
|
||||
|
||||
// (keep in sync with move_error::report_cannot_move_out_of )
|
||||
fn check_and_get_illegal_move_origin<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>)
|
||||
-> Option<mc::cmt_<'tcx>> {
|
||||
match cmt.cat {
|
||||
Categorization::Deref(_, mc::BorrowedPtr(..)) |
|
||||
Categorization::Deref(_, mc::UnsafePtr(..)) |
|
||||
Categorization::ThreadLocal(..) |
|
||||
Categorization::StaticItem => {
|
||||
Some(cmt.clone())
|
||||
}
|
||||
|
||||
Categorization::Rvalue(..) |
|
||||
Categorization::Local(..) |
|
||||
Categorization::Upvar(..) => {
|
||||
None
|
||||
}
|
||||
|
||||
Categorization::Downcast(ref b, _) |
|
||||
Categorization::Interior(ref b, mc::InteriorField(_)) |
|
||||
Categorization::Interior(ref b, mc::InteriorElement(Kind::Pattern)) => {
|
||||
match b.ty.kind {
|
||||
ty::Adt(def, _) => {
|
||||
if def.has_dtor(bccx.tcx) {
|
||||
Some(cmt.clone())
|
||||
} else {
|
||||
check_and_get_illegal_move_origin(bccx, b)
|
||||
}
|
||||
}
|
||||
ty::Slice(..) => Some(cmt.clone()),
|
||||
_ => {
|
||||
check_and_get_illegal_move_origin(bccx, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Categorization::Interior(_, mc::InteriorElement(Kind::Index)) => {
|
||||
// Forbid move of arr[i] for arr: [T; 3]; see RFC 533.
|
||||
Some(cmt.clone())
|
||||
}
|
||||
|
||||
Categorization::Deref(ref b, mc::Unique) => {
|
||||
check_and_get_illegal_move_origin(bccx, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
//! This module implements the check that the lifetime of a borrow
|
||||
//! does not exceed the lifetime of the value being borrowed.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use rustc::hir::HirId;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty;
|
||||
|
||||
use log::debug;
|
||||
|
||||
type R = Result<(),()>;
|
||||
|
||||
pub fn guarantee_lifetime<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
item_scope: region::Scope,
|
||||
cmt: &'a mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>)
|
||||
-> Result<(),()> {
|
||||
//! Reports error if `loan_region` is larger than S
|
||||
//! where S is `item_scope` if `cmt` is an upvar,
|
||||
//! and is scope of `cmt` otherwise.
|
||||
debug!("guarantee_lifetime(cmt={:?}, loan_region={:?})",
|
||||
cmt, loan_region);
|
||||
let ctxt = GuaranteeLifetimeContext { bccx, item_scope, loan_region };
|
||||
ctxt.check(cmt, None)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
||||
struct GuaranteeLifetimeContext<'a, 'tcx> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
|
||||
// the scope of the function body for the enclosing item
|
||||
item_scope: region::Scope,
|
||||
|
||||
loan_region: ty::Region<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> GuaranteeLifetimeContext<'a, 'tcx> {
|
||||
fn check(&self, cmt: &mc::cmt_<'tcx>, discr_scope: Option<HirId>) -> R {
|
||||
//! Main routine. Walks down `cmt` until we find the
|
||||
//! "guarantor". Reports an error if `self.loan_region` is
|
||||
//! larger than scope of `cmt`.
|
||||
debug!("guarantee_lifetime.check(cmt={:?}, loan_region={:?})",
|
||||
cmt,
|
||||
self.loan_region);
|
||||
|
||||
match cmt.cat {
|
||||
Categorization::Rvalue(..) |
|
||||
Categorization::ThreadLocal(..) |
|
||||
Categorization::Local(..) | // L-Local
|
||||
Categorization::Upvar(..) |
|
||||
Categorization::Deref(_, mc::BorrowedPtr(..)) | // L-Deref-Borrowed
|
||||
Categorization::Deref(_, mc::UnsafePtr(..)) => {
|
||||
self.check_scope(self.scope(cmt))
|
||||
}
|
||||
|
||||
Categorization::StaticItem => {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Categorization::Downcast(ref base, _) |
|
||||
Categorization::Deref(ref base, mc::Unique) | // L-Deref-Send
|
||||
Categorization::Interior(ref base, _) => { // L-Field
|
||||
self.check(base, discr_scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_scope(&self, max_scope: ty::Region<'tcx>) -> R {
|
||||
//! Reports an error if `loan_region` is larger than `max_scope`
|
||||
|
||||
if !self.bccx.is_subregion_of(self.loan_region, max_scope) {
|
||||
Err(self.bccx.signal_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn scope(&self, cmt: &mc::cmt_<'tcx>) -> ty::Region<'tcx> {
|
||||
//! Returns the maximal region scope for the which the
|
||||
//! place `cmt` is guaranteed to be valid without any
|
||||
//! rooting etc, and presuming `cmt` is not mutated.
|
||||
|
||||
match cmt.cat {
|
||||
Categorization::ThreadLocal(temp_scope) |
|
||||
Categorization::Rvalue(temp_scope) => {
|
||||
temp_scope
|
||||
}
|
||||
Categorization::Upvar(..) => {
|
||||
self.bccx.tcx.mk_region(ty::ReScope(self.item_scope))
|
||||
}
|
||||
Categorization::Local(hir_id) => {
|
||||
self.bccx.tcx.mk_region(ty::ReScope(
|
||||
self.bccx.region_scope_tree.var_scope(hir_id.local_id)))
|
||||
}
|
||||
Categorization::StaticItem |
|
||||
Categorization::Deref(_, mc::UnsafePtr(..)) => {
|
||||
self.bccx.tcx.lifetimes.re_static
|
||||
}
|
||||
Categorization::Deref(_, mc::BorrowedPtr(_, r)) => {
|
||||
r
|
||||
}
|
||||
Categorization::Downcast(ref cmt, _) |
|
||||
Categorization::Deref(ref cmt, mc::Unique) |
|
||||
Categorization::Interior(ref cmt, _) => {
|
||||
self.scope(cmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
// ----------------------------------------------------------------------
|
||||
// Gathering loans
|
||||
//
|
||||
// The borrow check proceeds in two phases. In phase one, we gather the full
|
||||
// set of loans that are required at any point. These are sorted according to
|
||||
// their associated scopes. In phase two, checking loans, we will then make
|
||||
// sure that all of these loans are honored.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use crate::borrowck::move_data::MoveData;
|
||||
use rustc::middle::expr_use_visitor as euv;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
|
||||
use syntax_pos::Span;
|
||||
use rustc::hir;
|
||||
use log::debug;
|
||||
|
||||
use restrictions::RestrictionResult;
|
||||
|
||||
mod lifetime;
|
||||
mod restrictions;
|
||||
mod gather_moves;
|
||||
|
||||
pub fn gather_loans_in_fn<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
body: hir::BodyId)
|
||||
-> (Vec<Loan<'tcx>>, move_data::MoveData<'tcx>) {
|
||||
let def_id = bccx.tcx.hir().body_owner_def_id(body);
|
||||
let param_env = bccx.tcx.param_env(def_id);
|
||||
let mut glcx = GatherLoanCtxt {
|
||||
bccx,
|
||||
all_loans: Vec::new(),
|
||||
item_ub: region::Scope {
|
||||
id: bccx.tcx.hir().body(body).value.hir_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
},
|
||||
move_data: MoveData::default(),
|
||||
};
|
||||
|
||||
let rvalue_promotable_map = bccx.tcx.rvalue_promotable_map(def_id);
|
||||
euv::ExprUseVisitor::new(&mut glcx,
|
||||
bccx.tcx,
|
||||
def_id,
|
||||
param_env,
|
||||
&bccx.region_scope_tree,
|
||||
bccx.tables,
|
||||
Some(rvalue_promotable_map))
|
||||
.consume_body(bccx.body);
|
||||
|
||||
let GatherLoanCtxt { all_loans, move_data, .. } = glcx;
|
||||
(all_loans, move_data)
|
||||
}
|
||||
|
||||
struct GatherLoanCtxt<'a, 'tcx> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
move_data: move_data::MoveData<'tcx>,
|
||||
all_loans: Vec<Loan<'tcx>>,
|
||||
/// `item_ub` is used as an upper-bound on the lifetime whenever we
|
||||
/// ask for the scope of an expression categorized as an upvar.
|
||||
item_ub: region::Scope,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> euv::Delegate<'tcx> for GatherLoanCtxt<'a, 'tcx> {
|
||||
fn consume(&mut self,
|
||||
consume_id: hir::HirId,
|
||||
_consume_span: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume(consume_id={}, cmt={:?}, mode={:?})",
|
||||
consume_id, cmt, mode);
|
||||
|
||||
match mode {
|
||||
euv::Move(_) => {
|
||||
gather_moves::gather_move_from_expr(
|
||||
self.bccx, &self.move_data,
|
||||
consume_id.local_id, cmt);
|
||||
}
|
||||
euv::Copy => { }
|
||||
}
|
||||
}
|
||||
|
||||
fn matched_pat(&mut self,
|
||||
matched_pat: &hir::Pat,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::MatchMode) {
|
||||
debug!("matched_pat(matched_pat={:?}, cmt={:?}, mode={:?})",
|
||||
matched_pat,
|
||||
cmt,
|
||||
mode);
|
||||
}
|
||||
|
||||
fn consume_pat(&mut self,
|
||||
consume_pat: &hir::Pat,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
mode: euv::ConsumeMode) {
|
||||
debug!("consume_pat(consume_pat={:?}, cmt={:?}, mode={:?})",
|
||||
consume_pat,
|
||||
cmt,
|
||||
mode);
|
||||
|
||||
match mode {
|
||||
euv::Copy => { return; }
|
||||
euv::Move(_) => { }
|
||||
}
|
||||
|
||||
gather_moves::gather_move_from_pat(
|
||||
self.bccx, &self.move_data,
|
||||
consume_pat, cmt);
|
||||
}
|
||||
|
||||
fn borrow(&mut self,
|
||||
borrow_id: hir::HirId,
|
||||
_: Span,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
bk: ty::BorrowKind,
|
||||
loan_cause: euv::LoanCause)
|
||||
{
|
||||
debug!("borrow(borrow_id={}, cmt={:?}, loan_region={:?}, \
|
||||
bk={:?}, loan_cause={:?})",
|
||||
borrow_id, cmt, loan_region,
|
||||
bk, loan_cause);
|
||||
|
||||
self.guarantee_valid(borrow_id.local_id,
|
||||
cmt,
|
||||
bk,
|
||||
loan_region);
|
||||
}
|
||||
|
||||
fn mutate(&mut self,
|
||||
assignment_id: hir::HirId,
|
||||
assignment_span: Span,
|
||||
assignee_cmt: &mc::cmt_<'tcx>,
|
||||
_: euv::MutateMode)
|
||||
{
|
||||
self.guarantee_assignment_valid(assignment_id,
|
||||
assignment_span,
|
||||
assignee_cmt);
|
||||
}
|
||||
|
||||
fn decl_without_init(&mut self, id: hir::HirId, _span: Span) {
|
||||
let ty = self.bccx
|
||||
.tables
|
||||
.node_type(id);
|
||||
gather_moves::gather_decl(self.bccx, &self.move_data, id, ty);
|
||||
}
|
||||
|
||||
fn nested_body(&mut self, body_id: hir::BodyId) {
|
||||
debug!("nested_body(body_id={:?})", body_id);
|
||||
// rust-lang/rust#58776: MIR and AST borrow check disagree on where
|
||||
// certain closure errors are reported. As such migrate borrowck has to
|
||||
// operate at the level of items, rather than bodies. Check if the
|
||||
// contained closure had any errors and set `signalled_any_error` if it
|
||||
// has.
|
||||
let bccx = self.bccx;
|
||||
if bccx.tcx.migrate_borrowck() {
|
||||
if let SignalledError::NoErrorsSeen = bccx.signalled_any_error.get() {
|
||||
let closure_def_id = bccx.tcx.hir().body_owner_def_id(body_id);
|
||||
debug!("checking closure: {:?}", closure_def_id);
|
||||
|
||||
bccx.signalled_any_error.set(bccx.tcx.borrowck(closure_def_id).signalled_any_error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the A-* rules in README.md.
|
||||
fn check_aliasability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
req_kind: ty::BorrowKind)
|
||||
-> Result<(),()> {
|
||||
|
||||
let aliasability = cmt.freely_aliasable();
|
||||
debug!("check_aliasability aliasability={:?} req_kind={:?}",
|
||||
aliasability, req_kind);
|
||||
|
||||
match (aliasability, req_kind) {
|
||||
(mc::Aliasability::NonAliasable, _) => {
|
||||
/* Uniquely accessible path -- OK for `&` and `&mut` */
|
||||
Ok(())
|
||||
}
|
||||
(mc::Aliasability::FreelyAliasable(mc::AliasableStatic), ty::ImmBorrow) => {
|
||||
// Borrow of an immutable static item.
|
||||
Ok(())
|
||||
}
|
||||
(mc::Aliasability::FreelyAliasable(mc::AliasableStaticMut), _) => {
|
||||
// Even touching a static mut is considered unsafe. We assume the
|
||||
// user knows what they're doing in these cases.
|
||||
Ok(())
|
||||
}
|
||||
(mc::Aliasability::FreelyAliasable(_), ty::UniqueImmBorrow) |
|
||||
(mc::Aliasability::FreelyAliasable(_), ty::MutBorrow) => {
|
||||
bccx.signal_error();
|
||||
Err(())
|
||||
}
|
||||
(..) => {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the M-* rules in README.md.
|
||||
fn check_mutability<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
req_kind: ty::BorrowKind)
|
||||
-> Result<(),()> {
|
||||
debug!("check_mutability(cmt={:?} req_kind={:?}", cmt, req_kind);
|
||||
match req_kind {
|
||||
ty::UniqueImmBorrow | ty::ImmBorrow => {
|
||||
match cmt.mutbl {
|
||||
// I am intentionally leaving this here to help
|
||||
// refactoring if, in the future, we should add new
|
||||
// kinds of mutability.
|
||||
mc::McImmutable | mc::McDeclared | mc::McInherited => {
|
||||
// both imm and mut data can be lent as imm;
|
||||
// for mutable data, this is a freeze
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ty::MutBorrow => {
|
||||
// Only mutable data can be lent as mutable.
|
||||
if !cmt.mutbl.is_mutable() {
|
||||
Err(bccx.signal_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> GatherLoanCtxt<'a, 'tcx> {
|
||||
pub fn tcx(&self) -> TyCtxt<'tcx> { self.bccx.tcx }
|
||||
|
||||
/// Guarantees that `cmt` is assignable, or reports an error.
|
||||
fn guarantee_assignment_valid(&mut self,
|
||||
assignment_id: hir::HirId,
|
||||
assignment_span: Span,
|
||||
cmt: &mc::cmt_<'tcx>) {
|
||||
|
||||
let opt_lp = opt_loan_path(cmt);
|
||||
debug!("guarantee_assignment_valid(assignment_id={}, cmt={:?}) opt_lp={:?}",
|
||||
assignment_id, cmt, opt_lp);
|
||||
|
||||
if let Categorization::Local(..) = cmt.cat {
|
||||
// Only re-assignments to locals require it to be
|
||||
// mutable - this is checked in check_loans.
|
||||
} else {
|
||||
// Check that we don't allow assignments to non-mutable data.
|
||||
if check_mutability(self.bccx, cmt, ty::MutBorrow).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
}
|
||||
|
||||
// Check that we don't allow assignments to aliasable data
|
||||
if check_aliasability(self.bccx, cmt, ty::MutBorrow).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
match opt_lp {
|
||||
Some(lp) => {
|
||||
gather_moves::gather_assignment(self.bccx, &self.move_data,
|
||||
assignment_id.local_id,
|
||||
assignment_span,
|
||||
lp);
|
||||
}
|
||||
None => {
|
||||
// This can occur with e.g., `*foo() = 5`. In such
|
||||
// cases, there is no need to check for conflicts
|
||||
// with moves etc, just ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Guarantees that `addr_of(cmt)` will be valid for the duration of `static_scope_r`, or
|
||||
/// reports an error. This may entail taking out loans, which will be added to the
|
||||
/// `req_loan_map`.
|
||||
fn guarantee_valid(&mut self,
|
||||
borrow_id: hir::ItemLocalId,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
req_kind: ty::BorrowKind,
|
||||
loan_region: ty::Region<'tcx>) {
|
||||
debug!("guarantee_valid(borrow_id={:?}, cmt={:?}, \
|
||||
req_mutbl={:?}, loan_region={:?})",
|
||||
borrow_id,
|
||||
cmt,
|
||||
req_kind,
|
||||
loan_region);
|
||||
|
||||
// a loan for the empty region can never be dereferenced, so
|
||||
// it is always safe
|
||||
if *loan_region == ty::ReEmpty {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the lifetime of the borrow does not exceed
|
||||
// the lifetime of the data being borrowed.
|
||||
if lifetime::guarantee_lifetime(self.bccx, self.item_ub, cmt, loan_region).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
// Check that we don't allow mutable borrows of non-mutable data.
|
||||
if check_mutability(self.bccx, cmt, req_kind).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
// Check that we don't allow mutable borrows of aliasable data.
|
||||
if check_aliasability(self.bccx, cmt, req_kind).is_err() {
|
||||
return; // reported an error, no sense in reporting more.
|
||||
}
|
||||
|
||||
// Compute the restrictions that are required to enforce the
|
||||
// loan is safe.
|
||||
let restr = restrictions::compute_restrictions(self.bccx, &cmt, loan_region);
|
||||
|
||||
debug!("guarantee_valid(): restrictions={:?}", restr);
|
||||
|
||||
// Create the loan record (if needed).
|
||||
let loan = match restr {
|
||||
RestrictionResult::Safe => {
|
||||
// No restrictions---no loan record necessary
|
||||
return;
|
||||
}
|
||||
|
||||
RestrictionResult::SafeIf(loan_path, restricted_paths) => {
|
||||
let loan_scope = match *loan_region {
|
||||
ty::ReScope(scope) => scope,
|
||||
|
||||
ty::ReEarlyBound(ref br) => {
|
||||
self.bccx.region_scope_tree.early_free_scope(self.tcx(), br)
|
||||
}
|
||||
|
||||
ty::ReFree(ref fr) => {
|
||||
self.bccx.region_scope_tree.free_scope(self.tcx(), fr)
|
||||
}
|
||||
|
||||
ty::ReStatic => self.item_ub,
|
||||
|
||||
ty::ReEmpty |
|
||||
ty::ReClosureBound(..) |
|
||||
ty::ReLateBound(..) |
|
||||
ty::ReVar(..) |
|
||||
ty::RePlaceholder(..) |
|
||||
ty::ReErased => {
|
||||
span_bug!(
|
||||
cmt.span,
|
||||
"invalid borrow lifetime: {:?}",
|
||||
loan_region);
|
||||
}
|
||||
};
|
||||
debug!("loan_scope = {:?}", loan_scope);
|
||||
|
||||
let borrow_scope = region::Scope {
|
||||
id: borrow_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
let gen_scope = self.compute_gen_scope(borrow_scope, loan_scope);
|
||||
debug!("gen_scope = {:?}", gen_scope);
|
||||
|
||||
let kill_scope = self.compute_kill_scope(loan_scope, &loan_path);
|
||||
debug!("kill_scope = {:?}", kill_scope);
|
||||
|
||||
Loan {
|
||||
index: self.all_loans.len(),
|
||||
loan_path,
|
||||
kind: req_kind,
|
||||
gen_scope,
|
||||
kill_scope,
|
||||
restricted_paths,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
debug!("guarantee_valid(borrow_id={:?}), loan={:?}",
|
||||
borrow_id, loan);
|
||||
|
||||
// let loan_path = loan.loan_path;
|
||||
// let loan_gen_scope = loan.gen_scope;
|
||||
// let loan_kill_scope = loan.kill_scope;
|
||||
self.all_loans.push(loan);
|
||||
}
|
||||
|
||||
pub fn compute_gen_scope(&self,
|
||||
borrow_scope: region::Scope,
|
||||
loan_scope: region::Scope)
|
||||
-> region::Scope {
|
||||
//! Determine when to introduce the loan. Typically the loan
|
||||
//! is introduced at the point of the borrow, but in some cases,
|
||||
//! notably method arguments, the loan may be introduced only
|
||||
//! later, once it comes into scope.
|
||||
|
||||
if self.bccx.region_scope_tree.is_subscope_of(borrow_scope, loan_scope) {
|
||||
borrow_scope
|
||||
} else {
|
||||
loan_scope
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_kill_scope(&self, loan_scope: region::Scope, lp: &LoanPath<'tcx>)
|
||||
-> region::Scope {
|
||||
//! Determine when the loan restrictions go out of scope.
|
||||
//! This is either when the lifetime expires or when the
|
||||
//! local variable which roots the loan-path goes out of scope,
|
||||
//! whichever happens faster.
|
||||
//!
|
||||
//! It may seem surprising that we might have a loan region
|
||||
//! larger than the variable which roots the loan-path; this can
|
||||
//! come about when variables of `&mut` type are re-borrowed,
|
||||
//! as in this example:
|
||||
//!
|
||||
//! struct Foo { counter: u32 }
|
||||
//!
|
||||
//! fn counter<'a>(v: &'a mut Foo) -> &'a mut u32 {
|
||||
//! &mut v.counter
|
||||
//! }
|
||||
//!
|
||||
//! In this case, the reference (`'a`) outlives the
|
||||
//! variable `v` that hosts it. Note that this doesn't come up
|
||||
//! with immutable `&` pointers, because borrows of such pointers
|
||||
//! do not require restrictions and hence do not cause a loan.
|
||||
|
||||
let lexical_scope = lp.kill_scope(self.bccx);
|
||||
if self.bccx.region_scope_tree.is_subscope_of(lexical_scope, loan_scope) {
|
||||
lexical_scope
|
||||
} else {
|
||||
assert!(self.bccx.region_scope_tree.is_subscope_of(loan_scope, lexical_scope));
|
||||
loan_scope
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
//! Computes the restrictions that result from a borrow.
|
||||
|
||||
use crate::borrowck::*;
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::ty;
|
||||
use log::debug;
|
||||
|
||||
use crate::borrowck::ToInteriorKind;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RestrictionResult<'tcx> {
|
||||
Safe,
|
||||
SafeIf(Rc<LoanPath<'tcx>>, Vec<Rc<LoanPath<'tcx>>>)
|
||||
}
|
||||
|
||||
pub fn compute_restrictions<'a, 'tcx>(bccx: &BorrowckCtxt<'a, 'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
loan_region: ty::Region<'tcx>)
|
||||
-> RestrictionResult<'tcx> {
|
||||
let ctxt = RestrictionsContext { bccx, loan_region };
|
||||
|
||||
ctxt.restrict(cmt)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Private
|
||||
|
||||
struct RestrictionsContext<'a, 'tcx> {
|
||||
bccx: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
loan_region: ty::Region<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> RestrictionsContext<'a, 'tcx> {
|
||||
fn restrict(&self,
|
||||
cmt: &mc::cmt_<'tcx>) -> RestrictionResult<'tcx> {
|
||||
debug!("restrict(cmt={:?})", cmt);
|
||||
|
||||
let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
|
||||
|
||||
match cmt.cat.clone() {
|
||||
Categorization::Rvalue(..) => {
|
||||
// Effectively, rvalues are stored into a
|
||||
// non-aliasable temporary on the stack. Since they
|
||||
// are inherently non-aliasable, they can only be
|
||||
// accessed later through the borrow itself and hence
|
||||
// must inherently comply with its terms.
|
||||
RestrictionResult::Safe
|
||||
}
|
||||
|
||||
Categorization::ThreadLocal(..) => {
|
||||
// Thread-locals are statics that have a scope, with
|
||||
// no underlying structure to provide restrictions.
|
||||
RestrictionResult::Safe
|
||||
}
|
||||
|
||||
Categorization::Local(local_id) => {
|
||||
// R-Variable, locally declared
|
||||
let lp = new_lp(LpVar(local_id));
|
||||
RestrictionResult::SafeIf(lp.clone(), vec![lp])
|
||||
}
|
||||
|
||||
Categorization::Upvar(mc::Upvar { id, .. }) => {
|
||||
// R-Variable, captured into closure
|
||||
let lp = new_lp(LpUpvar(id));
|
||||
RestrictionResult::SafeIf(lp.clone(), vec![lp])
|
||||
}
|
||||
|
||||
Categorization::Downcast(cmt_base, _) => {
|
||||
// When we borrow the interior of an enum, we have to
|
||||
// ensure the enum itself is not mutated, because that
|
||||
// could cause the type of the memory to change.
|
||||
self.restrict(&cmt_base)
|
||||
}
|
||||
|
||||
Categorization::Interior(cmt_base, interior) => {
|
||||
// R-Field
|
||||
//
|
||||
// Overwriting the base would not change the type of
|
||||
// the memory, so no additional restrictions are
|
||||
// needed.
|
||||
let opt_variant_id = match cmt_base.cat {
|
||||
Categorization::Downcast(_, variant_id) => Some(variant_id),
|
||||
_ => None
|
||||
};
|
||||
let interior = interior.cleaned();
|
||||
let base_ty = cmt_base.ty;
|
||||
let result = self.restrict(&cmt_base);
|
||||
// Borrowing one union field automatically borrows all its fields.
|
||||
match base_ty.kind {
|
||||
ty::Adt(adt_def, _) if adt_def.is_union() => match result {
|
||||
RestrictionResult::Safe => RestrictionResult::Safe,
|
||||
RestrictionResult::SafeIf(base_lp, mut base_vec) => {
|
||||
for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() {
|
||||
let field = InteriorKind::InteriorField(
|
||||
mc::FieldIndex(i, field.ident.name)
|
||||
);
|
||||
let field_ty = if field == interior {
|
||||
cmt.ty
|
||||
} else {
|
||||
self.bccx.tcx.types.err // Doesn't matter
|
||||
};
|
||||
let sibling_lp_kind = LpExtend(base_lp.clone(), cmt.mutbl,
|
||||
LpInterior(opt_variant_id, field));
|
||||
let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, field_ty));
|
||||
base_vec.push(sibling_lp);
|
||||
}
|
||||
|
||||
let lp = new_lp(LpExtend(base_lp, cmt.mutbl,
|
||||
LpInterior(opt_variant_id, interior)));
|
||||
RestrictionResult::SafeIf(lp, base_vec)
|
||||
}
|
||||
},
|
||||
_ => self.extend(result, &cmt, LpInterior(opt_variant_id, interior))
|
||||
}
|
||||
}
|
||||
|
||||
Categorization::StaticItem => {
|
||||
RestrictionResult::Safe
|
||||
}
|
||||
|
||||
Categorization::Deref(cmt_base, pk) => {
|
||||
match pk {
|
||||
mc::Unique => {
|
||||
// R-Deref-Send-Pointer
|
||||
//
|
||||
// When we borrow the interior of a box, we
|
||||
// cannot permit the base to be mutated, because that
|
||||
// would cause the unique pointer to be freed.
|
||||
//
|
||||
// Eventually we should make these non-special and
|
||||
// just rely on Deref<T> implementation.
|
||||
let result = self.restrict(&cmt_base);
|
||||
self.extend(result, &cmt, LpDeref(pk))
|
||||
}
|
||||
mc::BorrowedPtr(bk, lt) => {
|
||||
// R-Deref-[Mut-]Borrowed
|
||||
if !self.bccx.is_subregion_of(self.loan_region, lt) {
|
||||
self.bccx.signal_error();
|
||||
return RestrictionResult::Safe;
|
||||
}
|
||||
|
||||
match bk {
|
||||
ty::ImmBorrow => RestrictionResult::Safe,
|
||||
ty::MutBorrow | ty::UniqueImmBorrow => {
|
||||
// R-Deref-Mut-Borrowed
|
||||
//
|
||||
// The referent can be aliased after the
|
||||
// references lifetime ends (by a newly-unfrozen
|
||||
// borrow).
|
||||
let result = self.restrict(&cmt_base);
|
||||
self.extend(result, &cmt, LpDeref(pk))
|
||||
}
|
||||
}
|
||||
}
|
||||
// Borrowck is not relevant for raw pointers
|
||||
mc::UnsafePtr(..) => RestrictionResult::Safe
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend(&self,
|
||||
result: RestrictionResult<'tcx>,
|
||||
cmt: &mc::cmt_<'tcx>,
|
||||
elem: LoanPathElem<'tcx>) -> RestrictionResult<'tcx> {
|
||||
match result {
|
||||
RestrictionResult::Safe => RestrictionResult::Safe,
|
||||
RestrictionResult::SafeIf(base_lp, mut base_vec) => {
|
||||
let v = LpExtend(base_lp, cmt.mutbl, elem);
|
||||
let lp = Rc::new(LoanPath::new(v, cmt.ty));
|
||||
base_vec.push(lp.clone());
|
||||
RestrictionResult::SafeIf(lp, base_vec)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,621 +0,0 @@
|
||||
//! See The Book chapter on the borrow checker for more details.
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
pub use LoanPathKind::*;
|
||||
pub use LoanPathElem::*;
|
||||
|
||||
use InteriorKind::*;
|
||||
|
||||
use rustc::hir::HirId;
|
||||
use rustc::hir::Node;
|
||||
use rustc::middle::borrowck::{BorrowCheckResult, SignalledError};
|
||||
use rustc::hir::def_id::{DefId, LocalDefId};
|
||||
use rustc::middle::mem_categorization as mc;
|
||||
use rustc::middle::mem_categorization::Categorization;
|
||||
use rustc::middle::region;
|
||||
use rustc::middle::free_region::RegionRelations;
|
||||
use rustc::ty::{self, Ty, TyCtxt};
|
||||
use rustc::ty::query::Providers;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Cell};
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use log::debug;
|
||||
|
||||
use rustc::hir;
|
||||
|
||||
use crate::cfg;
|
||||
use crate::dataflow::{DataFlowContext, BitwiseOperator, DataFlowOperator, KillFrom};
|
||||
|
||||
pub mod check_loans;
|
||||
|
||||
pub mod gather_loans;
|
||||
|
||||
pub mod move_data;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LoanDataFlowOperator;
|
||||
|
||||
pub type LoanDataFlow<'tcx> = DataFlowContext<'tcx, LoanDataFlowOperator>;
|
||||
|
||||
pub fn check_crate(tcx: TyCtxt<'_>) {
|
||||
tcx.par_body_owners(|body_owner_def_id| {
|
||||
tcx.ensure().borrowck(body_owner_def_id);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn provide(providers: &mut Providers<'_>) {
|
||||
*providers = Providers {
|
||||
borrowck,
|
||||
..*providers
|
||||
};
|
||||
}
|
||||
|
||||
/// Collection of conclusions determined via borrow checker analyses.
|
||||
pub struct AnalysisData<'tcx> {
|
||||
pub all_loans: Vec<Loan<'tcx>>,
|
||||
pub loans: DataFlowContext<'tcx, LoanDataFlowOperator>,
|
||||
pub move_data: move_data::FlowedMoveData<'tcx>,
|
||||
}
|
||||
|
||||
fn borrowck(tcx: TyCtxt<'_>, owner_def_id: DefId) -> &BorrowCheckResult {
|
||||
assert!(tcx.use_ast_borrowck() || tcx.migrate_borrowck());
|
||||
|
||||
debug!("borrowck(body_owner_def_id={:?})", owner_def_id);
|
||||
|
||||
let signalled_error = tcx.check_match(owner_def_id);
|
||||
if let SignalledError::SawSomeError = signalled_error {
|
||||
return tcx.arena.alloc(BorrowCheckResult {
|
||||
signalled_any_error: SignalledError::SawSomeError,
|
||||
})
|
||||
}
|
||||
|
||||
let owner_id = tcx.hir().as_local_hir_id(owner_def_id).unwrap();
|
||||
|
||||
match tcx.hir().get(owner_id) {
|
||||
Node::Ctor(..) => {
|
||||
// We get invoked with anything that has MIR, but some of
|
||||
// those things (notably the synthesized constructors from
|
||||
// tuple structs/variants) do not have an associated body
|
||||
// and do not need borrowchecking.
|
||||
return tcx.arena.alloc(BorrowCheckResult {
|
||||
signalled_any_error: SignalledError::NoErrorsSeen,
|
||||
})
|
||||
}
|
||||
_ => { }
|
||||
}
|
||||
|
||||
let body_id = tcx.hir().body_owned_by(owner_id);
|
||||
let tables = tcx.typeck_tables_of(owner_def_id);
|
||||
let region_scope_tree = tcx.region_scope_tree(owner_def_id);
|
||||
let body = tcx.hir().body(body_id);
|
||||
let mut bccx = BorrowckCtxt {
|
||||
tcx,
|
||||
tables,
|
||||
region_scope_tree,
|
||||
owner_def_id,
|
||||
body,
|
||||
signalled_any_error: Cell::new(SignalledError::NoErrorsSeen),
|
||||
};
|
||||
|
||||
// Eventually, borrowck will always read the MIR, but at the
|
||||
// moment we do not. So, for now, we always force MIR to be
|
||||
// constructed for a given fn, since this may result in errors
|
||||
// being reported and we want that to happen.
|
||||
//
|
||||
// Note that `mir_validated` is a "stealable" result; the
|
||||
// thief, `optimized_mir()`, forces borrowck, so we know that
|
||||
// is not yet stolen.
|
||||
tcx.ensure().mir_validated(owner_def_id);
|
||||
|
||||
// option dance because you can't capture an uninitialized variable
|
||||
// by mut-ref.
|
||||
let mut cfg = None;
|
||||
if let Some(AnalysisData { all_loans,
|
||||
loans: loan_dfcx,
|
||||
move_data: flowed_moves }) =
|
||||
build_borrowck_dataflow_data(&mut bccx, false, body_id,
|
||||
|bccx| {
|
||||
cfg = Some(cfg::CFG::new(bccx.tcx, &body));
|
||||
cfg.as_mut().unwrap()
|
||||
})
|
||||
{
|
||||
check_loans::check_loans(&mut bccx, &loan_dfcx, &flowed_moves, &all_loans, body);
|
||||
}
|
||||
|
||||
tcx.arena.alloc(BorrowCheckResult {
|
||||
signalled_any_error: bccx.signalled_any_error.into_inner(),
|
||||
})
|
||||
}
|
||||
|
||||
fn build_borrowck_dataflow_data<'a, 'c, 'tcx, F>(this: &mut BorrowckCtxt<'a, 'tcx>,
|
||||
force_analysis: bool,
|
||||
body_id: hir::BodyId,
|
||||
get_cfg: F)
|
||||
-> Option<AnalysisData<'tcx>>
|
||||
where F: FnOnce(&mut BorrowckCtxt<'a, 'tcx>) -> &'c cfg::CFG
|
||||
{
|
||||
// Check the body of fn items.
|
||||
let (all_loans, move_data) =
|
||||
gather_loans::gather_loans_in_fn(this, body_id);
|
||||
|
||||
if !force_analysis && move_data.is_empty() && all_loans.is_empty() {
|
||||
// large arrays of data inserted as constants can take a lot of
|
||||
// time and memory to borrow-check - see issue #36799. However,
|
||||
// they don't have places, so no borrow-check is actually needed.
|
||||
// Recognize that case and skip borrow-checking.
|
||||
debug!("skipping loan propagation for {:?} because of no loans", body_id);
|
||||
return None;
|
||||
} else {
|
||||
debug!("propagating loans in {:?}", body_id);
|
||||
}
|
||||
|
||||
let cfg = get_cfg(this);
|
||||
let mut loan_dfcx =
|
||||
DataFlowContext::new(this.tcx,
|
||||
"borrowck",
|
||||
Some(this.body),
|
||||
cfg,
|
||||
LoanDataFlowOperator,
|
||||
all_loans.len());
|
||||
for (loan_idx, loan) in all_loans.iter().enumerate() {
|
||||
loan_dfcx.add_gen(loan.gen_scope.item_local_id(), loan_idx);
|
||||
loan_dfcx.add_kill(KillFrom::ScopeEnd,
|
||||
loan.kill_scope.item_local_id(),
|
||||
loan_idx);
|
||||
}
|
||||
loan_dfcx.add_kills_from_flow_exits(cfg);
|
||||
loan_dfcx.propagate(cfg, this.body);
|
||||
|
||||
let flowed_moves = move_data::FlowedMoveData::new(move_data,
|
||||
this,
|
||||
cfg,
|
||||
this.body);
|
||||
|
||||
Some(AnalysisData { all_loans,
|
||||
loans: loan_dfcx,
|
||||
move_data:flowed_moves })
|
||||
}
|
||||
|
||||
/// Accessor for introspective clients inspecting `AnalysisData` and
|
||||
/// the `BorrowckCtxt` itself , e.g., the flowgraph visualizer.
|
||||
pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body_id: hir::BodyId,
|
||||
cfg: &cfg::CFG)
|
||||
-> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'tcx>)
|
||||
{
|
||||
let owner_id = tcx.hir().body_owner(body_id);
|
||||
let owner_def_id = tcx.hir().local_def_id(owner_id);
|
||||
let tables = tcx.typeck_tables_of(owner_def_id);
|
||||
let region_scope_tree = tcx.region_scope_tree(owner_def_id);
|
||||
let body = tcx.hir().body(body_id);
|
||||
let mut bccx = BorrowckCtxt {
|
||||
tcx,
|
||||
tables,
|
||||
region_scope_tree,
|
||||
owner_def_id,
|
||||
body,
|
||||
signalled_any_error: Cell::new(SignalledError::NoErrorsSeen),
|
||||
};
|
||||
|
||||
let dataflow_data = build_borrowck_dataflow_data(&mut bccx, true, body_id, |_| cfg);
|
||||
(bccx, dataflow_data.unwrap())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Type definitions
|
||||
|
||||
pub struct BorrowckCtxt<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
||||
// tables for the current thing we are checking; set to
|
||||
// Some in `borrowck_fn` and cleared later
|
||||
tables: &'a ty::TypeckTables<'tcx>,
|
||||
|
||||
region_scope_tree: &'tcx region::ScopeTree,
|
||||
|
||||
owner_def_id: DefId,
|
||||
|
||||
body: &'tcx hir::Body,
|
||||
|
||||
signalled_any_error: Cell<SignalledError>,
|
||||
}
|
||||
|
||||
|
||||
impl<'a, 'tcx: 'a> BorrowckCtxt<'a, 'tcx> {
|
||||
fn signal_error(&self) {
|
||||
self.signalled_any_error.set(SignalledError::SawSomeError);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Loans and loan paths
|
||||
|
||||
/// Record of a loan that was issued.
|
||||
pub struct Loan<'tcx> {
|
||||
index: usize,
|
||||
loan_path: Rc<LoanPath<'tcx>>,
|
||||
kind: ty::BorrowKind,
|
||||
restricted_paths: Vec<Rc<LoanPath<'tcx>>>,
|
||||
|
||||
/// gen_scope indicates where loan is introduced. Typically the
|
||||
/// loan is introduced at the point of the borrow, but in some
|
||||
/// cases, notably method arguments, the loan may be introduced
|
||||
/// only later, once it comes into scope. See also
|
||||
/// `GatherLoanCtxt::compute_gen_scope`.
|
||||
gen_scope: region::Scope,
|
||||
|
||||
/// kill_scope indicates when the loan goes out of scope. This is
|
||||
/// either when the lifetime expires or when the local variable
|
||||
/// which roots the loan-path goes out of scope, whichever happens
|
||||
/// faster. See also `GatherLoanCtxt::compute_kill_scope`.
|
||||
kill_scope: region::Scope,
|
||||
}
|
||||
|
||||
impl<'tcx> Loan<'tcx> {
|
||||
pub fn loan_path(&self) -> Rc<LoanPath<'tcx>> {
|
||||
self.loan_path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq)]
|
||||
pub struct LoanPath<'tcx> {
|
||||
kind: LoanPathKind<'tcx>,
|
||||
ty: Ty<'tcx>,
|
||||
}
|
||||
|
||||
impl<'tcx> PartialEq for LoanPath<'tcx> {
|
||||
fn eq(&self, that: &LoanPath<'tcx>) -> bool {
|
||||
self.kind == that.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> Hash for LoanPath<'tcx> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.kind.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Debug)]
|
||||
pub enum LoanPathKind<'tcx> {
|
||||
LpVar(hir::HirId), // `x` in README.md
|
||||
LpUpvar(ty::UpvarId), // `x` captured by-value into closure
|
||||
LpDowncast(Rc<LoanPath<'tcx>>, DefId), // `x` downcast to particular enum variant
|
||||
LpExtend(Rc<LoanPath<'tcx>>, mc::MutabilityCategory, LoanPathElem<'tcx>)
|
||||
}
|
||||
|
||||
impl<'tcx> LoanPath<'tcx> {
|
||||
fn new(kind: LoanPathKind<'tcx>, ty: Ty<'tcx>) -> LoanPath<'tcx> {
|
||||
LoanPath { kind: kind, ty: ty }
|
||||
}
|
||||
|
||||
fn to_type(&self) -> Ty<'tcx> { self.ty }
|
||||
}
|
||||
|
||||
// FIXME (pnkfelix): See discussion here
|
||||
// https://github.com/pnkfelix/rust/commit/
|
||||
// b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003
|
||||
const DOWNCAST_PRINTED_OPERATOR: &'static str = " as ";
|
||||
|
||||
// A local, "cleaned" version of `mc::InteriorKind` that drops
|
||||
// information that is not relevant to loan-path analysis. (In
|
||||
// particular, the distinction between how precisely an array-element
|
||||
// is tracked is irrelevant here.)
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum InteriorKind {
|
||||
InteriorField(mc::FieldIndex),
|
||||
InteriorElement,
|
||||
}
|
||||
|
||||
trait ToInteriorKind { fn cleaned(self) -> InteriorKind; }
|
||||
impl ToInteriorKind for mc::InteriorKind {
|
||||
fn cleaned(self) -> InteriorKind {
|
||||
match self {
|
||||
mc::InteriorField(name) => InteriorField(name),
|
||||
mc::InteriorElement(_) => InteriorElement,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This can be:
|
||||
// - a pointer dereference (`*P` in README.md)
|
||||
// - a field reference, with an optional definition of the containing
|
||||
// enum variant (`P.f` in README.md)
|
||||
// `DefId` is present when the field is part of struct that is in
|
||||
// a variant of an enum. For instance in:
|
||||
// `enum E { X { foo: u32 }, Y { foo: u32 }}`
|
||||
// each `foo` is qualified by the definitition id of the variant (`X` or `Y`).
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum LoanPathElem<'tcx> {
|
||||
LpDeref(mc::PointerKind<'tcx>),
|
||||
LpInterior(Option<DefId>, InteriorKind),
|
||||
}
|
||||
|
||||
fn closure_to_block(closure_id: LocalDefId, tcx: TyCtxt<'_>) -> HirId {
|
||||
let closure_id = tcx.hir().local_def_id_to_hir_id(closure_id);
|
||||
match tcx.hir().get(closure_id) {
|
||||
Node::Expr(expr) => match expr.kind {
|
||||
hir::ExprKind::Closure(.., body_id, _, _) => {
|
||||
body_id.hir_id
|
||||
}
|
||||
_ => {
|
||||
bug!("encountered non-closure id: {}", closure_id)
|
||||
}
|
||||
},
|
||||
_ => bug!("encountered non-expr id: {}", closure_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LoanPath<'tcx> {
|
||||
pub fn kill_scope(&self, bccx: &BorrowckCtxt<'a, 'tcx>) -> region::Scope {
|
||||
match self.kind {
|
||||
LpVar(hir_id) => {
|
||||
bccx.region_scope_tree.var_scope(hir_id.local_id)
|
||||
}
|
||||
LpUpvar(upvar_id) => {
|
||||
let block_id = closure_to_block(upvar_id.closure_expr_id, bccx.tcx);
|
||||
region::Scope { id: block_id.local_id, data: region::ScopeData::Node }
|
||||
}
|
||||
LpDowncast(ref base, _) |
|
||||
LpExtend(ref base, ..) => base.kill_scope(bccx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid "cannot borrow immutable field `self.x` as mutable" as that implies that a field *can* be
|
||||
// mutable independently of the struct it belongs to. (#35937)
|
||||
pub fn opt_loan_path_is_field<'tcx>(cmt: &mc::cmt_<'tcx>) -> (Option<Rc<LoanPath<'tcx>>>, bool) {
|
||||
let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
|
||||
|
||||
match cmt.cat {
|
||||
Categorization::Rvalue(..) |
|
||||
Categorization::ThreadLocal(..) |
|
||||
Categorization::StaticItem => {
|
||||
(None, false)
|
||||
}
|
||||
|
||||
Categorization::Local(id) => {
|
||||
(Some(new_lp(LpVar(id))), false)
|
||||
}
|
||||
|
||||
Categorization::Upvar(mc::Upvar { id, .. }) => {
|
||||
(Some(new_lp(LpUpvar(id))), false)
|
||||
}
|
||||
|
||||
Categorization::Deref(ref cmt_base, pk) => {
|
||||
let lp = opt_loan_path_is_field(cmt_base);
|
||||
(lp.0.map(|lp| {
|
||||
new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
|
||||
}), lp.1)
|
||||
}
|
||||
|
||||
Categorization::Interior(ref cmt_base, ik) => {
|
||||
(opt_loan_path(cmt_base).map(|lp| {
|
||||
let opt_variant_id = match cmt_base.cat {
|
||||
Categorization::Downcast(_, did) => Some(did),
|
||||
_ => None
|
||||
};
|
||||
new_lp(LpExtend(lp, cmt.mutbl, LpInterior(opt_variant_id, ik.cleaned())))
|
||||
}), true)
|
||||
}
|
||||
|
||||
Categorization::Downcast(ref cmt_base, variant_def_id) => {
|
||||
let lp = opt_loan_path_is_field(cmt_base);
|
||||
(lp.0.map(|lp| {
|
||||
new_lp(LpDowncast(lp, variant_def_id))
|
||||
}), lp.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the `LoanPath` (if any) for a `cmt`.
|
||||
/// Note that this logic is somewhat duplicated in
|
||||
/// the method `compute()` found in `gather_loans::restrictions`,
|
||||
/// which allows it to share common loan path pieces as it
|
||||
/// traverses the CMT.
|
||||
pub fn opt_loan_path<'tcx>(cmt: &mc::cmt_<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
|
||||
opt_loan_path_is_field(cmt).0
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Misc
|
||||
|
||||
impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
|
||||
pub fn is_subregion_of(&self,
|
||||
r_sub: ty::Region<'tcx>,
|
||||
r_sup: ty::Region<'tcx>)
|
||||
-> bool
|
||||
{
|
||||
let region_rels = RegionRelations::new(self.tcx,
|
||||
self.owner_def_id,
|
||||
&self.region_scope_tree,
|
||||
&self.tables.free_region_map);
|
||||
region_rels.is_subregion_of(r_sub, r_sup)
|
||||
}
|
||||
|
||||
pub fn append_loan_path_to_string(&self,
|
||||
loan_path: &LoanPath<'tcx>,
|
||||
out: &mut String) {
|
||||
match loan_path.kind {
|
||||
LpUpvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id: id }, closure_expr_id: _ }) => {
|
||||
out.push_str(&self.tcx.hir().name(id).as_str());
|
||||
}
|
||||
LpVar(id) => {
|
||||
out.push_str(&self.tcx.hir().name(id).as_str());
|
||||
}
|
||||
|
||||
LpDowncast(ref lp_base, variant_def_id) => {
|
||||
out.push('(');
|
||||
self.append_loan_path_to_string(&lp_base, out);
|
||||
out.push_str(DOWNCAST_PRINTED_OPERATOR);
|
||||
out.push_str(&self.tcx.def_path_str(variant_def_id));
|
||||
out.push(')');
|
||||
}
|
||||
|
||||
LpExtend(ref lp_base, _, LpInterior(_, InteriorField(mc::FieldIndex(_, info)))) => {
|
||||
self.append_autoderefd_loan_path_to_string(&lp_base, out);
|
||||
out.push('.');
|
||||
out.push_str(&info.as_str());
|
||||
}
|
||||
|
||||
LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) => {
|
||||
self.append_autoderefd_loan_path_to_string(&lp_base, out);
|
||||
out.push_str("[..]");
|
||||
}
|
||||
|
||||
LpExtend(ref lp_base, _, LpDeref(_)) => {
|
||||
out.push('*');
|
||||
self.append_loan_path_to_string(&lp_base, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_autoderefd_loan_path_to_string(&self,
|
||||
loan_path: &LoanPath<'tcx>,
|
||||
out: &mut String) {
|
||||
match loan_path.kind {
|
||||
LpExtend(ref lp_base, _, LpDeref(_)) => {
|
||||
// For a path like `(*x).f` or `(*x)[3]`, autoderef
|
||||
// rules would normally allow users to omit the `*x`.
|
||||
// So just serialize such paths to `x.f` or x[3]` respectively.
|
||||
self.append_autoderefd_loan_path_to_string(&lp_base, out)
|
||||
}
|
||||
|
||||
LpDowncast(ref lp_base, variant_def_id) => {
|
||||
out.push('(');
|
||||
self.append_autoderefd_loan_path_to_string(&lp_base, out);
|
||||
out.push_str(DOWNCAST_PRINTED_OPERATOR);
|
||||
out.push_str(&self.tcx.def_path_str(variant_def_id));
|
||||
out.push(')');
|
||||
}
|
||||
|
||||
LpVar(..) | LpUpvar(..) | LpExtend(.., LpInterior(..)) => {
|
||||
self.append_loan_path_to_string(loan_path, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn loan_path_to_string(&self, loan_path: &LoanPath<'tcx>) -> String {
|
||||
let mut result = String::new();
|
||||
self.append_loan_path_to_string(loan_path, &mut result);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn cmt_to_cow_str(&self, cmt: &mc::cmt_<'tcx>) -> Cow<'static, str> {
|
||||
cmt.descriptive_string(self.tcx)
|
||||
}
|
||||
|
||||
pub fn cmt_to_path_or_string(&self, cmt: &mc::cmt_<'tcx>) -> String {
|
||||
match opt_loan_path(cmt) {
|
||||
Some(lp) => format!("`{}`", self.loan_path_to_string(&lp)),
|
||||
None => self.cmt_to_cow_str(cmt).into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BitwiseOperator for LoanDataFlowOperator {
|
||||
#[inline]
|
||||
fn join(&self, succ: usize, pred: usize) -> usize {
|
||||
succ | pred // loans from both preds are in scope
|
||||
}
|
||||
}
|
||||
|
||||
impl DataFlowOperator for LoanDataFlowOperator {
|
||||
#[inline]
|
||||
fn initial_value(&self) -> bool {
|
||||
false // no loans in scope by default
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for InteriorKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
InteriorField(mc::FieldIndex(_, info)) => write!(f, "{}", info),
|
||||
InteriorElement => write!(f, "[]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for Loan<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Loan_{}({:?}, {:?}, {:?}-{:?}, {:?})",
|
||||
self.index,
|
||||
self.loan_path,
|
||||
self.kind,
|
||||
self.gen_scope,
|
||||
self.kill_scope,
|
||||
self.restricted_paths)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for LoanPath<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
LpVar(id) => {
|
||||
write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().node_to_string(id)))
|
||||
}
|
||||
|
||||
LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath {hir_id: var_id}, closure_expr_id }) => {
|
||||
let s = ty::tls::with(|tcx| {
|
||||
tcx.hir().node_to_string(var_id)
|
||||
});
|
||||
write!(f, "$({} captured by id={:?})", s, closure_expr_id)
|
||||
}
|
||||
|
||||
LpDowncast(ref lp, variant_def_id) => {
|
||||
let variant_str = if variant_def_id.is_local() {
|
||||
ty::tls::with(|tcx| tcx.def_path_str(variant_def_id))
|
||||
} else {
|
||||
format!("{:?}", variant_def_id)
|
||||
};
|
||||
write!(f, "({:?}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpDeref(_)) => {
|
||||
write!(f, "{:?}.*", lp)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpInterior(_, ref interior)) => {
|
||||
write!(f, "{:?}.{:?}", lp, interior)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Display for LoanPath<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.kind {
|
||||
LpVar(id) => {
|
||||
write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().hir_to_user_string(id)))
|
||||
}
|
||||
|
||||
LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath { hir_id }, closure_expr_id: _ }) => {
|
||||
let s = ty::tls::with(|tcx| {
|
||||
tcx.hir().node_to_string(hir_id)
|
||||
});
|
||||
write!(f, "$({} captured by closure)", s)
|
||||
}
|
||||
|
||||
LpDowncast(ref lp, variant_def_id) => {
|
||||
let variant_str = if variant_def_id.is_local() {
|
||||
ty::tls::with(|tcx| tcx.def_path_str(variant_def_id))
|
||||
} else {
|
||||
format!("{:?}", variant_def_id)
|
||||
};
|
||||
write!(f, "({}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpDeref(_)) => {
|
||||
write!(f, "{}.*", lp)
|
||||
}
|
||||
|
||||
LpExtend(ref lp, _, LpInterior(_, ref interior)) => {
|
||||
write!(f, "{}.{:?}", lp, interior)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,730 +0,0 @@
|
||||
//! Data structures used for tracking moves. Please see the extensive
|
||||
//! comments in the section "Moves and initialization" in `README.md`.
|
||||
|
||||
use crate::dataflow::{DataFlowContext, BitwiseOperator, DataFlowOperator, KillFrom};
|
||||
|
||||
use crate::borrowck::*;
|
||||
use crate::cfg;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
use rustc::util::nodemap::FxHashMap;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::usize;
|
||||
use syntax_pos::Span;
|
||||
use rustc::hir;
|
||||
use log::debug;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MoveData<'tcx> {
|
||||
/// Move paths. See section "Move paths" in `README.md`.
|
||||
pub paths: RefCell<Vec<MovePath<'tcx>>>,
|
||||
|
||||
/// Cache of loan path to move path index, for easy lookup.
|
||||
pub path_map: RefCell<FxHashMap<Rc<LoanPath<'tcx>>, MovePathIndex>>,
|
||||
|
||||
/// Each move or uninitialized variable gets an entry here.
|
||||
pub moves: RefCell<Vec<Move>>,
|
||||
|
||||
/// Assignments to a variable, like `x = foo`. These are assigned
|
||||
/// bits for dataflow, since we must track them to ensure that
|
||||
/// immutable variables are assigned at most once along each path.
|
||||
pub var_assignments: RefCell<Vec<Assignment>>,
|
||||
|
||||
/// Assignments to a path, like `x.f = foo`. These are not
|
||||
/// assigned dataflow bits, but we track them because they still
|
||||
/// kill move bits.
|
||||
pub path_assignments: RefCell<Vec<Assignment>>,
|
||||
}
|
||||
|
||||
pub struct FlowedMoveData<'tcx> {
|
||||
pub move_data: MoveData<'tcx>,
|
||||
|
||||
pub dfcx_moves: MoveDataFlow<'tcx>,
|
||||
|
||||
// We could (and maybe should, for efficiency) combine both move
|
||||
// and assign data flow into one, but this way it's easier to
|
||||
// distinguish the bits that correspond to moves and assignments.
|
||||
pub dfcx_assign: AssignDataFlow<'tcx>,
|
||||
}
|
||||
|
||||
/// Index into `MoveData.paths`, used like a pointer
|
||||
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct MovePathIndex(usize);
|
||||
|
||||
impl MovePathIndex {
|
||||
fn get(&self) -> usize {
|
||||
let MovePathIndex(v) = *self; v
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for MovePathIndex {
|
||||
fn clone(&self) -> MovePathIndex {
|
||||
MovePathIndex(self.get())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const InvalidMovePathIndex: MovePathIndex = MovePathIndex(usize::MAX);
|
||||
|
||||
/// Index into `MoveData.moves`, used like a pointer
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct MoveIndex(usize);
|
||||
|
||||
impl MoveIndex {
|
||||
fn get(&self) -> usize {
|
||||
let MoveIndex(v) = *self; v
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const InvalidMoveIndex: MoveIndex = MoveIndex(usize::MAX);
|
||||
|
||||
pub struct MovePath<'tcx> {
|
||||
/// Loan path corresponding to this move path
|
||||
pub loan_path: Rc<LoanPath<'tcx>>,
|
||||
|
||||
/// Parent pointer, `InvalidMovePathIndex` if root
|
||||
pub parent: MovePathIndex,
|
||||
|
||||
/// Head of linked list of moves to this path,
|
||||
/// `InvalidMoveIndex` if not moved
|
||||
pub first_move: MoveIndex,
|
||||
|
||||
/// First node in linked list of children, `InvalidMovePathIndex` if leaf
|
||||
pub first_child: MovePathIndex,
|
||||
|
||||
/// Next node in linked list of parent's children (siblings),
|
||||
/// `InvalidMovePathIndex` if none.
|
||||
pub next_sibling: MovePathIndex,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Move {
|
||||
/// Path being moved.
|
||||
pub path: MovePathIndex,
|
||||
|
||||
/// ID of node that is doing the move.
|
||||
pub id: hir::ItemLocalId,
|
||||
|
||||
/// Next node in linked list of moves from `path`, or `InvalidMoveIndex`
|
||||
pub next_move: MoveIndex
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Assignment {
|
||||
/// Path being assigned.
|
||||
pub path: MovePathIndex,
|
||||
|
||||
/// ID where assignment occurs
|
||||
pub id: hir::ItemLocalId,
|
||||
|
||||
/// span of node where assignment occurs
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MoveDataFlowOperator;
|
||||
|
||||
pub type MoveDataFlow<'tcx> = DataFlowContext<'tcx, MoveDataFlowOperator>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AssignDataFlowOperator;
|
||||
|
||||
pub type AssignDataFlow<'tcx> = DataFlowContext<'tcx, AssignDataFlowOperator>;
|
||||
|
||||
fn loan_path_is_precise(loan_path: &LoanPath<'_>) -> bool {
|
||||
match loan_path.kind {
|
||||
LpVar(_) | LpUpvar(_) => {
|
||||
true
|
||||
}
|
||||
LpExtend(.., LpInterior(_, InteriorKind::InteriorElement)) => {
|
||||
// Paths involving element accesses a[i] do not refer to a unique
|
||||
// location, as there is no accurate tracking of the indices.
|
||||
//
|
||||
// (Paths involving element accesses via slice pattern bindings
|
||||
// can in principle be tracked precisely, but that is future
|
||||
// work. For now, continue claiming that they are imprecise.)
|
||||
false
|
||||
}
|
||||
LpDowncast(ref lp_base, _) |
|
||||
LpExtend(ref lp_base, ..) => {
|
||||
loan_path_is_precise(&lp_base)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveData<'tcx> {
|
||||
/// Returns `true` if there are no trackable assignments or moves
|
||||
/// in this move data -- that means that there is nothing that
|
||||
/// could cause a borrow error.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.moves.borrow().is_empty() &&
|
||||
self.path_assignments.borrow().is_empty() &&
|
||||
self.var_assignments.borrow().is_empty()
|
||||
}
|
||||
|
||||
pub fn path_loan_path(&self, index: MovePathIndex) -> Rc<LoanPath<'tcx>> {
|
||||
(*self.paths.borrow())[index.get()].loan_path.clone()
|
||||
}
|
||||
|
||||
fn path_parent(&self, index: MovePathIndex) -> MovePathIndex {
|
||||
(*self.paths.borrow())[index.get()].parent
|
||||
}
|
||||
|
||||
fn path_first_move(&self, index: MovePathIndex) -> MoveIndex {
|
||||
(*self.paths.borrow())[index.get()].first_move
|
||||
}
|
||||
|
||||
/// Returns the index of first child, or `InvalidMovePathIndex` if
|
||||
/// `index` is leaf.
|
||||
fn path_first_child(&self, index: MovePathIndex) -> MovePathIndex {
|
||||
(*self.paths.borrow())[index.get()].first_child
|
||||
}
|
||||
|
||||
fn path_next_sibling(&self, index: MovePathIndex) -> MovePathIndex {
|
||||
(*self.paths.borrow())[index.get()].next_sibling
|
||||
}
|
||||
|
||||
fn set_path_first_move(&self,
|
||||
index: MovePathIndex,
|
||||
first_move: MoveIndex) {
|
||||
(*self.paths.borrow_mut())[index.get()].first_move = first_move
|
||||
}
|
||||
|
||||
fn set_path_first_child(&self,
|
||||
index: MovePathIndex,
|
||||
first_child: MovePathIndex) {
|
||||
(*self.paths.borrow_mut())[index.get()].first_child = first_child
|
||||
}
|
||||
|
||||
fn move_next_move(&self, index: MoveIndex) -> MoveIndex {
|
||||
//! Type safe indexing operator
|
||||
(*self.moves.borrow())[index.get()].next_move
|
||||
}
|
||||
|
||||
fn is_var_path(&self, index: MovePathIndex) -> bool {
|
||||
//! True if `index` refers to a variable
|
||||
self.path_parent(index) == InvalidMovePathIndex
|
||||
}
|
||||
|
||||
/// Returns the existing move path index for `lp`, if any, and otherwise adds a new index for
|
||||
/// `lp` and any of its base paths that do not yet have an index.
|
||||
pub fn move_path(&self, tcx: TyCtxt<'tcx>, lp: Rc<LoanPath<'tcx>>) -> MovePathIndex {
|
||||
if let Some(&index) = self.path_map.borrow().get(&lp) {
|
||||
return index;
|
||||
}
|
||||
|
||||
let index = match lp.kind {
|
||||
LpVar(..) | LpUpvar(..) => {
|
||||
let index = MovePathIndex(self.paths.borrow().len());
|
||||
|
||||
self.paths.borrow_mut().push(MovePath {
|
||||
loan_path: lp.clone(),
|
||||
parent: InvalidMovePathIndex,
|
||||
first_move: InvalidMoveIndex,
|
||||
first_child: InvalidMovePathIndex,
|
||||
next_sibling: InvalidMovePathIndex,
|
||||
});
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
LpDowncast(ref base, _) |
|
||||
LpExtend(ref base, ..) => {
|
||||
let parent_index = self.move_path(tcx, base.clone());
|
||||
|
||||
let index = MovePathIndex(self.paths.borrow().len());
|
||||
|
||||
let next_sibling = self.path_first_child(parent_index);
|
||||
self.set_path_first_child(parent_index, index);
|
||||
|
||||
self.paths.borrow_mut().push(MovePath {
|
||||
loan_path: lp.clone(),
|
||||
parent: parent_index,
|
||||
first_move: InvalidMoveIndex,
|
||||
first_child: InvalidMovePathIndex,
|
||||
next_sibling,
|
||||
});
|
||||
|
||||
index
|
||||
}
|
||||
};
|
||||
|
||||
debug!("move_path(lp={:?}, index={:?})",
|
||||
lp,
|
||||
index);
|
||||
|
||||
assert_eq!(index.get(), self.paths.borrow().len() - 1);
|
||||
self.path_map.borrow_mut().insert(lp, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
fn existing_move_path(&self, lp: &Rc<LoanPath<'tcx>>)
|
||||
-> Option<MovePathIndex> {
|
||||
self.path_map.borrow().get(lp).cloned()
|
||||
}
|
||||
|
||||
fn existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>)
|
||||
-> Vec<MovePathIndex> {
|
||||
let mut result = vec![];
|
||||
self.add_existing_base_paths(lp, &mut result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Adds any existing move path indices for `lp` and any base paths of `lp` to `result`, but
|
||||
/// does not add new move paths
|
||||
fn add_existing_base_paths(&self, lp: &Rc<LoanPath<'tcx>>,
|
||||
result: &mut Vec<MovePathIndex>) {
|
||||
match self.path_map.borrow().get(lp).cloned() {
|
||||
Some(index) => {
|
||||
self.each_base_path(index, |p| {
|
||||
result.push(p);
|
||||
true
|
||||
});
|
||||
}
|
||||
None => {
|
||||
match lp.kind {
|
||||
LpVar(..) | LpUpvar(..) => { }
|
||||
LpDowncast(ref b, _) |
|
||||
LpExtend(ref b, ..) => {
|
||||
self.add_existing_base_paths(b, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Adds a new move entry for a move of `lp` that occurs at location `id` with kind `kind`.
|
||||
pub fn add_move(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
orig_lp: Rc<LoanPath<'tcx>>,
|
||||
id: hir::ItemLocalId,
|
||||
) {
|
||||
// Moving one union field automatically moves all its fields. Also move siblings of
|
||||
// all parent union fields, moves do not propagate upwards automatically.
|
||||
let mut lp = orig_lp.clone();
|
||||
while let LpExtend(ref base_lp, mutbl, lp_elem) = lp.clone().kind {
|
||||
if let (&ty::Adt(adt_def, _), LpInterior(opt_variant_id, interior))
|
||||
= (&base_lp.ty.kind, lp_elem) {
|
||||
if adt_def.is_union() {
|
||||
for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() {
|
||||
let field =
|
||||
InteriorKind::InteriorField(mc::FieldIndex(i, field.ident.name));
|
||||
if field != interior {
|
||||
let sibling_lp_kind =
|
||||
LpExtend(base_lp.clone(), mutbl, LpInterior(opt_variant_id, field));
|
||||
let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, tcx.types.err));
|
||||
self.add_move_helper(tcx, sibling_lp, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lp = base_lp.clone();
|
||||
}
|
||||
|
||||
self.add_move_helper(tcx, orig_lp, id);
|
||||
}
|
||||
|
||||
fn add_move_helper(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
lp: Rc<LoanPath<'tcx>>,
|
||||
id: hir::ItemLocalId,
|
||||
) {
|
||||
debug!("add_move(lp={:?}, id={:?})", lp, id);
|
||||
|
||||
let path_index = self.move_path(tcx, lp);
|
||||
let move_index = MoveIndex(self.moves.borrow().len());
|
||||
|
||||
let next_move = self.path_first_move(path_index);
|
||||
self.set_path_first_move(path_index, move_index);
|
||||
|
||||
self.moves.borrow_mut().push(Move {
|
||||
path: path_index,
|
||||
id,
|
||||
next_move,
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a new record for an assignment to `lp` that occurs at location `id` with the given
|
||||
/// `span`.
|
||||
pub fn add_assignment(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
lp: Rc<LoanPath<'tcx>>,
|
||||
assign_id: hir::ItemLocalId,
|
||||
span: Span,
|
||||
) {
|
||||
// Assigning to one union field automatically assigns to all its fields.
|
||||
if let LpExtend(ref base_lp, mutbl, LpInterior(opt_variant_id, interior)) = lp.kind {
|
||||
if let ty::Adt(adt_def, _) = base_lp.ty.kind {
|
||||
if adt_def.is_union() {
|
||||
for (i, field) in adt_def.non_enum_variant().fields.iter().enumerate() {
|
||||
let field =
|
||||
InteriorKind::InteriorField(mc::FieldIndex(i, field.ident.name));
|
||||
let field_ty = if field == interior {
|
||||
lp.ty
|
||||
} else {
|
||||
tcx.types.err // Doesn't matter
|
||||
};
|
||||
let sibling_lp_kind = LpExtend(base_lp.clone(), mutbl,
|
||||
LpInterior(opt_variant_id, field));
|
||||
let sibling_lp = Rc::new(LoanPath::new(sibling_lp_kind, field_ty));
|
||||
self.add_assignment_helper(tcx, sibling_lp, assign_id,
|
||||
span);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.add_assignment_helper(tcx, lp, assign_id, span);
|
||||
}
|
||||
|
||||
fn add_assignment_helper(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
lp: Rc<LoanPath<'tcx>>,
|
||||
assign_id: hir::ItemLocalId,
|
||||
span: Span,
|
||||
) {
|
||||
debug!("add_assignment(lp={:?}, assign_id={:?}", lp, assign_id);
|
||||
|
||||
let path_index = self.move_path(tcx, lp.clone());
|
||||
|
||||
let assignment = Assignment {
|
||||
path: path_index,
|
||||
id: assign_id,
|
||||
span,
|
||||
};
|
||||
|
||||
if self.is_var_path(path_index) {
|
||||
debug!("add_assignment[var](lp={:?}, assignment={}, path_index={:?})",
|
||||
lp, self.var_assignments.borrow().len(), path_index);
|
||||
|
||||
self.var_assignments.borrow_mut().push(assignment);
|
||||
} else {
|
||||
debug!("add_assignment[path](lp={:?}, path_index={:?})",
|
||||
lp, path_index);
|
||||
|
||||
self.path_assignments.borrow_mut().push(assignment);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the gen/kills for the various moves and
|
||||
/// assignments into the provided data flow contexts.
|
||||
/// Moves are generated by moves and killed by assignments and
|
||||
/// scoping. Assignments are generated by assignment to variables and
|
||||
/// killed by scoping. See `README.md` for more details.
|
||||
fn add_gen_kills(
|
||||
&self,
|
||||
bccx: &BorrowckCtxt<'_, 'tcx>,
|
||||
dfcx_moves: &mut MoveDataFlow<'_>,
|
||||
dfcx_assign: &mut AssignDataFlow<'_>,
|
||||
) {
|
||||
for (i, the_move) in self.moves.borrow().iter().enumerate() {
|
||||
dfcx_moves.add_gen(the_move.id, i);
|
||||
}
|
||||
|
||||
for (i, assignment) in self.var_assignments.borrow().iter().enumerate() {
|
||||
dfcx_assign.add_gen(assignment.id, i);
|
||||
self.kill_moves(assignment.path, assignment.id,
|
||||
KillFrom::Execution, dfcx_moves);
|
||||
}
|
||||
|
||||
for assignment in self.path_assignments.borrow().iter() {
|
||||
self.kill_moves(assignment.path, assignment.id,
|
||||
KillFrom::Execution, dfcx_moves);
|
||||
}
|
||||
|
||||
// Kill all moves related to a variable `x` when
|
||||
// it goes out of scope:
|
||||
for path in self.paths.borrow().iter() {
|
||||
match path.loan_path.kind {
|
||||
LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
|
||||
let kill_scope = path.loan_path.kill_scope(bccx);
|
||||
let path = *self.path_map.borrow().get(&path.loan_path).unwrap();
|
||||
self.kill_moves(path, kill_scope.item_local_id(),
|
||||
KillFrom::ScopeEnd, dfcx_moves);
|
||||
}
|
||||
LpExtend(..) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Kill all assignments when the variable goes out of scope:
|
||||
for (assignment_index, assignment) in
|
||||
self.var_assignments.borrow().iter().enumerate() {
|
||||
let lp = self.path_loan_path(assignment.path);
|
||||
match lp.kind {
|
||||
LpVar(..) | LpUpvar(..) | LpDowncast(..) => {
|
||||
let kill_scope = lp.kill_scope(bccx);
|
||||
dfcx_assign.add_kill(KillFrom::ScopeEnd,
|
||||
kill_scope.item_local_id(),
|
||||
assignment_index);
|
||||
}
|
||||
LpExtend(..) => {
|
||||
bug!("var assignment for non var path");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn each_base_path<F>(&self, index: MovePathIndex, mut f: F) -> bool where
|
||||
F: FnMut(MovePathIndex) -> bool,
|
||||
{
|
||||
let mut p = index;
|
||||
while p != InvalidMovePathIndex {
|
||||
if !f(p) {
|
||||
return false;
|
||||
}
|
||||
p = self.path_parent(p);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME(#19596) This is a workaround, but there should be better way to do this
|
||||
fn each_extending_path_<F>(&self, index: MovePathIndex, f: &mut F) -> bool where
|
||||
F: FnMut(MovePathIndex) -> bool,
|
||||
{
|
||||
if !(*f)(index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut p = self.path_first_child(index);
|
||||
while p != InvalidMovePathIndex {
|
||||
if !self.each_extending_path_(p, f) {
|
||||
return false;
|
||||
}
|
||||
p = self.path_next_sibling(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn each_extending_path<F>(&self, index: MovePathIndex, mut f: F) -> bool where
|
||||
F: FnMut(MovePathIndex) -> bool,
|
||||
{
|
||||
self.each_extending_path_(index, &mut f)
|
||||
}
|
||||
|
||||
fn each_applicable_move<F>(&self, index0: MovePathIndex, mut f: F) -> bool where
|
||||
F: FnMut(MoveIndex) -> bool,
|
||||
{
|
||||
let mut ret = true;
|
||||
self.each_extending_path(index0, |index| {
|
||||
let mut p = self.path_first_move(index);
|
||||
while p != InvalidMoveIndex {
|
||||
if !f(p) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
p = self.move_next_move(p);
|
||||
}
|
||||
ret
|
||||
});
|
||||
ret
|
||||
}
|
||||
|
||||
fn kill_moves(
|
||||
&self,
|
||||
path: MovePathIndex,
|
||||
kill_id: hir::ItemLocalId,
|
||||
kill_kind: KillFrom,
|
||||
dfcx_moves: &mut MoveDataFlow<'_>,
|
||||
) {
|
||||
// We can only perform kills for paths that refer to a unique location,
|
||||
// since otherwise we may kill a move from one location with an
|
||||
// assignment referring to another location.
|
||||
|
||||
let loan_path = self.path_loan_path(path);
|
||||
if loan_path_is_precise(&loan_path) {
|
||||
self.each_applicable_move(path, |move_index| {
|
||||
debug!("kill_moves add_kill {:?} kill_id={:?} move_index={}",
|
||||
kill_kind, kill_id, move_index.get());
|
||||
dfcx_moves.add_kill(kill_kind, kill_id, move_index.get());
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> FlowedMoveData<'tcx> {
|
||||
pub fn new(
|
||||
move_data: MoveData<'tcx>,
|
||||
bccx: &BorrowckCtxt<'_, 'tcx>,
|
||||
cfg: &cfg::CFG,
|
||||
body: &hir::Body,
|
||||
) -> FlowedMoveData<'tcx> {
|
||||
let tcx = bccx.tcx;
|
||||
|
||||
let mut dfcx_moves =
|
||||
DataFlowContext::new(tcx,
|
||||
"flowed_move_data_moves",
|
||||
Some(body),
|
||||
cfg,
|
||||
MoveDataFlowOperator,
|
||||
move_data.moves.borrow().len());
|
||||
let mut dfcx_assign =
|
||||
DataFlowContext::new(tcx,
|
||||
"flowed_move_data_assigns",
|
||||
Some(body),
|
||||
cfg,
|
||||
AssignDataFlowOperator,
|
||||
move_data.var_assignments.borrow().len());
|
||||
|
||||
move_data.add_gen_kills(bccx,
|
||||
&mut dfcx_moves,
|
||||
&mut dfcx_assign);
|
||||
|
||||
dfcx_moves.add_kills_from_flow_exits(cfg);
|
||||
dfcx_assign.add_kills_from_flow_exits(cfg);
|
||||
|
||||
dfcx_moves.propagate(cfg, body);
|
||||
dfcx_assign.propagate(cfg, body);
|
||||
|
||||
FlowedMoveData {
|
||||
move_data,
|
||||
dfcx_moves,
|
||||
dfcx_assign,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_move_path(&self, id: hir::ItemLocalId, loan_path: &Rc<LoanPath<'tcx>>) -> bool {
|
||||
//! Returns the kind of a move of `loan_path` by `id`, if one exists.
|
||||
|
||||
let mut ret = false;
|
||||
if let Some(loan_path_index) = self.move_data.path_map.borrow().get(&*loan_path) {
|
||||
self.dfcx_moves.each_gen_bit(id, |move_index| {
|
||||
let the_move = self.move_data.moves.borrow();
|
||||
let the_move = (*the_move)[move_index];
|
||||
if the_move.path == *loan_path_index {
|
||||
ret = true;
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Iterates through each move of `loan_path` (or some base path of `loan_path`) that *may*
|
||||
/// have occurred on entry to `id` without an intervening assignment. In other words, any moves
|
||||
/// that would invalidate a reference to `loan_path` at location `id`.
|
||||
pub fn each_move_of<F>(&self,
|
||||
id: hir::ItemLocalId,
|
||||
loan_path: &Rc<LoanPath<'tcx>>,
|
||||
mut f: F)
|
||||
-> bool where
|
||||
F: FnMut(&Move, &LoanPath<'tcx>) -> bool,
|
||||
{
|
||||
// Bad scenarios:
|
||||
//
|
||||
// 1. Move of `a.b.c`, use of `a.b.c`
|
||||
// 2. Move of `a.b.c`, use of `a.b.c.d`
|
||||
// 3. Move of `a.b.c`, use of `a` or `a.b`
|
||||
//
|
||||
// OK scenario:
|
||||
//
|
||||
// 4. move of `a.b.c`, use of `a.b.d`
|
||||
|
||||
let base_indices = self.move_data.existing_base_paths(loan_path);
|
||||
if base_indices.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
let opt_loan_path_index = self.move_data.existing_move_path(loan_path);
|
||||
|
||||
let mut ret = true;
|
||||
|
||||
self.dfcx_moves.each_bit_on_entry(id, |index| {
|
||||
let the_move = self.move_data.moves.borrow();
|
||||
let the_move = &(*the_move)[index];
|
||||
let moved_path = the_move.path;
|
||||
if base_indices.iter().any(|x| x == &moved_path) {
|
||||
// Scenario 1 or 2: `loan_path` or some base path of
|
||||
// `loan_path` was moved.
|
||||
if !f(the_move, &self.move_data.path_loan_path(moved_path)) {
|
||||
ret = false;
|
||||
}
|
||||
} else {
|
||||
if let Some(loan_path_index) = opt_loan_path_index {
|
||||
let cont = self.move_data.each_base_path(moved_path, |p| {
|
||||
if p == loan_path_index {
|
||||
// Scenario 3: some extension of `loan_path`
|
||||
// was moved
|
||||
f(the_move,
|
||||
&self.move_data.path_loan_path(moved_path))
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
if !cont { ret = false; }
|
||||
}
|
||||
}
|
||||
ret
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterates through every assignment to `loan_path` that may have occurred on entry to `id`.
|
||||
/// `loan_path` must be a single variable.
|
||||
pub fn each_assignment_of<F>(&self,
|
||||
id: hir::ItemLocalId,
|
||||
loan_path: &Rc<LoanPath<'tcx>>,
|
||||
mut f: F)
|
||||
-> bool where
|
||||
F: FnMut(&Assignment) -> bool,
|
||||
{
|
||||
let loan_path_index = {
|
||||
match self.move_data.existing_move_path(loan_path) {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
// if there were any assignments, it'd have an index
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.dfcx_assign.each_bit_on_entry(id, |index| {
|
||||
let assignment = self.move_data.var_assignments.borrow();
|
||||
let assignment = &(*assignment)[index];
|
||||
if assignment.path == loan_path_index && !f(assignment) {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BitwiseOperator for MoveDataFlowOperator {
|
||||
#[inline]
|
||||
fn join(&self, succ: usize, pred: usize) -> usize {
|
||||
succ | pred // moves from both preds are in scope
|
||||
}
|
||||
}
|
||||
|
||||
impl DataFlowOperator for MoveDataFlowOperator {
|
||||
#[inline]
|
||||
fn initial_value(&self) -> bool {
|
||||
false // no loans in scope by default
|
||||
}
|
||||
}
|
||||
|
||||
impl BitwiseOperator for AssignDataFlowOperator {
|
||||
#[inline]
|
||||
fn join(&self, succ: usize, pred: usize) -> usize {
|
||||
succ | pred // moves from both preds are in scope
|
||||
}
|
||||
}
|
||||
|
||||
impl DataFlowOperator for AssignDataFlowOperator {
|
||||
#[inline]
|
||||
fn initial_value(&self) -> bool {
|
||||
false // no assignments in scope by default
|
||||
}
|
||||
}
|
||||
@@ -1,545 +0,0 @@
|
||||
use crate::cfg::*;
|
||||
|
||||
use rustc::hir::{self, PatKind};
|
||||
use rustc::hir::def_id::DefId;
|
||||
use rustc::hir::ptr::P;
|
||||
use rustc::middle::region;
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
|
||||
use rustc_data_structures::graph::implementation as graph;
|
||||
|
||||
struct CFGBuilder<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
owner_def_id: DefId,
|
||||
tables: &'a ty::TypeckTables<'tcx>,
|
||||
graph: CFGGraph,
|
||||
fn_exit: CFGIndex,
|
||||
loop_scopes: Vec<LoopScope>,
|
||||
breakable_block_scopes: Vec<BlockScope>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct BlockScope {
|
||||
block_expr_id: hir::ItemLocalId, // ID of breakable block expr node
|
||||
break_index: CFGIndex, // where to go on `break`
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct LoopScope {
|
||||
loop_id: hir::ItemLocalId, // ID of `loop`/`while` node
|
||||
continue_index: CFGIndex, // where to go on a `loop`
|
||||
break_index: CFGIndex, // where to go on a `break`
|
||||
}
|
||||
|
||||
pub(super) fn construct(tcx: TyCtxt<'_>, body: &hir::Body) -> CFG {
|
||||
let mut graph = graph::Graph::new();
|
||||
let entry = graph.add_node(CFGNodeData::Entry);
|
||||
|
||||
// `fn_exit` is target of return exprs, which lies somewhere
|
||||
// outside input `body`. (Distinguishing `fn_exit` and `body_exit`
|
||||
// also resolves chicken-and-egg problem that arises if you try to
|
||||
// have return exprs jump to `body_exit` during construction.)
|
||||
let fn_exit = graph.add_node(CFGNodeData::Exit);
|
||||
let body_exit;
|
||||
|
||||
// Find the tables for this body.
|
||||
let owner_def_id = tcx.hir().body_owner_def_id(body.id());
|
||||
let tables = tcx.typeck_tables_of(owner_def_id);
|
||||
|
||||
let mut cfg_builder = CFGBuilder {
|
||||
tcx,
|
||||
owner_def_id,
|
||||
tables,
|
||||
graph,
|
||||
fn_exit,
|
||||
loop_scopes: Vec::new(),
|
||||
breakable_block_scopes: Vec::new(),
|
||||
};
|
||||
body_exit = cfg_builder.expr(&body.value, entry);
|
||||
cfg_builder.add_contained_edge(body_exit, fn_exit);
|
||||
let CFGBuilder { graph, .. } = cfg_builder;
|
||||
CFG {
|
||||
owner_def_id,
|
||||
graph,
|
||||
entry,
|
||||
exit: fn_exit,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> CFGBuilder<'a, 'tcx> {
|
||||
fn block(&mut self, blk: &hir::Block, pred: CFGIndex) -> CFGIndex {
|
||||
if blk.targeted_by_break {
|
||||
let expr_exit = self.add_ast_node(blk.hir_id.local_id, &[]);
|
||||
|
||||
self.breakable_block_scopes.push(BlockScope {
|
||||
block_expr_id: blk.hir_id.local_id,
|
||||
break_index: expr_exit,
|
||||
});
|
||||
|
||||
let mut stmts_exit = pred;
|
||||
for stmt in &blk.stmts {
|
||||
stmts_exit = self.stmt(stmt, stmts_exit);
|
||||
}
|
||||
let blk_expr_exit = self.opt_expr(&blk.expr, stmts_exit);
|
||||
self.add_contained_edge(blk_expr_exit, expr_exit);
|
||||
|
||||
self.breakable_block_scopes.pop();
|
||||
|
||||
expr_exit
|
||||
} else {
|
||||
let mut stmts_exit = pred;
|
||||
for stmt in &blk.stmts {
|
||||
stmts_exit = self.stmt(stmt, stmts_exit);
|
||||
}
|
||||
|
||||
let expr_exit = self.opt_expr(&blk.expr, stmts_exit);
|
||||
|
||||
self.add_ast_node(blk.hir_id.local_id, &[expr_exit])
|
||||
}
|
||||
}
|
||||
|
||||
fn stmt(&mut self, stmt: &hir::Stmt, pred: CFGIndex) -> CFGIndex {
|
||||
let exit = match stmt.kind {
|
||||
hir::StmtKind::Local(ref local) => {
|
||||
let init_exit = self.opt_expr(&local.init, pred);
|
||||
self.pat(&local.pat, init_exit)
|
||||
}
|
||||
hir::StmtKind::Item(_) => pred,
|
||||
hir::StmtKind::Expr(ref expr) |
|
||||
hir::StmtKind::Semi(ref expr) => {
|
||||
self.expr(&expr, pred)
|
||||
}
|
||||
};
|
||||
self.add_ast_node(stmt.hir_id.local_id, &[exit])
|
||||
}
|
||||
|
||||
fn pat(&mut self, pat: &hir::Pat, pred: CFGIndex) -> CFGIndex {
|
||||
match pat.kind {
|
||||
PatKind::Binding(.., None) |
|
||||
PatKind::Path(_) |
|
||||
PatKind::Lit(..) |
|
||||
PatKind::Range(..) |
|
||||
PatKind::Wild => self.add_ast_node(pat.hir_id.local_id, &[pred]),
|
||||
|
||||
PatKind::Box(ref subpat) |
|
||||
PatKind::Ref(ref subpat, _) |
|
||||
PatKind::Binding(.., Some(ref subpat)) => {
|
||||
let subpat_exit = self.pat(&subpat, pred);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[subpat_exit])
|
||||
}
|
||||
|
||||
PatKind::TupleStruct(_, ref subpats, _) |
|
||||
PatKind::Tuple(ref subpats, _) => {
|
||||
let pats_exit = self.pats_all(subpats.iter(), pred);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[pats_exit])
|
||||
}
|
||||
|
||||
PatKind::Struct(_, ref subpats, _) => {
|
||||
let pats_exit = self.pats_all(subpats.iter().map(|f| &f.pat), pred);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[pats_exit])
|
||||
}
|
||||
|
||||
PatKind::Or(ref pats) => {
|
||||
let branches: Vec<_> = pats.iter().map(|p| self.pat(p, pred)).collect();
|
||||
self.add_ast_node(pat.hir_id.local_id, &branches)
|
||||
}
|
||||
|
||||
PatKind::Slice(ref pre, ref vec, ref post) => {
|
||||
let pre_exit = self.pats_all(pre.iter(), pred);
|
||||
let vec_exit = self.pats_all(vec.iter(), pre_exit);
|
||||
let post_exit = self.pats_all(post.iter(), vec_exit);
|
||||
self.add_ast_node(pat.hir_id.local_id, &[post_exit])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles case where all of the patterns must match.
|
||||
fn pats_all<'b, I: Iterator<Item = &'b P<hir::Pat>>>(
|
||||
&mut self,
|
||||
pats: I,
|
||||
pred: CFGIndex,
|
||||
) -> CFGIndex {
|
||||
pats.fold(pred, |pred, pat| self.pat(&pat, pred))
|
||||
}
|
||||
|
||||
fn expr(&mut self, expr: &hir::Expr, pred: CFGIndex) -> CFGIndex {
|
||||
match expr.kind {
|
||||
hir::ExprKind::Block(ref blk, _) => {
|
||||
let blk_exit = self.block(&blk, pred);
|
||||
self.add_ast_node(expr.hir_id.local_id, &[blk_exit])
|
||||
}
|
||||
|
||||
hir::ExprKind::Loop(ref body, _, _) => {
|
||||
//
|
||||
// [pred]
|
||||
// |
|
||||
// v 1
|
||||
// [loopback] <---+
|
||||
// | 4 |
|
||||
// v 3 |
|
||||
// [body] ------+
|
||||
//
|
||||
// [expr] 2
|
||||
//
|
||||
// Note that `break` and `loop` statements
|
||||
// may cause additional edges.
|
||||
|
||||
let loopback = self.add_dummy_node(&[pred]); // 1
|
||||
let expr_exit = self.add_ast_node(expr.hir_id.local_id, &[]); // 2
|
||||
self.loop_scopes.push(LoopScope {
|
||||
loop_id: expr.hir_id.local_id,
|
||||
continue_index: loopback,
|
||||
break_index: expr_exit,
|
||||
});
|
||||
let body_exit = self.block(&body, loopback); // 3
|
||||
self.add_contained_edge(body_exit, loopback); // 4
|
||||
self.loop_scopes.pop();
|
||||
expr_exit
|
||||
}
|
||||
|
||||
hir::ExprKind::Match(ref discr, ref arms, _) => {
|
||||
self.match_(expr.hir_id.local_id, &discr, &arms, pred)
|
||||
}
|
||||
|
||||
hir::ExprKind::Binary(op, ref l, ref r) if op.node.is_lazy() => {
|
||||
//
|
||||
// [pred]
|
||||
// |
|
||||
// v 1
|
||||
// [l]
|
||||
// |
|
||||
// / \
|
||||
// / \
|
||||
// v 2 *
|
||||
// [r] |
|
||||
// | |
|
||||
// v 3 v 4
|
||||
// [..exit..]
|
||||
//
|
||||
let l_exit = self.expr(&l, pred); // 1
|
||||
let r_exit = self.expr(&r, l_exit); // 2
|
||||
self.add_ast_node(expr.hir_id.local_id, &[l_exit, r_exit]) // 3,4
|
||||
}
|
||||
|
||||
hir::ExprKind::Ret(ref v) => {
|
||||
let v_exit = self.opt_expr(v, pred);
|
||||
let b = self.add_ast_node(expr.hir_id.local_id, &[v_exit]);
|
||||
self.add_returning_edge(expr, b);
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprKind::Break(destination, ref opt_expr) => {
|
||||
let v = self.opt_expr(opt_expr, pred);
|
||||
let (target_scope, break_dest) =
|
||||
self.find_scope_edge(expr, destination, ScopeCfKind::Break);
|
||||
let b = self.add_ast_node(expr.hir_id.local_id, &[v]);
|
||||
self.add_exiting_edge(expr, b, target_scope, break_dest);
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprKind::Continue(destination) => {
|
||||
let (target_scope, cont_dest) =
|
||||
self.find_scope_edge(expr, destination, ScopeCfKind::Continue);
|
||||
let a = self.add_ast_node(expr.hir_id.local_id, &[pred]);
|
||||
self.add_exiting_edge(expr, a, target_scope, cont_dest);
|
||||
self.add_unreachable_node()
|
||||
}
|
||||
|
||||
hir::ExprKind::Array(ref elems) => {
|
||||
self.straightline(expr, pred, elems.iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Call(ref func, ref args) => {
|
||||
self.call(expr, pred, &func, args.iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::MethodCall(.., ref args) => {
|
||||
self.call(expr, pred, &args[0], args[1..].iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Index(ref l, ref r) |
|
||||
hir::ExprKind::Binary(_, ref l, ref r) if self.tables.is_method_call(expr) => {
|
||||
self.call(expr, pred, &l, Some(&**r).into_iter())
|
||||
}
|
||||
|
||||
hir::ExprKind::Unary(_, ref e) if self.tables.is_method_call(expr) => {
|
||||
self.call(expr, pred, &e, None::<hir::Expr>.iter())
|
||||
}
|
||||
|
||||
hir::ExprKind::Tup(ref exprs) => {
|
||||
self.straightline(expr, pred, exprs.iter().map(|e| &*e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Struct(_, ref fields, ref base) => {
|
||||
let field_cfg = self.straightline(expr, pred, fields.iter().map(|f| &*f.expr));
|
||||
self.opt_expr(base, field_cfg)
|
||||
}
|
||||
|
||||
hir::ExprKind::Assign(ref l, ref r) |
|
||||
hir::ExprKind::AssignOp(_, ref l, ref r) => {
|
||||
self.straightline(expr, pred, [r, l].iter().map(|&e| &**e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Index(ref l, ref r) |
|
||||
hir::ExprKind::Binary(_, ref l, ref r) => { // N.B., && and || handled earlier
|
||||
self.straightline(expr, pred, [l, r].iter().map(|&e| &**e))
|
||||
}
|
||||
|
||||
hir::ExprKind::Box(ref e) |
|
||||
hir::ExprKind::AddrOf(_, ref e) |
|
||||
hir::ExprKind::Cast(ref e, _) |
|
||||
hir::ExprKind::Type(ref e, _) |
|
||||
hir::ExprKind::DropTemps(ref e) |
|
||||
hir::ExprKind::Unary(_, ref e) |
|
||||
hir::ExprKind::Field(ref e, _) |
|
||||
hir::ExprKind::Yield(ref e, _) |
|
||||
hir::ExprKind::Repeat(ref e, _) => {
|
||||
self.straightline(expr, pred, Some(&**e).into_iter())
|
||||
}
|
||||
|
||||
hir::ExprKind::InlineAsm(_, ref outputs, ref inputs) => {
|
||||
let post_outputs = self.exprs(outputs.iter().map(|e| &*e), pred);
|
||||
let post_inputs = self.exprs(inputs.iter().map(|e| &*e), post_outputs);
|
||||
self.add_ast_node(expr.hir_id.local_id, &[post_inputs])
|
||||
}
|
||||
|
||||
hir::ExprKind::Closure(..) |
|
||||
hir::ExprKind::Lit(..) |
|
||||
hir::ExprKind::Path(_) |
|
||||
hir::ExprKind::Err => {
|
||||
self.straightline(expr, pred, None::<hir::Expr>.iter())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn call<'b, I: Iterator<Item = &'b hir::Expr>>(
|
||||
&mut self,
|
||||
call_expr: &hir::Expr,
|
||||
pred: CFGIndex,
|
||||
func_or_rcvr: &hir::Expr,
|
||||
args: I,
|
||||
) -> CFGIndex {
|
||||
let func_or_rcvr_exit = self.expr(func_or_rcvr, pred);
|
||||
let ret = self.straightline(call_expr, func_or_rcvr_exit, args);
|
||||
let m = self.tcx.hir().get_module_parent(call_expr.hir_id);
|
||||
if self.tcx.is_ty_uninhabited_from(m, self.tables.expr_ty(call_expr)) {
|
||||
self.add_unreachable_node()
|
||||
} else {
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs graph for `exprs` evaluated in order.
|
||||
fn exprs<'b, I: Iterator<Item = &'b hir::Expr>>(
|
||||
&mut self,
|
||||
exprs: I,
|
||||
pred: CFGIndex,
|
||||
) -> CFGIndex {
|
||||
exprs.fold(pred, |p, e| self.expr(e, p))
|
||||
}
|
||||
|
||||
/// Constructs graph for `opt_expr` evaluated, if `Some`.
|
||||
fn opt_expr(
|
||||
&mut self,
|
||||
opt_expr: &Option<P<hir::Expr>>,
|
||||
pred: CFGIndex,
|
||||
) -> CFGIndex {
|
||||
opt_expr.iter().fold(pred, |p, e| self.expr(&e, p))
|
||||
}
|
||||
|
||||
/// Handles case of an expression that evaluates `subexprs` in order.
|
||||
fn straightline<'b, I: Iterator<Item = &'b hir::Expr>>(
|
||||
&mut self,
|
||||
expr: &hir::Expr,
|
||||
pred: CFGIndex,
|
||||
subexprs: I,
|
||||
) -> CFGIndex {
|
||||
let subexprs_exit = self.exprs(subexprs, pred);
|
||||
self.add_ast_node(expr.hir_id.local_id, &[subexprs_exit])
|
||||
}
|
||||
|
||||
fn match_(&mut self, id: hir::ItemLocalId, discr: &hir::Expr,
|
||||
arms: &[hir::Arm], pred: CFGIndex) -> CFGIndex {
|
||||
// The CFG for match expressions is quite complex, so no ASCII
|
||||
// art for it (yet).
|
||||
//
|
||||
// The CFG generated below matches roughly what MIR contains.
|
||||
// Each pattern and guard is visited in parallel, with
|
||||
// arms containing multiple patterns generating multiple nodes
|
||||
// for the same guard expression. The guard expressions chain
|
||||
// into each other from top to bottom, with a specific
|
||||
// exception to allow some additional valid programs
|
||||
// (explained below). MIR differs slightly in that the
|
||||
// pattern matching may continue after a guard but the visible
|
||||
// behaviour should be the same.
|
||||
//
|
||||
// What is going on is explained in further comments.
|
||||
|
||||
// Visit the discriminant expression.
|
||||
let discr_exit = self.expr(discr, pred);
|
||||
|
||||
// Add a node for the exit of the match expression as a whole.
|
||||
let expr_exit = self.add_ast_node(id, &[]);
|
||||
|
||||
// Keep track of the previous guard expressions.
|
||||
let mut prev_guard = None;
|
||||
let match_scope = region::Scope { id, data: region::ScopeData::Node };
|
||||
|
||||
for arm in arms {
|
||||
// Add an exit node for when we've visited all the
|
||||
// patterns and the guard (if there is one) in the arm.
|
||||
let bindings_exit = self.add_dummy_node(&[]);
|
||||
|
||||
for pat in arm.top_pats_hack() {
|
||||
// Visit the pattern, coming from the discriminant exit
|
||||
let mut pat_exit = self.pat(&pat, discr_exit);
|
||||
|
||||
// If there is a guard expression, handle it here.
|
||||
if let Some(ref guard) = arm.guard {
|
||||
// Add a dummy node for the previous guard
|
||||
// expression to target.
|
||||
let guard_start = self.add_dummy_node(&[pat_exit]);
|
||||
// Visit the guard expression.
|
||||
let guard_exit = match guard {
|
||||
hir::Guard::If(ref e) => (&**e, self.expr(e, guard_start)),
|
||||
};
|
||||
// #47295: We used to have very special case code
|
||||
// here for when a pair of arms are both formed
|
||||
// solely from constants, and if so, not add these
|
||||
// edges. But this was not actually sound without
|
||||
// other constraints that we stopped enforcing at
|
||||
// some point.
|
||||
if let Some((prev_guard, prev_index)) = prev_guard.take() {
|
||||
self.add_exiting_edge(prev_guard, prev_index, match_scope, guard_start);
|
||||
}
|
||||
|
||||
// Push the guard onto the list of previous guards.
|
||||
prev_guard = Some(guard_exit);
|
||||
|
||||
// Update the exit node for the pattern.
|
||||
pat_exit = guard_exit.1;
|
||||
}
|
||||
|
||||
// Add an edge from the exit of this pattern to the exit of the arm.
|
||||
self.add_contained_edge(pat_exit, bindings_exit);
|
||||
}
|
||||
|
||||
// Visit the body of this arm.
|
||||
let body_exit = self.expr(&arm.body, bindings_exit);
|
||||
|
||||
let arm_exit = self.add_ast_node(arm.hir_id.local_id, &[body_exit]);
|
||||
|
||||
// Link the body to the exit of the expression.
|
||||
self.add_contained_edge(arm_exit, expr_exit);
|
||||
}
|
||||
|
||||
expr_exit
|
||||
}
|
||||
|
||||
fn add_dummy_node(&mut self, preds: &[CFGIndex]) -> CFGIndex {
|
||||
self.add_node(CFGNodeData::Dummy, preds)
|
||||
}
|
||||
|
||||
fn add_ast_node(&mut self, id: hir::ItemLocalId, preds: &[CFGIndex]) -> CFGIndex {
|
||||
self.add_node(CFGNodeData::AST(id), preds)
|
||||
}
|
||||
|
||||
fn add_unreachable_node(&mut self) -> CFGIndex {
|
||||
self.add_node(CFGNodeData::Unreachable, &[])
|
||||
}
|
||||
|
||||
fn add_node(&mut self, data: CFGNodeData, preds: &[CFGIndex]) -> CFGIndex {
|
||||
let node = self.graph.add_node(data);
|
||||
for &pred in preds {
|
||||
self.add_contained_edge(pred, node);
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
fn add_contained_edge(
|
||||
&mut self,
|
||||
source: CFGIndex,
|
||||
target: CFGIndex,
|
||||
) {
|
||||
let data = CFGEdgeData {exiting_scopes: vec![] };
|
||||
self.graph.add_edge(source, target, data);
|
||||
}
|
||||
|
||||
fn add_exiting_edge(
|
||||
&mut self,
|
||||
from_expr: &hir::Expr,
|
||||
from_index: CFGIndex,
|
||||
target_scope: region::Scope,
|
||||
to_index: CFGIndex,
|
||||
) {
|
||||
let mut data = CFGEdgeData { exiting_scopes: vec![] };
|
||||
let mut scope = region::Scope {
|
||||
id: from_expr.hir_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
let region_scope_tree = self.tcx.region_scope_tree(self.owner_def_id);
|
||||
while scope != target_scope {
|
||||
data.exiting_scopes.push(scope.item_local_id());
|
||||
scope = region_scope_tree.encl_scope(scope);
|
||||
}
|
||||
self.graph.add_edge(from_index, to_index, data);
|
||||
}
|
||||
|
||||
fn add_returning_edge(
|
||||
&mut self,
|
||||
_from_expr: &hir::Expr,
|
||||
from_index: CFGIndex,
|
||||
) {
|
||||
let data = CFGEdgeData {
|
||||
exiting_scopes: self.loop_scopes.iter()
|
||||
.rev()
|
||||
.map(|&LoopScope { loop_id: id, .. }| id)
|
||||
.collect()
|
||||
};
|
||||
self.graph.add_edge(from_index, self.fn_exit, data);
|
||||
}
|
||||
|
||||
fn find_scope_edge(
|
||||
&self,
|
||||
expr: &hir::Expr,
|
||||
destination: hir::Destination,
|
||||
scope_cf_kind: ScopeCfKind,
|
||||
) -> (region::Scope, CFGIndex) {
|
||||
match destination.target_id {
|
||||
Ok(loop_id) => {
|
||||
for b in &self.breakable_block_scopes {
|
||||
if b.block_expr_id == loop_id.local_id {
|
||||
let scope = region::Scope {
|
||||
id: loop_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
return (scope, match scope_cf_kind {
|
||||
ScopeCfKind::Break => b.break_index,
|
||||
ScopeCfKind::Continue => bug!("can't continue to block"),
|
||||
});
|
||||
}
|
||||
}
|
||||
for l in &self.loop_scopes {
|
||||
if l.loop_id == loop_id.local_id {
|
||||
let scope = region::Scope {
|
||||
id: loop_id.local_id,
|
||||
data: region::ScopeData::Node
|
||||
};
|
||||
return (scope, match scope_cf_kind {
|
||||
ScopeCfKind::Break => l.break_index,
|
||||
ScopeCfKind::Continue => l.continue_index,
|
||||
});
|
||||
}
|
||||
}
|
||||
span_bug!(expr.span, "no scope for ID {}", loop_id);
|
||||
}
|
||||
Err(err) => span_bug!(expr.span, "scope error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
enum ScopeCfKind {
|
||||
Break,
|
||||
Continue,
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/// This module provides linkage between `rustc::middle::graph` and
|
||||
/// libgraphviz traits.
|
||||
|
||||
use crate::cfg;
|
||||
use rustc::hir;
|
||||
use rustc::ty::TyCtxt;
|
||||
|
||||
pub(crate) type Node<'a> = (cfg::CFGIndex, &'a cfg::CFGNode);
|
||||
pub(crate) type Edge<'a> = &'a cfg::CFGEdge;
|
||||
|
||||
pub struct LabelledCFG<'a, 'tcx> {
|
||||
pub tcx: TyCtxt<'tcx>,
|
||||
pub cfg: &'a cfg::CFG,
|
||||
pub name: String,
|
||||
/// `labelled_edges` controls whether we emit labels on the edges.
|
||||
pub labelled_edges: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LabelledCFG<'a, 'tcx> {
|
||||
fn local_id_to_string(&self, local_id: hir::ItemLocalId) -> String {
|
||||
assert!(self.cfg.owner_def_id.is_local());
|
||||
let hir_id = hir::HirId {
|
||||
owner: self.tcx.hir().def_index_to_hir_id(self.cfg.owner_def_id.index).owner,
|
||||
local_id
|
||||
};
|
||||
let s = self.tcx.hir().node_to_string(hir_id);
|
||||
|
||||
// Replacing newlines with `\\l` causes each line to be left-aligned,
|
||||
// improving presentation of (long) pretty-printed expressions.
|
||||
if s.contains("\n") {
|
||||
let mut s = s.replace("\n", "\\l");
|
||||
// Apparently left-alignment applies to the line that precedes
|
||||
// `\l`, not the line that follows; so, add `\l` at end of string
|
||||
// if not already present, ensuring last line gets left-aligned
|
||||
// as well.
|
||||
let mut last_two: Vec<_> =
|
||||
s.chars().rev().take(2).collect();
|
||||
last_two.reverse();
|
||||
if last_two != ['\\', 'l'] {
|
||||
s.push_str("\\l");
|
||||
}
|
||||
s
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'hir> dot::Labeller<'a> for LabelledCFG<'a, 'hir> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new(&self.name[..]).unwrap() }
|
||||
|
||||
fn node_id(&'a self, &(i,_): &Node<'a>) -> dot::Id<'a> {
|
||||
dot::Id::new(format!("N{}", i.node_id())).unwrap()
|
||||
}
|
||||
|
||||
fn node_label(&'a self, &(i, n): &Node<'a>) -> dot::LabelText<'a> {
|
||||
if i == self.cfg.entry {
|
||||
dot::LabelText::LabelStr("entry".into())
|
||||
} else if i == self.cfg.exit {
|
||||
dot::LabelText::LabelStr("exit".into())
|
||||
} else if n.data.id() == hir::DUMMY_ITEM_LOCAL_ID {
|
||||
dot::LabelText::LabelStr("(dummy_node)".into())
|
||||
} else {
|
||||
let s = self.local_id_to_string(n.data.id());
|
||||
dot::LabelText::EscStr(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
fn edge_label(&self, e: &Edge<'a>) -> dot::LabelText<'a> {
|
||||
let mut label = String::new();
|
||||
if !self.labelled_edges {
|
||||
return dot::LabelText::EscStr(label.into());
|
||||
}
|
||||
let mut put_one = false;
|
||||
for (i, &id) in e.data.exiting_scopes.iter().enumerate() {
|
||||
if put_one {
|
||||
label.push_str(",\\l");
|
||||
} else {
|
||||
put_one = true;
|
||||
}
|
||||
let s = self.local_id_to_string(id);
|
||||
label.push_str(&format!("exiting scope_{} {}",
|
||||
i,
|
||||
&s[..]));
|
||||
}
|
||||
dot::LabelText::EscStr(label.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> dot::GraphWalk<'a> for &'a cfg::CFG {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> {
|
||||
let v: Vec<_> = self.graph.enumerated_nodes().collect();
|
||||
v.into()
|
||||
}
|
||||
fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> {
|
||||
self.graph.all_edges().iter().collect()
|
||||
}
|
||||
fn source(&'a self, edge: &Edge<'a>) -> Node<'a> {
|
||||
let i = edge.source();
|
||||
(i, self.graph.node(i))
|
||||
}
|
||||
fn target(&'a self, edge: &Edge<'a>) -> Node<'a> {
|
||||
let i = edge.target();
|
||||
(i, self.graph.node(i))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'hir> dot::GraphWalk<'a> for LabelledCFG<'a, 'hir> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { self.cfg.nodes() }
|
||||
fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { self.cfg.edges() }
|
||||
fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { self.cfg.source(edge) }
|
||||
fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { self.cfg.target(edge) }
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
//! Module that constructs a control-flow graph representing an item.
|
||||
//! Uses `Graph` as the underlying representation.
|
||||
|
||||
use rustc_data_structures::graph::implementation as graph;
|
||||
use rustc::ty::TyCtxt;
|
||||
use rustc::hir;
|
||||
use rustc::hir::def_id::DefId;
|
||||
|
||||
mod construct;
|
||||
pub mod graphviz;
|
||||
|
||||
pub struct CFG {
|
||||
owner_def_id: DefId,
|
||||
pub(crate) graph: CFGGraph,
|
||||
pub(crate) entry: CFGIndex,
|
||||
exit: CFGIndex,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum CFGNodeData {
|
||||
AST(hir::ItemLocalId),
|
||||
Entry,
|
||||
Exit,
|
||||
Dummy,
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl CFGNodeData {
|
||||
pub(crate) fn id(&self) -> hir::ItemLocalId {
|
||||
if let CFGNodeData::AST(id) = *self {
|
||||
id
|
||||
} else {
|
||||
hir::DUMMY_ITEM_LOCAL_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CFGEdgeData {
|
||||
pub(crate) exiting_scopes: Vec<hir::ItemLocalId>
|
||||
}
|
||||
|
||||
pub(crate) type CFGIndex = graph::NodeIndex;
|
||||
|
||||
pub(crate) type CFGGraph = graph::Graph<CFGNodeData, CFGEdgeData>;
|
||||
|
||||
pub(crate) type CFGNode = graph::Node<CFGNodeData>;
|
||||
|
||||
pub(crate) type CFGEdge = graph::Edge<CFGEdgeData>;
|
||||
|
||||
impl CFG {
|
||||
pub fn new(tcx: TyCtxt<'_>, body: &hir::Body) -> CFG {
|
||||
construct::construct(tcx, body)
|
||||
}
|
||||
}
|
||||
@@ -1,672 +0,0 @@
|
||||
//! A module for propagating forward dataflow information. The analysis
|
||||
//! assumes that the items to be propagated can be represented as bits
|
||||
//! and thus uses bitvectors. Your job is simply to specify the so-called
|
||||
//! GEN and KILL bits for each expression.
|
||||
|
||||
use crate::cfg::{self, CFGIndex};
|
||||
use std::mem;
|
||||
use std::usize;
|
||||
use log::debug;
|
||||
|
||||
use rustc_data_structures::graph::implementation::OUTGOING;
|
||||
|
||||
use rustc::util::nodemap::FxHashMap;
|
||||
use rustc::hir;
|
||||
use rustc::hir::intravisit;
|
||||
use rustc::hir::print as pprust;
|
||||
use rustc::ty::TyCtxt;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EntryOrExit {
|
||||
Entry,
|
||||
Exit,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DataFlowContext<'tcx, O> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
|
||||
/// a name for the analysis using this dataflow instance
|
||||
analysis_name: &'static str,
|
||||
|
||||
/// the data flow operator
|
||||
oper: O,
|
||||
|
||||
/// number of bits to propagate per id
|
||||
bits_per_id: usize,
|
||||
|
||||
/// number of words we will use to store bits_per_id.
|
||||
/// equal to bits_per_id/usize::BITS rounded up.
|
||||
words_per_id: usize,
|
||||
|
||||
// mapping from node to cfg node index
|
||||
// FIXME (#6298): Shouldn't this go with CFG?
|
||||
local_id_to_index: FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>,
|
||||
|
||||
// Bit sets per cfg node. The following three fields (`gens`, `kills`,
|
||||
// and `on_entry`) all have the same structure. For each id in
|
||||
// `id_range`, there is a range of words equal to `words_per_id`.
|
||||
// So, to access the bits for any given id, you take a slice of
|
||||
// the full vector (see the method `compute_id_range()`).
|
||||
/// bits generated as we exit the cfg node. Updated by `add_gen()`.
|
||||
gens: Vec<usize>,
|
||||
|
||||
/// bits killed as we exit the cfg node, or non-locally jump over
|
||||
/// it. Updated by `add_kill(KillFrom::ScopeEnd)`.
|
||||
scope_kills: Vec<usize>,
|
||||
|
||||
/// bits killed as we exit the cfg node directly; if it is jumped
|
||||
/// over, e.g., via `break`, the kills are not reflected in the
|
||||
/// jump's effects. Updated by `add_kill(KillFrom::Execution)`.
|
||||
action_kills: Vec<usize>,
|
||||
|
||||
/// bits that are valid on entry to the cfg node. Updated by
|
||||
/// `propagate()`.
|
||||
on_entry: Vec<usize>,
|
||||
}
|
||||
|
||||
pub trait BitwiseOperator {
|
||||
/// Joins two predecessor bits together, typically either `|` or `&`
|
||||
fn join(&self, succ: usize, pred: usize) -> usize;
|
||||
}
|
||||
|
||||
/// Parameterization for the precise form of data flow that is used.
|
||||
pub trait DataFlowOperator : BitwiseOperator {
|
||||
/// Specifies the initial value for each bit in the `on_entry` set
|
||||
fn initial_value(&self) -> bool;
|
||||
}
|
||||
|
||||
struct PropagationContext<'a, 'tcx, O> {
|
||||
dfcx: &'a mut DataFlowContext<'tcx, O>,
|
||||
changed: bool,
|
||||
}
|
||||
|
||||
fn get_cfg_indices(id: hir::ItemLocalId,
|
||||
index: &FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>)
|
||||
-> &[CFGIndex] {
|
||||
index.get(&id).map_or(&[], |v| &v[..])
|
||||
}
|
||||
|
||||
impl<'tcx, O: DataFlowOperator> DataFlowContext<'tcx, O> {
|
||||
fn has_bitset_for_local_id(&self, n: hir::ItemLocalId) -> bool {
|
||||
assert!(n != hir::DUMMY_ITEM_LOCAL_ID);
|
||||
self.local_id_to_index.contains_key(&n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, O: DataFlowOperator> pprust::PpAnn for DataFlowContext<'tcx, O> {
|
||||
fn nested(&self, state: &mut pprust::State<'_>, nested: pprust::Nested) {
|
||||
pprust::PpAnn::nested(self.tcx.hir(), state, nested)
|
||||
}
|
||||
fn pre(&self,
|
||||
ps: &mut pprust::State<'_>,
|
||||
node: pprust::AnnNode<'_>) {
|
||||
let id = match node {
|
||||
pprust::AnnNode::Name(_) => return,
|
||||
pprust::AnnNode::Expr(expr) => expr.hir_id.local_id,
|
||||
pprust::AnnNode::Block(blk) => blk.hir_id.local_id,
|
||||
pprust::AnnNode::Item(_) |
|
||||
pprust::AnnNode::SubItem(_) => return,
|
||||
pprust::AnnNode::Pat(pat) => pat.hir_id.local_id,
|
||||
pprust::AnnNode::Arm(arm) => arm.hir_id.local_id,
|
||||
};
|
||||
|
||||
if !self.has_bitset_for_local_id(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert!(self.bits_per_id > 0);
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let on_entry = &self.on_entry[start.. end];
|
||||
let entry_str = bits_to_string(on_entry);
|
||||
|
||||
let gens = &self.gens[start.. end];
|
||||
let gens_str = if gens.iter().any(|&u| u != 0) {
|
||||
format!(" gen: {}", bits_to_string(gens))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let action_kills = &self.action_kills[start .. end];
|
||||
let action_kills_str = if action_kills.iter().any(|&u| u != 0) {
|
||||
format!(" action_kill: {}", bits_to_string(action_kills))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let scope_kills = &self.scope_kills[start .. end];
|
||||
let scope_kills_str = if scope_kills.iter().any(|&u| u != 0) {
|
||||
format!(" scope_kill: {}", bits_to_string(scope_kills))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
ps.synth_comment(
|
||||
format!("id {}: {}{}{}{}", id.as_usize(), entry_str,
|
||||
gens_str, action_kills_str, scope_kills_str));
|
||||
ps.s.space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_local_id_to_index(body: Option<&hir::Body>,
|
||||
cfg: &cfg::CFG)
|
||||
-> FxHashMap<hir::ItemLocalId, Vec<CFGIndex>> {
|
||||
let mut index = FxHashMap::default();
|
||||
|
||||
// FIXME(#15020) Would it be better to fold formals from decl
|
||||
// into cfg itself? i.e., introduce a fn-based flow-graph in
|
||||
// addition to the current block-based flow-graph, rather than
|
||||
// have to put traversals like this here?
|
||||
if let Some(body) = body {
|
||||
add_entries_from_fn_body(&mut index, body, cfg.entry);
|
||||
}
|
||||
|
||||
cfg.graph.each_node(|node_idx, node| {
|
||||
if let cfg::CFGNodeData::AST(id) = node.data {
|
||||
index.entry(id).or_default().push(node_idx);
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
return index;
|
||||
|
||||
/// Adds mappings from the ast nodes for the formal bindings to
|
||||
/// the entry-node in the graph.
|
||||
fn add_entries_from_fn_body(index: &mut FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>,
|
||||
body: &hir::Body,
|
||||
entry: CFGIndex) {
|
||||
use rustc::hir::intravisit::Visitor;
|
||||
|
||||
struct Formals<'a> {
|
||||
entry: CFGIndex,
|
||||
index: &'a mut FxHashMap<hir::ItemLocalId, Vec<CFGIndex>>,
|
||||
}
|
||||
let mut formals = Formals { entry: entry, index: index };
|
||||
for param in &body.params {
|
||||
formals.visit_pat(¶m.pat);
|
||||
}
|
||||
impl<'a, 'v> Visitor<'v> for Formals<'a> {
|
||||
fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'v> {
|
||||
intravisit::NestedVisitorMap::None
|
||||
}
|
||||
|
||||
fn visit_pat(&mut self, p: &hir::Pat) {
|
||||
self.index.entry(p.hir_id.local_id).or_default().push(self.entry);
|
||||
intravisit::walk_pat(self, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Flag used by `add_kill` to indicate whether the provided kill
|
||||
/// takes effect only when control flows directly through the node in
|
||||
/// question, or if the kill's effect is associated with any
|
||||
/// control-flow directly through or indirectly over the node.
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum KillFrom {
|
||||
/// A `ScopeEnd` kill is one that takes effect when any control
|
||||
/// flow goes over the node. A kill associated with the end of the
|
||||
/// scope of a variable declaration `let x;` is an example of a
|
||||
/// `ScopeEnd` kill.
|
||||
ScopeEnd,
|
||||
|
||||
/// An `Execution` kill is one that takes effect only when control
|
||||
/// flow goes through the node to completion. A kill associated
|
||||
/// with an assignment statement `x = expr;` is an example of an
|
||||
/// `Execution` kill.
|
||||
Execution,
|
||||
}
|
||||
|
||||
impl<'tcx, O: DataFlowOperator> DataFlowContext<'tcx, O> {
|
||||
pub fn new(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
analysis_name: &'static str,
|
||||
body: Option<&hir::Body>,
|
||||
cfg: &cfg::CFG,
|
||||
oper: O,
|
||||
bits_per_id: usize,
|
||||
) -> DataFlowContext<'tcx, O> {
|
||||
let usize_bits = mem::size_of::<usize>() * 8;
|
||||
let words_per_id = (bits_per_id + usize_bits - 1) / usize_bits;
|
||||
let num_nodes = cfg.graph.all_nodes().len();
|
||||
|
||||
debug!("DataFlowContext::new(analysis_name: {}, \
|
||||
bits_per_id={}, words_per_id={}) \
|
||||
num_nodes: {}",
|
||||
analysis_name, bits_per_id, words_per_id,
|
||||
num_nodes);
|
||||
|
||||
let entry = if oper.initial_value() { usize::MAX } else {0};
|
||||
|
||||
let zeroes = vec![0; num_nodes * words_per_id];
|
||||
let gens = zeroes.clone();
|
||||
let kills1 = zeroes.clone();
|
||||
let kills2 = zeroes;
|
||||
let on_entry = vec![entry; num_nodes * words_per_id];
|
||||
|
||||
let local_id_to_index = build_local_id_to_index(body, cfg);
|
||||
|
||||
DataFlowContext {
|
||||
tcx,
|
||||
analysis_name,
|
||||
words_per_id,
|
||||
local_id_to_index,
|
||||
bits_per_id,
|
||||
oper,
|
||||
gens,
|
||||
action_kills: kills1,
|
||||
scope_kills: kills2,
|
||||
on_entry,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_gen(&mut self, id: hir::ItemLocalId, bit: usize) {
|
||||
//! Indicates that `id` generates `bit`
|
||||
debug!("{} add_gen(id={:?}, bit={})",
|
||||
self.analysis_name, id, bit);
|
||||
assert!(self.local_id_to_index.contains_key(&id));
|
||||
assert!(self.bits_per_id > 0);
|
||||
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let gens = &mut self.gens[start.. end];
|
||||
set_bit(gens, bit);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_kill(&mut self, kind: KillFrom, id: hir::ItemLocalId, bit: usize) {
|
||||
//! Indicates that `id` kills `bit`
|
||||
debug!("{} add_kill(id={:?}, bit={})",
|
||||
self.analysis_name, id, bit);
|
||||
assert!(self.local_id_to_index.contains_key(&id));
|
||||
assert!(self.bits_per_id > 0);
|
||||
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let kills = match kind {
|
||||
KillFrom::Execution => &mut self.action_kills[start.. end],
|
||||
KillFrom::ScopeEnd => &mut self.scope_kills[start.. end],
|
||||
};
|
||||
set_bit(kills, bit);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_gen_kill(&self, cfgidx: CFGIndex, bits: &mut [usize]) {
|
||||
//! Applies the gen and kill sets for `cfgidx` to `bits`
|
||||
debug!("{} apply_gen_kill(cfgidx={:?}, bits={}) [before]",
|
||||
self.analysis_name, cfgidx, mut_bits_to_string(bits));
|
||||
assert!(self.bits_per_id > 0);
|
||||
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let gens = &self.gens[start.. end];
|
||||
bitwise(bits, gens, &Union);
|
||||
let kills = &self.action_kills[start.. end];
|
||||
bitwise(bits, kills, &Subtract);
|
||||
let kills = &self.scope_kills[start.. end];
|
||||
bitwise(bits, kills, &Subtract);
|
||||
|
||||
debug!("{} apply_gen_kill(cfgidx={:?}, bits={}) [after]",
|
||||
self.analysis_name, cfgidx, mut_bits_to_string(bits));
|
||||
}
|
||||
|
||||
fn compute_id_range(&self, cfgidx: CFGIndex) -> (usize, usize) {
|
||||
let n = cfgidx.node_id();
|
||||
let start = n * self.words_per_id;
|
||||
let end = start + self.words_per_id;
|
||||
|
||||
assert!(start < self.gens.len());
|
||||
assert!(end <= self.gens.len());
|
||||
assert!(self.gens.len() == self.action_kills.len());
|
||||
assert!(self.gens.len() == self.scope_kills.len());
|
||||
assert!(self.gens.len() == self.on_entry.len());
|
||||
|
||||
(start, end)
|
||||
}
|
||||
|
||||
|
||||
pub fn each_bit_on_entry<F>(&self, id: hir::ItemLocalId, mut f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Iterates through each bit that is set on entry to `id`.
|
||||
//! Only useful after `propagate()` has been called.
|
||||
if !self.has_bitset_for_local_id(id) {
|
||||
return true;
|
||||
}
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
if !self.each_bit_for_node(EntryOrExit::Entry, cfgidx, |i| f(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn each_bit_for_node<F>(&self, e: EntryOrExit, cfgidx: CFGIndex, f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Iterates through each bit that is set on entry/exit to `cfgidx`.
|
||||
//! Only useful after `propagate()` has been called.
|
||||
|
||||
if self.bits_per_id == 0 {
|
||||
// Skip the surprisingly common degenerate case. (Note
|
||||
// compute_id_range requires self.words_per_id > 0.)
|
||||
return true;
|
||||
}
|
||||
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let on_entry = &self.on_entry[start.. end];
|
||||
let temp_bits;
|
||||
let slice = match e {
|
||||
EntryOrExit::Entry => on_entry,
|
||||
EntryOrExit::Exit => {
|
||||
let mut t = on_entry.to_vec();
|
||||
self.apply_gen_kill(cfgidx, &mut t);
|
||||
temp_bits = t;
|
||||
&temp_bits[..]
|
||||
}
|
||||
};
|
||||
debug!("{} each_bit_for_node({:?}, cfgidx={:?}) bits={}",
|
||||
self.analysis_name, e, cfgidx, bits_to_string(slice));
|
||||
self.each_bit(slice, f)
|
||||
}
|
||||
|
||||
pub fn each_gen_bit<F>(&self, id: hir::ItemLocalId, mut f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Iterates through each bit in the gen set for `id`.
|
||||
if !self.has_bitset_for_local_id(id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.bits_per_id == 0 {
|
||||
// Skip the surprisingly common degenerate case. (Note
|
||||
// compute_id_range requires self.words_per_id > 0.)
|
||||
return true;
|
||||
}
|
||||
|
||||
let indices = get_cfg_indices(id, &self.local_id_to_index);
|
||||
for &cfgidx in indices {
|
||||
let (start, end) = self.compute_id_range(cfgidx);
|
||||
let gens = &self.gens[start.. end];
|
||||
debug!("{} each_gen_bit(id={:?}, gens={})",
|
||||
self.analysis_name, id, bits_to_string(gens));
|
||||
if !self.each_bit(gens, |i| f(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
fn each_bit<F>(&self, words: &[usize], mut f: F) -> bool where
|
||||
F: FnMut(usize) -> bool,
|
||||
{
|
||||
//! Helper for iterating over the bits in a bit set.
|
||||
//! Returns false on the first call to `f` that returns false;
|
||||
//! if all calls to `f` return true, then returns true.
|
||||
|
||||
let usize_bits = mem::size_of::<usize>() * 8;
|
||||
for (word_index, &word) in words.iter().enumerate() {
|
||||
if word != 0 {
|
||||
let base_index = word_index * usize_bits;
|
||||
for offset in 0..usize_bits {
|
||||
let bit = 1 << offset;
|
||||
if (word & bit) != 0 {
|
||||
// N.B., we round up the total number of bits
|
||||
// that we store in any given bit set so that
|
||||
// it is an even multiple of usize::BITS. This
|
||||
// means that there may be some stray bits at
|
||||
// the end that do not correspond to any
|
||||
// actual value. So before we callback, check
|
||||
// whether the bit_index is greater than the
|
||||
// actual value the user specified and stop
|
||||
// iterating if so.
|
||||
let bit_index = base_index + offset as usize;
|
||||
if bit_index >= self.bits_per_id {
|
||||
return true;
|
||||
} else if !f(bit_index) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn add_kills_from_flow_exits(&mut self, cfg: &cfg::CFG) {
|
||||
//! Whenever you have a `break` or `continue` statement, flow
|
||||
//! exits through any number of enclosing scopes on its way to
|
||||
//! the new destination. This function infers the kill bits of
|
||||
//! those control operators based on the kill bits associated
|
||||
//! with those scopes.
|
||||
//!
|
||||
//! This is usually called (if it is called at all), after
|
||||
//! all add_gen and add_kill calls, but before propagate.
|
||||
|
||||
debug!("{} add_kills_from_flow_exits", self.analysis_name);
|
||||
if self.bits_per_id == 0 {
|
||||
// Skip the surprisingly common degenerate case. (Note
|
||||
// compute_id_range requires self.words_per_id > 0.)
|
||||
return;
|
||||
}
|
||||
cfg.graph.each_edge(|_edge_index, edge| {
|
||||
let flow_exit = edge.source();
|
||||
let (start, end) = self.compute_id_range(flow_exit);
|
||||
let mut orig_kills = self.scope_kills[start.. end].to_vec();
|
||||
|
||||
let mut changed = false;
|
||||
for &id in &edge.data.exiting_scopes {
|
||||
let opt_cfg_idx = self.local_id_to_index.get(&id);
|
||||
match opt_cfg_idx {
|
||||
Some(indices) => {
|
||||
for &cfg_idx in indices {
|
||||
let (start, end) = self.compute_id_range(cfg_idx);
|
||||
let kills = &self.scope_kills[start.. end];
|
||||
if bitwise(&mut orig_kills, kills, &Union) {
|
||||
debug!("scope exits: scope id={:?} \
|
||||
(node={:?} of {:?}) added killset: {}",
|
||||
id, cfg_idx, indices,
|
||||
bits_to_string(kills));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("{} add_kills_from_flow_exits flow_exit={:?} \
|
||||
no cfg_idx for exiting_scope={:?}",
|
||||
self.analysis_name, flow_exit, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
let bits = &mut self.scope_kills[start.. end];
|
||||
debug!("{} add_kills_from_flow_exits flow_exit={:?} bits={} [before]",
|
||||
self.analysis_name, flow_exit, mut_bits_to_string(bits));
|
||||
bits.copy_from_slice(&orig_kills[..]);
|
||||
debug!("{} add_kills_from_flow_exits flow_exit={:?} bits={} [after]",
|
||||
self.analysis_name, flow_exit, mut_bits_to_string(bits));
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// N.B. `Clone + 'static` only needed for pretty printing.
|
||||
impl<'tcx, O: DataFlowOperator + Clone + 'static> DataFlowContext<'tcx, O> {
|
||||
pub fn propagate(&mut self, cfg: &cfg::CFG, body: &hir::Body) {
|
||||
//! Performs the data flow analysis.
|
||||
|
||||
if self.bits_per_id == 0 {
|
||||
// Optimize the surprisingly common degenerate case.
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let words_per_id = self.words_per_id;
|
||||
let mut propcx = PropagationContext {
|
||||
dfcx: &mut *self,
|
||||
changed: true
|
||||
};
|
||||
|
||||
let nodes_po = cfg.graph.nodes_in_postorder(OUTGOING, cfg.entry);
|
||||
let mut temp = vec![0; words_per_id];
|
||||
let mut num_passes = 0;
|
||||
while propcx.changed {
|
||||
num_passes += 1;
|
||||
propcx.changed = false;
|
||||
propcx.reset(&mut temp);
|
||||
propcx.walk_cfg(cfg, &nodes_po, &mut temp);
|
||||
}
|
||||
debug!("finished in {} iterations", num_passes);
|
||||
}
|
||||
|
||||
debug!("Dataflow result for {}:", self.analysis_name);
|
||||
debug!("{}", pprust::to_string(self, |s| {
|
||||
s.cbox(pprust::INDENT_UNIT);
|
||||
s.ibox(0);
|
||||
s.print_expr(&body.value)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: DataFlowOperator> PropagationContext<'_, 'tcx, O> {
|
||||
fn walk_cfg(&mut self,
|
||||
cfg: &cfg::CFG,
|
||||
nodes_po: &[CFGIndex],
|
||||
in_out: &mut [usize]) {
|
||||
debug!("DataFlowContext::walk_cfg(in_out={}) {}",
|
||||
bits_to_string(in_out), self.dfcx.analysis_name);
|
||||
assert!(self.dfcx.bits_per_id > 0);
|
||||
|
||||
// Iterate over nodes in reverse post-order.
|
||||
for &node_index in nodes_po.iter().rev() {
|
||||
let node = cfg.graph.node(node_index);
|
||||
debug!("DataFlowContext::walk_cfg idx={:?} id={:?} begin in_out={}",
|
||||
node_index, node.data.id(), bits_to_string(in_out));
|
||||
|
||||
let (start, end) = self.dfcx.compute_id_range(node_index);
|
||||
|
||||
// Initialize local bitvector with state on-entry.
|
||||
in_out.copy_from_slice(&self.dfcx.on_entry[start.. end]);
|
||||
|
||||
// Compute state on-exit by applying transfer function to
|
||||
// state on-entry.
|
||||
self.dfcx.apply_gen_kill(node_index, in_out);
|
||||
|
||||
// Propagate state on-exit from node into its successors.
|
||||
self.propagate_bits_into_graph_successors_of(in_out, cfg, node_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self, bits: &mut [usize]) {
|
||||
let e = if self.dfcx.oper.initial_value() {usize::MAX} else {0};
|
||||
for b in bits {
|
||||
*b = e;
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bits_into_graph_successors_of(&mut self,
|
||||
pred_bits: &[usize],
|
||||
cfg: &cfg::CFG,
|
||||
cfgidx: CFGIndex) {
|
||||
for (_, edge) in cfg.graph.outgoing_edges(cfgidx) {
|
||||
self.propagate_bits_into_entry_set_for(pred_bits, edge);
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bits_into_entry_set_for(&mut self,
|
||||
pred_bits: &[usize],
|
||||
edge: &cfg::CFGEdge) {
|
||||
let source = edge.source();
|
||||
let cfgidx = edge.target();
|
||||
debug!("{} propagate_bits_into_entry_set_for(pred_bits={}, {:?} to {:?})",
|
||||
self.dfcx.analysis_name, bits_to_string(pred_bits), source, cfgidx);
|
||||
assert!(self.dfcx.bits_per_id > 0);
|
||||
|
||||
let (start, end) = self.dfcx.compute_id_range(cfgidx);
|
||||
let changed = {
|
||||
// (scoping mutable borrow of self.dfcx.on_entry)
|
||||
let on_entry = &mut self.dfcx.on_entry[start.. end];
|
||||
bitwise(on_entry, pred_bits, &self.dfcx.oper)
|
||||
};
|
||||
if changed {
|
||||
debug!("{} changed entry set for {:?} to {}",
|
||||
self.dfcx.analysis_name, cfgidx,
|
||||
bits_to_string(&self.dfcx.on_entry[start.. end]));
|
||||
self.changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mut_bits_to_string(words: &mut [usize]) -> String {
|
||||
bits_to_string(words)
|
||||
}
|
||||
|
||||
fn bits_to_string(words: &[usize]) -> String {
|
||||
let mut result = String::new();
|
||||
let mut sep = '[';
|
||||
|
||||
// Note: this is a little endian printout of bytes.
|
||||
|
||||
for &word in words {
|
||||
let mut v = word;
|
||||
for _ in 0..mem::size_of::<usize>() {
|
||||
result.push(sep);
|
||||
result.push_str(&format!("{:02x}", v & 0xFF));
|
||||
v >>= 8;
|
||||
sep = '-';
|
||||
}
|
||||
}
|
||||
result.push(']');
|
||||
return result
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bitwise<Op: BitwiseOperator>(out_vec: &mut [usize],
|
||||
in_vec: &[usize],
|
||||
op: &Op) -> bool {
|
||||
assert_eq!(out_vec.len(), in_vec.len());
|
||||
let mut changed = false;
|
||||
for (out_elt, in_elt) in out_vec.iter_mut().zip(in_vec) {
|
||||
let old_val = *out_elt;
|
||||
let new_val = op.join(old_val, *in_elt);
|
||||
*out_elt = new_val;
|
||||
changed |= old_val != new_val;
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn set_bit(words: &mut [usize], bit: usize) -> bool {
|
||||
debug!("set_bit: words={} bit={}",
|
||||
mut_bits_to_string(words), bit_str(bit));
|
||||
let usize_bits = mem::size_of::<usize>() * 8;
|
||||
let word = bit / usize_bits;
|
||||
let bit_in_word = bit % usize_bits;
|
||||
let bit_mask = 1 << bit_in_word;
|
||||
debug!("word={} bit_in_word={} bit_mask={}", word, bit_in_word, bit_mask);
|
||||
let oldv = words[word];
|
||||
let newv = oldv | bit_mask;
|
||||
words[word] = newv;
|
||||
oldv != newv
|
||||
}
|
||||
|
||||
fn bit_str(bit: usize) -> String {
|
||||
let byte = bit >> 3;
|
||||
let lobits = 1 << (bit & 0b111);
|
||||
format!("[{}:{}-{:02x}]", bit, byte, lobits)
|
||||
}
|
||||
|
||||
struct Union;
|
||||
impl BitwiseOperator for Union {
|
||||
fn join(&self, a: usize, b: usize) -> usize { a | b }
|
||||
}
|
||||
struct Subtract;
|
||||
impl BitwiseOperator for Subtract {
|
||||
fn join(&self, a: usize, b: usize) -> usize { a & !b }
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
//! This module provides linkage between rustc::middle::graph and
|
||||
//! libgraphviz traits, specialized to attaching borrowck analysis
|
||||
//! data to rendered labels.
|
||||
|
||||
pub use Variant::*;
|
||||
|
||||
pub(crate) use crate::cfg::graphviz::{Node, Edge};
|
||||
use crate::cfg::graphviz as cfg_dot;
|
||||
use crate::cfg::CFGIndex;
|
||||
use crate::borrowck::{self, BorrowckCtxt, LoanPath};
|
||||
use crate::dataflow::{DataFlowOperator, DataFlowContext, EntryOrExit};
|
||||
use log::debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Variant {
|
||||
Loans,
|
||||
Moves,
|
||||
Assigns,
|
||||
}
|
||||
|
||||
impl Variant {
|
||||
pub fn short_name(&self) -> &'static str {
|
||||
match *self {
|
||||
Loans => "loans",
|
||||
Moves => "moves",
|
||||
Assigns => "assigns",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataflowLabeller<'a, 'tcx> {
|
||||
pub inner: cfg_dot::LabelledCFG<'a, 'tcx>,
|
||||
pub variants: Vec<Variant>,
|
||||
pub borrowck_ctxt: &'a BorrowckCtxt<'a, 'tcx>,
|
||||
pub analysis_data: &'a borrowck::AnalysisData<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> DataflowLabeller<'a, 'tcx> {
|
||||
fn dataflow_for(&self, e: EntryOrExit, n: &Node<'a>) -> String {
|
||||
let id = n.1.data.id();
|
||||
debug!("dataflow_for({:?}, id={:?}) {:?}", e, id, self.variants);
|
||||
let mut sets = String::new();
|
||||
let mut seen_one = false;
|
||||
for &variant in &self.variants {
|
||||
if seen_one { sets.push_str(" "); } else { seen_one = true; }
|
||||
sets.push_str(variant.short_name());
|
||||
sets.push_str(": ");
|
||||
sets.push_str(&self.dataflow_for_variant(e, n, variant));
|
||||
}
|
||||
sets
|
||||
}
|
||||
|
||||
fn dataflow_for_variant(&self, e: EntryOrExit, n: &Node<'_>, v: Variant) -> String {
|
||||
let cfgidx = n.0;
|
||||
match v {
|
||||
Loans => self.dataflow_loans_for(e, cfgidx),
|
||||
Moves => self.dataflow_moves_for(e, cfgidx),
|
||||
Assigns => self.dataflow_assigns_for(e, cfgidx),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_set<O: DataFlowOperator, F>(
|
||||
&self,
|
||||
e: EntryOrExit,
|
||||
cfgidx: CFGIndex,
|
||||
dfcx: &DataFlowContext<'tcx, O>,
|
||||
mut to_lp: F,
|
||||
) -> String
|
||||
where
|
||||
F: FnMut(usize) -> Rc<LoanPath<'tcx>>,
|
||||
{
|
||||
let mut saw_some = false;
|
||||
let mut set = "{".to_string();
|
||||
dfcx.each_bit_for_node(e, cfgidx, |index| {
|
||||
let lp = to_lp(index);
|
||||
if saw_some {
|
||||
set.push_str(", ");
|
||||
}
|
||||
let loan_str = self.borrowck_ctxt.loan_path_to_string(&lp);
|
||||
set.push_str(&loan_str);
|
||||
saw_some = true;
|
||||
true
|
||||
});
|
||||
set.push_str("}");
|
||||
set
|
||||
}
|
||||
|
||||
fn dataflow_loans_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
|
||||
let dfcx = &self.analysis_data.loans;
|
||||
let loan_index_to_path = |loan_index| {
|
||||
let all_loans = &self.analysis_data.all_loans;
|
||||
let l: &borrowck::Loan<'_> = &all_loans[loan_index];
|
||||
l.loan_path()
|
||||
};
|
||||
self.build_set(e, cfgidx, dfcx, loan_index_to_path)
|
||||
}
|
||||
|
||||
fn dataflow_moves_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
|
||||
let dfcx = &self.analysis_data.move_data.dfcx_moves;
|
||||
let move_index_to_path = |move_index| {
|
||||
let move_data = &self.analysis_data.move_data.move_data;
|
||||
let moves = move_data.moves.borrow();
|
||||
let the_move: &borrowck::move_data::Move = &(*moves)[move_index];
|
||||
move_data.path_loan_path(the_move.path)
|
||||
};
|
||||
self.build_set(e, cfgidx, dfcx, move_index_to_path)
|
||||
}
|
||||
|
||||
fn dataflow_assigns_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
|
||||
let dfcx = &self.analysis_data.move_data.dfcx_assign;
|
||||
let assign_index_to_path = |assign_index| {
|
||||
let move_data = &self.analysis_data.move_data.move_data;
|
||||
let assignments = move_data.var_assignments.borrow();
|
||||
let assignment: &borrowck::move_data::Assignment = &(*assignments)[assign_index];
|
||||
move_data.path_loan_path(assignment.path)
|
||||
};
|
||||
self.build_set(e, cfgidx, dfcx, assign_index_to_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> dot::Labeller<'a> for DataflowLabeller<'a, 'tcx> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn graph_id(&'a self) -> dot::Id<'a> { self.inner.graph_id() }
|
||||
fn node_id(&'a self, n: &Node<'a>) -> dot::Id<'a> { self.inner.node_id(n) }
|
||||
fn node_label(&'a self, n: &Node<'a>) -> dot::LabelText<'a> {
|
||||
let prefix = self.dataflow_for(EntryOrExit::Entry, n);
|
||||
let suffix = self.dataflow_for(EntryOrExit::Exit, n);
|
||||
let inner_label = self.inner.node_label(n);
|
||||
inner_label
|
||||
.prefix_line(dot::LabelText::LabelStr(prefix.into()))
|
||||
.suffix_line(dot::LabelText::LabelStr(suffix.into()))
|
||||
}
|
||||
fn edge_label(&'a self, e: &Edge<'a>) -> dot::LabelText<'a> { self.inner.edge_label(e) }
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> dot::GraphWalk<'a> for DataflowLabeller<'a, 'tcx> {
|
||||
type Node = Node<'a>;
|
||||
type Edge = Edge<'a>;
|
||||
fn nodes(&'a self) -> dot::Nodes<'a, Node<'a>> { self.inner.nodes() }
|
||||
fn edges(&'a self) -> dot::Edges<'a, Edge<'a>> { self.inner.edges() }
|
||||
fn source(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.source(edge) }
|
||||
fn target(&'a self, edge: &Edge<'a>) -> Node<'a> { self.inner.target(edge) }
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/")]
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
#![feature(in_band_lifetimes)]
|
||||
#![feature(nll)]
|
||||
|
||||
#![recursion_limit="256"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rustc;
|
||||
|
||||
pub use borrowck::check_crate;
|
||||
pub use borrowck::build_borrowck_dataflow_data_for_fn;
|
||||
|
||||
mod borrowck;
|
||||
|
||||
pub mod graphviz;
|
||||
|
||||
mod dataflow;
|
||||
pub mod cfg;
|
||||
|
||||
pub use borrowck::provide;
|
||||
@@ -13,10 +13,9 @@ crate-type = ["dylib"]
|
||||
graphviz = { path = "../libgraphviz" }
|
||||
lazy_static = "1.0"
|
||||
log = "0.4"
|
||||
env_logger = { version = "0.6", default-features = false }
|
||||
env_logger = { version = "0.7", default-features = false }
|
||||
rustc = { path = "../librustc" }
|
||||
rustc_target = { path = "../librustc_target" }
|
||||
rustc_ast_borrowck = { path = "../librustc_ast_borrowck" }
|
||||
rustc_data_structures = { path = "../librustc_data_structures" }
|
||||
errors = { path = "../librustc_errors", package = "rustc_errors" }
|
||||
rustc_metadata = { path = "../librustc_metadata" }
|
||||
|
||||
+12
-162
@@ -2,7 +2,6 @@
|
||||
|
||||
use rustc::hir;
|
||||
use rustc::hir::map as hir_map;
|
||||
use rustc::hir::map::blocks;
|
||||
use rustc::hir::print as pprust_hir;
|
||||
use rustc::hir::def_id::LOCAL_CRATE;
|
||||
use rustc::session::Session;
|
||||
@@ -10,9 +9,6 @@
|
||||
use rustc::ty::{self, TyCtxt};
|
||||
use rustc::util::common::ErrorReported;
|
||||
use rustc_interface::util::ReplaceBodyWithLoop;
|
||||
use rustc_ast_borrowck as borrowck;
|
||||
use rustc_ast_borrowck::graphviz as borrowck_dot;
|
||||
use rustc_ast_borrowck::cfg::{self, graphviz::LabelledCFG};
|
||||
use rustc_mir::util::{write_mir_pretty, write_mir_graphviz};
|
||||
|
||||
use syntax::ast;
|
||||
@@ -20,11 +16,9 @@
|
||||
use syntax::print::{pprust};
|
||||
use syntax_pos::FileName;
|
||||
|
||||
use graphviz as dot;
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::io::Write;
|
||||
use std::option;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
@@ -48,21 +42,11 @@ pub enum PpSourceMode {
|
||||
PpmTyped,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum PpFlowGraphMode {
|
||||
Default,
|
||||
/// Drops the labels from the edges in the flowgraph output. This
|
||||
/// is mostly for use in the -Z unpretty flowgraph run-make tests,
|
||||
/// since the labels are largely uninteresting in those cases and
|
||||
/// have become a pain to maintain.
|
||||
UnlabelledEdges,
|
||||
}
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum PpMode {
|
||||
PpmSource(PpSourceMode),
|
||||
PpmHir(PpSourceMode),
|
||||
PpmHirTree(PpSourceMode),
|
||||
PpmFlowGraph(PpFlowGraphMode),
|
||||
PpmMir,
|
||||
PpmMirCFG,
|
||||
}
|
||||
@@ -80,15 +64,14 @@ pub fn needs_ast_map(&self, opt_uii: &Option<UserIdentifiedItem>) -> bool {
|
||||
PpmHir(_) |
|
||||
PpmHirTree(_) |
|
||||
PpmMir |
|
||||
PpmMirCFG |
|
||||
PpmFlowGraph(_) => true,
|
||||
PpmMirCFG => true,
|
||||
PpmSource(PpmTyped) => panic!("invalid state"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn needs_analysis(&self) -> bool {
|
||||
match *self {
|
||||
PpmMir | PpmMirCFG | PpmFlowGraph(_) => true,
|
||||
PpmMir | PpmMirCFG => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -114,13 +97,11 @@ pub fn parse_pretty(sess: &Session,
|
||||
("hir-tree", true) => PpmHirTree(PpmNormal),
|
||||
("mir", true) => PpmMir,
|
||||
("mir-cfg", true) => PpmMirCFG,
|
||||
("flowgraph", true) => PpmFlowGraph(PpFlowGraphMode::Default),
|
||||
("flowgraph,unlabelled", true) => PpmFlowGraph(PpFlowGraphMode::UnlabelledEdges),
|
||||
_ => {
|
||||
if extended {
|
||||
sess.fatal(&format!("argument to `unpretty` must be one of `normal`, \
|
||||
`expanded`, `flowgraph[,unlabelled]=<nodeid>`, \
|
||||
`identified`, `expanded,identified`, `everybody_loops`, \
|
||||
`expanded`, `identified`, `expanded,identified`, \
|
||||
`expanded,hygiene`, `everybody_loops`, \
|
||||
`hir`, `hir,identified`, `hir,typed`, `hir-tree`, \
|
||||
`mir` or `mir-cfg`; got {}",
|
||||
name));
|
||||
@@ -501,24 +482,6 @@ fn post(&self, s: &mut pprust_hir::State<'_>, node: pprust_hir::AnnNode<'_>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn gather_flowgraph_variants(sess: &Session) -> Vec<borrowck_dot::Variant> {
|
||||
let print_loans = sess.opts.debugging_opts.flowgraph_print_loans;
|
||||
let print_moves = sess.opts.debugging_opts.flowgraph_print_moves;
|
||||
let print_assigns = sess.opts.debugging_opts.flowgraph_print_assigns;
|
||||
let print_all = sess.opts.debugging_opts.flowgraph_print_all;
|
||||
let mut variants = Vec::new();
|
||||
if print_all || print_loans {
|
||||
variants.push(borrowck_dot::Loans);
|
||||
}
|
||||
if print_all || print_moves {
|
||||
variants.push(borrowck_dot::Moves);
|
||||
}
|
||||
if print_all || print_assigns {
|
||||
variants.push(borrowck_dot::Assigns);
|
||||
}
|
||||
variants
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum UserIdentifiedItem {
|
||||
ItemViaNode(ast::NodeId),
|
||||
@@ -609,81 +572,6 @@ fn to_one_node_id(self,
|
||||
}
|
||||
}
|
||||
|
||||
fn print_flowgraph<'tcx, W: Write>(
|
||||
variants: Vec<borrowck_dot::Variant>,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
code: blocks::Code<'tcx>,
|
||||
mode: PpFlowGraphMode,
|
||||
mut out: W,
|
||||
) -> io::Result<()> {
|
||||
let body_id = match code {
|
||||
blocks::Code::Expr(expr) => {
|
||||
// Find the function this expression is from.
|
||||
let mut hir_id = expr.hir_id;
|
||||
loop {
|
||||
let node = tcx.hir().get(hir_id);
|
||||
if let Some(n) = hir::map::blocks::FnLikeNode::from_node(node) {
|
||||
break n.body();
|
||||
}
|
||||
let parent = tcx.hir().get_parent_node(hir_id);
|
||||
assert_ne!(hir_id, parent);
|
||||
hir_id = parent;
|
||||
}
|
||||
}
|
||||
blocks::Code::FnLike(fn_like) => fn_like.body(),
|
||||
};
|
||||
let body = tcx.hir().body(body_id);
|
||||
let cfg = cfg::CFG::new(tcx, &body);
|
||||
let labelled_edges = mode != PpFlowGraphMode::UnlabelledEdges;
|
||||
let hir_id = code.id();
|
||||
// We have to disassemble the hir_id because name must be ASCII
|
||||
// alphanumeric. This does not appear in the rendered graph, so it does not
|
||||
// have to be user friendly.
|
||||
let name = format!(
|
||||
"hir_id_{}_{}",
|
||||
hir_id.owner.index(),
|
||||
hir_id.local_id.index(),
|
||||
);
|
||||
let lcfg = LabelledCFG {
|
||||
tcx,
|
||||
cfg: &cfg,
|
||||
name,
|
||||
labelled_edges,
|
||||
};
|
||||
|
||||
match code {
|
||||
_ if variants.is_empty() => {
|
||||
let r = dot::render(&lcfg, &mut out);
|
||||
return expand_err_details(r);
|
||||
}
|
||||
blocks::Code::Expr(_) => {
|
||||
tcx.sess.err("--pretty flowgraph with -Z flowgraph-print annotations requires \
|
||||
fn-like node id.");
|
||||
return Ok(());
|
||||
}
|
||||
blocks::Code::FnLike(fn_like) => {
|
||||
let (bccx, analysis_data) =
|
||||
borrowck::build_borrowck_dataflow_data_for_fn(tcx, fn_like.body(), &cfg);
|
||||
|
||||
let lcfg = borrowck_dot::DataflowLabeller {
|
||||
inner: lcfg,
|
||||
variants,
|
||||
borrowck_ctxt: &bccx,
|
||||
analysis_data: &analysis_data,
|
||||
};
|
||||
let r = dot::render(&lcfg, &mut out);
|
||||
return expand_err_details(r);
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_err_details(r: io::Result<()>) -> io::Result<()> {
|
||||
r.map_err(|ioerr| {
|
||||
io::Error::new(io::ErrorKind::Other,
|
||||
format!("graphviz::render failed: {}", ioerr))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visit_crate(sess: &Session, krate: &mut ast::Crate, ppm: PpMode) {
|
||||
if let PpmSource(PpmEveryBodyLoops) = ppm {
|
||||
ReplaceBodyWithLoop::new(sess).visit_crate(krate);
|
||||
@@ -872,55 +760,17 @@ fn print_with_analysis(
|
||||
|
||||
tcx.analysis(LOCAL_CRATE)?;
|
||||
|
||||
let mut print = || match ppm {
|
||||
match ppm {
|
||||
PpmMir | PpmMirCFG => {
|
||||
if let Some(nodeid) = nodeid {
|
||||
let def_id = tcx.hir().local_def_id_from_node_id(nodeid);
|
||||
match ppm {
|
||||
PpmMir => write_mir_pretty(tcx, Some(def_id), &mut out),
|
||||
PpmMirCFG => write_mir_graphviz(tcx, Some(def_id), &mut out),
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
} else {
|
||||
match ppm {
|
||||
PpmMir => write_mir_pretty(tcx, None, &mut out),
|
||||
PpmMirCFG => write_mir_graphviz(tcx, None, &mut out),
|
||||
_ => unreachable!(),
|
||||
}?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
PpmFlowGraph(mode) => {
|
||||
let nodeid =
|
||||
nodeid.expect("`pretty flowgraph=..` needs NodeId (int) or unique path \
|
||||
suffix (b::c::d)");
|
||||
let hir_id = tcx.hir().node_to_hir_id(nodeid);
|
||||
let node = tcx.hir().find(hir_id).unwrap_or_else(|| {
|
||||
tcx.sess.fatal(&format!("`--pretty=flowgraph` couldn't find ID: {}", nodeid))
|
||||
});
|
||||
|
||||
match blocks::Code::from_node(&tcx.hir(), hir_id) {
|
||||
Some(code) => {
|
||||
let variants = gather_flowgraph_variants(tcx.sess);
|
||||
|
||||
let out: &mut dyn Write = &mut out;
|
||||
|
||||
print_flowgraph(variants, tcx, code, mode, out)
|
||||
}
|
||||
None => {
|
||||
let message = format!("`--pretty=flowgraph` needs block, fn, or method; \
|
||||
got {:?}",
|
||||
node);
|
||||
|
||||
let hir_id = tcx.hir().node_to_hir_id(nodeid);
|
||||
tcx.sess.span_fatal(tcx.hir().span(hir_id), &message)
|
||||
}
|
||||
let def_id = nodeid.map(|nid| tcx.hir().local_def_id_from_node_id(nid));
|
||||
match ppm {
|
||||
PpmMir => write_mir_pretty(tcx, def_id, &mut out),
|
||||
PpmMirCFG => write_mir_graphviz(tcx, def_id, &mut out),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
print().unwrap();
|
||||
}.unwrap();
|
||||
|
||||
write_output(out, ofile);
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ syntax_ext = { path = "../libsyntax_ext" }
|
||||
syntax_pos = { path = "../libsyntax_pos" }
|
||||
rustc_serialize = { path = "../libserialize", package = "serialize" }
|
||||
rustc = { path = "../librustc" }
|
||||
rustc_ast_borrowck = { path = "../librustc_ast_borrowck" }
|
||||
rustc_incremental = { path = "../librustc_incremental" }
|
||||
rustc_traits = { path = "../librustc_traits" }
|
||||
rustc_data_structures = { path = "../librustc_data_structures" }
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
use rustc::session::Session;
|
||||
use rustc::session::config::{self, CrateType, Input, OutputFilenames, OutputType};
|
||||
use rustc::session::search_paths::PathKind;
|
||||
use rustc_ast_borrowck as borrowck;
|
||||
use rustc_codegen_ssa::back::link::emit_metadata;
|
||||
use rustc_codegen_utils::codegen_backend::CodegenBackend;
|
||||
use rustc_codegen_utils::link::filename_for_metadata;
|
||||
@@ -769,7 +768,6 @@ pub fn default_provide(providers: &mut ty::query::Providers<'_>) {
|
||||
proc_macro_decls::provide(providers);
|
||||
plugin::build::provide(providers);
|
||||
hir::provide(providers);
|
||||
borrowck::provide(providers);
|
||||
mir::provide(providers);
|
||||
reachable::provide(providers);
|
||||
resolve_lifetime::provide(providers);
|
||||
@@ -937,12 +935,6 @@ fn analysis(tcx: TyCtxt<'_>, cnum: CrateNum) -> Result<()> {
|
||||
});
|
||||
});
|
||||
|
||||
time(sess, "borrow checking", || {
|
||||
if tcx.use_ast_borrowck() {
|
||||
borrowck::check_crate(tcx);
|
||||
}
|
||||
});
|
||||
|
||||
time(sess, "MIR borrow checking", || {
|
||||
tcx.par_body_owners(|def_id| tcx.ensure().mir_borrowck(def_id));
|
||||
});
|
||||
|
||||
@@ -442,8 +442,8 @@ pub fn rustc_queries(input: TokenStream) -> TokenStream {
|
||||
.map(|c| c.is_green())
|
||||
.unwrap_or(false));
|
||||
|
||||
let key = RecoverKey::recover(tcx.global_tcx(), self).unwrap();
|
||||
if queries::#name::cache_on_disk(tcx.global_tcx(), key, None) {
|
||||
let key = RecoverKey::recover(tcx, self).unwrap();
|
||||
if queries::#name::cache_on_disk(tcx, key, None) {
|
||||
let _ = tcx.#name(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +621,7 @@ fn visit_terminator_entry(
|
||||
target: _,
|
||||
unwind: _,
|
||||
} => {
|
||||
let gcx = self.infcx.tcx.global_tcx();
|
||||
let tcx = self.infcx.tcx;
|
||||
|
||||
// Compute the type with accurate region information.
|
||||
let drop_place_ty = drop_place.ty(self.body, self.infcx.tcx);
|
||||
@@ -629,10 +629,10 @@ fn visit_terminator_entry(
|
||||
// Erase the regions.
|
||||
let drop_place_ty = self.infcx.tcx.erase_regions(&drop_place_ty).ty;
|
||||
|
||||
// "Lift" into the gcx -- once regions are erased, this type should be in the
|
||||
// "Lift" into the tcx -- once regions are erased, this type should be in the
|
||||
// global arenas; this "lift" operation basically just asserts that is true, but
|
||||
// that is useful later.
|
||||
gcx.lift_to_global(&drop_place_ty).unwrap();
|
||||
tcx.lift(&drop_place_ty).unwrap();
|
||||
|
||||
debug!("visit_terminator_drop \
|
||||
loc: {:?} term: {:?} drop_place: {:?} drop_place_ty: {:?} span: {:?}",
|
||||
@@ -1932,48 +1932,26 @@ fn check_access_permissions(
|
||||
}
|
||||
}
|
||||
|
||||
Reservation(wk @ WriteKind::Move)
|
||||
| Write(wk @ WriteKind::Move)
|
||||
| Reservation(wk @ WriteKind::StorageDeadOrDrop)
|
||||
| Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shared))
|
||||
| Reservation(wk @ WriteKind::MutableBorrow(BorrowKind::Shallow))
|
||||
| Write(wk @ WriteKind::StorageDeadOrDrop)
|
||||
| Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shared))
|
||||
| Write(wk @ WriteKind::MutableBorrow(BorrowKind::Shallow)) => {
|
||||
if let (Err(place_err), true) = (
|
||||
Reservation(WriteKind::Move)
|
||||
| Write(WriteKind::Move)
|
||||
| Reservation(WriteKind::StorageDeadOrDrop)
|
||||
| Reservation(WriteKind::MutableBorrow(BorrowKind::Shared))
|
||||
| Reservation(WriteKind::MutableBorrow(BorrowKind::Shallow))
|
||||
| Write(WriteKind::StorageDeadOrDrop)
|
||||
| Write(WriteKind::MutableBorrow(BorrowKind::Shared))
|
||||
| Write(WriteKind::MutableBorrow(BorrowKind::Shallow)) => {
|
||||
if let (Err(_), true) = (
|
||||
self.is_mutable(place.as_ref(), is_local_mutation_allowed),
|
||||
self.errors_buffer.is_empty()
|
||||
) {
|
||||
if self.infcx.tcx.migrate_borrowck() {
|
||||
// rust-lang/rust#46908: In pure NLL mode this
|
||||
// code path should be unreachable (and thus
|
||||
// we signal an ICE in the else branch
|
||||
// here). But we can legitimately get here
|
||||
// under borrowck=migrate mode, so instead of
|
||||
// ICE'ing we instead report a legitimate
|
||||
// error (which will then be downgraded to a
|
||||
// warning by the migrate machinery).
|
||||
error_access = match wk {
|
||||
WriteKind::MutableBorrow(_) => AccessKind::MutableBorrow,
|
||||
WriteKind::Move => AccessKind::Move,
|
||||
WriteKind::StorageDeadOrDrop |
|
||||
WriteKind::Mutate => AccessKind::Mutate,
|
||||
};
|
||||
self.report_mutability_error(
|
||||
place,
|
||||
span,
|
||||
place_err,
|
||||
error_access,
|
||||
location,
|
||||
);
|
||||
} else {
|
||||
span_bug!(
|
||||
span,
|
||||
"Accessing `{:?}` with the kind `{:?}` shouldn't be possible",
|
||||
place,
|
||||
kind,
|
||||
);
|
||||
}
|
||||
// rust-lang/rust#46908: In pure NLL mode this code path should
|
||||
// be unreachable (and thus we signal an ICE in the else branch here).
|
||||
span_bug!(
|
||||
span,
|
||||
"Accessing `{:?}` with the kind `{:?}` shouldn't be possible",
|
||||
place,
|
||||
kind,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
pub(super) enum AccessKind {
|
||||
MutableBorrow,
|
||||
Mutate,
|
||||
Move,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
|
||||
@@ -124,7 +123,6 @@ pub(super) fn report_mutability_error(
|
||||
if let Some(desc) = access_place_desc {
|
||||
item_msg = format!("`{}`", desc);
|
||||
reason = match error_access {
|
||||
AccessKind::Move |
|
||||
AccessKind::Mutate => format!(" which is behind {}", pointer_type),
|
||||
AccessKind::MutableBorrow => {
|
||||
format!(", as it is behind {}", pointer_type)
|
||||
@@ -194,12 +192,6 @@ pub(super) fn report_mutability_error(
|
||||
let acted_on;
|
||||
|
||||
let span = match error_access {
|
||||
AccessKind::Move => {
|
||||
err = self.cannot_move_out_of(span, &(item_msg + &reason));
|
||||
err.span_label(span, "cannot move");
|
||||
err.buffer(&mut self.errors_buffer);
|
||||
return;
|
||||
}
|
||||
AccessKind::Mutate => {
|
||||
err = self.cannot_assign(span, &(item_msg + &reason));
|
||||
act = "assign";
|
||||
|
||||
@@ -1894,9 +1894,8 @@ fn ensure_place_sized(&mut self, ty: Ty<'tcx>, span: Span) {
|
||||
// Erase the regions from `ty` to get a global type. The
|
||||
// `Sized` bound in no way depends on precise regions, so this
|
||||
// shouldn't affect `is_sized`.
|
||||
let gcx = tcx.global_tcx();
|
||||
let erased_ty = tcx.erase_regions(&ty);
|
||||
if !erased_ty.is_sized(gcx.at(span), self.param_env) {
|
||||
if !erased_ty.is_sized(tcx.at(span), self.param_env) {
|
||||
// in current MIR construction, all non-control-flow rvalue
|
||||
// expressions evaluate through `as_temp` or `into` a return
|
||||
// slot or local, so to find all unsized rvalues it is enough
|
||||
|
||||
@@ -521,9 +521,8 @@ fn compute_indices(
|
||||
defining_ty: DefiningTy<'tcx>,
|
||||
) -> UniversalRegionIndices<'tcx> {
|
||||
let tcx = self.infcx.tcx;
|
||||
let gcx = tcx.global_tcx();
|
||||
let closure_base_def_id = tcx.closure_base_def_id(self.mir_def_id);
|
||||
let identity_substs = InternalSubsts::identity_for_item(gcx, closure_base_def_id);
|
||||
let identity_substs = InternalSubsts::identity_for_item(tcx, closure_base_def_id);
|
||||
let fr_substs = match defining_ty {
|
||||
DefiningTy::Closure(_, ClosureSubsts { ref substs })
|
||||
| DefiningTy::Generator(_, GeneratorSubsts { ref substs }, _) => {
|
||||
@@ -542,7 +541,7 @@ fn compute_indices(
|
||||
DefiningTy::FnDef(_, substs) | DefiningTy::Const(_, substs) => substs,
|
||||
};
|
||||
|
||||
let global_mapping = iter::once((gcx.lifetimes.re_static, fr_static));
|
||||
let global_mapping = iter::once((tcx.lifetimes.re_static, fr_static));
|
||||
let subst_mapping = identity_substs
|
||||
.regions()
|
||||
.zip(fr_substs.regions().map(|r| r.to_region_vid()));
|
||||
|
||||
@@ -148,9 +148,8 @@ pub(crate) fn on_all_drop_children_bits<'tcx, F>(
|
||||
let ty = place.ty(body, tcx).ty;
|
||||
debug!("on_all_drop_children_bits({:?}, {:?} : {:?})", path, place, ty);
|
||||
|
||||
let gcx = tcx.global_tcx();
|
||||
let erased_ty = tcx.erase_regions(&ty);
|
||||
if erased_ty.needs_drop(gcx, ctxt.param_env) {
|
||||
if erased_ty.needs_drop(tcx, ctxt.param_env) {
|
||||
each_child(child);
|
||||
} else {
|
||||
debug!("on_all_drop_children_bits - skipping")
|
||||
|
||||
@@ -543,9 +543,9 @@ fn make_mirror_unadjusted<'a, 'tcx>(
|
||||
// Now comes the rote stuff:
|
||||
hir::ExprKind::Repeat(ref v, ref count) => {
|
||||
let def_id = cx.tcx.hir().local_def_id(count.hir_id);
|
||||
let substs = InternalSubsts::identity_for_item(cx.tcx.global_tcx(), def_id);
|
||||
let substs = InternalSubsts::identity_for_item(cx.tcx, def_id);
|
||||
let instance = ty::Instance::resolve(
|
||||
cx.tcx.global_tcx(),
|
||||
cx.tcx,
|
||||
cx.param_env,
|
||||
def_id,
|
||||
substs,
|
||||
|
||||
@@ -83,7 +83,7 @@ pub fn new(infcx: &'a InferCtxt<'a, 'tcx>, src_id: hir::HirId) -> Cx<'a, 'tcx> {
|
||||
infcx,
|
||||
root_lint_level: src_id,
|
||||
param_env: tcx.param_env(src_def_id),
|
||||
identity_substs: InternalSubsts::identity_for_item(tcx.global_tcx(), src_def_id),
|
||||
identity_substs: InternalSubsts::identity_for_item(tcx, src_def_id),
|
||||
region_scope_tree: tcx.region_scope_tree(src_def_id),
|
||||
tables,
|
||||
constness,
|
||||
@@ -154,12 +154,11 @@ pub fn const_eval_literal(
|
||||
}
|
||||
|
||||
pub fn pattern_from_hir(&mut self, p: &hir::Pat) -> Pat<'tcx> {
|
||||
let tcx = self.tcx.global_tcx();
|
||||
let p = match tcx.hir().get(p.hir_id) {
|
||||
let p = match self.tcx.hir().get(p.hir_id) {
|
||||
Node::Pat(p) | Node::Binding(p) => p,
|
||||
node => bug!("pattern became {:?}", node)
|
||||
};
|
||||
Pat::from_hir(tcx, self.param_env.and(self.identity_substs), self.tables(), p)
|
||||
Pat::from_hir(self.tcx, self.param_env.and(self.identity_substs), self.tables(), p)
|
||||
}
|
||||
|
||||
pub fn trait_method(&mut self,
|
||||
@@ -187,7 +186,7 @@ pub fn all_fields(&mut self, adt_def: &ty::AdtDef, variant_index: VariantIdx) ->
|
||||
}
|
||||
|
||||
pub fn needs_drop(&mut self, ty: Ty<'tcx>) -> bool {
|
||||
ty.needs_drop(self.tcx.global_tcx(), self.param_env)
|
||||
ty.needs_drop(self.tcx, self.param_env)
|
||||
}
|
||||
|
||||
pub fn tcx(&self) -> TyCtxt<'tcx> {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
use super::{PatCtxt, PatternError, PatKind};
|
||||
|
||||
use rustc::middle::borrowck::SignalledError;
|
||||
use rustc::session::Session;
|
||||
use rustc::ty::{self, Ty, TyCtxt};
|
||||
use rustc::ty::subst::{InternalSubsts, SubstsRef};
|
||||
@@ -21,11 +20,10 @@
|
||||
|
||||
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
|
||||
|
||||
crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) -> SignalledError {
|
||||
let body_id = if let Some(id) = tcx.hir().as_local_hir_id(def_id) {
|
||||
tcx.hir().body_owned_by(id)
|
||||
} else {
|
||||
return SignalledError::NoErrorsSeen;
|
||||
crate fn check_match(tcx: TyCtxt<'_>, def_id: DefId) {
|
||||
let body_id = match tcx.hir().as_local_hir_id(def_id) {
|
||||
None => return,
|
||||
Some(id) => tcx.hir().body_owned_by(id),
|
||||
};
|
||||
|
||||
let mut visitor = MatchVisitor {
|
||||
@@ -33,10 +31,8 @@
|
||||
tables: tcx.body_tables(body_id),
|
||||
param_env: tcx.param_env(def_id),
|
||||
identity_substs: InternalSubsts::identity_for_item(tcx, def_id),
|
||||
signalled_error: SignalledError::NoErrorsSeen,
|
||||
};
|
||||
visitor.visit_body(tcx.hir().body(body_id));
|
||||
visitor.signalled_error
|
||||
}
|
||||
|
||||
fn create_e0004(sess: &Session, sp: Span, error_message: String) -> DiagnosticBuilder<'_> {
|
||||
@@ -48,7 +44,6 @@ struct MatchVisitor<'a, 'tcx> {
|
||||
tables: &'a ty::TypeckTables<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
identity_substs: SubstsRef<'tcx>,
|
||||
signalled_error: SignalledError,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, 'tcx> {
|
||||
@@ -136,13 +131,7 @@ fn check_match(
|
||||
// First, check legality of move bindings.
|
||||
self.check_patterns(arm.guard.is_some(), &arm.pat);
|
||||
|
||||
// Second, if there is a guard on each arm, make sure it isn't
|
||||
// assigning or borrowing anything mutably.
|
||||
if arm.guard.is_some() {
|
||||
self.signalled_error = SignalledError::SawSomeError;
|
||||
}
|
||||
|
||||
// Third, perform some lints.
|
||||
// Second, perform some lints.
|
||||
check_for_bindings_named_same_as_variants(self, &arm.pat);
|
||||
}
|
||||
|
||||
@@ -151,10 +140,17 @@ fn check_match(
|
||||
let mut have_errors = false;
|
||||
|
||||
let inlined_arms : Vec<(Vec<_>, _)> = arms.iter().map(|arm| (
|
||||
arm.top_pats_hack().iter().map(|pat| {
|
||||
let mut patcx = PatCtxt::new(self.tcx,
|
||||
self.param_env.and(self.identity_substs),
|
||||
self.tables);
|
||||
// HACK(or_patterns; Centril | dlrobertson): Remove this and
|
||||
// correctly handle exhaustiveness checking for nested or-patterns.
|
||||
match &arm.pat.kind {
|
||||
hir::PatKind::Or(pats) => pats,
|
||||
_ => std::slice::from_ref(&arm.pat),
|
||||
}.iter().map(|pat| {
|
||||
let mut patcx = PatCtxt::new(
|
||||
self.tcx,
|
||||
self.param_env.and(self.identity_substs),
|
||||
self.tables
|
||||
);
|
||||
patcx.include_lint_checks();
|
||||
let pattern = expand_pattern(cx, patcx.lower_pattern(&pat));
|
||||
if !patcx.errors.is_empty() {
|
||||
@@ -270,20 +266,51 @@ fn check_irrefutable(&self, pat: &'tcx Pat, origin: &str) {
|
||||
"refutable pattern in {}: {} not covered",
|
||||
origin, joined_patterns
|
||||
);
|
||||
err.span_label(pat.span, match &pat.kind {
|
||||
match &pat.kind {
|
||||
hir::PatKind::Path(hir::QPath::Resolved(None, path))
|
||||
if path.segments.len() == 1 && path.segments[0].args.is_none() => {
|
||||
format!("interpreted as {} {} pattern, not new variable",
|
||||
path.res.article(), path.res.descr())
|
||||
if path.segments.len() == 1 && path.segments[0].args.is_none() =>
|
||||
{
|
||||
const_not_var(&mut err, cx.tcx, pat, path);
|
||||
}
|
||||
_ => pattern_not_convered_label(&witnesses, &joined_patterns),
|
||||
});
|
||||
_ => {
|
||||
err.span_label(
|
||||
pat.span,
|
||||
pattern_not_covered_label(&witnesses, &joined_patterns),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
adt_defined_here(cx, &mut err, pattern_ty, &witnesses);
|
||||
err.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// A path pattern was interpreted as a constant, not a new variable.
|
||||
/// This caused an irrefutable match failure in e.g. `let`.
|
||||
fn const_not_var(err: &mut DiagnosticBuilder<'_>, tcx: TyCtxt<'_>, pat: &Pat, path: &hir::Path) {
|
||||
let descr = path.res.descr();
|
||||
err.span_label(pat.span, format!(
|
||||
"interpreted as {} {} pattern, not a new variable",
|
||||
path.res.article(),
|
||||
descr,
|
||||
));
|
||||
|
||||
err.span_suggestion(
|
||||
pat.span,
|
||||
"introduce a variable instead",
|
||||
format!("{}_var", path.segments[0].ident).to_lowercase(),
|
||||
// Cannot use `MachineApplicable` as it's not really *always* correct
|
||||
// because there may be such an identifier in scope or the user maybe
|
||||
// really wanted to match against the constant. This is quite unlikely however.
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
|
||||
if let Some(span) = tcx.hir().res_span(path.res) {
|
||||
err.span_label(span, format!("{} defined here", descr));
|
||||
}
|
||||
}
|
||||
|
||||
fn check_for_bindings_named_same_as_variants(cx: &MatchVisitor<'_, '_>, pat: &Pat) {
|
||||
pat.walk(|p| {
|
||||
if let hir::PatKind::Binding(_, _, ident, None) = p.kind {
|
||||
@@ -449,7 +476,7 @@ fn check_exhaustive<'tcx>(
|
||||
cx.tcx.sess, sp,
|
||||
format!("non-exhaustive patterns: {} not covered", joined_patterns),
|
||||
);
|
||||
err.span_label(sp, pattern_not_convered_label(&witnesses, &joined_patterns));
|
||||
err.span_label(sp, pattern_not_covered_label(&witnesses, &joined_patterns));
|
||||
adt_defined_here(cx, &mut err, scrut_ty, &witnesses);
|
||||
err.help(
|
||||
"ensure that all possible cases are being handled, \
|
||||
@@ -475,7 +502,7 @@ fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn pattern_not_convered_label(witnesses: &[super::Pat<'_>], joined_patterns: &str) -> String {
|
||||
fn pattern_not_covered_label(witnesses: &[super::Pat<'_>], joined_patterns: &str) -> String {
|
||||
format!("pattern{} {} not covered", rustc_errors::pluralise!(witnesses.len()), joined_patterns)
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> &'tcx
|
||||
}
|
||||
ty::InstanceDef::ClosureOnceShim { call_once } => {
|
||||
let fn_mut = tcx.lang_items().fn_mut_trait().unwrap();
|
||||
let call_mut = tcx.global_tcx()
|
||||
let call_mut = tcx
|
||||
.associated_items(fn_mut)
|
||||
.find(|it| it.kind == ty::AssocKind::Method)
|
||||
.unwrap().def_id;
|
||||
|
||||
@@ -28,17 +28,7 @@ fn run_pass(&self, tcx: TyCtxt<'tcx>, src: MirSource<'tcx>, body: &mut Body<'tcx
|
||||
let param_env = tcx.param_env(src.def_id()).with_reveal_all();
|
||||
let move_data = match MoveData::gather_moves(body, tcx) {
|
||||
Ok(move_data) => move_data,
|
||||
Err((move_data, _move_errors)) => {
|
||||
// The only way we should be allowing any move_errors
|
||||
// in here is if we are in the migration path for the
|
||||
// NLL-based MIR-borrowck.
|
||||
//
|
||||
// If we are in the migration path, we have already
|
||||
// reported these errors as warnings to the user. So
|
||||
// we will just ignore them here.
|
||||
assert!(tcx.migrate_borrowck());
|
||||
move_data
|
||||
}
|
||||
Err(_) => bug!("No `move_errors` should be allowed in MIR borrowck"),
|
||||
};
|
||||
let elaborate_patch = {
|
||||
let body = &*body;
|
||||
|
||||
@@ -291,10 +291,6 @@ fn optimized_mir(tcx: TyCtxt<'_>, def_id: DefId) -> &Body<'_> {
|
||||
// execute before we can steal.
|
||||
tcx.ensure().mir_borrowck(def_id);
|
||||
|
||||
if tcx.use_ast_borrowck() {
|
||||
tcx.ensure().borrowck(def_id);
|
||||
}
|
||||
|
||||
let (body, _) = tcx.mir_validated(def_id);
|
||||
let mut body = body.steal();
|
||||
run_optimization_passes(tcx, &mut body, def_id, None);
|
||||
|
||||
@@ -474,7 +474,7 @@ fn lift_delayed_literal(
|
||||
&self,
|
||||
value: DelayedLiteral<ChalkArenas<'tcx>>,
|
||||
) -> DelayedLiteral<ChalkArenas<'tcx>> {
|
||||
match self.infcx.tcx.lift_to_global(&value) {
|
||||
match self.infcx.tcx.lift(&value) {
|
||||
Some(literal) => literal,
|
||||
None => bug!("cannot lift {:?}", value),
|
||||
}
|
||||
|
||||
@@ -1269,7 +1269,7 @@ fn conv_object_ty_poly_trait_ref(&self,
|
||||
// to avoid ICEs.
|
||||
for item in ®ular_traits {
|
||||
let object_safety_violations =
|
||||
tcx.global_tcx().astconv_object_safety_violations(item.trait_ref().def_id());
|
||||
tcx.astconv_object_safety_violations(item.trait_ref().def_id());
|
||||
if !object_safety_violations.is_empty() {
|
||||
tcx.report_object_safety_error(
|
||||
span,
|
||||
@@ -1368,11 +1368,8 @@ fn conv_object_ty_poly_trait_ref(&self,
|
||||
span,
|
||||
format!("associated type `{}` must be specified", assoc_item.ident),
|
||||
);
|
||||
if item_def_id.is_local() {
|
||||
err.span_label(
|
||||
tcx.def_span(*item_def_id),
|
||||
format!("`{}` defined here", assoc_item.ident),
|
||||
);
|
||||
if let Some(sp) = tcx.hir().span_if_local(*item_def_id) {
|
||||
err.span_label(sp, format!("`{}` defined here", assoc_item.ident));
|
||||
}
|
||||
if suggest {
|
||||
if let Ok(snippet) = tcx.sess.source_map().span_to_snippet(
|
||||
|
||||
@@ -351,16 +351,7 @@ fn confirm_builtin_call(
|
||||
|
||||
err.span_label(call_expr.span, "call expression requires function");
|
||||
|
||||
let def_span = match def {
|
||||
Res::Err => None,
|
||||
Res::Local(id) => {
|
||||
Some(self.tcx.hir().span(id))
|
||||
},
|
||||
_ => def
|
||||
.opt_def_id()
|
||||
.and_then(|did| self.tcx.hir().span_if_local(did)),
|
||||
};
|
||||
if let Some(span) = def_span {
|
||||
if let Some(span) = self.tcx.hir().res_span(def) {
|
||||
let label = match (unit_variant, inner_callee_path) {
|
||||
(Some(path), _) => format!("`{}` defined here", path),
|
||||
(_, Some(hir::QPath::Resolved(_, path))) => format!(
|
||||
|
||||
@@ -620,8 +620,12 @@ fn check_expr_return(
|
||||
expr: &'tcx hir::Expr
|
||||
) -> Ty<'tcx> {
|
||||
if self.ret_coercion.is_none() {
|
||||
struct_span_err!(self.tcx.sess, expr.span, E0572,
|
||||
"return statement outside of function body").emit();
|
||||
struct_span_err!(
|
||||
self.tcx.sess,
|
||||
expr.span,
|
||||
E0572,
|
||||
"return statement outside of function body",
|
||||
).emit();
|
||||
} else if let Some(ref e) = expr_opt {
|
||||
if self.ret_coercion_span.borrow().is_none() {
|
||||
*self.ret_coercion_span.borrow_mut() = Some(e.span);
|
||||
@@ -932,9 +936,9 @@ fn check_expr_repeat(
|
||||
Ok(self.to_const(count, tcx.type_of(count_def_id)))
|
||||
} else {
|
||||
let param_env = ty::ParamEnv::empty();
|
||||
let substs = InternalSubsts::identity_for_item(tcx.global_tcx(), count_def_id);
|
||||
let substs = InternalSubsts::identity_for_item(tcx, count_def_id);
|
||||
let instance = ty::Instance::resolve(
|
||||
tcx.global_tcx(),
|
||||
tcx,
|
||||
param_env,
|
||||
count_def_id,
|
||||
substs,
|
||||
|
||||
@@ -48,7 +48,7 @@ fn with_fcx<F>(&mut self, f: F)
|
||||
// empty `param_env`.
|
||||
check_false_global_bounds(&fcx, span, id);
|
||||
}
|
||||
let wf_tys = f(&fcx, fcx.tcx.global_tcx());
|
||||
let wf_tys = f(&fcx, fcx.tcx);
|
||||
fcx.select_all_obligations_or_error();
|
||||
fcx.regionck_item(id, span, &wf_tys);
|
||||
});
|
||||
@@ -366,8 +366,8 @@ fn check_item_type(
|
||||
) {
|
||||
debug!("check_item_type: {:?}", item_id);
|
||||
|
||||
for_id(tcx, item_id, ty_span).with_fcx(|fcx, gcx| {
|
||||
let ty = gcx.type_of(gcx.hir().local_def_id(item_id));
|
||||
for_id(tcx, item_id, ty_span).with_fcx(|fcx, tcx| {
|
||||
let ty = tcx.type_of(tcx.hir().local_def_id(item_id));
|
||||
let item_ty = fcx.normalize_associated_types_in(ty_span, &ty);
|
||||
|
||||
let mut forbid_unsized = true;
|
||||
|
||||
@@ -322,29 +322,29 @@ fn visit_implementation_of_dispatch_from_dyn(tcx: TyCtxt<'_>, impl_did: DefId) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUnsizedInfo {
|
||||
pub fn coerce_unsized_info<'tcx>(tcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUnsizedInfo {
|
||||
debug!("compute_coerce_unsized_info(impl_did={:?})", impl_did);
|
||||
let coerce_unsized_trait = gcx.lang_items().coerce_unsized_trait().unwrap();
|
||||
let coerce_unsized_trait = tcx.lang_items().coerce_unsized_trait().unwrap();
|
||||
|
||||
let unsize_trait = gcx.lang_items().require(UnsizeTraitLangItem).unwrap_or_else(|err| {
|
||||
gcx.sess.fatal(&format!("`CoerceUnsized` implementation {}", err));
|
||||
let unsize_trait = tcx.lang_items().require(UnsizeTraitLangItem).unwrap_or_else(|err| {
|
||||
tcx.sess.fatal(&format!("`CoerceUnsized` implementation {}", err));
|
||||
});
|
||||
|
||||
// this provider should only get invoked for local def-ids
|
||||
let impl_hir_id = gcx.hir().as_local_hir_id(impl_did).unwrap_or_else(|| {
|
||||
let impl_hir_id = tcx.hir().as_local_hir_id(impl_did).unwrap_or_else(|| {
|
||||
bug!("coerce_unsized_info: invoked for non-local def-id {:?}", impl_did)
|
||||
});
|
||||
|
||||
let source = gcx.type_of(impl_did);
|
||||
let trait_ref = gcx.impl_trait_ref(impl_did).unwrap();
|
||||
let source = tcx.type_of(impl_did);
|
||||
let trait_ref = tcx.impl_trait_ref(impl_did).unwrap();
|
||||
assert_eq!(trait_ref.def_id, coerce_unsized_trait);
|
||||
let target = trait_ref.substs.type_at(1);
|
||||
debug!("visit_implementation_of_coerce_unsized: {:?} -> {:?} (bound)",
|
||||
source,
|
||||
target);
|
||||
|
||||
let span = gcx.hir().span(impl_hir_id);
|
||||
let param_env = gcx.param_env(impl_did);
|
||||
let span = tcx.hir().span(impl_hir_id);
|
||||
let param_env = tcx.param_env(impl_did);
|
||||
assert!(!source.has_escaping_bound_vars());
|
||||
|
||||
let err_info = CoerceUnsizedInfo { custom_kind: None };
|
||||
@@ -353,7 +353,7 @@ pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
|
||||
source,
|
||||
target);
|
||||
|
||||
gcx.infer_ctxt().enter(|infcx| {
|
||||
tcx.infer_ctxt().enter(|infcx| {
|
||||
let cause = ObligationCause::misc(span, impl_hir_id);
|
||||
let check_mutbl = |mt_a: ty::TypeAndMut<'tcx>,
|
||||
mt_b: ty::TypeAndMut<'tcx>,
|
||||
@@ -372,24 +372,24 @@ pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
|
||||
infcx.sub_regions(infer::RelateObjectBound(span), r_b, r_a);
|
||||
let mt_a = ty::TypeAndMut { ty: ty_a, mutbl: mutbl_a };
|
||||
let mt_b = ty::TypeAndMut { ty: ty_b, mutbl: mutbl_b };
|
||||
check_mutbl(mt_a, mt_b, &|ty| gcx.mk_imm_ref(r_b, ty))
|
||||
check_mutbl(mt_a, mt_b, &|ty| tcx.mk_imm_ref(r_b, ty))
|
||||
}
|
||||
|
||||
(&ty::Ref(_, ty_a, mutbl_a), &ty::RawPtr(mt_b)) => {
|
||||
let mt_a = ty::TypeAndMut { ty: ty_a, mutbl: mutbl_a };
|
||||
check_mutbl(mt_a, mt_b, &|ty| gcx.mk_imm_ptr(ty))
|
||||
check_mutbl(mt_a, mt_b, &|ty| tcx.mk_imm_ptr(ty))
|
||||
}
|
||||
|
||||
(&ty::RawPtr(mt_a), &ty::RawPtr(mt_b)) => {
|
||||
check_mutbl(mt_a, mt_b, &|ty| gcx.mk_imm_ptr(ty))
|
||||
check_mutbl(mt_a, mt_b, &|ty| tcx.mk_imm_ptr(ty))
|
||||
}
|
||||
|
||||
(&ty::Adt(def_a, substs_a), &ty::Adt(def_b, substs_b)) if def_a.is_struct() &&
|
||||
def_b.is_struct() => {
|
||||
if def_a != def_b {
|
||||
let source_path = gcx.def_path_str(def_a.did);
|
||||
let target_path = gcx.def_path_str(def_b.did);
|
||||
span_err!(gcx.sess,
|
||||
let source_path = tcx.def_path_str(def_a.did);
|
||||
let target_path = tcx.def_path_str(def_b.did);
|
||||
span_err!(tcx.sess,
|
||||
span,
|
||||
E0377,
|
||||
"the trait `CoerceUnsized` may only be implemented \
|
||||
@@ -443,9 +443,9 @@ pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
|
||||
let diff_fields = fields.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, f)| {
|
||||
let (a, b) = (f.ty(gcx, substs_a), f.ty(gcx, substs_b));
|
||||
let (a, b) = (f.ty(tcx, substs_a), f.ty(tcx, substs_b));
|
||||
|
||||
if gcx.type_of(f.did).is_phantom_data() {
|
||||
if tcx.type_of(f.did).is_phantom_data() {
|
||||
// Ignore PhantomData fields
|
||||
return None;
|
||||
}
|
||||
@@ -472,7 +472,7 @@ pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if diff_fields.is_empty() {
|
||||
span_err!(gcx.sess,
|
||||
span_err!(tcx.sess,
|
||||
span,
|
||||
E0374,
|
||||
"the trait `CoerceUnsized` may only be implemented \
|
||||
@@ -480,14 +480,14 @@ pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
|
||||
being coerced, none found");
|
||||
return err_info;
|
||||
} else if diff_fields.len() > 1 {
|
||||
let item = gcx.hir().expect_item(impl_hir_id);
|
||||
let item = tcx.hir().expect_item(impl_hir_id);
|
||||
let span = if let ItemKind::Impl(.., Some(ref t), _, _) = item.kind {
|
||||
t.path.span
|
||||
} else {
|
||||
gcx.hir().span(impl_hir_id)
|
||||
tcx.hir().span(impl_hir_id)
|
||||
};
|
||||
|
||||
let mut err = struct_span_err!(gcx.sess,
|
||||
let mut err = struct_span_err!(tcx.sess,
|
||||
span,
|
||||
E0375,
|
||||
"implementing the trait \
|
||||
@@ -514,7 +514,7 @@ pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
|
||||
}
|
||||
|
||||
_ => {
|
||||
span_err!(gcx.sess,
|
||||
span_err!(tcx.sess,
|
||||
span,
|
||||
E0376,
|
||||
"the trait `CoerceUnsized` may only be implemented \
|
||||
@@ -527,7 +527,7 @@ pub fn coerce_unsized_info<'tcx>(gcx: TyCtxt<'tcx>, impl_did: DefId) -> CoerceUn
|
||||
|
||||
// Register an obligation for `A: Trait<B>`.
|
||||
let cause = traits::ObligationCause::misc(span, impl_hir_id);
|
||||
let predicate = gcx.predicate_for_trait_def(param_env,
|
||||
let predicate = tcx.predicate_for_trait_def(param_env,
|
||||
cause,
|
||||
trait_def_id,
|
||||
0,
|
||||
|
||||
@@ -1717,9 +1717,7 @@ fn visit_trait_item(&mut self, it: &'tcx TraitItem) {
|
||||
}
|
||||
|
||||
let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
|
||||
let scope = tcx.hir()
|
||||
.get_defining_scope(hir_id)
|
||||
.expect("could not get defining scope");
|
||||
let scope = tcx.hir().get_defining_scope(hir_id);
|
||||
let mut locator = ConstraintLocator {
|
||||
def_id,
|
||||
tcx,
|
||||
|
||||
@@ -714,7 +714,6 @@ pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(map_get_key_value)]
|
||||
/// use std::collections::HashMap;
|
||||
///
|
||||
/// let mut map = HashMap::new();
|
||||
@@ -722,7 +721,7 @@ pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
|
||||
/// assert_eq!(map.get_key_value(&1), Some((&1, &"a")));
|
||||
/// assert_eq!(map.get_key_value(&2), None);
|
||||
/// ```
|
||||
#[unstable(feature = "map_get_key_value", issue = "49347")]
|
||||
#[stable(feature = "map_get_key_value", since = "1.40.0")]
|
||||
#[inline]
|
||||
pub fn get_key_value<Q: ?Sized>(&self, k: &Q) -> Option<(&K, &V)>
|
||||
where
|
||||
|
||||
@@ -566,7 +566,9 @@ mod prim_array { }
|
||||
#[doc(alias = "[")]
|
||||
#[doc(alias = "]")]
|
||||
#[doc(alias = "[]")]
|
||||
/// A dynamically-sized view into a contiguous sequence, `[T]`.
|
||||
/// A dynamically-sized view into a contiguous sequence, `[T]`. Contiguous here
|
||||
/// means that elements are layed out so that every element is the same
|
||||
/// distance from its neighbors.
|
||||
///
|
||||
/// *[See also the `std::slice` module](slice/index.html).*
|
||||
///
|
||||
|
||||
@@ -54,8 +54,8 @@ macro_rules! t {
|
||||
|
||||
let ret = libc::rtpSpawn(
|
||||
self.get_argv()[0], // executing program
|
||||
self.get_argv().as_ptr() as *const _, // argv
|
||||
*sys::os::environ() as *const *const c_char,
|
||||
self.get_argv().as_ptr() as *mut *const c_char, // argv
|
||||
*sys::os::environ() as *mut *const c_char,
|
||||
100 as c_int, // initial priority
|
||||
thread::min_stack(), // initial stack size.
|
||||
0, // options
|
||||
|
||||
@@ -1546,6 +1546,7 @@ fn calc_result(desc: &TestDesc, task_result: Result<(), Box<dyn Any + Send>>) ->
|
||||
}
|
||||
}
|
||||
}
|
||||
(&ShouldPanic::Yes, Ok(())) => TrFailedMsg("test did not panic as expected".to_string()),
|
||||
_ if desc.allow_fail => TrAllowedFail,
|
||||
_ => TrFailed,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use crate::test::{
|
||||
filter_tests, parse_opts, run_test, DynTestFn, DynTestName, MetricMap, RunIgnored,
|
||||
ShouldPanic, StaticTestName, TestDesc, TestDescAndFn, TestOpts, TrFailed, TrFailedMsg,
|
||||
ShouldPanic, StaticTestName, TestDesc, TestDescAndFn, TestOpts, TrFailedMsg,
|
||||
TrIgnored, TrOk,
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
@@ -167,7 +167,7 @@ fn f() {}
|
||||
let (tx, rx) = channel();
|
||||
run_test(&TestOpts::new(), false, desc, tx, Concurrent::No);
|
||||
let (_, res, _, _) = rx.recv().unwrap();
|
||||
assert!(res == TrFailed);
|
||||
assert!(res == TrFailedMsg("test did not panic as expected".to_string()));
|
||||
}
|
||||
|
||||
fn report_time_test_template(report_time: bool) -> Option<TestExecTime> {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// This test checks that all expected errors occur when there are multiple invalid attributes
|
||||
// on an item.
|
||||
|
||||
#[inline]
|
||||
//~^ ERROR attribute should be applied to function or closure [E0518]
|
||||
#[target_feature(enable = "sse2")]
|
||||
//~^ ERROR attribute should be applied to a function
|
||||
const FOO: u8 = 0;
|
||||
|
||||
fn main() { }
|
||||
@@ -0,0 +1,21 @@
|
||||
error[E0518]: attribute should be applied to function or closure
|
||||
--> $DIR/multiple-invalid.rs:4:1
|
||||
|
|
||||
LL | #[inline]
|
||||
| ^^^^^^^^^
|
||||
...
|
||||
LL | const FOO: u8 = 0;
|
||||
| ------------------ not a function or closure
|
||||
|
||||
error: attribute should be applied to a function
|
||||
--> $DIR/multiple-invalid.rs:6:1
|
||||
|
|
||||
LL | #[target_feature(enable = "sse2")]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
LL |
|
||||
LL | const FOO: u8 = 0;
|
||||
| ------------------ not a function
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0518`.
|
||||
@@ -1,20 +1,38 @@
|
||||
error[E0005]: refutable pattern in local binding: `0u8..=1u8` and `3u8..=std::u8::MAX` not covered
|
||||
--> $DIR/const-pattern-irrefutable.rs:12:9
|
||||
|
|
||||
LL | const a: u8 = 2;
|
||||
| ---------------- constant defined here
|
||||
...
|
||||
LL | let a = 4;
|
||||
| ^ interpreted as a constant pattern, not new variable
|
||||
| ^
|
||||
| |
|
||||
| interpreted as a constant pattern, not a new variable
|
||||
| help: introduce a variable instead: `a_var`
|
||||
|
||||
error[E0005]: refutable pattern in local binding: `0u8..=1u8` and `3u8..=std::u8::MAX` not covered
|
||||
--> $DIR/const-pattern-irrefutable.rs:13:9
|
||||
|
|
||||
LL | pub const b: u8 = 2;
|
||||
| -------------------- constant defined here
|
||||
...
|
||||
LL | let c = 4;
|
||||
| ^ interpreted as a constant pattern, not new variable
|
||||
| ^
|
||||
| |
|
||||
| interpreted as a constant pattern, not a new variable
|
||||
| help: introduce a variable instead: `c_var`
|
||||
|
||||
error[E0005]: refutable pattern in local binding: `0u8..=1u8` and `3u8..=std::u8::MAX` not covered
|
||||
--> $DIR/const-pattern-irrefutable.rs:14:9
|
||||
|
|
||||
LL | pub const d: u8 = 2;
|
||||
| -------------------- constant defined here
|
||||
...
|
||||
LL | let d = 4;
|
||||
| ^ interpreted as a constant pattern, not new variable
|
||||
| ^
|
||||
| |
|
||||
| interpreted as a constant pattern, not a new variable
|
||||
| help: introduce a variable instead: `d_var`
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
fn main() {
|
||||
let gen = |start| { //~ ERROR generators cannot have explicit parameters
|
||||
//~^ ERROR type inside generator must be known in this context
|
||||
yield;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,5 +4,18 @@ error[E0628]: generators cannot have explicit parameters
|
||||
LL | let gen = |start| {
|
||||
| ^^^^^^^
|
||||
|
||||
error: aborting due to previous error
|
||||
error[E0698]: type inside generator must be known in this context
|
||||
--> $DIR/no-parameters-on-generators.rs:4:16
|
||||
|
|
||||
LL | let gen = |start| {
|
||||
| ^^^^^ cannot infer type
|
||||
|
|
||||
note: the type is part of the generator because of this `yield`
|
||||
--> $DIR/no-parameters-on-generators.rs:6:9
|
||||
|
|
||||
LL | yield;
|
||||
| ^^^^^
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0698`.
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
error[E0713]: borrow may still be in use when destructor runs
|
||||
--> $DIR/issue-45696-scribble-on-boxed-borrow.rs:34:5
|
||||
|
|
||||
LL | fn scribbled<'a>(s: Scribble<'a>) -> &'a mut u32 {
|
||||
| -- lifetime `'a` defined here
|
||||
LL | &mut *s.0
|
||||
| ^^^^^^^^^ returning this value requires that `*s.0` is borrowed for `'a`
|
||||
LL | }
|
||||
| - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
|
||||
|
||||
error[E0713]: borrow may still be in use when destructor runs
|
||||
--> $DIR/issue-45696-scribble-on-boxed-borrow.rs:39:5
|
||||
|
|
||||
LL | fn boxed_scribbled<'a>(s: Box<Scribble<'a>>) -> &'a mut u32 {
|
||||
| -- lifetime `'a` defined here
|
||||
LL | &mut *(*s).0
|
||||
| ^^^^^^^^^^^^ returning this value requires that `*s.0` is borrowed for `'a`
|
||||
LL | }
|
||||
| - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
|
||||
|
||||
error[E0713]: borrow may still be in use when destructor runs
|
||||
--> $DIR/issue-45696-scribble-on-boxed-borrow.rs:44:5
|
||||
|
|
||||
LL | fn boxed_boxed_scribbled<'a>(s: Box<Box<Scribble<'a>>>) -> &'a mut u32 {
|
||||
| -- lifetime `'a` defined here
|
||||
LL | &mut *(**s).0
|
||||
| ^^^^^^^^^^^^^ returning this value requires that `*s.0` is borrowed for `'a`
|
||||
LL | }
|
||||
| - here, drop of `s` needs exclusive access to `*s.0`, because the type `Scribble<'_>` implements the `Drop` trait
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0713`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user