Add a "diagnostic item" scheme

This allows lints and other diagnostics to refer to items
by a unique ID instead of relying on whacky path
resolution schemes that may break when items are
relocated.
This commit is contained in:
Oliver Scherer 2019-05-19 20:16:04 +02:00
parent fbdf1d2a71
commit 26e9990198
22 changed files with 260 additions and 18 deletions

View File

@ -291,6 +291,7 @@ use crate::raw_vec::RawVec;
/// [`reserve`]: ../../std/vec/struct.Vec.html#method.reserve
/// [owned slice]: ../../std/boxed/struct.Box.html
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(all(not(bootstrap), not(test)), rustc_diagnostic_item = "vec_type")]
pub struct Vec<T> {
buf: RawVec<T>,
len: usize,

View File

@ -518,7 +518,8 @@ impl Display for Arguments<'_> {
label="`{Self}` cannot be formatted using `{{:?}}` because it doesn't implement `{Debug}`",
)]
#[doc(alias = "{:?}")]
#[lang = "debug_trait"]
#[cfg_attr(boostrap_stdarch_ignore_this, lang = "debug_trait")]
#[cfg_attr(not(boostrap_stdarch_ignore_this), rustc_diagnostic_item = "debug_trait")]
pub trait Debug {
/// Formats the value using the given formatter.
///

View File

@ -94,6 +94,10 @@ macro_rules! arena_types {
rustc::hir::def_id::CrateNum
>
>,
[few] diagnostic_items: rustc_data_structures::fx::FxHashMap<
syntax::symbol::Symbol,
rustc::hir::def_id::DefId,
>,
[few] resolve_lifetimes: rustc::middle::resolve_lifetime::ResolveLifetimes,
[decode] generic_predicates: rustc::ty::GenericPredicates<'tcx>,
[few] lint_levels: rustc::lint::LintLevelMap,

View File

@ -62,6 +62,7 @@
#![feature(log_syntax)]
#![feature(mem_take)]
#![feature(associated_type_bounds)]
#![feature(rustc_attrs)]
#![recursion_limit="512"]
@ -109,6 +110,7 @@ pub mod middle {
pub mod cstore;
pub mod dead;
pub mod dependency_format;
pub mod diagnostic_items;
pub mod entry;
pub mod exported_symbols;
pub mod free_region;

View File

@ -159,29 +159,23 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TyTyKind {
}
fn lint_ty_kind_usage(cx: &LateContext<'_, '_>, segment: &PathSegment) -> bool {
if segment.ident.name == sym::TyKind {
if let Some(res) = segment.res {
if let Some(did) = res.opt_def_id() {
return cx.match_def_path(did, TYKIND_PATH);
}
if let Some(res) = segment.res {
if let Some(did) = res.opt_def_id() {
return cx.tcx.is_diagnostic_item(sym::TyKind, did);
}
}
false
}
const TYKIND_PATH: &[Symbol] = &[sym::rustc, sym::ty, sym::sty, sym::TyKind];
const TY_PATH: &[Symbol] = &[sym::rustc, sym::ty, sym::Ty];
const TYCTXT_PATH: &[Symbol] = &[sym::rustc, sym::ty, sym::context, sym::TyCtxt];
fn is_ty_or_ty_ctxt(cx: &LateContext<'_, '_>, ty: &Ty) -> Option<String> {
match &ty.node {
TyKind::Path(qpath) => {
if let QPath::Resolved(_, path) = qpath {
let did = path.res.opt_def_id()?;
if cx.match_def_path(did, TY_PATH) {
if cx.tcx.is_diagnostic_item(sym::Ty, did) {
return Some(format!("Ty{}", gen_args(path.segments.last().unwrap())));
} else if cx.match_def_path(did, TYCTXT_PATH) {
} else if cx.tcx.is_diagnostic_item(sym::TyCtxt, did) {
return Some(format!("TyCtxt{}", gen_args(path.segments.last().unwrap())));
}
}

View File

@ -0,0 +1,123 @@
//! Detecting diagnostic items.
//!
//! Diagnostic items are items that are not language-inherent, but can reasonably be expected to
//! exist for diagnostic purposes. This allows diagnostic authors to refer to specific items
//! directly, without having to guess module paths and crates.
//! Examples are:
//!
//! * Traits like `Debug`, that have no bearing on language semantics
//!
//! * Compiler internal types like `Ty` and `TyCtxt`
use crate::hir::def_id::{DefId, LOCAL_CRATE};
use crate::ty::TyCtxt;
use crate::util::nodemap::FxHashMap;
use syntax::ast;
use syntax::symbol::{Symbol, sym};
use crate::hir::itemlikevisit::ItemLikeVisitor;
use crate::hir;
struct DiagnosticItemCollector<'tcx> {
// items from this crate
items: FxHashMap<Symbol, DefId>,
tcx: TyCtxt<'tcx>,
}
impl<'v, 'tcx> ItemLikeVisitor<'v> for DiagnosticItemCollector<'tcx> {
fn visit_item(&mut self, item: &hir::Item) {
self.observe_item(&item.attrs, item.hir_id);
}
fn visit_trait_item(&mut self, trait_item: &hir::TraitItem) {
self.observe_item(&trait_item.attrs, trait_item.hir_id);
}
fn visit_impl_item(&mut self, impl_item: &hir::ImplItem) {
self.observe_item(&impl_item.attrs, impl_item.hir_id);
}
}
impl<'tcx> DiagnosticItemCollector<'tcx> {
fn new(tcx: TyCtxt<'tcx>) -> DiagnosticItemCollector<'tcx> {
DiagnosticItemCollector {
tcx,
items: Default::default(),
}
}
fn observe_item(&mut self, attrs: &[ast::Attribute], hir_id: hir::HirId) {
if let Some(name) = extract(attrs) {
let def_id = self.tcx.hir().local_def_id(hir_id);
// insert into our table
collect_item(self.tcx, &mut self.items, name, def_id);
}
}
}
fn collect_item(
tcx: TyCtxt<'_>,
items: &mut FxHashMap<Symbol, DefId>,
name: Symbol,
item_def_id: DefId,
) {
// Check for duplicates.
if let Some(original_def_id) = items.insert(name, item_def_id) {
if original_def_id != item_def_id {
let mut err = match tcx.hir().span_if_local(item_def_id) {
Some(span) => tcx.sess.struct_span_err(
span,
&format!("duplicate diagnostic item found: `{}`.", name)),
None => tcx.sess.struct_err(&format!(
"duplicate diagnostic item in crate `{}`: `{}`.",
tcx.crate_name(item_def_id.krate),
name)),
};
if let Some(span) = tcx.hir().span_if_local(original_def_id) {
span_note!(&mut err, span, "first defined here.");
} else {
err.note(&format!("first defined in crate `{}`.",
tcx.crate_name(original_def_id.krate)));
}
err.emit();
}
}
}
/// Extract the first `rustc_diagnostic_item = "$name"` out of a list of attributes.
fn extract(attrs: &[ast::Attribute]) -> Option<Symbol> {
attrs.iter().find_map(|attr| {
if attr.check_name(sym::rustc_diagnostic_item) {
attr.value_str()
} else {
None
}
})
}
/// Traverse and collect the diagnostic items in the current
pub fn collect<'tcx>(tcx: TyCtxt<'tcx>) -> &'tcx FxHashMap<Symbol, DefId> {
// Initialize the collector.
let mut collector = DiagnosticItemCollector::new(tcx);
// Collect diagnostic items in this crate.
tcx.hir().krate().visit_all_item_likes(&mut collector);
tcx.arena.alloc(collector.items)
}
/// Traverse and collect all the diagnostic items in all crates.
pub fn collect_all<'tcx>(tcx: TyCtxt<'tcx>) -> &'tcx FxHashMap<Symbol, DefId> {
// Initialize the collector.
let mut collector = FxHashMap::default();
// Collect diagnostic items in other crates.
for &cnum in tcx.crates().iter().chain(std::iter::once(&LOCAL_CRATE)) {
for (&name, &def_id) in tcx.diagnostic_items(cnum).iter() {
collect_item(tcx, &mut collector, name, def_id);
}
}
tcx.arena.alloc(collector)
}

View File

@ -367,8 +367,6 @@ language_item_table! {
MaybeUninitLangItem, "maybe_uninit", maybe_uninit, Target::Union;
DebugTraitLangItem, "debug_trait", debug_trait, Target::Trait;
// Align offset for stride != 1, must not panic.
AlignOffsetLangItem, "align_offset", align_offset_fn, Target::Fn;

View File

@ -804,7 +804,7 @@ rustc_queries! {
}
BorrowChecking {
// Lifetime resolution. See `middle::resolve_lifetimes`.
/// Lifetime resolution. See `middle::resolve_lifetimes`.
query resolve_lifetimes(_: CrateNum) -> &'tcx ResolveLifetimes {
desc { "resolving lifetimes" }
}
@ -846,13 +846,30 @@ rustc_queries! {
-> &'tcx [(Symbol, Option<Symbol>)] {
desc { "calculating the lib features defined in a crate" }
}
/// Returns the lang items defined in another crate by loading it from metadata.
// FIXME: It is illegal to pass a `CrateNum` other than `LOCAL_CRATE` here, just get rid
// of that argument?
query get_lang_items(_: CrateNum) -> &'tcx LanguageItems {
eval_always
desc { "calculating the lang items map" }
}
/// Returns all diagnostic items defined in all crates
query all_diagnostic_items(_: CrateNum) -> &'tcx FxHashMap<Symbol, DefId> {
eval_always
desc { "calculating the diagnostic items map" }
}
/// Returns the lang items defined in another crate by loading it from metadata.
query defined_lang_items(_: CrateNum) -> &'tcx [(DefId, usize)] {
desc { "calculating the lang items defined in a crate" }
}
/// Returns the diagnostic items defined in a crate
query diagnostic_items(_: CrateNum) -> &'tcx FxHashMap<Symbol, DefId> {
desc { "calculating the diagnostic items map in a crate" }
}
query missing_lang_items(_: CrateNum) -> &'tcx [LangItem] {
desc { "calculating the missing lang items in a crate" }
}

View File

@ -978,6 +978,7 @@ pub struct FreeRegionInfo {
///
/// [rustc guide]: https://rust-lang.github.io/rustc-guide/ty.html
#[derive(Copy, Clone)]
#[cfg_attr(not(bootstrap), rustc_diagnostic_item = "TyCtxt")]
pub struct TyCtxt<'tcx> {
gcx: &'tcx GlobalCtxt<'tcx>,
}
@ -1308,10 +1309,22 @@ impl<'tcx> TyCtxt<'tcx> {
self.get_lib_features(LOCAL_CRATE)
}
/// Obtain all lang items of this crate and all dependencies (recursively)
pub fn lang_items(self) -> &'tcx middle::lang_items::LanguageItems {
self.get_lang_items(LOCAL_CRATE)
}
/// Obtain the given diagnostic item's `DefId`. Use `is_diagnostic_item` if you just want to
/// compare against another `DefId`, since `is_diagnostic_item` is cheaper.
pub fn get_diagnostic_item(self, name: Symbol) -> Option<DefId> {
self.all_diagnostic_items(LOCAL_CRATE).get(&name).copied()
}
/// Check whether the diagnostic item with the given `name` has the given `DefId`.
pub fn is_diagnostic_item(self, name: Symbol, did: DefId) -> bool {
self.diagnostic_items(did.krate).get(&name) == Some(&did)
}
pub fn stability(self) -> &'tcx stability::Index<'tcx> {
self.stability_index(LOCAL_CRATE)
}
@ -2896,6 +2909,14 @@ pub fn provide(providers: &mut ty::query::Providers<'_>) {
assert_eq!(id, LOCAL_CRATE);
tcx.arena.alloc(middle::lang_items::collect(tcx))
};
providers.diagnostic_items = |tcx, id| {
assert_eq!(id, LOCAL_CRATE);
middle::diagnostic_items::collect(tcx)
};
providers.all_diagnostic_items = |tcx, id| {
assert_eq!(id, LOCAL_CRATE);
middle::diagnostic_items::collect_all(tcx)
};
providers.maybe_unused_trait_import = |tcx, id| {
tcx.maybe_unused_trait_imports.contains(&id)
};

