Merge branch 'master' into format-temporaries

This commit is contained in:
Jon Gjengset
2019-09-28 09:25:47 -04:00
110 changed files with 619 additions and 6620 deletions
+1
View File
@@ -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
View File
@@ -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",
-4
View File
@@ -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
+1 -2
View File
@@ -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
+2
View File
@@ -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")]
-1
View File
@@ -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
}
}
+1 -1
View File
@@ -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
-2
View File
@@ -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;
+52 -20
View File
@@ -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:
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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>),
+3 -1
View File
@@ -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);
}
}
+3 -5
View File
@@ -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);
-1
View File
@@ -100,7 +100,6 @@
pub mod lint;
pub mod middle {
pub mod borrowck;
pub mod expr_use_visitor;
pub mod cstore;
pub mod dead;
-31
View File
@@ -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);
}
}
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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),
+1 -1
View File
@@ -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()
+1 -5
View File
@@ -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() }
}
-18
View File
@@ -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),
-8
View File
@@ -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;
+1 -1
View File
@@ -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;
+1 -28
View File
@@ -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
+3 -4
View File
@@ -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 {
+1 -1
View File
@@ -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,
+1 -2
View File
@@ -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
+2 -2
View File
@@ -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() {
+1 -1
View File
@@ -97,7 +97,7 @@ pub fn implied_outlives_bounds(
let mut orig_values = OriginalQueryValues::default();
let key = self.canonicalize_query(&param_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 -7
View File
@@ -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! {
+2 -19
View File
@@ -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 -37
View File
@@ -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 -7
View File
@@ -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! {
+1 -1
View File
@@ -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,
+1 -2
View File
@@ -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()
}
}
+4 -23
View File
@@ -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)
}
}
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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]
+3 -3
View File
@@ -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)
+4 -4
View File
@@ -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
}
+1 -1
View File
@@ -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;
-1
View File
@@ -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;
+8 -8
View File
@@ -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)
})
}
+2 -39
View File
@@ -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
}
}
}
}
-20
View File
@@ -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" }
-1167
View File
@@ -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) = &LT' Ty OR &LT' 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) = &LT' 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) = &LT' 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)
}
}
}
}
-621
View File
@@ -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
}
}
-545
View File
@@ -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,
}
-119
View File
@@ -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) }
}
-55
View File
@@ -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)
}
}
-672
View File
@@ -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(&param.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 }
}
-145
View File
@@ -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) }
}
-23
View File
@@ -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;
+1 -2
View File
@@ -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
View File
@@ -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);
-1
View File
@@ -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" }
-8
View File
@@ -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));
});
+2 -2
View File
@@ -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);
}
}
+20 -42
View File
@@ -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")
+2 -2
View File
@@ -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,
+4 -5
View File
@@ -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> {
+55 -28
View File
@@ -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)
}
+1 -1
View File
@@ -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;
+1 -11
View File
@@ -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;
-4
View File
@@ -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);
+1 -1
View File
@@ -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),
}
+3 -6
View File
@@ -1269,7 +1269,7 @@ fn conv_object_ty_poly_trait_ref(&self,
// to avoid ICEs.
for item in &regular_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(
+1 -10
View File
@@ -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!(
+8 -4
View File
@@ -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,
+3 -3
View File
@@ -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;
+24 -24
View File
@@ -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,
+1 -3
View File
@@ -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,
+1 -2
View File
@@ -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
+3 -1
View File
@@ -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
+1
View File
@@ -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 -2
View File
@@ -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