Skip to content

Commit 7fd4b52

Browse files
committed
NLL: Broad rewrite of check_access_perimssions.
Tried to unify various common code paths and also vaguely approximate the AST-borrowck diagnostics. The change in (subjective) quality of diagnostics is not a universal improvement. But I think this is a better code base to work from for future fixes.
1 parent 6dfed7e commit 7fd4b52

File tree

1 file changed

+205
-100
lines changed
  • src/librustc_mir/borrow_check

1 file changed

+205
-100
lines changed

src/librustc_mir/borrow_check/mod.rs

Lines changed: 205 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use rustc::infer::InferCtxt;
1818
use rustc::ty::{self, ParamEnv, TyCtxt};
1919
use rustc::ty::query::Providers;
2020
use rustc::lint::builtin::UNUSED_MUT;
21-
use rustc::mir::{AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind};
21+
use rustc::mir::{self, AggregateKind, BasicBlock, BorrowCheckResult, BorrowKind};
2222
use rustc::mir::{ClearCrossCrate, Local, Location, Place, Mir, Mutability, Operand};
2323
use rustc::mir::{Projection, ProjectionElem, Rvalue, Field, Statement, StatementKind};
2424
use rustc::mir::{Terminator, TerminatorKind};
@@ -1658,36 +1658,6 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
16581658
}
16591659
}
16601660

1661-
fn specialized_description(&self, place:&Place<'tcx>) -> Option<String>{
1662-
if let Some(_name) = self.describe_place(place) {
1663-
Some(format!("data in a `&` reference"))
1664-
} else {
1665-
None
1666-
}
1667-
}
1668-
1669-
fn get_default_err_msg(&self, place:&Place<'tcx>) -> String{
1670-
match self.describe_place(place) {
1671-
Some(name) => format!("immutable item `{}`", name),
1672-
None => "immutable item".to_owned(),
1673-
}
1674-
}
1675-
1676-
fn get_secondary_err_msg(&self, place:&Place<'tcx>) -> String{
1677-
match self.specialized_description(place) {
1678-
Some(_) => format!("data in a `&` reference"),
1679-
None => self.get_default_err_msg(place)
1680-
}
1681-
}
1682-
1683-
fn get_primary_err_msg(&self, place:&Place<'tcx>) -> String{
1684-
if let Some(name) = self.describe_place(place) {
1685-
format!("`{}` is a `&` reference, so the data it refers to cannot be written", name)
1686-
} else {
1687-
format!("cannot assign through `&`-reference")
1688-
}
1689-
}
1690-
16911661
/// Check the permissions for the given place and read or write kind
16921662
///
16931663
/// Returns true if an error is reported, false otherwise.
@@ -1703,6 +1673,14 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
17031673
place, kind, is_local_mutation_allowed
17041674
);
17051675

