Skip to content

Commit fc49152

Browse files
committed
Auto merge of #52168 - nikomatsakis:nll-region-name, r=estebank
find and highlight the `&` or `'_` in `region_name` Before: ``` --> $DIR/dyn-trait-underscore.rs:18:5 | LL | fn a<T>(items: &[T]) -> Box<dyn Iterator<Item=&T>> { - | ----- lifetime `'1` appears in this argument LL | Box::new(items.iter()) //~ ERROR cannot infer an appropriate lifetime | ^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static` ``` After: ``` --> $DIR/dyn-trait-underscore.rs:18:5 | LL | fn a<T>(items: &[T]) -> Box<dyn Iterator<Item=&T>> { + | - let's call the lifetime of this reference `'1` LL | Box::new(items.iter()) //~ ERROR cannot infer an appropriate lifetime | ^^^^^^^^^^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static` ``` Not intended as the final end point necessarily in any sense. I intentionally left some to-do points to fill in later: - Does not apply to upvars in closures yet (should be relatively easy) - Does not handle the case where we can't find a precise match very well - And of course we can still tweak wording but shows the basic idea of how to make the `Ty` and `hir::Ty` to find a good spot to highlight. r? @estebank cc @davidtwco
2 parents e46bfa2 + a6adb1e commit fc49152

File tree

7 files changed

+300
-22
lines changed

7 files changed

+300
-22
lines changed

src/librustc/hir/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,15 @@ pub enum GenericArg {
402402
Type(Ty),
403403
}
404404

