Skip to content

Commit f49ebbb

Browse files
committed
Account for missing lifetime in opaque return type
When encountering an opaque closure return type that needs to bound a lifetime to the function's arguments, including borrows and type params, provide appropriate suggestions that lead to working code. Get the user from ```rust fn foo<G, T>(g: G, dest: &mut T) -> impl FnOnce() where G: Get<T> { move || { *dest = g.get(); } } ``` to ```rust fn foo<'a, G: 'a, T>(g: G, dest: &'a mut T) -> impl FnOnce() +'a where G: Get<T> { move || { *dest = g.get(); } } ```
1 parent 74e8046 commit f49ebbb

File tree

5 files changed

+359
-69
lines changed

5 files changed

+359
-69
lines changed

src/librustc_infer/infer/error_reporting/mod.rs

Lines changed: 98 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,49 +1682,70 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
16821682
bound_kind: GenericKind<'tcx>,
16831683
sub: Region<'tcx>,
16841684
) -> DiagnosticBuilder<'a> {
1685+
let hir = &self.tcx.hir();
16851686
// Attempt to obtain the span of the parameter so we can
16861687
// suggest adding an explicit lifetime bound to it.
1687-
let type_param_span = match (self.in_progress_tables, bound_kind) {
1688-
(Some(ref table), GenericKind::Param(ref param)) => {
1689-
let table_owner = table.borrow().hir_owner;
1690-
table_owner.and_then(|table_owner| {
1691-
let generics = self.tcx.generics_of(table_owner.to_def_id());
1692-
// Account for the case where `param` corresponds to `Self`,
1693-
// which doesn't have the expected type argument.
1694-
if !(generics.has_self && param.index == 0) {
1695-
let type_param = generics.type_param(param, self.tcx);
1696-
let hir = &self.tcx.hir();
1697-
type_param.def_id.as_local().map(|def_id| {
1698-
// Get the `hir::Param` to verify whether it already has any bounds.
1699-
// We do this to avoid suggesting code that ends up as `T: 'a'b`,
1700-
// instead we suggest `T: 'a + 'b` in that case.
1701-
let id = hir.as_local_hir_id(def_id);
1702-
let mut has_bounds = false;
1703-
if let Node::GenericParam(param) = hir.get(id) {
1704-
has_bounds = !param.bounds.is_empty();
1705-
}
1706-
let sp = hir.span(id);
1707-
// `sp` only covers `T`, change it so that it covers
1708-
// `T:` when appropriate
1709-
let is_impl_trait = bound_kind.to_string().starts_with("impl ");
1710-
let sp = if has_bounds && !is_impl_trait {
1711-
sp.to(self
1712-
.tcx
1713-
.sess
1714-
.source_map()
1715-
.next_point(self.tcx.sess.source_map().next_point(sp)))
1716-
} else {
1717-
sp
1718-
};
1719-
(sp, has_bounds, is_impl_trait)
1720-
})
1721-
} else {
1722-
None
1723-
}
1724-
})
1688+
let generics = self
1689+
.in_progress_tables
1690+
.and_then(|table| table.borrow().hir_owner)
1691+
.map(|table_owner| self.tcx.generics_of(table_owner.to_def_id()));
1692+
let type_param_span = match (generics, bound_kind) {
1693+
(Some(ref generics), GenericKind::Param(ref param)) => {
1694+
// Account for the case where `param` corresponds to `Self`,
1695+
// which doesn't have the expected type argument.
1696+
if !(generics.has_self && param.index == 0) {
1697+
let type_param = generics.type_param(param, self.tcx);
1698+
type_param.def_id.as_local().map(|def_id| {
1699+
// Get the `hir::Param` to verify whether it already has any bounds.
1700+
// We do this to avoid suggesting code that ends up as `T: 'a'b`,
1701+
// instead we suggest `T: 'a + 'b` in that case.
1702+
let id = hir.as_local_hir_id(def_id);
1703+
let mut has_bounds = false;
1704+
if let Node::GenericParam(param) = hir.get(id) {
1705+
has_bounds = !param.bounds.is_empty();
1706+
}
1707+
let sp = hir.span(id);
1708+
// `sp` only covers `T`, change it so that it covers
1709+
// `T:` when appropriate
1710+
let is_impl_trait = bound_kind.to_string().starts_with("impl ");
1711+
let sp = if has_bounds && !is_impl_trait {
1712+
sp.to(self
1713+
.tcx
1714+
.sess
1715+
.source_map()
1716+
.next_point(self.tcx.sess.source_map().next_point(sp)))
1717+
} else {
1718+
sp
1719+
};
1720+
(sp, has_bounds, is_impl_trait)
1721+
})
1722+
} else {
1723+
None
1724+
}
17251725
}
17261726
_ => None,
17271727
};
1728+
let new_lt = generics
1729+
.as_ref()
1730+
.and_then(|g| {
1731+
let possible = ["'a", "'b", "'c", "'d", "'e", "'f", "'g", "'h", "'i", "'j", "'k"];
1732+
let lts_names = g
1733+
.params
1734+
.iter()
1735+
.filter(|p| matches!(p.kind, ty::GenericParamDefKind::Lifetime))
1736+
.map(|p| p.name.as_str())
1737+
.collect::<Vec<_>>();
1738+
let lts = lts_names.iter().map(|s| -> &str { &*s }).collect::<Vec<_>>();
1739+
possible.iter().filter(|&candidate| !lts.contains(&*candidate)).next().map(|s| *s)
1740+
})
1741+
.unwrap_or("'lt");
1742+
let add_lt_sugg = generics
1743+
.as_ref()
1744+
.and_then(|g| g.params.first())
1745+
.and_then(|param| param.def_id.as_local())
1746+
.map(|def_id| {
1747+
(hir.span(hir.as_local_hir_id(def_id)).shrink_to_lo(), format!("{}, ", new_lt))
1748+
});
17281749

17291750
let labeled_user_string = match bound_kind {
17301751
GenericKind::Param(ref p) => format!("the parameter type `{}`", p),
@@ -1781,6 +1802,30 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
17811802
}
17821803
}
17831804

1805+
let new_binding_suggestion =
1806+
|err: &mut DiagnosticBuilder<'tcx>,
1807+
type_param_span: Option<(Span, bool, bool)>,
1808+
bound_kind: GenericKind<'tcx>| {
1809+
let msg = "consider introducing an explicit lifetime bound to unify the type \
1810+
parameter and the output";
1811+
if let Some((sp, has_lifetimes, is_impl_trait)) = type_param_span {
1812+
let suggestion = if is_impl_trait {
1813+
(sp.shrink_to_hi(), format!(" + {}", new_lt))
1814+
} else {
1815+
let tail = if has_lifetimes { " +" } else { "" };
1816+
(sp, format!("{}: {}{}", bound_kind, new_lt, tail))
1817+
};
1818+
let mut sugg =
1819+
vec![suggestion, (span.shrink_to_hi(), format!(" + {}", new_lt))];
1820+
if let Some(lt) = add_lt_sugg {
1821+
sugg.push(lt);
1822+
sugg.rotate_right(1);
1823+
}
1824+
// `MaybeIncorrect` due to issue #41966.
1825+
err.multipart_suggestion(msg, sugg, Applicability::MaybeIncorrect);
1826+
}
1827+
};
1828+
17841829
let mut err = match *sub {
17851830
ty::ReEarlyBound(ty::EarlyBoundRegion { name, .. })
17861831
| ty::ReFree(ty::FreeRegion { bound_region: ty::BrNamed(_, name), .. }) => {
@@ -1822,17 +1867,28 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
18221867
"{} may not live long enough",
18231868
labeled_user_string
18241869
);
1825-
err.help(&format!(
1826-
"consider adding an explicit lifetime bound for `{}`",
1827-
bound_kind
1828-
));
18291870
note_and_explain_region(
18301871
self.tcx,
18311872
&mut err,
18321873
&format!("{} must be valid for ", labeled_user_string),
18331874
sub,
18341875
"...",
18351876
);
1877+
if let Some(infer::RelateParamBound(_, t)) = origin {
1878+
let t = self.resolve_vars_if_possible(&t);
1879+
match t.kind {
1880+
// We've got:
1881+
// fn get_later<G, T>(g: G, dest: &mut T) -> impl FnOnce() + '_
1882+
// suggest:
1883+
// fn get_later<'a, G: 'a, T>(g: G, dest: &mut T) -> impl FnOnce() + '_ + 'a
1884+
ty::Closure(_, _substs) | ty::Opaque(_, _substs) => {
1885+
new_binding_suggestion(&mut err, type_param_span, bound_kind);
1886+
}
1887+
_ => {
1888+
binding_suggestion(&mut err, type_param_span, bound_kind, new_lt);
1889+
}
1890+
}
1891+
}
18361892
err
18371893
}
18381894
};

