Skip to content

Commit 413f490

Browse files
authored
Rollup merge of rust-lang#92183 - tmandry:issue-74256, r=estebank
Point at correct argument when async fn output type lifetime disagrees with signature Fixes most of rust-lang#74256. ## Problems fixed This PR fixes a couple of related problems in the error reporting code. ### Highlighting the wrong argument First, the error reporting code was looking at the desugared return type of an `async fn` to decide which parameter to highlight. For example, a function like ```rust async fn async_fn(self: &Struct, f: &u32) -> &u32 { f } ``` desugars to ```rust async fn async_fn<'a, 'b>(self: &'a Struct, f: &'b u32) -> impl Future<Output = &'a u32> + 'a + 'b { f } ``` Since `f: &'b u32` is returned but the output type is `&'a u32`, the error would occur when checking that `'a: 'b`. The reporting code would look to see if the "offending" lifetime `'b` was included in the return type, and because the code was looking at the desugared future type, it was included. So it defaulted to reporting that the source of the other lifetime `'a` (the `self` type) was the problem, when it was really the type of `f`. (Note that if it had chosen instead to look at `'a` first, it too would have been included in the output type, and it would have arbitrarily reported the error (correctly this time) on the type of `f`.) Looking at the actual future type isn't useful for this reason; it captures all input lifetimes. Using the written return type for `async fn` solves this problem and results in less confusing error messages for the user. This isn't a perfect fix, unfortunately; writing the "manually desugared" form of the above function still results in the wrong parameter being highlighted. Looking at the output type of every `impl Future` return type doesn't feel like a very principled approach, though it might work. The problem would remain for function signatures that look like the desugared one above but use different traits. There may be deeper changes required to pinpoint which part of each type is conflicting. ### Lying about await point capture causing lifetime conflicts The second issue fixed by this PR is the unnecessary complexity in `try_report_anon_anon_conflict`. It turns out that the root cause I suggested in rust-lang#76547 (comment) wasn't really the root cause. Adding special handling to report that a variable was captured over an await point only made the error messages less correct and pointed to a problem other than the one that actually occurred. Given the above discussion, it's easy to see why: `async fn`s capture all input lifetimes in their return type, so holding an argument across an await point should never cause a lifetime conflict! Removing the special handling simplified the code and improved the error messages (though they still aren't very good!) ## Future work * Fix error reporting on the "desugared" form of this code * Get the `suggest_adding_lifetime_params` suggestion firing on these examples * cc rust-lang#42703, I think r? `@estebank`
2 parents 405cf20 + 698631e commit 413f490

File tree

17 files changed

+244
-350
lines changed

17 files changed

+244
-350
lines changed

compiler/rustc_hir/src/hir.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -2726,6 +2726,10 @@ pub struct FnHeader {
27262726
}
27272727

27282728
impl FnHeader {
2729+
pub fn is_async(&self) -> bool {
2730+
matches!(&self.asyncness, IsAsync::Async)
2731+
}
2732+
27292733
pub fn is_const(&self) -> bool {
27302734
matches!(&self.constness, Constness::Const)
27312735
}
@@ -3169,7 +3173,7 @@ impl<'hir> Node<'hir> {
31693173
}
31703174
}
31713175

3172-
pub fn fn_decl(&self) -> Option<&FnDecl<'hir>> {
3176+
pub fn fn_decl(&self) -> Option<&'hir FnDecl<'hir>> {
31733177
match self {
31743178
Node::TraitItem(TraitItem { kind: TraitItemKind::Fn(fn_sig, _), .. })
31753179
| Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(fn_sig, _), .. })
@@ -3181,6 +3185,15 @@ impl<'hir> Node<'hir> {
31813185
}
31823186
}
31833187

