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

Add hint for missing lifetime bound on trait object when type alias is used #105345

Merged
merged 1 commit into from
Jan 26, 2023
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
Add hint for missing lifetime bound on trait object when type alias i…
…s used
  • Loading branch information
yanchen4791 committed Jan 23, 2023
commit 62a1e76d2beaa87d7f02a55e2d7faa03cdd5fd7f
59 changes: 40 additions & 19 deletions compiler/rustc_borrowck/src/diagnostics/region_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -813,17 +813,10 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
if *outlived_f != ty::ReStatic {
return;
}
let suitable_region = self.infcx.tcx.is_suitable_region(f);
let Some(suitable_region) = suitable_region else { return; };

let fn_returns = self
.infcx
.tcx
.is_suitable_region(f)
.map(|r| self.infcx.tcx.return_type_impl_or_dyn_traits(r.def_id))
.unwrap_or_default();

if fn_returns.is_empty() {
return;
}
let fn_returns = self.infcx.tcx.return_type_impl_or_dyn_traits(suitable_region.def_id);

let param = if let Some(param) = find_param_with_region(self.infcx.tcx, f, outlived_f) {
param
Expand All @@ -839,15 +832,43 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
};
let captures = format!("captures data from {arg}");

return nice_region_error::suggest_new_region_bound(
self.infcx.tcx,
diag,
fn_returns,
lifetime.to_string(),
Some(arg),
captures,
Some((param.param_ty_span, param.param_ty.to_string())),
self.infcx.tcx.is_suitable_region(f).map(|r| r.def_id),
if !fn_returns.is_empty() {
nice_region_error::suggest_new_region_bound(
self.infcx.tcx,
diag,
fn_returns,
lifetime.to_string(),
Some(arg),
captures,
Some((param.param_ty_span, param.param_ty.to_string())),
Some(suitable_region.def_id),
);
return;
}

let Some((alias_tys, alias_span)) = self
.infcx
.tcx
.return_type_impl_or_dyn_traits_with_type_alias(suitable_region.def_id) else { return; };

// in case the return type of the method is a type alias
let mut spans_suggs: Vec<_> = Vec::new();
for alias_ty in alias_tys {
if alias_ty.span.desugaring_kind().is_some() {
// Skip `async` desugaring `impl Future`.
()
}
if let TyKind::TraitObject(_, lt, _) = alias_ty.kind {
spans_suggs.push((lt.ident.span.shrink_to_hi(), " + 'a".to_string()));
}
}
spans_suggs.push((alias_span.shrink_to_hi(), "<'a>".to_string()));
diag.multipart_suggestion_verbose(
&format!(
"to declare that the trait object {captures}, you can add a lifetime parameter `'a` in the type alias"
),
spans_suggs,
Applicability::MaybeIncorrect,
);
}
}
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3524,6 +3524,13 @@ impl<'hir> Node<'hir> {
}
}

pub fn alias_ty(self) -> Option<&'hir Ty<'hir>> {
match self {
Node::Item(Item { kind: ItemKind::TyAlias(ty, ..), .. }) => Some(ty),
_ => None,
}
}

