Skip to content

Commit

Permalink
Rollup merge of rust-lang#76468 - SNCPlay42:lifetime-names, r=Mark-Si…
Browse files Browse the repository at this point in the history
…mulacrum

Improve lifetime name annotations for closures & async functions

* Don't refer to async functions as "generators" in error output
* Where possible, emit annotations pointing exactly at the `&` in the return type of closures (when they have explicit return types) and async functions, like we do for arguments.
Addresses rust-lang#74072, but I wouldn't call that *closed* until annotations are identical for async and non-async functions.
* Emit a better annotation when the lifetime doesn't appear in the full name type, which currently happens for opaque types like `impl Future`. Addresses rust-lang#74497, but further improves could probably be made (why *doesn't* it appear in the type as `impl Future + '1`?)
This is included in the same PR because the changes to `give_name_if_anonymous_region_appears_in_output` would introduce ICE otherwise (it would return `None` in cases where it didn't previously, which then gets `unwrap`ped)
  • Loading branch information
Dylan-DPC committed Nov 9, 2020
2 parents 46bce9f + 61b52a3 commit 99f16e6
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 46 deletions.
204 changes: 161 additions & 43 deletions compiler/rustc_mir/src/borrow_check/diagnostics/region_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use rustc_hir::def::{DefKind, Res};
use rustc_middle::ty::print::RegionHighlightMode;
use rustc_middle::ty::subst::{GenericArgKind, SubstsRef};
use rustc_middle::ty::{self, RegionVid, Ty};
use rustc_span::symbol::kw;
use rustc_span::{symbol::Symbol, Span, DUMMY_SP};
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::{Span, DUMMY_SP};

use crate::borrow_check::{nll::ToRegionVid, universal_regions::DefiningTy, MirBorrowckCtxt};

Expand Down Expand Up @@ -39,7 +39,7 @@ crate enum RegionNameSource {
/// The region corresponding to a closure upvar.
AnonRegionFromUpvar(Span, String),
/// The region corresponding to the return type of a closure.
AnonRegionFromOutput(Span, String, String),
AnonRegionFromOutput(RegionNameHighlight, String),
/// The region from a type yielded by a generator.
AnonRegionFromYieldTy(Span, String),
/// An anonymous region from an async fn.
Expand All @@ -57,6 +57,10 @@ crate enum RegionNameHighlight {
/// The anonymous region corresponds to a region where the type annotation is completely missing
/// from the code, e.g. in a closure arguments `|x| { ... }`, where `x` is a reference.
CannotMatchHirTy(Span, String),
/// The anonymous region corresponds to a region where the type annotation is completely missing
/// from the code, and *even if* we print out the full name of the type, the region name won't
/// be included. This currently occurs for opaque types like `impl Future`.
Occluded(Span, String),
}

impl RegionName {
Expand All @@ -81,13 +85,14 @@ impl RegionName {
| RegionNameSource::NamedFreeRegion(span)
| RegionNameSource::SynthesizedFreeEnvRegion(span, _)
| RegionNameSource::AnonRegionFromUpvar(span, _)
| RegionNameSource::AnonRegionFromOutput(span, _, _)
| RegionNameSource::AnonRegionFromYieldTy(span, _)
| RegionNameSource::AnonRegionFromAsyncFn(span) => Some(span),
RegionNameSource::AnonRegionFromArgument(ref highlight) => match *highlight {
RegionNameSource::AnonRegionFromArgument(ref highlight)
| RegionNameSource::AnonRegionFromOutput(ref highlight, _) => match *highlight {
RegionNameHighlight::MatchedHirTy(span)
| RegionNameHighlight::MatchedAdtAndSegment(span)
| RegionNameHighlight::CannotMatchHirTy(span, _) => Some(span),
| RegionNameHighlight::CannotMatchHirTy(span, _)
| RegionNameHighlight::Occluded(span, _) => Some(span),
},
}
}
Expand All @@ -112,6 +117,7 @@ impl RegionName {
diag.span_label(*span, format!("has type `{}`", type_name));
}
RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::MatchedHirTy(span))
| RegionNameSource::AnonRegionFromOutput(RegionNameHighlight::MatchedHirTy(span), _)
| RegionNameSource::AnonRegionFromAsyncFn(span) => {
diag.span_label(
*span,
Expand All @@ -120,16 +126,44 @@ impl RegionName {
}
RegionNameSource::AnonRegionFromArgument(
RegionNameHighlight::MatchedAdtAndSegment(span),
)
| RegionNameSource::AnonRegionFromOutput(
RegionNameHighlight::MatchedAdtAndSegment(span),
_,
) => {
diag.span_label(*span, format!("let's call this `{}`", self));
}
RegionNameSource::AnonRegionFromArgument(RegionNameHighlight::Occluded(
span,
type_name,
)) => {
diag.span_label(
*span,
format!("lifetime `{}` appears in the type {}", self, type_name),
);
}
RegionNameSource::AnonRegionFromOutput(
RegionNameHighlight::Occluded(span, type_name),
mir_description,
) => {
diag.span_label(
*span,
format!(
"return type{} `{}` contains a lifetime `{}`",
mir_description, type_name, self
),
);
}
RegionNameSource::AnonRegionFromUpvar(span, upvar_name) => {
diag.span_label(
*span,
format!("lifetime `{}` appears in the type of `{}`", self, upvar_name),
);
}
RegionNameSource::AnonRegionFromOutput(span, mir_description, type_name) => {
RegionNameSource::AnonRegionFromOutput(
RegionNameHighlight::CannotMatchHirTy(span, type_name),
mir_description,
) => {
diag.span_label(*span, format!("return type{} is {}", mir_description, type_name));
}
RegionNameSource::AnonRegionFromYieldTy(span, type_name) => {
Expand Down Expand Up @@ -349,19 +383,21 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
argument_index,
);

self.get_argument_hir_ty_for_highlighting(argument_index)
let highlight = self
.get_argument_hir_ty_for_highlighting(argument_index)
.and_then(|arg_hir_ty| self.highlight_if_we_can_match_hir_ty(fr, arg_ty, arg_hir_ty))
.or_else(|| {
.unwrap_or_else(|| {
// `highlight_if_we_cannot_match_hir_ty` needs to know the number we will give to
// the anonymous region. If it succeeds, the `synthesize_region_name` call below
// will increment the counter, "reserving" the number we just used.
let counter = *self.next_region_name.try_borrow().unwrap();
self.highlight_if_we_cannot_match_hir_ty(fr, arg_ty, span, counter)
})
.map(|highlight| RegionName {
name: self.synthesize_region_name(),
source: RegionNameSource::AnonRegionFromArgument(highlight),
})
});

Some(RegionName {
name: self.synthesize_region_name(),
source: RegionNameSource::AnonRegionFromArgument(highlight),
})
}

fn get_argument_hir_ty_for_highlighting(
Expand Down Expand Up @@ -399,7 +435,7 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
ty: Ty<'tcx>,
span: Span,
counter: usize,
) -> Option<RegionNameHighlight> {
) -> RegionNameHighlight {
let mut highlight = RegionHighlightMode::default();
highlight.highlighting_region_vid(needle_fr, counter);
let type_name =
Expand All @@ -411,9 +447,9 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
);
if type_name.find(&format!("'{}", counter)).is_some() {
// Only add a label if we can confirm that a region was labelled.
Some(RegionNameHighlight::CannotMatchHirTy(span, type_name))
RegionNameHighlight::CannotMatchHirTy(span, type_name)
} else {
None
RegionNameHighlight::Occluded(span, type_name)
}
}

Expand Down Expand Up @@ -643,49 +679,131 @@ impl<'tcx> MirBorrowckCtxt<'_, 'tcx> {
/// or be early bound (named, not in argument).
fn give_name_if_anonymous_region_appears_in_output(&self, fr: RegionVid) -> Option<RegionName> {
let tcx = self.infcx.tcx;
let hir = tcx.hir();

let return_ty = self.regioncx.universal_regions().unnormalized_output_ty;
debug!("give_name_if_anonymous_region_appears_in_output: return_ty = {:?}", return_ty);
if !tcx.any_free_region_meets(&return_ty, |r| r.to_region_vid() == fr) {
return None;
}

let mut highlight = RegionHighlightMode::default();
highlight.highlighting_region_vid(fr, *self.next_region_name.try_borrow().unwrap());
let type_name =
self.infcx.extract_inference_diagnostics_data(return_ty.into(), Some(highlight)).name;
let mir_hir_id = self.mir_hir_id();

let (return_span, mir_description) = match tcx.hir().get(self.mir_hir_id()) {
let (return_span, mir_description, hir_ty) = match hir.get(mir_hir_id) {
hir::Node::Expr(hir::Expr {
kind: hir::ExprKind::Closure(_, return_ty, _, span, gen_move),
..
}) => (
match return_ty.output {
hir::FnRetTy::DefaultReturn(_) => tcx.sess.source_map().end_point(*span),
hir::FnRetTy::Return(_) => return_ty.output.span(),
},
if gen_move.is_some() { " of generator" } else { " of closure" },
),
hir::Node::ImplItem(hir::ImplItem {
kind: hir::ImplItemKind::Fn(method_sig, _),
kind: hir::ExprKind::Closure(_, return_ty, body_id, span, _),
..
}) => (method_sig.decl.output.span(), ""),
_ => (self.body.span, ""),
}) => {
let (mut span, mut hir_ty) = match return_ty.output {
hir::FnRetTy::DefaultReturn(_) => {
(tcx.sess.source_map().end_point(*span), None)
}
hir::FnRetTy::Return(hir_ty) => (return_ty.output.span(), Some(hir_ty)),
};
let mir_description = match hir.body(*body_id).generator_kind {
Some(hir::GeneratorKind::Async(gen)) => match gen {
hir::AsyncGeneratorKind::Block => " of async block",
hir::AsyncGeneratorKind::Closure => " of async closure",
hir::AsyncGeneratorKind::Fn => {
let parent_item = hir.get(hir.get_parent_item(mir_hir_id));
let output = &parent_item
.fn_decl()
.expect("generator lowered from async fn should be in fn")
.output;
span = output.span();
if let hir::FnRetTy::Return(ret) = output {
hir_ty = Some(self.get_future_inner_return_ty(*ret));
}
" of async function"
}
},
Some(hir::GeneratorKind::Gen) => " of generator",
None => " of closure",
};
(span, mir_description, hir_ty)
}
node => match node.fn_decl() {
Some(fn_decl) => {
let hir_ty = match fn_decl.output {
hir::FnRetTy::DefaultReturn(_) => None,
hir::FnRetTy::Return(ty) => Some(ty),
};
(fn_decl.output.span(), "", hir_ty)
}
None => (self.body.span, "", None),
},
};

let highlight = hir_ty
.and_then(|hir_ty| self.highlight_if_we_can_match_hir_ty(fr, return_ty, hir_ty))
.unwrap_or_else(|| {
// `highlight_if_we_cannot_match_hir_ty` needs to know the number we will give to
// the anonymous region. If it succeeds, the `synthesize_region_name` call below
// will increment the counter, "reserving" the number we just used.
let counter = *self.next_region_name.try_borrow().unwrap();
self.highlight_if_we_cannot_match_hir_ty(fr, return_ty, return_span, counter)
});

Some(RegionName {
// This counter value will already have been used, so this function will increment it
// so the next value will be used next and return the region name that would have been
// used.
name: self.synthesize_region_name(),
source: RegionNameSource::AnonRegionFromOutput(
return_span,
mir_description.to_string(),
type_name,
),
source: RegionNameSource::AnonRegionFromOutput(highlight, mir_description.to_string()),
})
}

/// From the [`hir::Ty`] of an async function's lowered return type,
/// retrieve the `hir::Ty` representing the type the user originally wrote.
///
/// e.g. given the function:
///
/// ```
/// async fn foo() -> i32 {}
/// ```
///
/// this function, given the lowered return type of `foo`, an [`OpaqueDef`] that implements `Future<Output=i32>`,
/// returns the `i32`.
///
/// [`OpaqueDef`]: hir::TyKind::OpaqueDef
fn get_future_inner_return_ty(&self, hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
let hir = self.infcx.tcx.hir();

if let hir::TyKind::OpaqueDef(id, _) = hir_ty.kind {
let opaque_ty = hir.item(id.id);
if let hir::ItemKind::OpaqueTy(hir::OpaqueTy {
bounds:
[hir::GenericBound::LangItemTrait(
hir::LangItem::Future,
_,
_,
hir::GenericArgs {
bindings:
[hir::TypeBinding {
ident: Ident { name: sym::Output, .. },
kind: hir::TypeBindingKind::Equality { ty },
..
}],
..
},
)],
..
}) = opaque_ty.kind
{
ty
} else {
span_bug!(
hir_ty.span,
"bounds from lowered return type of async fn did not match expected format: {:?}",
opaque_ty
);
}
} else {
span_bug!(
hir_ty.span,
"lowered return type of async fn is not OpaqueDef: {:?}",
hir_ty
);
}
}

fn give_name_if_anonymous_region_appears_in_yield_ty(
&self,
fr: RegionVid,
Expand Down
37 changes: 37 additions & 0 deletions src/test/ui/async-await/issue-74072-lifetime-name-annotations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// edition:2018
#![feature(async_closure)]
use std::future::Future;

// test the quality of annotations giving lifetimes names (`'1`) when async constructs are involved

pub async fn async_fn(x: &mut i32) -> &i32 {
let y = &*x;
*x += 1; //~ ERROR cannot assign to `*x` because it is borrowed
y
}

pub fn async_closure(x: &mut i32) -> impl Future<Output=&i32> {
(async move || {
let y = &*x;
*x += 1; //~ ERROR cannot assign to `*x` because it is borrowed
y
})()
}

pub fn async_closure_explicit_return_type(x: &mut i32) -> impl Future<Output=&i32> {
(async move || -> &i32 {
let y = &*x;
*x += 1; //~ ERROR cannot assign to `*x` because it is borrowed
y
})()
}

pub fn async_block(x: &mut i32) -> impl Future<Output=&i32> {
async move {
let y = &*x;
*x += 1; //~ ERROR cannot assign to `*x` because it is borrowed
y
}
}

fn main() {}
Loading

0 comments on commit 99f16e6

Please sign in to comment.