From d72bcdb42cc10a20c2eef49e5f8cd2782f44b922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 19 Jan 2020 17:27:37 -0800 Subject: [PATCH] When object unsafe trait uses itself in associated item suggest using `Self` --- src/librustc_typeck/check/wfcheck.rs | 69 ++++++++++++++++++- .../object-unsafe-trait-should-use-self.rs | 16 +++++ ...object-unsafe-trait-should-use-self.stderr | 47 +++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/suggestions/object-unsafe-trait-should-use-self.rs create mode 100644 src/test/ui/suggestions/object-unsafe-trait-should-use-self.stderr diff --git a/src/librustc_typeck/check/wfcheck.rs b/src/librustc_typeck/check/wfcheck.rs index ef3dcf15873..faeaedce8d0 100644 --- a/src/librustc_typeck/check/wfcheck.rs +++ b/src/librustc_typeck/check/wfcheck.rs @@ -10,10 +10,10 @@ use rustc::ty::{ self, AdtKind, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeFoldable, WithConstness, }; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_errors::{struct_span_err, DiagnosticBuilder}; +use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder}; use rustc_hir::def_id::DefId; use rustc_hir::ItemKind; -use rustc_span::symbol::sym; +use rustc_span::symbol::{sym, Ident}; use rustc_span::Span; use syntax::ast; @@ -176,9 +176,74 @@ pub fn check_trait_item(tcx: TyCtxt<'_>, def_id: DefId) { hir::TraitItemKind::Method(ref sig, _) => Some(sig), _ => None, }; + check_bare_self_trait_by_name(tcx, &trait_item); check_associated_item(tcx, trait_item.hir_id, trait_item.span, method_sig); } +fn could_be_self(trait_name: Ident, ty: &hir::Ty<'_>) -> bool { + match ty.kind { + hir::TyKind::TraitObject([trait_ref], ..) => { + let mut p = trait_ref.trait_ref.path.segments.iter().map(|s| s.ident); + match (p.next(), p.next()) { + (Some(ident), None) => ident == trait_name, + _ => false, + } + } + _ => false, + } +} + +/// Detect when an object unsafe trait is referring to itself in one of its associated items. +/// When this is done, suggest using `Self` instead. +fn check_bare_self_trait_by_name(tcx: TyCtxt<'_>, item: &hir::TraitItem<'_>) { + let (trait_name, trait_def_id) = match tcx.hir().get(tcx.hir().get_parent_item(item.hir_id)) { + hir::Node::Item(item) => match item.kind { + hir::ItemKind::Trait(..) => (item.ident, tcx.hir().local_def_id(item.hir_id)), + _ => return, + }, + _ => return, + }; + let mut trait_should_be_self = vec![]; + match &item.kind { + hir::TraitItemKind::Const(ty, _) | hir::TraitItemKind::Type(_, Some(ty)) + if could_be_self(trait_name, ty) => + { + trait_should_be_self.push(ty.span) + } + hir::TraitItemKind::Method(sig, _) => { + for ty in sig.decl.inputs { + if could_be_self(trait_name, ty) { + trait_should_be_self.push(ty.span); + } + } + match sig.decl.output { + hir::FunctionRetTy::Return(ty) if could_be_self(trait_name, ty) => { + trait_should_be_self.push(ty.span); + } + _ => {} + } + } + _ => {} + } + if !trait_should_be_self.is_empty() { + if rustc::traits::object_safety_violations(tcx, trait_def_id).is_empty() { + return; + } + let sugg = trait_should_be_self.iter().map(|span| (*span, "Self".to_string())).collect(); + let mut err = tcx.sess.struct_span_err( + trait_should_be_self, + "associated item referring to unboxed trait object for its own trait", + ); + err.span_label(trait_name.span, "in this trait"); + err.multipart_suggestion( + "you might have meant to use `Self` to refer to the materialized type", + sugg, + Applicability::MachineApplicable, + ); + err.emit(); + } +} + pub fn check_impl_item(tcx: TyCtxt<'_>, def_id: DefId) { let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap(); let impl_item = tcx.hir().expect_impl_item(hir_id); diff --git a/src/test/ui/suggestions/object-unsafe-trait-should-use-self.rs b/src/test/ui/suggestions/object-unsafe-trait-should-use-self.rs new file mode 100644 index 00000000000..75f99075eb1 --- /dev/null +++ b/src/test/ui/suggestions/object-unsafe-trait-should-use-self.rs @@ -0,0 +1,16 @@ +#![allow(bare_trait_objects)] +trait A: Sized { + fn f(a: A) -> A; + //~^ ERROR associated item referring to unboxed trait object for its own trait + //~| ERROR the trait `A` cannot be made into an object +} +trait B { + fn f(a: B) -> B; + //~^ ERROR associated item referring to unboxed trait object for its own trait + //~| ERROR the trait `B` cannot be made into an object +} +trait C { + fn f(&self, a: C) -> C; +} + +fn main() {} diff --git a/src/test/ui/suggestions/object-unsafe-trait-should-use-self.stderr b/src/test/ui/suggestions/object-unsafe-trait-should-use-self.stderr new file mode 100644 index 00000000000..70d069d2aa2 --- /dev/null +++ b/src/test/ui/suggestions/object-unsafe-trait-should-use-self.stderr @@ -0,0 +1,47 @@ +error: associated item referring to unboxed trait object for its own trait + --> $DIR/object-unsafe-trait-should-use-self.rs:3:13 + | +LL | trait A: Sized { + | - in this trait +LL | fn f(a: A) -> A; + | ^ ^ + | +help: you might have meant to use `Self` to refer to the materialized type + | +LL | fn f(a: Self) -> Self; + | ^^^^ ^^^^ + +error[E0038]: the trait `A` cannot be made into an object + --> $DIR/object-unsafe-trait-should-use-self.rs:3:13 + | +LL | trait A: Sized { + | ----- the trait cannot require that `Self : Sized` +LL | fn f(a: A) -> A; + | ^ the trait `A` cannot be made into an object + | + = note: the trait cannot require that `Self : Sized` + +error: associated item referring to unboxed trait object for its own trait + --> $DIR/object-unsafe-trait-should-use-self.rs:8:13 + | +LL | trait B { + | - in this trait +LL | fn f(a: B) -> B; + | ^ ^ + | +help: you might have meant to use `Self` to refer to the materialized type + | +LL | fn f(a: Self) -> Self; + | ^^^^ ^^^^ + +error[E0038]: the trait `B` cannot be made into an object + --> $DIR/object-unsafe-trait-should-use-self.rs:8:13 + | +LL | fn f(a: B) -> B; + | - ^ the trait `B` cannot be made into an object + | | + | associated function `f` has no `self` parameter + +error: aborting due to 4 previous errors + +For more information about this error, try `rustc --explain E0038`.