Skip to content

Commit f28c7ae

Browse files
committed
Auto merge of #51275 - pnkfelix:nll-diagnostics-revise-check-access-permissions, r=nikomatsakis
NLL diagnostics: revise `fn check_access_permissions` NLL: revise `fn check_access_permissions` so that its (still branchy) shares more code paths between the different cases, and also provide more diagnostics in more cases (though the added diagnostics still do not always meet the quality bar established by AST-borrowck) ---- Transcribing "checklist" suggested by Niko, except I am rendering it as a table to make it clear that I do not regard every item in the list to be a "must have" for landing this PR. goal | does this PR do it? -----|------------------------------ no suggestions for `ref mut` | yes suggestions for direct local assignment (`{ let x = 3; x = 4; }`) | yes (see commits at end) suggestions for direct field assignment (`{ let x = (3, 4); x.0 = 5; }` | yes (see commits at end) suggestions for upvars (`let x = 3; let c = \|\| { &mut x; }`) | yes Note that I added support for a couple of rows via changes that are not strictly part of `fn check_access_permissions`. If desired I can remove those commits from this PR and leave them for a later PR. Fix #51031 Fix #51032 (bug #51191 needs a little more investigation before closing.) Fix #51578
2 parents d692ab4 + 4684649 commit f28c7ae

File tree

58 files changed

+670
-263
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+670
-263
lines changed

src/librustc/hir/map/mod.rs

+42
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,40 @@ impl<'hir> MapEntry<'hir> {
170170
})
171171
}
172172