src/librustc_infer/infer/error_reporting/nice_region_error/static_impl_trait.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::infer::error_reporting::msg_span_from_free_region;
44
use crate::infer::error_reporting::nice_region_error::NiceRegionError;
55
use crate::infer::lexical_region_resolve::RegionResolutionError;
66
use rustc_errors::{Applicability, ErrorReported};
7-
use rustc_middle::ty::{BoundRegion, FreeRegion, RegionKind};
7+
use rustc_middle::ty::RegionKind;
88

99
impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
1010
/// Print the error message for lifetime errors when the return type is a static impl Trait.
@@ -37,13 +37,8 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
3737
err.span_note(lifetime_sp, &format!("...can't outlive {}", lifetime));
3838
}
3939

40-
let lifetime_name = match sup_r {
41-
RegionKind::ReFree(FreeRegion {
42-
bound_region: BoundRegion::BrNamed(_, ref name),
43-
..
44-
}) => name.to_string(),
45-
_ => "'_".to_owned(),
46-
};
40+
let lifetime_name =
41+
if sup_r.has_name() { sup_r.to_string() } else { "'_".to_owned() };
4742
let fn_return_span = return_ty.unwrap().1;
4843
if let Ok(snippet) =
4944
self.tcx().sess.source_map().span_to_snippet(fn_return_span)
@@ -54,9 +49,9 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
5449
err.span_suggestion(
5550
fn_return_span,
5651
&format!(
57-
"you can add a bound to the return type to make it last \
58-
less than `'static` and match {}",
59-
lifetime,
52+
"you can add a bound to the return type to make it last less \
53+
than `'static` and match {}",
54+
lifetime
6055
),
6156
format!("{} + {}", snippet, lifetime_name),
6257
Applicability::Unspecified,

src/librustc_infer/infer/error_reporting/note.rs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
5656
err.span_note(
5757
span,
5858
&format!(
59-
"...so that the reference type `{}` does not outlive the \
60-
data it points at",
59+
"...so that the reference type `{}` does not outlive the data it points at",
6160
self.ty_to_string(ty)
6261
),
6362
);
@@ -66,8 +65,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
6665
err.span_note(
6766
span,
6867
&format!(
69-
"...so that the type `{}` will meet its required \
70-
lifetime bounds",
68+
"...so that the type `{}` will meet its required lifetime bounds",
7169
self.ty_to_string(t)
7270
),
7371
);
@@ -81,8 +79,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
8179
infer::CompareImplMethodObligation { span, .. } => {
8280
err.span_note(
8381
span,
84-
"...so that the definition in impl matches the definition from the \
85-
trait",
82+
"...so that the definition in impl matches the definition from the trait",
8683
);
8784
}
8885
}
@@ -113,8 +110,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
113110
self.tcx.sess,
114111
span,
115112
E0312,
116-
"lifetime of reference outlives lifetime of \
117-
borrowed content..."
113+
"lifetime of reference outlives lifetime of borrowed content..."
118114
);
119115
note_and_explain_region(
120116
self.tcx,
@@ -138,8 +134,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
138134
self.tcx.sess,
139135
span,
140136
E0313,
141-
"lifetime of borrowed pointer outlives lifetime \
142-
of captured variable `{}`...",
137+
"lifetime of borrowed pointer outlives lifetime of captured variable `{}`...",
143138
var_name
144139
);
145140
note_and_explain_region(
@@ -163,8 +158,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
163158
self.tcx.sess,
164159
span,
165160
E0476,
166-
"lifetime of the source pointer does not outlive \
167-
lifetime bound of the object type"
161+
"lifetime of the source pointer does not outlive lifetime bound of the \
162+
object type"
168163
);
169164
note_and_explain_region(self.tcx, &mut err, "object type is valid for ", sub, "");
170165
note_and_explain_region(
@@ -181,8 +176,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
181176
self.tcx.sess,
182177
span,
183178
E0477,
184-
"the type `{}` does not fulfill the required \
185-
lifetime",
179+
"the type `{}` does not fulfill the required lifetime",
186180
self.ty_to_string(ty)
187181
);
188182
match *sub {
@@ -217,8 +211,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
217211
self.tcx.sess,
218212
span,
219213
E0482,
220-
"lifetime of return value does not outlive the \
221-
function call"
214+
"lifetime of return value does not outlive the function call"
222215
);
223216
note_and_explain_region(
224217
self.tcx,
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
pub trait Get<T> {
2+
fn get(self) -> T;
3+
}
4+
5+
struct Foo {
6+
x: usize,
7+
}
8+
9+
impl Get<usize> for Foo {
10+
fn get(self) -> usize {
11+
self.x
12+
}
13+
}
14+
15+
fn foo<G, T>(g: G, dest: &mut T) -> impl FnOnce()
16+
where
17+
G: Get<T>
18+
{
19+
move || { //~ ERROR cannot infer an appropriate lifetime
20+
*dest = g.get();
21+
}
22+
}
23+
24+
// After applying suggestion for `foo`:
25+
fn bar<G, T>(g: G, dest: &mut T) -> impl FnOnce() + '_
26+
//~^ ERROR the parameter type `G` may not live long enough
27+
where
28+
G: Get<T>
29+
{
30+
move || {
31+
*dest = g.get();
32+
}
33+
}
34+
35+
36+
// After applying suggestion for `bar`:
37+
fn baz<G: 'a, T>(g: G, dest: &mut T) -> impl FnOnce() + '_ //~ ERROR undeclared lifetime
38+
where
39+
G: Get<T>
40+
{
41+
move || {
42+
*dest = g.get();
43+
}
44+
}
45+
46+
// After applying suggestion for `baz`:
47+
fn qux<'a, G: 'a, T>(g: G, dest: &mut T) -> impl FnOnce() + '_
48+
//~^ ERROR the parameter type `G` may not live long enough
49+
where
50+
G: Get<T>
51+
{
52+
move || {
53+
*dest = g.get();
54+
}
55+
}
56+
57+
// After applying suggestion for `qux`:
58+
// FIXME: we should suggest be suggesting to change `dest` to `&'a mut T`.
59+
fn bat<'a, G: 'a, T>(g: G, dest: &mut T) -> impl FnOnce() + '_ + 'a
60+
where
61+
G: Get<T>
62+
{
63+
move || { //~ ERROR cannot infer an appropriate lifetime
64+
*dest = g.get();
65+
}
66+
}
67+
68+
// Potential incorrect attempt:
69+
fn bak<'a, G, T>(g: G, dest: &'a mut T) -> impl FnOnce() + 'a
70+
//~^ ERROR the parameter type `G` may not live long enough
71+
where
72+
G: Get<T>
73+
{
74+
move || {
75+
*dest = g.get();
76+
}
77+
}
78+
79+
80+
// We need to tie the lifetime of `G` with the lifetime of `&mut T` and the returned closure:
81+
fn ok<'a, G: 'a, T>(g: G, dest: &'a mut T) -> impl FnOnce() + 'a
82+
where
83+
G: Get<T>
84+
{
85+
move || {
86+
*dest = g.get();
87+
}
88+
}
89+
90+
// This also works. The `'_` isn't necessary but it's where we arrive to following the suggestions:
91+
fn ok2<'a, G: 'a, T>(g: G, dest: &'a mut T) -> impl FnOnce() + '_ + 'a
92+
where
93+
G: Get<T>
94+
{
95+
move || {
96+
*dest = g.get();
97+
}
98+
}
99+
100+
fn main() {}

0 commit comments

Comments
 (0)