405+
impl GenericArg {
406+
pub fn span(&self) -> Span {
407+
match self {
408+
GenericArg::Lifetime(l) => l.span,
409+
GenericArg::Type(t) => t.span,
410+
}
411+
}
412+
}
413+
405414
#[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug)]
406415
pub struct GenericArgs {
407416
/// The generic arguments for this path segment.

src/librustc_mir/borrow_check/nll/region_infer/error_reporting/region_name.rs

+277-17
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
use borrow_check::nll::region_infer::RegionInferenceContext;
1212
use borrow_check::nll::ToRegionVid;
13+
use rustc::hir;
1314
use rustc::hir::def_id::DefId;
1415
use rustc::mir::{Local, Mir};
15-
use rustc::ty::{self, RegionVid, TyCtxt};
16+
use rustc::ty::subst::{Substs, UnpackedKind};
17+
use rustc::ty::{self, RegionVid, Ty, TyCtxt};
1618
use rustc_data_structures::indexed_vec::Idx;
1719
use rustc_errors::DiagnosticBuilder;
1820
use syntax::ast::Name;
@@ -60,7 +62,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
6062

6163
self.give_name_from_error_region(tcx, mir_def_id, fr, counter, diag)
6264
.or_else(|| {
63-
self.give_name_if_anonymous_region_appears_in_arguments(tcx, mir, fr, counter, diag)
65+
self.give_name_if_anonymous_region_appears_in_arguments(
66+
tcx, mir, mir_def_id, fr, counter, diag,
67+
)
6468
})
6569
.or_else(|| {
6670
self.give_name_if_anonymous_region_appears_in_upvars(tcx, mir, fr, counter, diag)
@@ -129,30 +133,48 @@ impl<'tcx> RegionInferenceContext<'tcx> {
129133
&self,
130134
tcx: TyCtxt<'_, '_, 'tcx>,
131135
mir: &Mir<'tcx>,
136+
mir_def_id: DefId,
132137
fr: RegionVid,
133138
counter: &mut usize,
134139
diag: &mut DiagnosticBuilder<'_>,
135140
) -> Option<InternedString> {
136141
let implicit_inputs = self.universal_regions.defining_ty.implicit_inputs();
137-
let argument_index = self.universal_regions
142+
let argument_index = self
143+
.universal_regions
138144
.unnormalized_input_tys
139145
.iter()
140146
.skip(implicit_inputs)
141147
.position(|arg_ty| {
142-
debug!("give_name_if_anonymous_region_appears_in_arguments: arg_ty = {:?}", arg_ty);
148+
debug!(
149+
"give_name_if_anonymous_region_appears_in_arguments: arg_ty = {:?}",
150+
arg_ty
151+
);
143152
tcx.any_free_region_meets(arg_ty, |r| r.to_region_vid() == fr)
144-
})?
145-
+ implicit_inputs;
153+
})?;
146154

147155
debug!(
148156
"give_name_if_anonymous_region_appears_in_arguments: \
149157
found {:?} in argument {} which has type {:?}",
150158
fr, argument_index, self.universal_regions.unnormalized_input_tys[argument_index],
151159
);
152160

161+
let arg_ty =
162+
self.universal_regions.unnormalized_input_tys[implicit_inputs + argument_index];
163+
if let Some(region_name) = self.give_name_if_we_can_match_hir_ty_from_argument(
164+
tcx,
165+
mir_def_id,
166+
fr,
167+
arg_ty,
168+
argument_index,
169+
counter,
170+
diag,
171+
) {
172+
return Some(region_name);
173+
}
174+
153175
let region_name = self.synthesize_region_name(counter);
154176

155-
let argument_local = Local::new(argument_index + 1);
177+
let argument_local = Local::new(argument_index + implicit_inputs + 1);
156178
let argument_span = mir.local_decls[argument_local].source_info.span;
157179
diag.span_label(
158180
argument_span,
@@ -162,6 +184,240 @@ impl<'tcx> RegionInferenceContext<'tcx> {
162184
Some(region_name)
163185
}
164186

187+
fn give_name_if_we_can_match_hir_ty_from_argument(
188+
&self,
189+
tcx: TyCtxt<'_, '_, 'tcx>,
190+
mir_def_id: DefId,
191+
needle_fr: RegionVid,
192+
argument_ty: Ty<'tcx>,
193+
argument_index: usize,
194+
counter: &mut usize,
195+
diag: &mut DiagnosticBuilder<'_>,
196+
) -> Option<InternedString> {
197+
let mir_node_id = tcx.hir.as_local_node_id(mir_def_id)?;
198+
let fn_decl = tcx.hir.fn_decl(mir_node_id)?;
199+
let argument_hir_ty: &hir::Ty = &fn_decl.inputs[argument_index];
200+
match argument_hir_ty.node {
201+
// This indicates a variable with no type annotation, like
202+
// `|x|`... in that case, we can't highlight the type but
203+
// must highlight the variable.
204+
hir::TyInfer => None,
205+
206+
_ => self.give_name_if_we_can_match_hir_ty(
207+
tcx,
208+
needle_fr,
209+
argument_ty,
210+
argument_hir_ty,
211+
counter,
212+
diag,
213+
),
214+
}
215+
}
216+
217+
/// Attempts to highlight the specific part of a type annotation
218+
/// that contains the anonymous reference we want to give a name
219+
/// to. For example, we might produce an annotation like this:
220+
///
221+
/// ```
222+
/// | fn a<T>(items: &[T]) -> Box<dyn Iterator<Item=&T>> {
223+
/// | - let's call the lifetime of this reference `'1`
224+
/// ```
225+
///
226+
/// the way this works is that we match up `argument_ty`, which is
227+
/// a `Ty<'tcx>` (the internal form of the type) with
228+
/// `argument_hir_ty`, a `hir::Ty` (the syntax of the type
229+
/// annotation). We are descending through the types stepwise,
230+
/// looking in to find the region `needle_fr` in the internal
231+
/// type. Once we find that, we can use the span of the `hir::Ty`
232+
/// to add the highlight.
233+
///
234+
/// This is a somewhat imperfect process, so long the way we also
235+
/// keep track of the **closest** type we've found. If we fail to
236+
/// find the exact `&` or `'_` to highlight, then we may fall back
237+
/// to highlighting that closest type instead.
238+
fn give_name_if_we_can_match_hir_ty(
239+
&self,
240+
tcx: TyCtxt<'_, '_, 'tcx>,
241+
needle_fr: RegionVid,
242+
argument_ty: Ty<'tcx>,
243+
argument_hir_ty: &hir::Ty,
244+
counter: &mut usize,
245+
diag: &mut DiagnosticBuilder<'_>,
246+
) -> Option<InternedString> {
247+
let search_stack: &mut Vec<(Ty<'tcx>, &hir::Ty)> = &mut Vec::new();
248+
249+
search_stack.push((argument_ty, argument_hir_ty));
250+
251+
let mut closest_match: &hir::Ty = argument_hir_ty;
252+
253+
while let Some((ty, hir_ty)) = search_stack.pop() {
254+
// While we search, also track the closet match.
255+
if tcx.any_free_region_meets(&ty, |r| r.to_region_vid() == needle_fr) {
256+
closest_match = hir_ty;
257+
}
258+
259+
match (&ty.sty, &hir_ty.node) {
260+
// Check if the `argument_ty` is `&'X ..` where `'X`
261+
// is the region we are looking for -- if so, and we have a `&T`
262+
// on the RHS, then we want to highlight the `&` like so:
263+
//
264+
// &
265+
// - let's call the lifetime of this reference `'1`
266+
(ty::TyRef(region, referent_ty, _), hir::TyRptr(_lifetime, referent_hir_ty)) => {
267+
if region.to_region_vid() == needle_fr {
268+
let region_name = self.synthesize_region_name(counter);
269+
270+
// Just grab the first character, the `&`.
271+
let codemap = tcx.sess.codemap();
272+
let ampersand_span = codemap.start_point(hir_ty.span);
273+
274+
diag.span_label(
275+
ampersand_span,
276+
format!(
277+
"let's call the lifetime of this reference `{}`",
278+
region_name
279+
),
280+
);
281+
282+
return Some(region_name);
283+
}
284+
285+
// Otherwise, let's descend into the referent types.
286+
search_stack.push((referent_ty, &referent_hir_ty.ty));
287+
}
288+
289+
// Match up something like `Foo<'1>`
290+
(ty::TyAdt(_adt_def, substs), hir::TyPath(hir::QPath::Resolved(None, path))) => {
291+
if let Some(last_segment) = path.segments.last() {
292+
if let Some(name) = self.match_adt_and_segment(
293+
substs,
294+
needle_fr,
295+
last_segment,
296+
counter,
297+
diag,
298+
search_stack,
299+
) {
300+
return Some(name);
301+
}
302+
}
303+
}
304+
305+
// The following cases don't have lifetimes, so we
306+
// just worry about trying to match up the rustc type
307+
// with the HIR types:
308+
(ty::TyTuple(elem_tys), hir::TyTup(elem_hir_tys)) => {
309+
search_stack.extend(elem_tys.iter().cloned().zip(elem_hir_tys));
310+
}
311+
312+
(ty::TySlice(elem_ty), hir::TySlice(elem_hir_ty))
313+
| (ty::TyArray(elem_ty, _), hir::TyArray(elem_hir_ty, _)) => {
314+
search_stack.push((elem_ty, elem_hir_ty));
315+
}
316+
317+
(ty::TyRawPtr(mut_ty), hir::TyPtr(mut_hir_ty)) => {
318+
search_stack.push((mut_ty.ty, &mut_hir_ty.ty));
319+
}
320+
321+
_ => {
322+
// FIXME there are other cases that we could trace
323+
}
324+
}
325+
}
326+
327+
let region_name = self.synthesize_region_name(counter);
328+
diag.span_label(
329+
closest_match.span,
330+
format!("lifetime `{}` appears in this type", region_name),
331+
);
332+
333+
return Some(region_name);
334+
}
335+
336+
/// We've found an enum/struct/union type with the substitutions
337+
/// `substs` and -- in the HIR -- a path type with the final
338+
/// segment `last_segment`. Try to find a `'_` to highlight in
339+
/// the generic args (or, if not, to produce new zipped pairs of
340+
/// types+hir to search through).
341+
fn match_adt_and_segment<'hir>(
342+
&self,
343+
substs: &'tcx Substs<'tcx>,
344+
needle_fr: RegionVid,
345+
last_segment: &'hir hir::PathSegment,
346+
counter: &mut usize,
347+
diag: &mut DiagnosticBuilder<'_>,
348+
search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty)>,
349+
) -> Option<InternedString> {
350+
// Did the user give explicit arguments? (e.g., `Foo<..>`)
351+
let args = last_segment.args.as_ref()?;
352+
let lifetime = self.try_match_adt_and_generic_args(substs, needle_fr, args, search_stack)?;
353+
match lifetime.name {
354+
hir::LifetimeName::Param(_)
355+
| hir::LifetimeName::Static
356+
| hir::LifetimeName::Underscore => {
357+
let region_name = self.synthesize_region_name(counter);
358+
let ampersand_span = lifetime.span;
359+
diag.span_label(ampersand_span, format!("let's call this `{}`", region_name));
360+
return Some(region_name);
361+
}
362+
363+
hir::LifetimeName::Implicit => {
364+
// In this case, the user left off the lifetime; so
365+
// they wrote something like:
366+
//
367+
// ```
368+
// x: Foo<T>
369+
// ```
370+
//
371+
// where the fully elaborated form is `Foo<'_, '1,
372+
// T>`. We don't consider this a match; instead we let
373+
// the "fully elaborated" type fallback above handle
374+
// it.
375+
return None;
376+
}
377+
}
378+
}
379+
380+
/// We've found an enum/struct/union type with the substitutions
381+
/// `substs` and -- in the HIR -- a path with the generic
382+
/// arguments `args`. If `needle_fr` appears in the args, return
383+
/// the `hir::Lifetime` that corresponds to it. If not, push onto
384+
/// `search_stack` the types+hir to search through.
385+
fn try_match_adt_and_generic_args<'hir>(
386+
&self,
387+
substs: &'tcx Substs<'tcx>,
388+
needle_fr: RegionVid,
389+
args: &'hir hir::GenericArgs,
390+
search_stack: &mut Vec<(Ty<'tcx>, &'hir hir::Ty)>,
391+
) -> Option<&'hir hir::Lifetime> {
392+
for (kind, hir_arg) in substs.iter().zip(&args.args) {
393+
match (kind.unpack(), hir_arg) {
394+
(UnpackedKind::Lifetime(r), hir::GenericArg::Lifetime(lt)) => {
395+
if r.to_region_vid() == needle_fr {
396+
return Some(lt);
397+
}
398+
}
399+
400+
(UnpackedKind::Type(ty), hir::GenericArg::Type(hir_ty)) => {
401+
search_stack.push((ty, hir_ty));
402+
}
403+
404+
(UnpackedKind::Lifetime(_), _) | (UnpackedKind::Type(_), _) => {
405+
// I *think* that HIR lowering should ensure this
406+
// doesn't happen, even in erroneous
407+
// programs. Else we should use delay-span-bug.
408+
span_bug!(
409+
hir_arg.span(),
410+
"unmatched subst and hir arg: found {:?} vs {:?}",
411+
kind,
412+
hir_arg,
413+
);
414+
}
415+
}
416+
}
417+
418+
None
419+
}
420+
165421
/// Find a closure upvar that contains `fr` and label it with a
166422
/// fully elaborated type, returning something like `'1`. Result
167423
/// looks like:
@@ -178,7 +434,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
178434
counter: &mut usize,
179435
diag: &mut DiagnosticBuilder<'_>,
180436
) -> Option<InternedString> {
181-
let upvar_index = self.universal_regions
437+
let upvar_index = self
438+
.universal_regions
182439
.defining_ty
183440
.upvar_tys(tcx)
184441
.position(|upvar_ty| {
@@ -189,15 +446,16 @@ impl<'tcx> RegionInferenceContext<'tcx> {
189446
tcx.any_free_region_meets(&upvar_ty, |r| r.to_region_vid() == fr)
190447
})?;
191448

449+
let upvar_ty = self
450+
.universal_regions
451+
.defining_ty
452+
.upvar_tys(tcx)
453+
.nth(upvar_index);
454+
192455
debug!(
193456
"give_name_if_anonymous_region_appears_in_upvars: \
194457
found {:?} in upvar {} which has type {:?}",
195-
fr,
196-
upvar_index,
197-
self.universal_regions
198-
.defining_ty
199-
.upvar_tys(tcx)
200-
.nth(upvar_index),
458+
fr, upvar_index, upvar_ty,
201459
);
202460

203461
let region_name = self.synthesize_region_name(counter);
@@ -229,9 +487,11 @@ impl<'tcx> RegionInferenceContext<'tcx> {
229487
counter: &mut usize,
230488
diag: &mut DiagnosticBuilder<'_>,
231489
) -> Option<InternedString> {
232-
let return_ty = self.universal_regions
233-
.unnormalized_output_ty;
234-
debug!("give_name_if_anonymous_region_appears_in_output: return_ty = {:?}", return_ty);
490+
let return_ty = self.universal_regions.unnormalized_output_ty;
491+
debug!(
492+
"give_name_if_anonymous_region_appears_in_output: return_ty = {:?}",
493+
return_ty
494+
);
235495
if !tcx.any_free_region_meets(&return_ty, |r| r.to_region_vid() == fr) {
236496
return None;
237497
}

src/libsyntax/codemap.rs

+9
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,15 @@ impl CodeMap {
689689
self.span_until_char(sp, '{')
690690
}
691691

692+
/// Returns a new span representing just the start-point of this span
693+
pub fn start_point(&self, sp: Span) -> Span {
694+
let pos = sp.lo().0;
695+
let width = self.find_width_of_character_at_span(sp, false);
696+
let corrected_start_position = pos.checked_add(width).unwrap_or(pos);
697+
let end_point = BytePos(cmp::max(corrected_start_position, sp.lo().0));
698+
sp.with_hi(end_point)
699+
}
700+
692701
/// Returns a new span representing just the end-point of this span
693702
pub fn end_point(&self, sp: Span) -> Span {
694703
let pos = sp.hi().0;

0 commit comments

Comments
 (0)