From 5642733030e4e49a03b0c478b2e74431e5223511 Mon Sep 17 00:00:00 2001 From: Falco Hirschenberger Date: Fri, 18 Oct 2024 10:39:09 +0200 Subject: [PATCH] Fix suggestions when returning a bare trait from an async fn. Don't assume we always return a trait object and suggest the dyn keyword. Suggest adding impl instead. --- Cargo.lock | 1 + compiler/rustc_borrowck/Cargo.toml | 1 + .../src/diagnostics/region_name.rs | 44 ++------------ .../src/hir_ty_lowering/lint.rs | 58 +++++++++++++++++-- .../suggest-impl-on-bare-trait-return.rs | 8 +++ .../suggest-impl-on-bare-trait-return.stderr | 18 ++++++ 6 files changed, 85 insertions(+), 45 deletions(-) create mode 100644 tests/ui/traits/suggest-impl-on-bare-trait-return.rs create mode 100644 tests/ui/traits/suggest-impl-on-bare-trait-return.stderr diff --git a/Cargo.lock b/Cargo.lock index 5f81a5a84966a..552e1c9710239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3365,6 +3365,7 @@ dependencies = [ "rustc_fluent_macro", "rustc_graphviz", "rustc_hir", + "rustc_hir_analysis", "rustc_index", "rustc_infer", "rustc_lexer", diff --git a/compiler/rustc_borrowck/Cargo.toml b/compiler/rustc_borrowck/Cargo.toml index bafc62c7318b4..d246d49e2157a 100644 --- a/compiler/rustc_borrowck/Cargo.toml +++ b/compiler/rustc_borrowck/Cargo.toml @@ -13,6 +13,7 @@ rustc_errors = { path = "../rustc_errors" } rustc_fluent_macro = { path = "../rustc_fluent_macro" } rustc_graphviz = { path = "../rustc_graphviz" } rustc_hir = { path = "../rustc_hir" } +rustc_hir_analysis = { path = "../rustc_hir_analysis" } rustc_index = { path = "../rustc_index" } rustc_infer = { path = "../rustc_infer" } rustc_lexer = { path = "../rustc_lexer" } diff --git a/compiler/rustc_borrowck/src/diagnostics/region_name.rs b/compiler/rustc_borrowck/src/diagnostics/region_name.rs index b4b8373ac9747..7e0c634f5ca85 100644 --- a/compiler/rustc_borrowck/src/diagnostics/region_name.rs +++ b/compiler/rustc_borrowck/src/diagnostics/region_name.rs @@ -8,10 +8,11 @@ use rustc_data_structures::fx::IndexEntry; use rustc_errors::Diag; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; +use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer; +use rustc_middle::bug; use rustc_middle::ty::print::RegionHighlightMode; use rustc_middle::ty::{self, GenericArgKind, GenericArgsRef, RegionVid, Ty}; -use rustc_middle::{bug, span_bug}; -use rustc_span::symbol::{Symbol, kw, sym}; +use rustc_span::symbol::{Symbol, kw}; use rustc_span::{DUMMY_SP, Span}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; use tracing::{debug, instrument}; @@ -722,7 +723,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { .output; span = output.span(); if let hir::FnRetTy::Return(ret) = output { - hir_ty = Some(self.get_future_inner_return_ty(ret)); + hir_ty = >::get_future_inner_return_ty(ret); } " of async function" } @@ -816,43 +817,6 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> { }) } - /// From the [`hir::Ty`] of an async function's lowered return type, - /// retrieve the `hir::Ty` representing the type the user originally wrote. - /// - /// e.g. given the function: - /// - /// ``` - /// async fn foo() -> i32 { 2 } - /// ``` - /// - /// this function, given the lowered return type of `foo`, an [`OpaqueDef`] that implements `Future`, - /// returns the `i32`. - /// - /// [`OpaqueDef`]: hir::TyKind::OpaqueDef - fn get_future_inner_return_ty(&self, hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> { - let hir::TyKind::OpaqueDef(opaque_ty, _) = hir_ty.kind else { - span_bug!( - hir_ty.span, - "lowered return type of async fn is not OpaqueDef: {:?}", - hir_ty - ); - }; - if let hir::OpaqueTy { bounds: [hir::GenericBound::Trait(trait_ref)], .. } = opaque_ty - && let Some(segment) = trait_ref.trait_ref.path.segments.last() - && let Some(args) = segment.args - && let [constraint] = args.constraints - && constraint.ident.name == sym::Output - && let Some(ty) = constraint.ty() - { - ty - } else { - span_bug!( - hir_ty.span, - "bounds from lowered return type of async fn did not match expected format: {opaque_ty:?}", - ); - } - } - #[instrument(level = "trace", skip(self))] fn give_name_if_anonymous_region_appears_in_yield_ty( &self, diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs index fd49e7e44398b..1f38f810ed238 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/lint.rs @@ -5,7 +5,7 @@ use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_lint_defs::Applicability; use rustc_lint_defs::builtin::BARE_TRAIT_OBJECTS; -use rustc_span::Span; +use rustc_span::{Span, sym}; use rustc_trait_selection::error_reporting::traits::suggestions::NextTypeParamName; use super::HirTyLowerer; @@ -181,14 +181,14 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { /// Make sure that we are in the condition to suggest `impl Trait`. fn maybe_suggest_impl_trait(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) -> bool { let tcx = self.tcx(); - let parent_id = tcx.hir().get_parent_item(self_ty.hir_id).def_id; + let parent_node = tcx.hir_node_by_def_id(tcx.hir().get_parent_item(self_ty.hir_id).def_id); // FIXME: If `type_alias_impl_trait` is enabled, also look for `Trait0` // and suggest `Trait0`. // Functions are found in three different contexts. // 1. Independent functions // 2. Functions inside trait blocks // 3. Functions inside impl blocks - let (sig, generics) = match tcx.hir_node_by_def_id(parent_id) { + let (sig, generics) = match parent_node { hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(sig, generics, _), .. }) => { (sig, generics) } @@ -223,10 +223,17 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { tcx.parent_hir_node(self_ty.hir_id), hir::Node::Ty(hir::Ty { kind: hir::TyKind::Ref(..), .. }) ); + let is_non_trait_object = |ty: &'tcx hir::Ty<'_>| { + if sig.header.is_async() { + Self::get_future_inner_return_ty(ty).map_or(false, |ty| ty.hir_id == self_ty.hir_id) + } else { + ty.peel_refs().hir_id == self_ty.hir_id + } + }; // Suggestions for function return type. if let hir::FnRetTy::Return(ty) = sig.decl.output - && ty.peel_refs().hir_id == self_ty.hir_id + && is_non_trait_object(ty) { let pre = if !is_dyn_compatible { format!("`{trait_name}` is dyn-incompatible, ") @@ -311,10 +318,21 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { } fn maybe_suggest_assoc_ty_bound(&self, self_ty: &hir::Ty<'_>, diag: &mut Diag<'_>) { - let mut parents = self.tcx().hir().parent_iter(self_ty.hir_id); + let mut parents = self.tcx().hir().parent_iter(self_ty.hir_id).peekable(); + let is_async_fn = if let Some((_, parent)) = parents.peek() + && let Some(sig) = parent.fn_sig() + && sig.header.is_async() + && let hir::FnRetTy::Return(ty) = sig.decl.output + && Self::get_future_inner_return_ty(ty).is_some() + { + true + } else { + false + }; if let Some((_, hir::Node::AssocItemConstraint(constraint))) = parents.next() && let Some(obj_ty) = constraint.ty() + && !is_async_fn { if let Some((_, hir::Node::TraitRef(..))) = parents.next() && let Some((_, hir::Node::Ty(ty))) = parents.next() @@ -343,4 +361,34 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { ); } } + + /// From the [`hir::Ty`] of an async function's lowered return type, + /// retrieve the `hir::Ty` representing the type the user originally wrote. + /// + /// e.g. given the function: + /// + /// ``` + /// async fn foo() -> i32 { 2 } + /// ``` + /// + /// this function, given the lowered return type of `foo`, an [`OpaqueDef`] that implements `Future`, + /// returns the `i32`. + /// + /// [`OpaqueDef`]: hir::TyKind::OpaqueDef + pub fn get_future_inner_return_ty<'a>(hir_ty: &'a hir::Ty<'a>) -> Option<&'a hir::Ty<'a>> { + let hir::TyKind::OpaqueDef(opaque_ty, _) = hir_ty.kind else { + return None; + }; + if let hir::OpaqueTy { bounds: [hir::GenericBound::Trait(trait_ref)], .. } = opaque_ty + && let Some(segment) = trait_ref.trait_ref.path.segments.last() + && let Some(args) = segment.args + && let [constraint] = args.constraints + && constraint.ident.name == sym::Output + && let Some(ty) = constraint.ty() + { + Some(ty) + } else { + None + } + } } diff --git a/tests/ui/traits/suggest-impl-on-bare-trait-return.rs b/tests/ui/traits/suggest-impl-on-bare-trait-return.rs new file mode 100644 index 0000000000000..5ef3467243298 --- /dev/null +++ b/tests/ui/traits/suggest-impl-on-bare-trait-return.rs @@ -0,0 +1,8 @@ +//@ edition:2021 +trait Trait {} + +async fn fun() -> Trait { //~ ERROR expected a type, found a trait + todo!() +} + +fn main() {} diff --git a/tests/ui/traits/suggest-impl-on-bare-trait-return.stderr b/tests/ui/traits/suggest-impl-on-bare-trait-return.stderr new file mode 100644 index 0000000000000..cffc3514f8e80 --- /dev/null +++ b/tests/ui/traits/suggest-impl-on-bare-trait-return.stderr @@ -0,0 +1,18 @@ +error[E0782]: expected a type, found a trait + --> $DIR/suggest-impl-on-bare-trait-return.rs:4:13 + | +LL | fn fun() -> Trait { + | ^^^^^ + | +help: use `impl Trait` to return an opaque type, as long as you return a single underlying type + | +LL | fn fun() -> impl Trait { + | ++++ +help: alternatively, you can return an owned trait object + | +LL | fn fun() -> Box { + | +++++++ + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0782`.