View File

@ -581,6 +581,7 @@ impl<'a, 'tcx> HashStable<StableHashingContext<'a>> for ty::TyS<'tcx> {
}
}
#[cfg_attr(not(bootstrap), rustc_diagnostic_item = "Ty")]
pub type Ty<'tcx> = &'tcx TyS<'tcx>;
impl<'tcx> rustc_serialize::UseSpecializedEncodable for Ty<'tcx> {}

View File

@ -86,6 +86,7 @@ impl BoundRegion {
/// AST structure in `libsyntax/ast.rs` as well.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
RustcEncodable, RustcDecodable, HashStable, Debug)]
#[cfg_attr(not(bootstrap), rustc_diagnostic_item = "TyKind")]
pub enum TyKind<'tcx> {
/// The primitive boolean type. Written as `bool`.
Bool,

View File

@ -570,7 +570,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MissingDebugImplementations {
_ => return,
}
let debug = match cx.tcx.lang_items().debug_trait() {
let debug = match cx.tcx.get_diagnostic_item(sym::debug_trait) {
Some(debug) => debug,
None => return,
};

View File

@ -226,6 +226,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
}
defined_lib_features => { cdata.get_lib_features(tcx) }
defined_lang_items => { cdata.get_lang_items(tcx) }
diagnostic_items => { cdata.get_diagnostic_items(tcx) }
missing_lang_items => { cdata.get_missing_lang_items(tcx) }
missing_extern_crate_item => {

View File

@ -12,6 +12,7 @@ use rustc::middle::exported_symbols::{ExportedSymbol, SymbolExportLevel};
use rustc::hir::def::{self, Res, DefKind, CtorOf, CtorKind};
use rustc::hir::def_id::{CrateNum, DefId, DefIndex, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE};
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::fx::FxHashMap;
use rustc::middle::lang_items;
use rustc::mir::{self, interpret};
use rustc::mir::interpret::AllocDecodingSession;
@ -757,6 +758,23 @@ impl<'a, 'tcx> CrateMetadata {
}
}
/// Iterates over the diagnostic items in the given crate.
pub fn get_diagnostic_items(
&self,
tcx: TyCtxt<'tcx>,
) -> &'tcx FxHashMap<Symbol, DefId> {
tcx.arena.alloc(if self.is_proc_macro_crate() {
// Proc macro crates do not export any diagnostic-items to the target.
Default::default()
} else {
self.root
.diagnostic_items
.decode(self)
.map(|(name, def_index)| (name, self.local_def_id(def_index)))
.collect()
})
}
/// Iterates over each child of the given item.
pub fn each_child_of_item<F>(&self, id: DefIndex, mut callback: F, sess: &Session)
where F: FnMut(def::Export<hir::HirId>)