pub fn body_id(&self) -> Option<BodyId> {
match self {
Node::TraitItem(TraitItem {
Expand Down
24 changes: 24 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,30 @@ impl<'tcx> TyCtxt<'tcx> {
v.0
}

/// Given a `DefId` for an `fn`, return all the `dyn` and `impl` traits in its return type and associated alias span when type alias is used
pub fn return_type_impl_or_dyn_traits_with_type_alias(
self,
scope_def_id: LocalDefId,
) -> Option<(Vec<&'tcx hir::Ty<'tcx>>, Span)> {
let hir_id = self.hir().local_def_id_to_hir_id(scope_def_id);
let mut v = TraitObjectVisitor(vec![], self.hir());
// when the return type is a type alias
if let Some(hir::FnDecl { output: hir::FnRetTy::Return(hir_output), .. }) = self.hir().fn_decl_by_hir_id(hir_id)
&& let hir::TyKind::Path(hir::QPath::Resolved(
None,
hir::Path { res: hir::def::Res::Def(DefKind::TyAlias, def_id), .. }, )) = hir_output.kind
&& let Some(local_id) = def_id.as_local()
&& let Some(alias_ty) = self.hir().get_by_def_id(local_id).alias_ty() // it is type alias
&& let Some(alias_generics) = self.hir().get_by_def_id(local_id).generics()
{
v.visit_ty(alias_ty);
if !v.0.is_empty() {
return Some((v.0, alias_generics.span));
}
}
return None;
}

pub fn return_type_impl_trait(self, scope_def_id: LocalDefId) -> Option<(Ty<'tcx>, Span)> {
// `type_of()` will fail on these (#55796, #86483), so only allow `fn`s or closures.
match self.hir().get_by_def_id(scope_def_id) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// run-rustfix

trait Greeter0 {
fn greet(&self);
}

trait Greeter1 {
fn greet(&self);
}

type BoxedGreeter<'a> = (Box<dyn Greeter0 + 'a>, Box<dyn Greeter1 + 'a>);
//~^ HELP to declare that the trait object captures data from argument `self`, you can add a lifetime parameter `'a` in the type alias

struct FixedGreeter<'a>(pub &'a str);

impl Greeter0 for FixedGreeter<'_> {
fn greet(&self) {
println!("0 {}", self.0)
}
}

impl Greeter1 for FixedGreeter<'_> {
fn greet(&self) {
println!("1 {}", self.0)
}
}

struct Greetings(pub Vec<String>);

impl Greetings {
pub fn get(&self, i: usize) -> BoxedGreeter {
(Box::new(FixedGreeter(&self.0[i])), Box::new(FixedGreeter(&self.0[i])))
//~^ ERROR lifetime may not live long enough
}
}

fn main() {
let mut g = Greetings {0 : vec!()};
g.0.push("a".to_string());
g.0.push("b".to_string());
g.get(0).0.greet();
g.get(0).1.greet();
g.get(1).0.greet();
g.get(1).1.greet();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// run-rustfix

trait Greeter0 {
fn greet(&self);
}

trait Greeter1 {
fn greet(&self);
}

type BoxedGreeter = (Box<dyn Greeter0>, Box<dyn Greeter1>);
//~^ HELP to declare that the trait object captures data from argument `self`, you can add a lifetime parameter `'a` in the type alias

struct FixedGreeter<'a>(pub &'a str);

impl Greeter0 for FixedGreeter<'_> {
fn greet(&self) {
println!("0 {}", self.0)
}
}

impl Greeter1 for FixedGreeter<'_> {
fn greet(&self) {
println!("1 {}", self.0)
}
}

struct Greetings(pub Vec<String>);

impl Greetings {
pub fn get(&self, i: usize) -> BoxedGreeter {
(Box::new(FixedGreeter(&self.0[i])), Box::new(FixedGreeter(&self.0[i])))
//~^ ERROR lifetime may not live long enough
}
}

fn main() {
let mut g = Greetings {0 : vec!()};
g.0.push("a".to_string());
g.0.push("b".to_string());
g.get(0).0.greet();
g.get(0).1.greet();
g.get(1).0.greet();
g.get(1).1.greet();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: lifetime may not live long enough
--> $DIR/issue-103582-hint-for-missing-lifetime-bound-on-trait-object-using-type-alias.rs:32:9
|
LL | pub fn get(&self, i: usize) -> BoxedGreeter {
| - let's call the lifetime of this reference `'1`
LL | (Box::new(FixedGreeter(&self.0[i])), Box::new(FixedGreeter(&self.0[i])))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static`
|
help: to declare that the trait object captures data from argument `self`, you can add a lifetime parameter `'a` in the type alias
|
LL | type BoxedGreeter<'a> = (Box<dyn Greeter0 + 'a>, Box<dyn Greeter1 + 'a>);
| ++++ ++++ ++++

error: aborting due to previous error