173+
fn fn_decl(&self) -> Option<&FnDecl> {
174+
match self {
175+
EntryItem(_, _, ref item) => {
176+
match item.node {
177+
ItemFn(ref fn_decl, _, _, _, _, _) => Some(&fn_decl),
178+
_ => None,
179+
}
180+
}
181+
182+
EntryTraitItem(_, _, ref item) => {
183+
match item.node {
184+
TraitItemKind::Method(ref method_sig, _) => Some(&method_sig.decl),
185+
_ => None
186+
}
187+
}
188+
189+
EntryImplItem(_, _, ref item) => {
190+
match item.node {
191+
ImplItemKind::Method(ref method_sig, _) => Some(&method_sig.decl),
192+
_ => None,
193+
}
194+
}
195+
196+
EntryExpr(_, _, ref expr) => {
197+
match expr.node {
198+
ExprClosure(_, ref fn_decl, ..) => Some(&fn_decl),
199+
_ => None,
200+
}
201+
}
202+
203+
_ => None
204+
}
205+
}
206+
173207
fn associated_body(self) -> Option<BodyId> {
174208
match self {
175209
EntryItem(_, _, item) => {
@@ -502,6 +536,14 @@ impl<'hir> Map<'hir> {
502536
self.forest.krate.body(id)
503537
}
504538

539+
pub fn fn_decl(&self, node_id: ast::NodeId) -> Option<FnDecl> {
540+
if let Some(entry) = self.find_entry(node_id) {
541+
entry.fn_decl().map(|fd| fd.clone())
542+
} else {
543+
bug!("no entry for node_id `{}`", node_id)
544+
}
545+
}
546+
505547
/// Returns the `NodeId` that corresponds to the definition of
506548
/// which this is the body of, i.e. a `fn`, `const` or `static`
507549
/// item (possibly associated), a closure, or a `hir::AnonConst`.

src/librustc/mir/mod.rs

+91-9
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ impl<'tcx> Mir<'tcx> {
228228
pub fn temps_iter<'a>(&'a self) -> impl Iterator<Item=Local> + 'a {
229229
(self.arg_count+1..self.local_decls.len()).filter_map(move |index| {
230230
let local = Local::new(index);
231-
if self.local_decls[local].is_user_variable {
231+
if self.local_decls[local].is_user_variable.is_some() {
232232
None
233233
} else {
234234
Some(local)
@@ -241,7 +241,7 @@ impl<'tcx> Mir<'tcx> {
241241
pub fn vars_iter<'a>(&'a self) -> impl Iterator<Item=Local> + 'a {
242242
(self.arg_count+1..self.local_decls.len()).filter_map(move |index| {
243243
let local = Local::new(index);
244-
if self.local_decls[local].is_user_variable {
244+
if self.local_decls[local].is_user_variable.is_some() {
245245
Some(local)
246246
} else {
247247
None
@@ -255,7 +255,7 @@ impl<'tcx> Mir<'tcx> {
255255
(1..self.local_decls.len()).filter_map(move |index| {
256256
let local = Local::new(index);
257257
let decl = &self.local_decls[local];
258-
if (decl.is_user_variable || index < self.arg_count + 1)
258+
if (decl.is_user_variable.is_some() || index < self.arg_count + 1)
259259
&& decl.mutability == Mutability::Mut
260260
{
261261
Some(local)
@@ -351,7 +351,7 @@ impl<'tcx> IndexMut<BasicBlock> for Mir<'tcx> {
351351
}
352352
}
353353

354-
#[derive(Clone, Debug)]
354+
#[derive(Copy, Clone, Debug)]
355355
pub enum ClearCrossCrate<T> {
356356
Clear,
357357
Set(T)
@@ -382,6 +382,16 @@ pub enum Mutability {
382382
Not,
383383
}
384384

385+
impl From<Mutability> for hir::Mutability {
386+
fn from(m: Mutability) -> Self {
387+
match m {
388+
Mutability::Mut => hir::MutMutable,
389+
Mutability::Not => hir::MutImmutable,
390+
}
391+
}
392+
}
393+
394+
385395
#[derive(Copy, Clone, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)]
386396
pub enum BorrowKind {
387397
/// Data must be immutable and is aliasable.
@@ -463,6 +473,33 @@ pub enum LocalKind {
463473
ReturnPointer,
464474
}
465475

476+
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)]
477+
pub struct VarBindingForm {
478+
/// Is variable bound via `x`, `mut x`, `ref x`, or `ref mut x`?
479+
pub binding_mode: ty::BindingMode,
480+
/// If an explicit type was provided for this variable binding,
481+
/// this holds the source Span of that type.
482+
///
483+
/// NOTE: If you want to change this to a `HirId`, be wary that
484+
/// doing so breaks incremental compilation (as of this writing),
485+
/// while a `Span` does not cause our tests to fail.
486+
pub opt_ty_info: Option<Span>,
487+
}
488+
489+
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, RustcEncodable, RustcDecodable)]
490+
pub enum BindingForm {
491+
/// This is a binding for a non-`self` binding, or a `self` that has an explicit type.
492+
Var(VarBindingForm),
493+
/// Binding for a `self`/`&self`/`&mut self` binding where the type is implicit.
494+
ImplicitSelf,
495+
}
496+
497+
CloneTypeFoldableAndLiftImpls! { BindingForm, }
498+
499+
impl_stable_hash_for!(struct self::VarBindingForm { binding_mode, opt_ty_info });
500+
501+
impl_stable_hash_for!(enum self::BindingForm { Var(binding), ImplicitSelf, });
502+
466503
/// A MIR local.
467504
///
468505
/// This can be a binding declared by the user, a temporary inserted by the compiler, a function
@@ -474,8 +511,14 @@ pub struct LocalDecl<'tcx> {
474511
/// Temporaries and the return place are always mutable.
475512
pub mutability: Mutability,
476513

477-
/// True if this corresponds to a user-declared local variable.
478-
pub is_user_variable: bool,
514+
/// Some(binding_mode) if this corresponds to a user-declared local variable.
515+
///
516+
/// This is solely used for local diagnostics when generating
517+
/// warnings/errors when compiling the current crate, and
518+
/// therefore it need not be visible across crates. pnkfelix
519+
/// currently hypothesized we *need* to wrap this in a
520+
/// `ClearCrossCrate` as long as it carries as `HirId`.
521+
pub is_user_variable: Option<ClearCrossCrate<BindingForm>>,
479522

480523
/// True if this is an internal local
481524
///
@@ -592,6 +635,45 @@ pub struct LocalDecl<'tcx> {
592635
}
593636

594637
impl<'tcx> LocalDecl<'tcx> {
638+
/// Returns true only if local is a binding that can itself be
639+
/// made mutable via the addition of the `mut` keyword, namely
640+
/// something like the occurrences of `x` in:
641+
/// - `fn foo(x: Type) { ... }`,
642+
/// - `let x = ...`,
643+
/// - or `match ... { C(x) => ... }`
644+
pub fn can_be_made_mutable(&self) -> bool
645+
{
646+
match self.is_user_variable {
647+
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
648+
binding_mode: ty::BindingMode::BindByValue(_),
649+
opt_ty_info: _,
650+
}))) => true,
651+
652+
// FIXME: might be able to thread the distinction between
653+
// `self`/`mut self`/`&self`/`&mut self` into the
654+
// `BindingForm::ImplicitSelf` variant, (and then return
655+
// true here for solely the first case).
656+
_ => false,
657+
}
658+
}
659+
660+
/// Returns true if local is definitely not a `ref ident` or
661+
/// `ref mut ident` binding. (Such bindings cannot be made into
662+
/// mutable bindings, but the inverse does not necessarily hold).
663+
pub fn is_nonref_binding(&self) -> bool
664+
{
665+
match self.is_user_variable {
666+
Some(ClearCrossCrate::Set(BindingForm::Var(VarBindingForm {
667+
binding_mode: ty::BindingMode::BindByValue(_),
668+
opt_ty_info: _,
669+
}))) => true,
670+
671+
Some(ClearCrossCrate::Set(BindingForm::ImplicitSelf)) => true,
672+
673+
_ => false,
674+
}
675+
}
676+
595677
/// Create a new `LocalDecl` for a temporary.
596678
#[inline]
597679
pub fn new_temp(ty: Ty<'tcx>, span: Span) -> Self {
@@ -605,7 +687,7 @@ impl<'tcx> LocalDecl<'tcx> {
605687
},
606688
visibility_scope: OUTERMOST_SOURCE_SCOPE,
607689
internal: false,
608-
is_user_variable: false
690+
is_user_variable: None,
609691
}
610692
}
611693

@@ -622,7 +704,7 @@ impl<'tcx> LocalDecl<'tcx> {
622704
},
623705
visibility_scope: OUTERMOST_SOURCE_SCOPE,
624706
internal: true,
625-
is_user_variable: false
707+
is_user_variable: None,
626708
}
627709
}
628710

@@ -641,7 +723,7 @@ impl<'tcx> LocalDecl<'tcx> {
641723
visibility_scope: OUTERMOST_SOURCE_SCOPE,
642724
internal: false,
643725
name: None, // FIXME maybe we do want some name here?
644-
is_user_variable: false
726+
is_user_variable: None,
645727
}
646728
}
647729
}

src/librustc/ty/binding.rs

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub enum BindingMode {
1818
BindByValue(Mutability),
1919
}
2020

21+
CloneTypeFoldableAndLiftImpls! { BindingMode, }
22+
2123
impl BindingMode {
2224
pub fn convert(ba: BindingAnnotation) -> BindingMode {
2325
match ba {

src/librustc_mir/borrow_check/error_reporting.rs

+17-3
Original file line numberDiff line numberDiff line change
@@ -593,11 +593,18 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
593593
err.emit();
594594
}
595595

596+
/// Reports an illegal reassignment; for example, an assignment to
597+
/// (part of) a non-`mut` local that occurs potentially after that
598+
/// local has already been initialized. `place` is the path being
599+
/// assigned; `err_place` is a place providing a reason why
600+
/// `place` is not mutable (e.g. the non-`mut` local `x` in an
601+
/// assignment to `x.f`).
596602
pub(super) fn report_illegal_reassignment(
597603
&mut self,
598604
_context: Context,
599605
(place, span): (&Place<'tcx>, Span),
600606
assigned_span: Span,
607+
err_place: &Place<'tcx>,
601608
) {
602609
let is_arg = if let Place::Local(local) = place {
603610
if let LocalKind::Arg = self.mir.local_kind(*local) {
@@ -621,16 +628,23 @@ impl<'cx, 'gcx, 'tcx> MirBorrowckCtxt<'cx, 'gcx, 'tcx> {
621628
"cannot assign twice to immutable variable"
622629
};
623630
if span != assigned_span {
624-
if is_arg {
625-
err.span_label(assigned_span, "argument not declared as `mut`");
626-
} else {
631+
if !is_arg {
627632
let value_msg = match self.describe_place(place) {
628633
Some(name) => format!("`{}`", name),
629634
None => "value".to_owned(),
630635
};
631636
err.span_label(assigned_span, format!("first assignment to {}", value_msg));
632637
}
633638
}
639+
if let Place::Local(local) = err_place {
640+
let local_decl = &self.mir.local_decls[*local];
641+
if let Some(name) = local_decl.name {
642+
if local_decl.can_be_made_mutable() {
643+
err.span_label(local_decl.source_info.span,
644+
format!("consider changing this to `mut {}`", name));
645+
}
646+
}
647+
}
634648
err.span_label(span, msg);
635649
err.emit();
636650
}

0 commit comments

Comments
 (0)