View File

@ -33,7 +33,7 @@ use syntax::ast;
use syntax::attr;
use syntax::ext::proc_macro::is_proc_macro_attr;
use syntax::source_map::Spanned;
use syntax::symbol::{kw, sym, Ident};
use syntax::symbol::{kw, sym, Ident, Symbol};
use syntax_pos::{self, FileName, SourceFile, Span};
use log::{debug, trace};
@ -404,6 +404,11 @@ impl<'tcx> EncodeContext<'tcx> {
let lang_items_missing = self.encode_lang_items_missing();
let lang_item_bytes = self.position() - i;
// Encode the diagnostic items.
i = self.position();
let diagnostic_items = self.encode_diagnostic_items();
let diagnostic_item_bytes = self.position() - i;
// Encode the native libraries used
i = self.position();
let native_libraries = self.encode_native_libraries();
@ -520,6 +525,7 @@ impl<'tcx> EncodeContext<'tcx> {
dylib_dependency_formats,
lib_features,
lang_items,
diagnostic_items,
lang_items_missing,
native_libraries,
foreign_modules,
@ -545,6 +551,7 @@ impl<'tcx> EncodeContext<'tcx> {
println!(" dep bytes: {}", dep_bytes);
println!(" lib feature bytes: {}", lib_feature_bytes);
println!(" lang item bytes: {}", lang_item_bytes);
println!(" diagnostic item bytes: {}", diagnostic_item_bytes);
println!(" native bytes: {}", native_lib_bytes);
println!(" source_map bytes: {}", source_map_bytes);
println!(" impl bytes: {}", impl_bytes);
@ -1555,6 +1562,12 @@ impl EncodeContext<'tcx> {
self.lazy(lib_features.to_vec())
}
fn encode_diagnostic_items(&mut self) -> Lazy<[(Symbol, DefIndex)]> {
let tcx = self.tcx;
let diagnostic_items = tcx.diagnostic_items(LOCAL_CRATE);
self.lazy(diagnostic_items.iter().map(|(&name, def_id)| (name, def_id.index)))
}
fn encode_lang_items(&mut self) -> Lazy<[(DefIndex, usize)]> {
let tcx = self.tcx;
let lang_items = tcx.lang_items();

View File

@ -176,6 +176,7 @@ pub struct CrateRoot<'tcx> {
pub lib_features: Lazy<[(Symbol, Option<Symbol>)]>,
pub lang_items: Lazy<[(DefIndex, usize)]>,
pub lang_items_missing: Lazy<[lang_items::LangItem]>,
pub diagnostic_items: Lazy<[(Symbol, DefIndex)]>,
pub native_libraries: Lazy<[NativeLibrary]>,
pub foreign_modules: Lazy<[ForeignModule]>,
pub source_map: Lazy<[syntax_pos::SourceFile]>,

View File

@ -461,6 +461,17 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
lang, Normal, template!(NameValueStr: "name"), lang_items,
"language items are subject to change",
),
(
sym::rustc_diagnostic_item,
Normal,
template!(NameValueStr: "name"),
Gated(
Stability::Unstable,
sym::rustc_attrs,
"diagnostic items compiler internal support for linting",
cfg_fn!(rustc_attrs),
),
),
(
sym::no_debug, Whitelisted, template!(Word),
Gated(

View File

@ -225,9 +225,10 @@ symbols! {
custom_inner_attributes,
custom_test_frameworks,
c_variadic,
Debug,
debug_trait,
declare_lint_pass,
decl_macro,
Debug,
Decodable,
Default,
default_lib_allocator,
@ -238,6 +239,7 @@ symbols! {
deref,
deref_mut,
derive,
diagnostic,
direct,
doc,
doc_alias,
@ -569,6 +571,7 @@ symbols! {
rustc_conversion_suggestion,
rustc_def_path,
rustc_deprecated,
rustc_diagnostic_item,
rustc_diagnostic_macros,
rustc_dirty,
rustc_dummy,

View File

@ -0,0 +1,2 @@
#[rustc_diagnostic_item = "foomp"] //~ ERROR will never be stabilized
struct Foomp;

View File

@ -0,0 +1,17 @@
error[E0658]: diagnostic items compiler are internal support for linting and will never be stabilized
--> $DIR/diagnostic_item.rs:1:1
|
LL | #[rustc_diagnostic_item = "foomp"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/29642
= help: add `#![feature(rustc_attrs)]` to the crate attributes to enable
error[E0601]: `main` function not found in crate `diagnostic_item`
|
= note: consider adding a `main` function to `$DIR/diagnostic_item.rs`
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0601, E0658.
For more information about an error, try `rustc --explain E0601`.

View File

@ -0,0 +1,6 @@
// check-pass
#[clippy::diagnostic_item = "mep"]
struct Mep;
fn main() {}

View File

@ -0,0 +1,7 @@
// check-pass
#![feature(rustc_attrs)]
#[rustc_diagnostic_item = "foomp"]
struct Foomp;
fn main() {}