diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 2aa2746959fc..9a36ea1e65cd 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -79,8 +79,12 @@ pub fn compile_input(sess: Session, &sess); let id = link::find_crate_id(krate.attrs.as_slice(), outputs.out_filestem.as_slice()); - let (expanded_crate, ast_map) = - phase_2_configure_and_expand(&sess, krate, &id); + let (expanded_crate, ast_map) + = match phase_2_configure_and_expand(&sess, krate, &id) { + None => return, + Some(p) => p, + }; + (outputs, expanded_crate, ast_map) }; write_out_deps(&sess, input, &outputs, &expanded_crate); @@ -173,10 +177,12 @@ pub fn phase_1_parse_input(sess: &Session, cfg: ast::CrateConfig, input: &Input) /// syntax expansion, secondary `cfg` expansion, synthesis of a test /// harness if one is to be provided and injection of a dependency on the /// standard library and prelude. +/// +/// Returns `None` if we're aborting after handling -W help. pub fn phase_2_configure_and_expand(sess: &Session, mut krate: ast::Crate, crate_id: &CrateId) - -> (ast::Crate, syntax::ast_map::Map) { + -> Option<(ast::Crate, syntax::ast_map::Map)> { let time_passes = sess.time_passes(); *sess.crate_types.borrow_mut() = collect_crate_types(sess, krate.attrs.as_slice()); @@ -212,6 +218,17 @@ pub fn phase_2_configure_and_expand(sess: &Session, let Registry { syntax_exts, .. } = registry; + // Process command line flags for lints. + // Do this here because we will have lint plugins eventually. + if sess.opts.describe_lints { + super::describe_lints(&*sess.lint_store.borrow()); + return None; + } + sess.lint_store.borrow_mut().process_command_line(sess); + + // Abort if there are errors from lint processing or a plugin registrar. + sess.abort_if_errors(); + krate = time(time_passes, "expansion", (krate, macros, syntax_exts), |(krate, macros, syntax_exts)| { // Windows dlls do not have rpaths, so they don't know how to find their @@ -254,7 +271,7 @@ pub fn phase_2_configure_and_expand(sess: &Session, krate.encode(&mut json).unwrap(); } - (krate, map) + Some((krate, map)) } pub struct CrateAnalysis { @@ -631,9 +648,11 @@ pub fn pretty_print_input(sess: Session, let (krate, ast_map, is_expanded) = match ppm { PpmExpanded | PpmExpandedIdentified | PpmTyped | PpmFlowGraph(_) => { - let (krate, ast_map) = phase_2_configure_and_expand(&sess, - krate, - &id); + let (krate, ast_map) + = match phase_2_configure_and_expand(&sess, krate, &id) { + None => return, + Some(p) => p, + }; (krate, Some(ast_map), true) } _ => (krate, None, false) diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index 623698f6c71a..7c8f5a90b5a8 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -13,6 +13,7 @@ pub use syntax::diagnostic; use back::link; use driver::driver::{Input, FileInput, StrInput}; use driver::session::{Session, build_session}; +use lint::Lint; use lint; use metadata; @@ -48,15 +49,18 @@ fn run_compiler(args: &[String]) { Some(matches) => matches, None => return }; - let sopts = config::build_session_options(&matches); - if sopts.describe_lints { - describe_lints(); - return; - } let (input, input_file_path) = match matches.free.len() { - 0u => early_error("no input filename given"), + 0u => { + if sopts.describe_lints { + let mut ls = lint::LintStore::new(); + ls.register_builtin(None); + describe_lints(&ls); + return; + } + early_error("no input filename given"); + } 1u => { let ifile = matches.free.get(0).as_slice(); if ifile == "-" { @@ -128,43 +132,56 @@ Additional help: config::optgroups().as_slice())); } -fn describe_lints() { +fn describe_lints(lint_store: &lint::LintStore) { println!(" Available lint options: -W Warn about -A Allow -D Deny -F Forbid (deny, and deny all overrides) + "); - let mut builtin_specs = lint::builtin_lint_specs(); - builtin_specs.sort_by(|x, y| { - match x.default_level.cmp(&y.default_level) { - Equal => x.name.cmp(&y.name), - r => r, - } - }); + fn sort_lints(lints: Vec<(&'static Lint, bool)>) -> Vec<&'static Lint> { + let mut lints: Vec<_> = lints.move_iter().map(|(x, _)| x).collect(); + lints.sort_by(|x: &&Lint, y: &&Lint| { + match x.default_level.cmp(&y.default_level) { + Equal => x.name.cmp(&y.name), + r => r, + } + }); + lints + } - // FIXME: What if someone uses combining characters or East Asian fullwidth - // characters in a lint name?!?!? - let max_name_len = builtin_specs.iter() + let (_plugin, builtin) = lint_store.get_lints().partitioned(|&(_, p)| p); + // let plugin = sort_lints(plugin); + let builtin = sort_lints(builtin); + + // FIXME (#7043): We should use the width in character cells rather than + // the number of codepoints. + let max_name_len = builtin.iter() .map(|&s| s.name.char_len()) .max().unwrap_or(0); let padded = |x: &str| { - format!("{}{}", " ".repeat(max_name_len - x.char_len()), x) + " ".repeat(max_name_len - x.char_len()).append(x) }; - println!("\nAvailable lint checks:\n"); + println!("Lint checks provided by rustc:\n"); println!(" {} {:7.7s} {}", padded("name"), "default", "meaning"); println!(" {} {:7.7s} {}", padded("----"), "-------", "-------"); - println!(""); - for spec in builtin_specs.move_iter() { - let name = spec.name.replace("_", "-"); - println!(" {} {:7.7s} {}", - padded(name.as_slice()), spec.default_level.as_str(), spec.desc); - } - println!(""); + let print_lints = |lints: Vec<&Lint>| { + for lint in lints.move_iter() { + let name = lint.name.replace("_", "-"); + println!(" {} {:7.7s} {}", + padded(name.as_slice()), lint.default_level.as_str(), lint.desc); + } + println!("\n"); + }; + + print_lints(builtin); + + // Describe lint plugins here once they exist. } fn describe_debug_flags() { diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index 7f20059b6570..07366f34c4e0 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -43,6 +43,7 @@ pub struct Session { // expected to be absolute. `None` means that there is no source file. pub local_crate_source_file: Option, pub working_dir: Path, + pub lint_store: RefCell, pub lints: RefCell>>, pub node_id: Cell, pub crate_types: RefCell>, @@ -226,7 +227,7 @@ pub fn build_session_(sopts: config::Options, } ); - Session { + let sess = Session { targ_cfg: target_cfg, opts: sopts, cstore: CStore::new(token::get_ident_interner()), @@ -238,12 +239,16 @@ pub fn build_session_(sopts: config::Options, default_sysroot: default_sysroot, local_crate_source_file: local_crate_source_file, working_dir: os::getcwd(), + lint_store: RefCell::new(lint::LintStore::new()), lints: RefCell::new(NodeMap::new()), node_id: Cell::new(1), crate_types: RefCell::new(Vec::new()), features: front::feature_gate::Features::new(), recursion_limit: Cell::new(64), - } + }; + + sess.lint_store.borrow_mut().register_builtin(Some(&sess)); + sess } // Seems out of place, but it uses session, so I'm putting it here diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 7fbefbf94160..83a0c22dec19 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -1517,7 +1517,7 @@ impl LintPass for GatherNodeLevels { match it.node { ast::ItemEnum(..) => { let lint_id = lint::LintId::of(variant_size_difference); - match cx.get_level_source(lint_id) { + match cx.lints.get_level_source(lint_id) { lvlsrc @ (lvl, _) if lvl != lint::Allow => { cx.insert_node_level(it.id, lint_id, lvlsrc); }, diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index 1664dd6f309f..c481c5081d17 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -48,6 +48,7 @@ use middle::ty; use middle::typeck::astconv::AstConv; use middle::typeck::infer; use driver::session::Session; +use driver::early_error; use std::collections::HashMap; use std::rc::Rc; @@ -58,6 +59,7 @@ use std::default::Default; use std::hash::Hash; use std::tuple::Tuple2; use std::hash; +use std::mem; use syntax::ast_util::IdVisitingOperation; use syntax::attr::AttrMetaMethods; use syntax::attr; @@ -115,7 +117,7 @@ pub struct Lint { pub desc: &'static str, } -type LintArray = &'static [&'static Lint]; +pub type LintArray = &'static [&'static Lint]; /// Trait for types providing lint checks. Each `check` method checks a single /// syntax node, and should not invoke methods recursively (unlike `Visitor`). @@ -123,7 +125,7 @@ type LintArray = &'static [&'static Lint]; // // FIXME: eliminate the duplication with `Visitor`. But this also // contains a few lint-specific methods with no equivalent in `Visitor`. -trait LintPass { +pub trait LintPass { /// Get descriptions of the lints this `LintPass` object can emit. /// /// NB: there is no enforcement that the object only emits lints it registered. @@ -246,15 +248,117 @@ pub enum LintSource { pub type LevelSource = (Level, LintSource); -struct Context<'a> { +/// Information about the registered lints. +/// This is basically the subset of `Context` that we can +/// build early in the compile pipeline. +pub struct LintStore { + /// Registered lints. The bool is true if the lint was + /// added by a plugin. + lints: Vec<(&'static Lint, bool)>, + /// Trait objects for each lint pass. - lint_objects: Vec>, + passes: Vec>, /// Lints indexed by name. - lints_by_name: HashMap<&'static str, LintId>, + by_name: HashMap<&'static str, LintId>, /// Current levels of each lint, and where they were set. levels: HashMap, +} + +impl LintStore { + fn get_level_source(&self, lint: LintId) -> LevelSource { + match self.levels.find(&lint) { + Some(&s) => s, + None => (Allow, Default), + } + } + + fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { + if lvlsrc.val0() == Allow { + self.levels.remove(&lint); + } else { + self.levels.insert(lint, lvlsrc); + } + } + + pub fn new() -> LintStore { + LintStore { + lints: vec!(), + passes: vec!(), + by_name: HashMap::new(), + levels: HashMap::new(), + } + } + + pub fn get_lints<'t>(&'t self) -> &'t [(&'static Lint, bool)] { + self.lints.as_slice() + } + + pub fn register_pass(&mut self, sess: Option<&Session>, + from_plugin: bool, pass: LintPassObject) { + for &lint in pass.get_lints().iter() { + self.lints.push((lint, from_plugin)); + + let id = LintId::of(lint); + if !self.by_name.insert(lint.name, id) { + let msg = format!("duplicate specification of lint {}", lint.name); + match (sess, from_plugin) { + // We load builtin lints first, so a duplicate is a compiler bug. + // Use early_error when handling -W help with no crate. + (None, _) => early_error(msg.as_slice()), + (Some(sess), false) => sess.bug(msg.as_slice()), + + // A duplicate name from a plugin is a user error. + (Some(sess), true) => sess.err(msg.as_slice()), + } + } + + if lint.default_level != Allow { + self.levels.insert(id, (lint.default_level, Default)); + } + } + self.passes.push(RefCell::new(pass)); + } + + pub fn register_builtin(&mut self, sess: Option<&Session>) { + macro_rules! add_builtin_lints ( ( $sess:ident, $($name:ident),*, ) => ( + {$( + { + let obj: builtin::$name = Default::default(); + self.register_pass($sess, false, box obj as LintPassObject); + }; + )*} + )) + + add_builtin_lints!(sess, + WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, + RawPointerDeriving, UnusedAttribute, PathStatement, + UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, + NonSnakeCaseFunctions, NonUppercaseStatics, + NonUppercasePatternStatics, UppercaseVariables, + UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, + UnnecessaryAllocation, MissingDoc, Stability, + + GatherNodeLevels, HardwiredLints, + ) + } + + pub fn process_command_line(&mut self, sess: &Session) { + for &(ref lint_name, level) in sess.opts.lint_opts.iter() { + match self.by_name.find_equiv(&lint_name.as_slice()) { + Some(&lint_id) => self.set_level(lint_id, (level, CommandLine)), + None => sess.err(format!("unknown {} flag: {}", + level.as_str(), lint_name).as_slice()), + } + } + } +} + +/// Context for lint checking. +pub struct Context<'a> { + /// The store of registered lints. + lints: LintStore, /// Context we're checking in (used to access fields like sess). tcx: &'a ty::ctxt, @@ -271,7 +375,7 @@ struct Context<'a> { /// Convenience macro for calling a `LintPass` method on every pass in the context. macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ( - for obj in $cx.lint_objects.iter() { + for obj in $cx.lints.passes.iter() { obj.borrow_mut().$f($cx, $($args),*); } )) @@ -316,26 +420,11 @@ pub fn emit_lint(sess: &Session, lint: &'static Lint, } impl<'a> Context<'a> { - fn get_level_source(&self, lint: LintId) -> LevelSource { - match self.levels.find(&lint) { - Some(&s) => s, - None => (Allow, Default), - } - } - - fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { - if lvlsrc.val0() == Allow { - self.levels.remove(&lint); - } else { - self.levels.insert(lint, lvlsrc); - } - } - - fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) { - let (level, src) = match self.levels.find(&LintId::of(lint)) { + pub fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) { + let (level, src) = match self.lints.levels.find(&LintId::of(lint)) { None => return, Some(&(Warn, src)) - => (self.get_level_source(LintId::of(builtin::warnings)).val0(), src), + => (self.lints.get_level_source(LintId::of(builtin::warnings)).val0(), src), Some(&pair) => pair, }; @@ -357,17 +446,17 @@ impl<'a> Context<'a> { let lint_attrs = self.gather_lint_attrs(attrs); let mut pushed = 0u; for (lint_id, level, span) in lint_attrs.move_iter() { - let now = self.get_level_source(lint_id).val0(); + let now = self.lints.get_level_source(lint_id).val0(); if now == Forbid && level != Forbid { let lint_name = lint_id.as_str(); self.tcx.sess.span_err(span, format!("{}({}) overruled by outer forbid({})", level.as_str(), lint_name, lint_name).as_slice()); } else if now != level { - let src = self.get_level_source(lint_id).val1(); + let src = self.lints.get_level_source(lint_id).val1(); self.level_stack.push((lint_id, (now, src))); pushed += 1; - self.set_level(lint_id, (level, Node(span))); + self.lints.set_level(lint_id, (level, Node(span))); } } @@ -378,7 +467,7 @@ impl<'a> Context<'a> { // rollback for _ in range(0, pushed) { let (lint, lvlsrc) = self.level_stack.pop().unwrap(); - self.set_level(lint, lvlsrc); + self.lints.set_level(lint, lvlsrc); } } @@ -419,7 +508,7 @@ impl<'a> Context<'a> { for meta in metas.iter() { match meta.node { ast::MetaWord(ref lint_name) => { - match self.lints_by_name.find_equiv(lint_name) { + match self.lints.by_name.find_equiv(lint_name) { Some(lint_id) => out.push((*lint_id, level, meta.span)), None => self.span_lint(builtin::unrecognized_lint, @@ -636,75 +725,21 @@ impl<'a> IdVisitingOperation for Context<'a> { } } -fn builtin_lints() -> Vec> { - macro_rules! builtin_lints (( $($name:ident),*, ) => ( - vec!($( - { - let obj: builtin::$name = Default::default(); - box obj as LintPassObject - } - ),*) - )) - - builtin_lints!( - WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, - RawPointerDeriving, UnusedAttribute, PathStatement, - UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, - NonSnakeCaseFunctions, NonUppercaseStatics, - NonUppercasePatternStatics, UppercaseVariables, - UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, - UnnecessaryAllocation, MissingDoc, Stability, - - GatherNodeLevels, HardwiredLints, - ) -} - -/// Get specs for all builtin lints. Used for `-W help`. -pub fn builtin_lint_specs() -> Vec<&'static Lint> { - builtin_lints().move_iter() - .flat_map(|x| x.get_lints().iter().map(|&y| y)) - .collect() -} - pub fn check_crate(tcx: &ty::ctxt, exported_items: &ExportedItems, krate: &ast::Crate) { - let lints = builtin_lints().move_iter().map(|x| RefCell::new(x)).collect(); + + // We want to own the lint store, so move it out of the session. + let lint_store = mem::replace(&mut *tcx.sess.lint_store.borrow_mut(), + LintStore::new()); let mut cx = Context { - lint_objects: lints, - lints_by_name: HashMap::new(), - levels: HashMap::new(), + lints: lint_store, tcx: tcx, level_stack: Vec::new(), node_levels: RefCell::new(HashMap::new()), }; - // Index the lints by name, and set the default levels. - for obj in cx.lint_objects.iter() { - for &lint in obj.borrow_mut().get_lints().iter() { - let id = LintId::of(lint); - if !cx.lints_by_name.insert(lint.name, id) { - cx.tcx.sess.err(format!("duplicate specification of lint {}", - lint.name).as_slice()); - } - if lint.default_level != Allow { - cx.levels.insert(id, (lint.default_level, Default)); - } - } - } - - // Set command line lint levels. - for &(ref lint_name, level) in tcx.sess.opts.lint_opts.iter() { - match cx.lints_by_name.find_equiv(&lint_name.as_slice()) { - Some(&lint_id) => cx.set_level(lint_id, (level, CommandLine)), - None => cx.tcx.sess.err(format!("unknown {} flag: {}", - level.as_str(), lint_name).as_slice()), - } - } - - tcx.sess.abort_if_errors(); - // Visit the whole crate. cx.with_lint_attrs(krate.attrs.as_slice(), |cx| { cx.visit_id(ast::CRATE_NODE_ID); diff --git a/src/librustc/middle/typeck/infer/test.rs b/src/librustc/middle/typeck/infer/test.rs index f08cbb06c9e3..5ae469c41f2d 100644 --- a/src/librustc/middle/typeck/infer/test.rs +++ b/src/librustc/middle/typeck/infer/test.rs @@ -120,7 +120,8 @@ fn test_env(_test_name: &str, name: "test".to_owned(), version: None }; let (krate, ast_map) = - driver::phase_2_configure_and_expand(&sess, krate, &krate_id); + driver::phase_2_configure_and_expand(&sess, krate, &krate_id) + .expect("phase 2 aborted"); // run just enough stuff to build a tcx: let lang_items = lang_items::collect_language_items(&krate, &sess); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index ef8367dfc761..36bf02cac305 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -102,8 +102,10 @@ fn get_ast_and_resolve(cpath: &Path, libs: HashSet, cfgs: Vec) } let krate = phase_1_parse_input(&sess, cfg, &input); - let (krate, ast_map) = phase_2_configure_and_expand(&sess, krate, - &from_str("rustdoc").unwrap()); + let (krate, ast_map) + = phase_2_configure_and_expand(&sess, krate, &from_str("rustdoc").unwrap()) + .expect("phase_2_configure_and_expand aborted in rustdoc!"); + let driver::driver::CrateAnalysis { exported_items, public_items, ty_cx, .. } = phase_3_run_analysis_passes(sess, &krate, ast_map); diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index c1d87fbb03bd..e7fc3cedf5ec 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -69,7 +69,8 @@ pub fn run(input: &str, })); let krate = driver::phase_1_parse_input(&sess, cfg, &input); let (krate, _) = driver::phase_2_configure_and_expand(&sess, krate, - &from_str("rustdoc-test").unwrap()); + &from_str("rustdoc-test").unwrap()) + .expect("phase_2_configure_and_expand aborted in rustdoc!"); let ctx = box(GC) core::DocContext { krate: krate,