Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve lifetime name annotations for closures & async functions #76468

Merged
merged 3 commits into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Contributor Author

@SNCPlay42 SNCPlay42 Sep 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this live somewhere more general, like on rustc_middle::hir::map::Map? Is it useful anywhere else?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#78755
This one is actually very close to the one in #76765, basically only differing in how we obtain the OpaqueDef that represents the Future that is the return type of the async fn

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