1676+
#[derive(Copy, Clone, Debug)]
1677+
enum AccessKind {
1678+
MutableBorrow,
1679+
Mutate,
1680+
}
1681+
let error_access;
1682+
let the_place_err;
1683+
17061684
match kind {
17071685
Reservation(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Unique))
17081686
| Reservation(WriteKind::MutableBorrow(borrow_kind @ BorrowKind::Mut { .. }))
@@ -1720,20 +1698,8 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
17201698
return false;
17211699
}
17221700
Err(place_err) => {
1723-
let item_msg = self.get_default_err_msg(place);
1724-
let mut err = self.tcx
1725-
.cannot_borrow_path_as_mutable(span, &item_msg, Origin::Mir);
1726-
err.span_label(span, "cannot borrow as mutable");
1727-
1728-
if place != place_err {
1729-
if let Some(name) = self.describe_place(place_err) {
1730-
err.note(&format!("the value which is causing this path not to be \
1731-
mutable is...: `{}`", name));
1732-
}
1733-
}
1734-
1735-
err.emit();
1736-
return true;
1701+
error_access = AccessKind::MutableBorrow;
1702+
the_place_err = place_err;
17371703
}
17381704
}
17391705
}
@@ -1744,64 +1710,12 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
17441710
return false;
17451711
}
17461712
Err(place_err) => {
1747-
let err_info = if let Place::Projection(
1748-
box Projection {
1749-
base: Place::Local(local),
1750-
elem: ProjectionElem::Deref
1751-
}
1752-
) = *place_err {
1753-
let locations = self.mir.find_assignments(local);
1754-
if locations.len() > 0 {
1755-
let item_msg = self.get_secondary_err_msg(&Place::Local(local));
1756-
let sp = self.mir.source_info(locations[0]).span;
1757-
let mut to_suggest_span = String::new();
1758-
if let Ok(src) =
1759-
self.tcx.sess.codemap().span_to_snippet(sp) {
1760-
to_suggest_span = src[1..].to_string();
1761-
};
1762-
Some((sp,
1763-
"consider changing this to be a \
1764-
mutable reference",
1765-
to_suggest_span,
1766-
item_msg,
1767-
self.get_primary_err_msg(&Place::Local(local))))
1768-
} else {
1769-
None
1770-
}
1771-
} else {
1772-
None
1773-
};
1774-
1775-
if let Some((err_help_span,
1776-
err_help_stmt,
1777-
to_suggest_span,
1778-
item_msg,
1779-
sec_span)) = err_info {
1780-
let mut err = self.tcx.cannot_assign(span, &item_msg, Origin::Mir);
1781-
err.span_suggestion(err_help_span,
1782-
err_help_stmt,
1783-
format!("&mut {}", to_suggest_span));
1784-
if place != place_err {
1785-
err.span_label(span, sec_span);
1786-
}
1787-
err.emit()
1788-
} else {
1789-
let item_msg = self.get_default_err_msg(place);
1790-
let mut err = self.tcx.cannot_assign(span, &item_msg, Origin::Mir);
1791-
err.span_label(span, "cannot mutate");
1792-
if place != place_err {
1793-
if let Some(name) = self.describe_place(place_err) {
1794-
err.note(&format!("the value which is causing this path not \
1795-
to be mutable is...: `{}`", name));
1796-
}
1797-
}
1798-
err.emit();
1799-
}
1800-
1801-
return true;
1713+
error_access = AccessKind::Mutate;
1714+
the_place_err = place_err;
18021715
}
18031716
}
18041717
}
1718+
18051719
Reservation(WriteKind::Move)
18061720
| Write(WriteKind::Move)
18071721
| Reservation(WriteKind::StorageDeadOrDrop)
@@ -1831,6 +1745,197 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
18311745
return false;
18321746
}
18331747
}
1748+
1749+
// at this point, we have set up the error reporting state.
1750+
1751+
let mut err;
1752+
let item_msg = match self.describe_place(place) {
1753+
Some(name) => format!("immutable item `{}`", name),
1754+
None => "immutable item".to_owned(),
1755+
};
1756+
1757+
// `act` and `acted_on` are strings that let us abstract over
1758+
// the verbs used in some diagnostic messages.
1759+
let act; let acted_on;
1760+
1761+
match error_access {
1762+
AccessKind::Mutate => {
1763+
let item_msg = match the_place_err {
1764+
Place::Projection(box Projection {
1765+
base: _,
1766+
elem: ProjectionElem::Deref }
1767+
) => match self.describe_place(place) {
1768+
Some(description) =>
1769+
format!("`{}` which is behind a `&` reference", description),
1770+
None => format!("data in a `&` reference"),
1771+
},
1772+
_ => item_msg,
1773+
};
1774+
err = self.tcx.cannot_assign(span, &item_msg, Origin::Mir);
1775+
act = "assign"; acted_on = "written";
1776+
}
1777+
AccessKind::MutableBorrow => {
1778+
err = self.tcx
1779+
.cannot_borrow_path_as_mutable(span, &item_msg, Origin::Mir);
1780+
act = "borrow as mutable"; acted_on = "borrowed as mutable";
1781+
}
1782+
}
1783+
1784+
match the_place_err {
1785+
// We want to suggest users use `let mut` for local (user
1786+
// variable) mutations...
1787+
Place::Local(local) if local_can_be_made_mutable(self.mir, *local) => {
1788+
// ... but it doesn't make sense to suggest it on
1789+
// variables that are `ref x`, `ref mut x`, `&self`,
1790+
// or `&mut self` (such variables are simply not
1791+
// mutable)..
1792+
let local_decl = &self.mir.local_decls[*local];
1793+
assert_eq!(local_decl.mutability, Mutability::Not);
1794+
1795+
err.span_label(span, format!("cannot {ACT}", ACT=act));
1796+
err.span_suggestion(local_decl.source_info.span,
1797+
"consider changing this to be mutable",
1798+
format!("mut {}", local_decl.name.unwrap()));
1799+
}
1800+
1801+
// complete hack to approximate old AST-borrowck
1802+
// diagnostic: if the span starts with a mutable borrow of
1803+
// a local variable, then just suggest the user remove it.
1804+
Place::Local(_) if {
1805+
if let Ok(snippet) = self.tcx.sess.codemap().span_to_snippet(span) {
1806+
snippet.starts_with("&mut ")
1807+
} else {
1808+
false
1809+
}
1810+
} => {
1811+
err.span_label(span, format!("cannot {ACT}", ACT=act));
1812+
err.span_label(span, "try removing `&mut` here");
1813+
}
1814+
1815+
// We want to point out when a `&` can be readily replaced
1816+
// with an `&mut`.
1817+
//
1818+
// FIXME: can this case be generalized to work for an
1819+
// arbitrary base for the projection?
1820+
Place::Projection(box Projection { base: Place::Local(local),
1821+
elem: ProjectionElem::Deref })
1822+
if local_is_nonref_binding(self.mir, *local) =>
1823+
{
1824+
let (err_help_span, suggested_code) =
1825+
find_place_to_suggest_ampmut(self.tcx, self.mir, *local);
1826+
err.span_suggestion(err_help_span,
1827+
"consider changing this to be a mutable reference",
1828+
suggested_code);
1829+
1830+
let local_decl = &self.mir.local_decls[*local];
1831+
if let Some(name) = local_decl.name {
1832+
err.span_label(
1833+
span, format!("`{NAME}` is a `&` reference, \
1834+
so the data it refers to cannot be {ACTED_ON}",
1835+
NAME=name, ACTED_ON=acted_on));
1836+
} else {
1837+
err.span_label(span, format!("cannot {ACT} through `&`-reference", ACT=act));
1838+
}
1839+
}
1840+
1841+
_ => {
1842+
err.span_label(span, format!("cannot {ACT}", ACT=act));
1843+
}
1844+
}
1845+
1846+
err.emit();
1847+
return true;
1848+
1849+
// Returns true if local is a binding that can itself be made
1850+
// mutable via the addition of the `mut` keyword, namely
1851+
// something like:
1852+
// - `fn foo(x: Type) { ... }`,
1853+
// - `let x = ...`,
1854+
// - or `match ... { C(x) => ... }`
1855+
fn local_can_be_made_mutable(mir: &Mir, local: mir::Local) -> bool
1856+
{
1857+
let local = &mir.local_decls[local];
1858+
match local.is_user_variable {
1859+
Some(ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
1860+
binding_mode: ty::BindingMode::BindByValue(_),
1861+
opt_ty_info: _,
1862+
}))) => true,
1863+
1864+
_ => false,
1865+
}
1866+
}
1867+
1868+
// Returns true if local is definitely not a `ref ident` or
1869+
// `ref mut ident` binding. (Such bindings cannot be made into
1870+
// mutable bindings.)
1871+
fn local_is_nonref_binding(mir: &Mir, local: mir::Local) -> bool
1872+
{
1873+
let local = &mir.local_decls[local];
1874+
match local.is_user_variable {
1875+
Some(ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
1876+
binding_mode: ty::BindingMode::BindByValue(_),
1877+
opt_ty_info: _,
1878+
}))) => true,
1879+
1880+
Some(ClearCrossCrate::Set(mir::BindingForm::ImplicitSelf)) => true,
1881+
1882+
_ => false,
1883+
}
1884+
}
1885+
1886+
// Returns the span to highlight and the associated text to
1887+
// present when suggesting that the user use an `&mut`.
1888+
//
1889+
// When we want to suggest a user change a local variable to be a `&mut`, there
1890+
// are three potential "obvious" things to highlight:
1891+
//
1892+
// let ident [: Type] [= RightHandSideExresssion];
1893+
// ^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
1894+
// (1.) (2.) (3.)
1895+
//
1896+
// We can always fallback on highlighting the first. But chances are good that
1897+
// the user experience will be better if we highlight one of the others if possible;
1898+
// for example, if the RHS is present and the Type is not, then the type is going to
1899+
// be inferred *from* the RHS, which means we should highlight that (and suggest
1900+
// that they borrow the RHS mutably).
1901+
fn find_place_to_suggest_ampmut<'cx, 'gcx, 'tcx>(tcx: TyCtxt<'cx, 'gcx, 'tcx>,
1902+
mir: &Mir<'tcx>,
1903+
local: Local) -> (Span, String)
1904+
{
1905+
// This implementation attempts to emulate AST-borrowck prioritization
1906+
// by trying (3.), then (2.) and finally falling back on (1.).
1907+
let locations = mir.find_assignments(local);
1908+
if locations.len() > 0 {
1909+
let assignment_rhs_span = mir.source_info(locations[0]).span;
1910+
let snippet = tcx.sess.codemap().span_to_snippet(assignment_rhs_span);
1911+
if let Ok(src) = snippet {
1912+
// pnkfelix inherited code; believes intention is
1913+
// highlighted text will always be `&<expr>` and
1914+
// thus can transform to `&mut` by slicing off
1915+
// first ASCII character and prepending "&mut ".
1916+
let borrowed_expr = src[1..].to_string();
1917+
return (assignment_rhs_span, format!("&mut {}", borrowed_expr));
1918+
}
1919+
}
1920+
1921+
let local_decl = &mir.local_decls[local];
1922+
let highlight_span = match local_decl.is_user_variable {
1923+
// if this is a variable binding with an explicit type,
1924+
// try to highlight that for the suggestion.
1925+
Some(ClearCrossCrate::Set(mir::BindingForm::Var(mir::VarBindingForm {
1926+
opt_ty_info: Some(ty_span), .. }))) => ty_span,
1927+
1928+
Some(ClearCrossCrate::Clear) => bug!("saw cleared local state"),
1929+
1930+
// otherwise, just highlight the span associated with
1931+
// the (MIR) LocalDecl.
1932+
_ => local_decl.source_info.span,
1933+
};
1934+
1935+
let ty_mut = local_decl.ty.builtin_deref(true).unwrap();
1936+
assert_eq!(ty_mut.mutbl, hir::MutImmutable);
1937+
return (highlight_span, format!("&mut {}", ty_mut.ty));
1938+
}
18341939
}
18351940

18361941
/// Adds the place into the used mutable variables set

0 commit comments

Comments
 (0)