diff --git a/src/librustc/diagnostics.rs b/src/librustc/diagnostics.rs index 182405a640dbc..b0fc4982a841c 100644 --- a/src/librustc/diagnostics.rs +++ b/src/librustc/diagnostics.rs @@ -517,5 +517,3 @@ register_diagnostics! { E0316, // nested quantification of lifetimes E0370 // discriminant overflow } - -__build_diagnostic_array! { DIAGNOSTICS } diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index d4012f2057b5d..53f46ff1605fc 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -161,3 +161,9 @@ pub mod lib { mod rustc { pub use lint; } + +// Build the diagnostics array at the end so that the metadata includes error use sites. +#[cfg(stage0)] +__build_diagnostic_array! { DIAGNOSTICS } +#[cfg(not(stage0))] +__build_diagnostic_array! { librustc, DIAGNOSTICS } diff --git a/src/librustc_borrowck/diagnostics.rs b/src/librustc_borrowck/diagnostics.rs index 981b28593f9a4..a43268ff1a882 100644 --- a/src/librustc_borrowck/diagnostics.rs +++ b/src/librustc_borrowck/diagnostics.rs @@ -13,5 +13,3 @@ register_diagnostics! { E0373 // closure may outlive current fn, but it borrows {}, which is owned by current fn } - -__build_diagnostic_array! { DIAGNOSTICS } diff --git a/src/librustc_borrowck/lib.rs b/src/librustc_borrowck/lib.rs index 647ea3555ba91..5e083ed1ea912 100644 --- a/src/librustc_borrowck/lib.rs +++ b/src/librustc_borrowck/lib.rs @@ -47,3 +47,8 @@ pub mod diagnostics; mod borrowck; pub mod graphviz; + +#[cfg(stage0)] +__build_diagnostic_array! { DIAGNOSTICS } +#[cfg(not(stage0))] +__build_diagnostic_array! { librustc_borrowck, DIAGNOSTICS } diff --git a/src/librustc_driver/lib.rs b/src/librustc_driver/lib.rs index 38217f8dad24e..b02774afa86c8 100644 --- a/src/librustc_driver/lib.rs +++ b/src/librustc_driver/lib.rs @@ -854,9 +854,10 @@ pub fn diagnostics_registry() -> diagnostics::registry::Registry { use syntax::diagnostics::registry::Registry; let all_errors = Vec::new() + - &rustc::diagnostics::DIAGNOSTICS[..] + - &rustc_typeck::diagnostics::DIAGNOSTICS[..] + - &rustc_resolve::diagnostics::DIAGNOSTICS[..]; + &rustc::DIAGNOSTICS[..] + + &rustc_typeck::DIAGNOSTICS[..] + + &rustc_borrowck::DIAGNOSTICS[..] + + &rustc_resolve::DIAGNOSTICS[..]; Registry::new(&*all_errors) } diff --git a/src/librustc_resolve/diagnostics.rs b/src/librustc_resolve/diagnostics.rs index c586faae6e886..a896bd311698c 100644 --- a/src/librustc_resolve/diagnostics.rs +++ b/src/librustc_resolve/diagnostics.rs @@ -28,5 +28,3 @@ register_diagnostics! { E0364, // item is private E0365 // item is private } - -__build_diagnostic_array! { DIAGNOSTICS } diff --git a/src/librustc_resolve/lib.rs b/src/librustc_resolve/lib.rs index d300045c0ec01..67af2a60e0f82 100644 --- a/src/librustc_resolve/lib.rs +++ b/src/librustc_resolve/lib.rs @@ -3580,3 +3580,8 @@ pub fn resolve_crate<'a, 'tcx>(session: &'a Session, }, } } + +#[cfg(stage0)] +__build_diagnostic_array! { DIAGNOSTICS } +#[cfg(not(stage0))] +__build_diagnostic_array! { librustc_resolve, DIAGNOSTICS } diff --git a/src/librustc_typeck/diagnostics.rs b/src/librustc_typeck/diagnostics.rs index b17702cfb8cb5..876a8bb41c6d2 100644 --- a/src/librustc_typeck/diagnostics.rs +++ b/src/librustc_typeck/diagnostics.rs @@ -183,5 +183,3 @@ register_diagnostics! { E0371, // impl Trait for Trait is illegal E0372 // impl Trait for Trait where Trait is not object safe } - -__build_diagnostic_array! { DIAGNOSTICS } diff --git a/src/librustc_typeck/lib.rs b/src/librustc_typeck/lib.rs index be3fc860b2b12..4d56ef95122ff 100644 --- a/src/librustc_typeck/lib.rs +++ b/src/librustc_typeck/lib.rs @@ -344,3 +344,8 @@ pub fn check_crate(tcx: &ty::ctxt, trait_map: ty::TraitMap) { check_for_entry_fn(&ccx); tcx.sess.abort_if_errors(); } + +#[cfg(stage0)] +__build_diagnostic_array! { DIAGNOSTICS } +#[cfg(not(stage0))] +__build_diagnostic_array! { librustc_typeck, DIAGNOSTICS } diff --git a/src/libsyntax/diagnostics/metadata.rs b/src/libsyntax/diagnostics/metadata.rs new file mode 100644 index 0000000000000..6cb4f70b8607a --- /dev/null +++ b/src/libsyntax/diagnostics/metadata.rs @@ -0,0 +1,155 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This module contains utilities for outputting metadata for diagnostic errors. +//! +//! Each set of errors is mapped to a metadata file by a name, which is +//! currently always a crate name. + +use std::collections::BTreeMap; +use std::env; +use std::path::PathBuf; +use std::fs::{read_dir, create_dir_all, OpenOptions, File}; +use std::io::{Read, Write}; +use std::error::Error; +use rustc_serialize::json::{self, as_json}; + +use codemap::Span; +use ext::base::ExtCtxt; +use diagnostics::plugin::{ErrorMap, ErrorInfo}; + +pub use self::Uniqueness::*; + +// Default metadata directory to use for extended error JSON. +const ERROR_METADATA_DIR_DEFAULT: &'static str = "tmp/extended-errors"; + +// The name of the environment variable that sets the metadata dir. +const ERROR_METADATA_VAR: &'static str = "ERROR_METADATA_DIR"; + +/// JSON encodable/decodable version of `ErrorInfo`. +#[derive(PartialEq, RustcDecodable, RustcEncodable)] +pub struct ErrorMetadata { + pub description: Option, + pub use_site: Option +} + +/// Mapping from error codes to metadata that can be (de)serialized. +pub type ErrorMetadataMap = BTreeMap; + +/// JSON encodable error location type with filename and line number. +#[derive(PartialEq, RustcDecodable, RustcEncodable)] +pub struct ErrorLocation { + pub filename: String, + pub line: usize +} + +impl ErrorLocation { + /// Create an error location from a span. + pub fn from_span(ecx: &ExtCtxt, sp: Span) -> ErrorLocation { + let loc = ecx.codemap().lookup_char_pos_adj(sp.lo); + ErrorLocation { + filename: loc.filename, + line: loc.line + } + } +} + +/// Type for describing the uniqueness of a set of error codes, as returned by `check_uniqueness`. +pub enum Uniqueness { + /// All errors in the set checked are unique according to the metadata files checked. + Unique, + /// One or more errors in the set occur in another metadata file. + /// This variant contains the first duplicate error code followed by the name + /// of the metadata file where the duplicate appears. + Duplicate(String, String) +} + +/// Get the directory where metadata files should be stored. +pub fn get_metadata_dir() -> PathBuf { + match env::var(ERROR_METADATA_VAR) { + Ok(v) => From::from(v), + Err(_) => From::from(ERROR_METADATA_DIR_DEFAULT) + } +} + +/// Get the path where error metadata for the set named by `name` should be stored. +fn get_metadata_path(name: &str) -> PathBuf { + get_metadata_dir().join(format!("{}.json", name)) +} + +/// Check that the errors in `err_map` aren't present in any metadata files in the +/// metadata directory except the metadata file corresponding to `name`. +pub fn check_uniqueness(name: &str, err_map: &ErrorMap) -> Result> { + let metadata_dir = get_metadata_dir(); + let metadata_path = get_metadata_path(name); + + // Create the error directory if it does not exist. + try!(create_dir_all(&metadata_dir)); + + // Check each file in the metadata directory. + for entry in try!(read_dir(&metadata_dir)) { + let path = try!(entry).path(); + + // Skip any existing file for this set. + if path == metadata_path { + continue; + } + + // Read the metadata file into a string. + let mut metadata_str = String::new(); + try!( + File::open(&path).and_then(|mut f| + f.read_to_string(&mut metadata_str)) + ); + + // Parse the JSON contents. + let metadata: ErrorMetadataMap = try!(json::decode(&metadata_str)); + + // Check for duplicates. + for err in err_map.keys() { + let err_code = err.as_str(); + if metadata.contains_key(err_code) { + return Ok(Duplicate( + err_code.to_string(), + path.to_string_lossy().into_owned() + )); + } + } + } + + Ok(Unique) +} + +/// Write metadata for the errors in `err_map` to disk, to a file corresponding to `name`. +pub fn output_metadata(ecx: &ExtCtxt, name: &str, err_map: &ErrorMap) + -> Result<(), Box> +{ + let metadata_path = get_metadata_path(name); + + // Open the dump file. + let mut dump_file = try!(OpenOptions::new() + .write(true) + .create(true) + .open(&metadata_path) + ); + + // Construct a serializable map. + let json_map = err_map.iter().map(|(k, &ErrorInfo { description, use_site })| { + let key = k.as_str().to_string(); + let value = ErrorMetadata { + description: description.map(|n| n.as_str().to_string()), + use_site: use_site.map(|sp| ErrorLocation::from_span(ecx, sp)) + }; + (key, value) + }).collect::(); + + try!(write!(&mut dump_file, "{}", as_json(&json_map))); + Ok(()) +} diff --git a/src/libsyntax/diagnostics/plugin.rs b/src/libsyntax/diagnostics/plugin.rs index 6de4edafa0bf5..16841bd903981 100644 --- a/src/libsyntax/diagnostics/plugin.rs +++ b/src/libsyntax/diagnostics/plugin.rs @@ -14,6 +14,7 @@ use std::collections::BTreeMap; use ast; use ast::{Ident, Name, TokenTree}; use codemap::Span; +use diagnostics::metadata::{check_uniqueness, output_metadata, Duplicate}; use ext::base::{ExtCtxt, MacEager, MacResult}; use ext::build::AstBuilder; use parse::token; @@ -24,32 +25,28 @@ use util::small_vector::SmallVector; const MAX_DESCRIPTION_WIDTH: usize = 80; thread_local! { - static REGISTERED_DIAGNOSTICS: RefCell>> = { + static REGISTERED_DIAGNOSTICS: RefCell = { RefCell::new(BTreeMap::new()) } } -thread_local! { - static USED_DIAGNOSTICS: RefCell> = { - RefCell::new(BTreeMap::new()) - } + +/// Error information type. +pub struct ErrorInfo { + pub description: Option, + pub use_site: Option } +/// Mapping from error codes to metadata. +pub type ErrorMap = BTreeMap; + fn with_registered_diagnostics(f: F) -> T where - F: FnOnce(&mut BTreeMap>) -> T, + F: FnOnce(&mut ErrorMap) -> T, { REGISTERED_DIAGNOSTICS.with(move |slot| { f(&mut *slot.borrow_mut()) }) } -fn with_used_diagnostics(f: F) -> T where - F: FnOnce(&mut BTreeMap) -> T, -{ - USED_DIAGNOSTICS.with(move |slot| { - f(&mut *slot.borrow_mut()) - }) -} - pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt, span: Span, token_tree: &[TokenTree]) @@ -58,23 +55,26 @@ pub fn expand_diagnostic_used<'cx>(ecx: &'cx mut ExtCtxt, (1, Some(&ast::TtToken(_, token::Ident(code, _)))) => code, _ => unreachable!() }; - with_used_diagnostics(|diagnostics| { - match diagnostics.insert(code.name, span) { - Some(previous_span) => { + + with_registered_diagnostics(|diagnostics| { + match diagnostics.get_mut(&code.name) { + // Previously used errors. + Some(&mut ErrorInfo { description: _, use_site: Some(previous_span) }) => { ecx.span_warn(span, &format!( "diagnostic code {} already used", &token::get_ident(code) )); ecx.span_note(previous_span, "previous invocation"); - }, - None => () - } - () - }); - with_registered_diagnostics(|diagnostics| { - if !diagnostics.contains_key(&code.name) { - ecx.span_err(span, &format!( - "used diagnostic code {} not registered", &token::get_ident(code) - )); + } + // Newly used errors. + Some(ref mut info) => { + info.use_site = Some(span); + } + // Unregistered errors. + None => { + ecx.span_err(span, &format!( + "used diagnostic code {} not registered", &token::get_ident(code) + )); + } } }); MacEager::expr(ecx.expr_tuple(span, Vec::new())) @@ -116,10 +116,14 @@ pub fn expand_register_diagnostic<'cx>(ecx: &'cx mut ExtCtxt, token::get_ident(*code), MAX_DESCRIPTION_WIDTH )); } - raw_msg }); + // Add the error to the map. with_registered_diagnostics(|diagnostics| { - if diagnostics.insert(code.name, description).is_some() { + let info = ErrorInfo { + description: description, + use_site: None + }; + if diagnostics.insert(code.name, info).is_some() { ecx.span_err(span, &format!( "diagnostic code {} already registered", &token::get_ident(*code) )); @@ -143,19 +147,43 @@ pub fn expand_build_diagnostic_array<'cx>(ecx: &'cx mut ExtCtxt, span: Span, token_tree: &[TokenTree]) -> Box { - let name = match (token_tree.len(), token_tree.get(0)) { - (1, Some(&ast::TtToken(_, token::Ident(ref name, _)))) => name, + assert_eq!(token_tree.len(), 3); + let (crate_name, name) = match (&token_tree[0], &token_tree[2]) { + ( + // Crate name. + &ast::TtToken(_, token::Ident(ref crate_name, _)), + // DIAGNOSTICS ident. + &ast::TtToken(_, token::Ident(ref name, _)) + ) => (crate_name.as_str(), name), _ => unreachable!() }; + // Check uniqueness of errors and output metadata. + with_registered_diagnostics(|diagnostics| { + match check_uniqueness(crate_name, &*diagnostics) { + Ok(Duplicate(err, location)) => { + ecx.span_err(span, &format!( + "error {} from `{}' also found in `{}'", + err, crate_name, location + )); + }, + Ok(_) => (), + Err(e) => panic!("{}", e.description()) + } + + output_metadata(&*ecx, crate_name, &*diagnostics).ok().expect("metadata output error"); + }); + + // Construct the output expression. let (count, expr) = with_registered_diagnostics(|diagnostics| { let descriptions: Vec> = - diagnostics.iter().filter_map(|(code, description)| { - description.map(|description| { + diagnostics.iter().filter_map(|(code, info)| { + info.description.map(|description| { ecx.expr_tuple(span, vec![ ecx.expr_str(span, token::get_name(*code)), - ecx.expr_str(span, token::get_name(description))]) + ecx.expr_str(span, token::get_name(description)) + ]) }) }).collect(); (descriptions.len(), ecx.expr_vec(span, descriptions)) diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index d8beeb6a5503b..4d9d71fe56e9b 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -69,6 +69,7 @@ pub mod diagnostics { pub mod macros; pub mod plugin; pub mod registry; + pub mod metadata; } pub mod syntax { diff --git a/src/test/run-make/issue-19371/foo.rs b/src/test/run-make/issue-19371/foo.rs index 0d42e0be58d07..8745cbecf9137 100644 --- a/src/test/run-make/issue-19371/foo.rs +++ b/src/test/run-make/issue-19371/foo.rs @@ -49,7 +49,7 @@ fn basic_sess(sysroot: PathBuf) -> Session { opts.output_types = vec![OutputTypeExe]; opts.maybe_sysroot = Some(sysroot); - let descriptions = Registry::new(&rustc::diagnostics::DIAGNOSTICS); + let descriptions = Registry::new(&rustc::DIAGNOSTICS); let sess = build_session(opts, None, descriptions); rustc_lint::register_builtins(&mut sess.lint_store.borrow_mut(), Some(&sess)); sess