Skip to content

Commit da7d405

Browse files
committed
Auto merge of rust-lang#87244 - jackh726:issue-71883, r=estebank
Better diagnostics with mismatched types due to implicit static lifetime Fixes rust-lang#78113 I think this is my first diagnostics PR...definitely happy to hear thoughts on the direction/implementation here. I was originally just trying to solve the error above, where the lifetime on a GAT was causing a cryptic "mismatched types" error. But as I was writing this, I realized that this (unintentionally) also applied to a different case: `wf-in-foreign-fn-decls-issue-80468.rs`. I'm not sure if this diagnostic should get a new error code, or even reuse an existing one. And, there might be some ways to make this even more generalized. Also, the error is a bit more lengthy and verbose than probably needed. So thoughts there are welcome too. This PR essentially ended up adding a new nice region error pass that triggers if a type doesn't match the self type of an impl which is selected because of a predicate because of an implicit static bound on that self type. r? `@estebank`
2 parents 718d53b + ae02491 commit da7d405

File tree

16 files changed

+299
-38
lines changed

16 files changed

+299
-38
lines changed

compiler/rustc_ast_lowering/src/path.rs

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
336336
insertion_sp,
337337
suggestion,
338338
);
339+
err.note("assuming a `'static` lifetime...");
339340
err.emit();
340341
}
341342
AnonymousLifetimeMode::PassThrough | AnonymousLifetimeMode::ReportError => {

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

+7-11
Original file line numberDiff line numberDiff line change
@@ -1590,17 +1590,13 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
15901590
}
15911591
};
15921592
if let Some((expected, found)) = expected_found {
1593-
let expected_label = match exp_found {
1594-
Mismatch::Variable(ef) => ef.expected.prefix_string(self.tcx),
1595-
Mismatch::Fixed(s) => s.into(),
1596-
};
1597-
let found_label = match exp_found {
1598-
Mismatch::Variable(ef) => ef.found.prefix_string(self.tcx),
1599-
Mismatch::Fixed(s) => s.into(),
1600-
};
1601-
let exp_found = match exp_found {
1602-
Mismatch::Variable(exp_found) => Some(exp_found),
1603-
Mismatch::Fixed(_) => None,
1593+
let (expected_label, found_label, exp_found) = match exp_found {
1594+
Mismatch::Variable(ef) => (
1595+
ef.expected.prefix_string(self.tcx),
1596+
ef.found.prefix_string(self.tcx),
1597+
Some(ef),
1598+
),
1599+
Mismatch::Fixed(s) => (s.into(), s.into(), None),
16041600
};
16051601
match (&terr, expected == found) {
16061602
(TypeError::Sorts(values), extra) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//! Error Reporting for when the lifetime for a type doesn't match the `impl` selected for a predicate
2+
//! to hold.
3+
4+
use crate::infer::error_reporting::nice_region_error::NiceRegionError;
5+
use crate::infer::error_reporting::note_and_explain_region;
6+
use crate::infer::lexical_region_resolve::RegionResolutionError;
7+
use crate::infer::{SubregionOrigin, TypeTrace};
8+
use crate::traits::ObligationCauseCode;
9+
use rustc_data_structures::stable_set::FxHashSet;
10+
use rustc_errors::{Applicability, ErrorReported};
11+
use rustc_hir as hir;
12+
use rustc_hir::intravisit::Visitor;
13+
use rustc_middle::ty::{self, TypeVisitor};
14+
use rustc_span::MultiSpan;
15+
16+
impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
17+
pub(super) fn try_report_mismatched_static_lifetime(&self) -> Option<ErrorReported> {
18+
let error = self.error.as_ref()?;
19+
debug!("try_report_mismatched_static_lifetime {:?}", error);
20+
21+
let (origin, sub, sup) = match error.clone() {
22+
RegionResolutionError::ConcreteFailure(origin, sub, sup) => (origin, sub, sup),
23+
_ => return None,
24+
};
25+
if *sub != ty::RegionKind::ReStatic {
26+
return None;
27+
}
28+
let cause = match origin {
29+
SubregionOrigin::Subtype(box TypeTrace { ref cause, .. }) => cause,
30+
_ => return None,
31+
};
32+
let (parent, impl_def_id) = match &cause.code {
33+
ObligationCauseCode::MatchImpl(parent, impl_def_id) => (parent, impl_def_id),
34+
_ => return None,
35+
};
36+
let binding_span = match **parent {
37+
ObligationCauseCode::BindingObligation(_def_id, binding_span) => binding_span,
38+
_ => return None,
39+
};
40+
let mut err = self.tcx().sess.struct_span_err(cause.span, "incompatible lifetime on type");
41+
// FIXME: we should point at the lifetime
42+
let mut multi_span: MultiSpan = vec![binding_span].into();
43+
multi_span
44+
.push_span_label(binding_span, "introduces a `'static` lifetime requirement".into());
45+
err.span_note(multi_span, "because this has an unmet lifetime requirement");
46+
note_and_explain_region(self.tcx(), &mut err, "", sup, "...");
47+
if let Some(impl_node) = self.tcx().hir().get_if_local(*impl_def_id) {
48+
// If an impl is local, then maybe this isn't what they want. Try to
49+
// be as helpful as possible with implicit lifetimes.
50+
51+
// First, let's get the hir self type of the impl
52+
let impl_self_ty = match impl_node {
53+
hir::Node::Item(hir::Item {
54+
kind: hir::ItemKind::Impl(hir::Impl { self_ty, .. }),
55+
..
56+
}) => self_ty,
57+
_ => bug!("Node not an impl."),
58+
};
59+
60+
// Next, let's figure out the set of trait objects with implict static bounds
61+
let ty = self.tcx().type_of(*impl_def_id);
62+
let mut v = super::static_impl_trait::TraitObjectVisitor(FxHashSet::default());
63+
v.visit_ty(ty);
64+
let mut traits = vec![];
65+
for matching_def_id in v.0 {
66+
let mut hir_v =
67+
super::static_impl_trait::HirTraitObjectVisitor(&mut traits, matching_def_id);
68+
hir_v.visit_ty(&impl_self_ty);
69+
}
70+
71+
if traits.is_empty() {
72+
// If there are no trait object traits to point at, either because
73+
// there aren't trait objects or because none are implicit, then just
74+
// write a single note on the impl itself.
75+
76+
let impl_span = self.tcx().def_span(*impl_def_id);
77+
err.span_note(impl_span, "...does not necessarily outlive the static lifetime introduced by the compatible `impl`");
78+
} else {
79+
// Otherwise, point at all implicit static lifetimes
80+
81+
err.note("...does not necessarily outlive the static lifetime introduced by the compatible `impl`");
82+
for span in &traits {
83+
err.span_note(*span, "this has an implicit `'static` lifetime requirement");
84+
// It would be nice to put this immediately under the above note, but they get
85+
// pushed to the end.
86+
err.span_suggestion_verbose(
87+
span.shrink_to_hi(),
88+
"consider relaxing the implicit `'static` requirement",
89+
" + '_".to_string(),
90+
Applicability::MaybeIncorrect,
91+
);
92+
}
93+
}
94+
} else {
95+
// Otherwise just point out the impl.
96+
97+
let impl_span = self.tcx().def_span(*impl_def_id);
98+
err.span_note(impl_span, "...does not necessarily outlive the static lifetime introduced by the compatible `impl`");
99+
}
100+
err.emit();
101+
Some(ErrorReported)
102+
}
103+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_span::source_map::Span;
77

88
mod different_lifetimes;
99
pub mod find_anon_type;
10+
mod mismatched_static_lifetime;
1011
mod named_anon_conflict;
1112
mod placeholder_error;
1213
mod static_impl_trait;
@@ -58,6 +59,7 @@ impl<'cx, 'tcx> NiceRegionError<'cx, 'tcx> {
5859
.or_else(|| self.try_report_impl_not_conforming_to_trait())
5960
.or_else(|| self.try_report_anon_anon_conflict())
6061
.or_else(|| self.try_report_static_impl_trait())
62+
.or_else(|| self.try_report_mismatched_static_lifetime())
6163
}
6264

6365
pub fn regions(&self) -> Option<(Span, ty::Region<'tcx>, ty::Region<'tcx>)> {

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

+24-18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::infer::error_reporting::nice_region_error::NiceRegionError;
44
use crate::infer::lexical_region_resolve::RegionResolutionError;
55
use crate::infer::{SubregionOrigin, TypeTrace};
66
use crate::traits::{ObligationCauseCode, UnifyReceiverContext};
7+
use rustc_data_structures::stable_set::FxHashSet;
78
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorReported};
89
use rustc_hir::def_id::DefId;
910
use rustc_hir::intravisit::{walk_ty, ErasedMap, NestedVisitorMap, Visitor};
@@ -185,17 +186,20 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
185186
}
186187
}
187188
if let SubregionOrigin::Subtype(box TypeTrace { cause, .. }) = &sub_origin {
188-
if let ObligationCauseCode::ItemObligation(item_def_id) = cause.code {
189+
let code = match &cause.code {
190+
ObligationCauseCode::MatchImpl(parent, ..) => &**parent,
191+
_ => &cause.code,
192+
};
193+
if let ObligationCauseCode::ItemObligation(item_def_id) = *code {
189194
// Same case of `impl Foo for dyn Bar { fn qux(&self) {} }` introducing a `'static`
190195
// lifetime as above, but called using a fully-qualified path to the method:
191196
// `Foo::qux(bar)`.
192-
let mut v = TraitObjectVisitor(vec![]);
197+
let mut v = TraitObjectVisitor(FxHashSet::default());
193198
v.visit_ty(param.param_ty);
194199
if let Some((ident, self_ty)) =
195-
self.get_impl_ident_and_self_ty_from_trait(item_def_id, &v.0[..])
200+
self.get_impl_ident_and_self_ty_from_trait(item_def_id, &v.0)
196201
{
197-
if self.suggest_constrain_dyn_trait_in_impl(&mut err, &v.0[..], ident, self_ty)
198-
{
202+
if self.suggest_constrain_dyn_trait_in_impl(&mut err, &v.0, ident, self_ty) {
199203
override_error_code = Some(ident);
200204
}
201205
}
@@ -336,7 +340,7 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
336340
fn get_impl_ident_and_self_ty_from_trait(
337341
&self,
338342
def_id: DefId,
339-
trait_objects: &[DefId],
343+
trait_objects: &FxHashSet<DefId>,
340344
) -> Option<(Ident, &'tcx hir::Ty<'tcx>)> {
341345
let tcx = self.tcx();
342346
match tcx.hir().get_if_local(def_id) {
@@ -373,9 +377,10 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
373377
// multiple `impl`s for the same trait like
374378
// `impl Foo for Box<dyn Bar>` and `impl Foo for dyn Bar`.
375379
// In that case, only the first one will get suggestions.
376-
let mut hir_v = HirTraitObjectVisitor(vec![], *did);
380+
let mut traits = vec![];
381+
let mut hir_v = HirTraitObjectVisitor(&mut traits, *did);
377382
hir_v.visit_ty(self_ty);
378-
!hir_v.0.is_empty()
383+
!traits.is_empty()
379384
}) =>
380385
{
381386
Some(self_ty)
@@ -417,33 +422,34 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
417422
_ => return false,
418423
};
419424

420-
let mut v = TraitObjectVisitor(vec![]);
425+
let mut v = TraitObjectVisitor(FxHashSet::default());
421426
v.visit_ty(ty);
422427

423428
// Get the `Ident` of the method being called and the corresponding `impl` (to point at
424429
// `Bar` in `impl Foo for dyn Bar {}` and the definition of the method being called).
425430
let (ident, self_ty) =
426-
match self.get_impl_ident_and_self_ty_from_trait(instance.def_id(), &v.0[..]) {
431+
match self.get_impl_ident_and_self_ty_from_trait(instance.def_id(), &v.0) {
427432
Some((ident, self_ty)) => (ident, self_ty),
428433
None => return false,
429434
};
430435

431436
// Find the trait object types in the argument, so we point at *only* the trait object.
432-
self.suggest_constrain_dyn_trait_in_impl(err, &v.0[..], ident, self_ty)
437+
self.suggest_constrain_dyn_trait_in_impl(err, &v.0, ident, self_ty)
433438
}
434439

435440
fn suggest_constrain_dyn_trait_in_impl(
436441
&self,
437442
err: &mut DiagnosticBuilder<'_>,
438-
found_dids: &[DefId],
443+
found_dids: &FxHashSet<DefId>,
439444
ident: Ident,
440445
self_ty: &hir::Ty<'_>,
441446
) -> bool {
442447
let mut suggested = false;
443448
for found_did in found_dids {
444-
let mut hir_v = HirTraitObjectVisitor(vec![], *found_did);
449+
let mut traits = vec![];
450+
let mut hir_v = HirTraitObjectVisitor(&mut traits, *found_did);
445451
hir_v.visit_ty(&self_ty);
446-
for span in &hir_v.0 {
452+
for span in &traits {
447453
let mut multi_span: MultiSpan = vec![*span].into();
448454
multi_span.push_span_label(
449455
*span,
@@ -468,14 +474,14 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
468474
}
469475

470476
/// Collect all the trait objects in a type that could have received an implicit `'static` lifetime.
471-
struct TraitObjectVisitor(Vec<DefId>);
477+
pub(super) struct TraitObjectVisitor(pub(super) FxHashSet<DefId>);
472478

473479
impl TypeVisitor<'_> for TraitObjectVisitor {
474480
fn visit_ty(&mut self, t: Ty<'_>) -> ControlFlow<Self::BreakTy> {
475481
match t.kind() {
476482
ty::Dynamic(preds, RegionKind::ReStatic) => {
477483
if let Some(def_id) = preds.principal_def_id() {
478-
self.0.push(def_id);
484+
self.0.insert(def_id);
479485
}
480486
ControlFlow::CONTINUE
481487
}
@@ -485,9 +491,9 @@ impl TypeVisitor<'_> for TraitObjectVisitor {
485491
}
486492

487493
/// Collect all `hir::Ty<'_>` `Span`s for trait objects with an implicit lifetime.
488-
struct HirTraitObjectVisitor(Vec<Span>, DefId);
494+
pub(super) struct HirTraitObjectVisitor<'a>(pub(super) &'a mut Vec<Span>, pub(super) DefId);
489495

490-
impl<'tcx> Visitor<'tcx> for HirTraitObjectVisitor {
496+
impl<'a, 'tcx> Visitor<'tcx> for HirTraitObjectVisitor<'a> {
491497
type Map = ErasedMap<'tcx>;
492498

493499
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {

compiler/rustc_middle/src/traits/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ pub enum ObligationCauseCode<'tcx> {
333333
/// This is purely for diagnostic purposes - it is always
334334
/// correct to use `MiscObligation` instead
335335
WellFormed(Option<hir::HirId>),
336+
337+
/// From `match_impl`. The cause for us having to match an impl, and the DefId we are matching against.
338+
MatchImpl(Lrc<ObligationCauseCode<'tcx>>, DefId),
336339
}
337340

338341
impl ObligationCauseCode<'_> {

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1903,7 +1903,8 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
19031903
| ObligationCauseCode::UnifyReceiver(..)
19041904
| ObligationCauseCode::OpaqueType
19051905
| ObligationCauseCode::MiscObligation
1906-
| ObligationCauseCode::WellFormed(..) => {}
1906+
| ObligationCauseCode::WellFormed(..)
1907+
| ObligationCauseCode::MatchImpl(..) => {}
19071908
ObligationCauseCode::SliceOrArrayElem => {
19081909
err.note("slice and array elements must have `Sized` type");
19091910
}

compiler/rustc_trait_selection/src/traits/select/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -1903,9 +1903,15 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
19031903

19041904
debug!(?impl_trait_ref, ?placeholder_obligation_trait_ref);
19051905

1906+
let cause = ObligationCause::new(
1907+
obligation.cause.span,
1908+
obligation.cause.body_id,
1909+
ObligationCauseCode::MatchImpl(Lrc::new(obligation.cause.code.clone()), impl_def_id),
1910+
);
1911+
19061912
let InferOk { obligations, .. } = self
19071913
.infcx
1908-
.at(&obligation.cause, obligation.param_env)
1914+
.at(&cause, obligation.param_env)
19091915
.eq(placeholder_obligation_trait_ref, impl_trait_ref)
19101916
.map_err(|e| debug!("match_impl: failed eq_trait_refs due to `{}`", e))?;
19111917
nested_obligations.extend(obligations);

src/test/ui/async-await/async-fn-path-elision.stderr

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ error[E0726]: implicit elided lifetime not allowed here
33
|
44
LL | async fn error(lt: HasLifetime) {
55
| ^^^^^^^^^^^- help: indicate the anonymous lifetime: `<'_>`
6+
|
7+
= note: assuming a `'static` lifetime...
68

79
error: aborting due to previous error
810

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Test for diagnostics when we have mismatched lifetime due to implict 'static lifetime in GATs
2+
3+
// check-fail
4+
5+
#![feature(generic_associated_types)]
6+
7+
pub trait A {}
8+
impl A for &dyn A {}
9+
impl A for Box<dyn A> {}
10+
11+
pub trait B {
12+
type T<'a>: A;
13+
}
14+
15+
impl B for () {
16+
// `'a` doesn't match implicit `'static`: suggest `'_`
17+
type T<'a> = Box<dyn A + 'a>; //~ incompatible lifetime on type
18+
}
19+
20+
trait C {}
21+
impl C for Box<dyn A + 'static> {}
22+
pub trait D {
23+
type T<'a>: C;
24+
}
25+
impl D for () {
26+
// `'a` doesn't match explicit `'static`: we *should* suggest removing `'static`
27+
type T<'a> = Box<dyn A + 'a>; //~ incompatible lifetime on type
28+
}
29+
30+
trait E {}
31+
impl E for (Box<dyn A>, Box<dyn A>) {}
32+
pub trait F {
33+
type T<'a>: E;
34+
}
35+
impl F for () {
36+
// `'a` doesn't match explicit `'static`: suggest `'_`
37+
type T<'a> = (Box<dyn A + 'a>, Box<dyn A + 'a>); //~ incompatible lifetime on type
38+
}
39+
40+
fn main() {}

0 commit comments

Comments
 (0)