3188+
pub fn fn_sig(&self) -> Option<&'hir FnSig<'hir>> {
3189+
match self {
3190+
Node::TraitItem(TraitItem { kind: TraitItemKind::Fn(fn_sig, _), .. })
3191+
| Node::ImplItem(ImplItem { kind: ImplItemKind::Fn(fn_sig, _), .. })
3192+
| Node::Item(Item { kind: ItemKind::Fn(fn_sig, _, _), .. }) => Some(fn_sig),
3193+
_ => None,
3194+
}
3195+
}
3196+
31843197
pub fn body_id(&self) -> Option<BodyId> {
31853198
match self {
31863199
Node::TraitItem(TraitItem {

compiler/rustc_infer/src/infer/error_reporting/mod.rs

+19-12
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ use rustc_hir::def_id::DefId;
6565
use rustc_hir::lang_items::LangItem;
6666
use rustc_hir::{Item, ItemKind, Node};
6767
use rustc_middle::dep_graph::DepContext;
68-
use rustc_middle::ty::error::TypeError;
6968
use rustc_middle::ty::{
7069
self,
70+
error::TypeError,
7171
subst::{GenericArgKind, Subst, SubstsRef},
72-
Region, Ty, TyCtxt, TypeFoldable,
72+
Binder, Region, Ty, TyCtxt, TypeFoldable,
7373
};
7474
use rustc_span::{sym, BytePos, DesugaringKind, MultiSpan, Pos, Span};
7575
use rustc_target::spec::abi;
@@ -1765,7 +1765,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
17651765
self.note_error_origin(diag, cause, exp_found, terr);
17661766
}
17671767

1768-
pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
1768+
pub fn get_impl_future_output_ty(&self, ty: Ty<'tcx>) -> Option<Binder<'tcx, Ty<'tcx>>> {
17691769
if let ty::Opaque(def_id, substs) = ty.kind() {
17701770
let future_trait = self.tcx.require_lang_item(LangItem::Future, None);
17711771
// Future::Output
@@ -1775,13 +1775,20 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
17751775

17761776
for (predicate, _) in bounds {
17771777
let predicate = predicate.subst(self.tcx, substs);
1778-
if let ty::PredicateKind::Projection(projection_predicate) =
1779-
predicate.kind().skip_binder()
1780-
{
1781-
if projection_predicate.projection_ty.item_def_id == item_def_id {
1782-
// We don't account for multiple `Future::Output = Ty` contraints.
1783-
return projection_predicate.term.ty();
1784-
}
1778+
let output = predicate
1779+
.kind()
1780+
.map_bound(|kind| match kind {
1781+
ty::PredicateKind::Projection(projection_predicate)
1782+
if projection_predicate.projection_ty.item_def_id == item_def_id =>
1783+
{
1784+
projection_predicate.term.ty()
1785+
}
1786+
_ => None,
1787+
})
1788+
.transpose();
1789+
if output.is_some() {
1790+
// We don't account for multiple `Future::Output = Ty` contraints.
1791+
return output;
17851792
}
17861793
}
17871794
}
@@ -1823,8 +1830,8 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
18231830
}
18241831

18251832
match (
1826-
self.get_impl_future_output_ty(exp_found.expected),
1827-
self.get_impl_future_output_ty(exp_found.found),
1833+
self.get_impl_future_output_ty(exp_found.expected).map(Binder::skip_binder),
1834+
self.get_impl_future_output_ty(exp_found.found).map(Binder::skip_binder),
18281835
) {
18291836
(Some(exp), Some(found)) if same_type_modulo_infer(exp, found) => match cause.code() {
18301837
ObligationCauseCode::IfExpression(box IfExpressionCause { then, .. }) => {

compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs

+33-76
Original file line numberDiff line numberDiff line change
@@ -106,90 +106,47 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
106106
None => String::new(),
107107
};
108108

109-
let (span_1, span_2, main_label, span_label, future_return_type) =
110-
match (sup_is_ret_type, sub_is_ret_type) {
111-
(None, None) => {
112-
let (main_label_1, span_label_1) = if ty_sup.hir_id == ty_sub.hir_id {
113-
(
114-
"this type is declared with multiple lifetimes...".to_owned(),
115-
"...but data with one lifetime flows into the other here".to_owned(),
116-
)
117-
} else {
118-
(
119-
"these two types are declared with different lifetimes...".to_owned(),
120-
format!("...but data{} flows{} here", span_label_var1, span_label_var2),
121-
)
122-
};
123-
(ty_sup.span, ty_sub.span, main_label_1, span_label_1, None)
124-
}
109+
debug!(
110+
"try_report_anon_anon_conflict: sub_is_ret_type={:?} sup_is_ret_type={:?}",
111+
sub_is_ret_type, sup_is_ret_type
112+
);
125113

126-
(Some(ret_span), _) => {
127-
let sup_future = self.future_return_type(scope_def_id_sup);
128-
let (return_type, action) = if sup_future.is_some() {
129-
("returned future", "held across an await point")
130-
} else {
131-
("return type", "returned")
132-
};
114+
let mut err = struct_span_err!(self.tcx().sess, span, E0623, "lifetime mismatch");
133115

134-
(
135-
ty_sub.span,
136-
ret_span,
137-
format!(
138-
"this parameter and the {} are declared with different lifetimes...",
139-
return_type
140-
),
141-
format!("...but data{} is {} here", span_label_var1, action),
142-
sup_future,
143-
)
144-
}
145-
(_, Some(ret_span)) => {
146-
let sub_future = self.future_return_type(scope_def_id_sub);
147-
let (return_type, action) = if sub_future.is_some() {
148-
("returned future", "held across an await point")
149-
} else {
150-
("return type", "returned")
151-
};
116+
match (sup_is_ret_type, sub_is_ret_type) {
117+
(ret_capture @ Some(ret_span), _) | (_, ret_capture @ Some(ret_span)) => {
118+
let param_span =
119+
if sup_is_ret_type == ret_capture { ty_sub.span } else { ty_sup.span };
120+
121+
err.span_label(
122+
param_span,
123+
"this parameter and the return type are declared with different lifetimes...",
124+
);
125+
err.span_label(ret_span, "");
126+
err.span_label(span, format!("...but data{} is returned here", span_label_var1));
127+
}
152128

153-
(
129+
(None, None) => {
130+
if ty_sup.hir_id == ty_sub.hir_id {
131+
err.span_label(ty_sup.span, "this type is declared with multiple lifetimes...");
132+
err.span_label(ty_sub.span, "");
133+
err.span_label(span, "...but data with one lifetime flows into the other here");
134+
} else {
135+
err.span_label(
154136
ty_sup.span,
155-
ret_span,
156-
format!(
157-
"this parameter and the {} are declared with different lifetimes...",
158-
return_type
159-
),
160-
format!("...but data{} is {} here", span_label_var1, action),
161-
sub_future,
162-
)
137+
"these two types are declared with different lifetimes...",
138+
);
139+
err.span_label(ty_sub.span, "");
140+
err.span_label(
141+
span,
142+
format!("...but data{} flows{} here", span_label_var1, span_label_var2),
143+
);
163144
}
164-
};
165-
166-
let mut err = struct_span_err!(self.tcx().sess, span, E0623, "lifetime mismatch");
167-
168-
err.span_label(span_1, main_label);
169-
err.span_label(span_2, String::new());
170-
err.span_label(span, span_label);
145+
}
146+
}
171147

172148
self.suggest_adding_lifetime_params(sub, ty_sup, ty_sub, &mut err);
173149

174-
if let Some(t) = future_return_type {
175-
let snip = self
176-
.tcx()
177-
.sess
178-
.source_map()
179-
.span_to_snippet(t.span)
180-
.ok()
181-
.and_then(|s| match (&t.kind, s.as_str()) {
182-
(rustc_hir::TyKind::Tup(&[]), "") => Some("()".to_string()),
183-
(_, "") => None,
184-
_ => Some(s),
185-
})
186-
.unwrap_or_else(|| "{unnamed_type}".to_string());
187-
188-
err.span_label(
189-
t.span,
190-
&format!("this `async fn` implicitly returns an `impl Future<Output = {}>`", snip),
191-
);
192-
}
193150
err.emit();
194151
Some(ErrorReported)
195152
}

compiler/rustc_infer/src/infer/error_reporting/nice_region_error/find_anon_type.rs

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use rustc_hir as hir;
22
use rustc_hir::intravisit::{self, Visitor};
3-
use rustc_hir::Node;
43
use rustc_middle::hir::map::Map;
54
use rustc_middle::hir::nested_filter;
65
use rustc_middle::middle::resolve_lifetime as rl;
@@ -25,25 +24,19 @@ pub(crate) fn find_anon_type<'tcx>(
2524
tcx: TyCtxt<'tcx>,
2625
region: Region<'tcx>,
2726
br: &ty::BoundRegionKind,
28-
) -> Option<(&'tcx hir::Ty<'tcx>, &'tcx hir::FnDecl<'tcx>)> {
27+
) -> Option<(&'tcx hir::Ty<'tcx>, &'tcx hir::FnSig<'tcx>)> {
2928
if let Some(anon_reg) = tcx.is_suitable_region(region) {
3029
let hir_id = tcx.hir().local_def_id_to_hir_id(anon_reg.def_id);
31-
let fndecl = match tcx.hir().get(hir_id) {
32-
Node::Item(&hir::Item { kind: hir::ItemKind::Fn(ref m, ..), .. })
33-
| Node::TraitItem(&hir::TraitItem {
34-
kind: hir::TraitItemKind::Fn(ref m, ..), ..
35-
})
36-
| Node::ImplItem(&hir::ImplItem { kind: hir::ImplItemKind::Fn(ref m, ..), .. }) => {
37-
&m.decl
38-
}
39-
_ => return None,
30+
let Some(fn_sig) = tcx.hir().get(hir_id).fn_sig() else {
31+
return None
4032
};
4133

42-
fndecl
34+
fn_sig
35+
.decl
4336
.inputs
4437
.iter()
4538
.find_map(|arg| find_component_for_bound_region(tcx, arg, br))
46-
.map(|ty| (ty, &**fndecl))
39+
.map(|ty| (ty, fn_sig))
4740
} else {
4841
None
4942
}

compiler/rustc_infer/src/infer/error_reporting/nice_region_error/util.rs

+26-65
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use crate::infer::error_reporting::nice_region_error::NiceRegionError;
55
use rustc_hir as hir;
66
use rustc_hir::def_id::LocalDefId;
7-
use rustc_middle::ty::{self, DefIdTree, Region, Ty};
7+
use rustc_middle::ty::{self, Binder, DefIdTree, Region, Ty, TypeFoldable};
88
use rustc_span::Span;
99

1010
/// Information about the anonymous region we are searching for.
@@ -94,81 +94,42 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
9494
})
9595
}
9696

97-
pub(super) fn future_return_type(
98-
&self,
99-
local_def_id: LocalDefId,
100-
) -> Option<&rustc_hir::Ty<'_>> {
101-
if let Some(hir::IsAsync::Async) = self.asyncness(local_def_id) {
102-
if let rustc_middle::ty::Opaque(def_id, _) =
103-
self.tcx().type_of(local_def_id).fn_sig(self.tcx()).output().skip_binder().kind()
104-
{
105-
match self.tcx().hir().get_if_local(*def_id) {
106-
Some(hir::Node::Item(hir::Item {
107-
kind:
108-
hir::ItemKind::OpaqueTy(hir::OpaqueTy {
109-
bounds,
110-
origin: hir::OpaqueTyOrigin::AsyncFn(..),
111-
..
112-
}),
113-
..
114-
})) => {
115-
for b in bounds.iter() {
116-
if let hir::GenericBound::LangItemTrait(
117-
hir::LangItem::Future,
118-
_span,
119-
_hir_id,
120-
generic_args,
121-
) = b
122-
{
123-
for type_binding in generic_args.bindings.iter() {
124-
if type_binding.ident.name == rustc_span::sym::Output {
125-
if let hir::TypeBindingKind::Equality {
126-
term: hir::Term::Ty(ty),
127-
} = type_binding.kind
128-
{
129-
return Some(ty);
130-
}
131-
}
132-
}
133-
}
134-
}
135-
}
136-
_ => {}
137-
}
138-
}
139-
}
140-
None
141-
}
142-
143-
pub(super) fn asyncness(&self, local_def_id: LocalDefId) -> Option<hir::IsAsync> {
144-
// similar to the asyncness fn in rustc_ty_utils::ty
145-
let hir_id = self.tcx().hir().local_def_id_to_hir_id(local_def_id);
146-
let node = self.tcx().hir().get(hir_id);
147-
let fn_kind = node.fn_kind()?;
148-
Some(fn_kind.asyncness())
149-
}
150-
15197
// Here, we check for the case where the anonymous region
152-
// is in the return type.
98+
// is in the return type as written by the user.
15399
// FIXME(#42703) - Need to handle certain cases here.
154100
pub(super) fn is_return_type_anon(
155101
&self,
156102
scope_def_id: LocalDefId,
157103
br: ty::BoundRegionKind,
158-
decl: &hir::FnDecl<'_>,
104+
hir_sig: &hir::FnSig<'_>,
159105
) -> Option<Span> {
160-
let ret_ty = self.tcx().type_of(scope_def_id);
161-
if let ty::FnDef(_, _) = ret_ty.kind() {
162-
let sig = ret_ty.fn_sig(self.tcx());
163-
let late_bound_regions =
164-
self.tcx().collect_referenced_late_bound_regions(&sig.output());
165-
if late_bound_regions.iter().any(|r| *r == br) {
166-
return Some(decl.output.span());
167-
}
106+
let fn_ty = self.tcx().type_of(scope_def_id);
107+
if let ty::FnDef(_, _) = fn_ty.kind() {
108+
let ret_ty = fn_ty.fn_sig(self.tcx()).output();
109+
let span = hir_sig.decl.output.span();
110+
let future_output = if hir_sig.header.is_async() {
111+
ret_ty.map_bound(|ty| self.infcx.get_impl_future_output_ty(ty)).transpose()
112+
} else {
113+
None
114+
};
115+
return match future_output {
116+
Some(output) if self.includes_region(output, br) => Some(span),
117+
None if self.includes_region(ret_ty, br) => Some(span),
118+
_ => None,
119+
};
168120
}
169121
None
170122
}
171123

124+
fn includes_region(
125+
&self,
126+
ty: Binder<'tcx, impl TypeFoldable<'tcx>>,
127+
region: ty::BoundRegionKind,
128+
) -> bool {
129+
let late_bound_regions = self.tcx().collect_referenced_late_bound_regions(&ty);
130+
late_bound_regions.iter().any(|r| *r == region)
131+
}
132+
172133
// Here we check for the case where anonymous region
173134
// corresponds to self and if yes, we display E0312.
174135
// FIXME(#42700) - Need to format self properly to

compiler/rustc_typeck/src/check/expr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1909,7 +1909,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19091909
_ => return,
19101910
};
19111911
let mut add_label = true;
1912-
if let ty::Adt(def, _) = output_ty.kind() {
1912+
if let ty::Adt(def, _) = output_ty.skip_binder().kind() {
19131913
// no field access on enum type
19141914
if !def.is_enum() {
19151915
if def

0 commit comments

Comments
 (0)