diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 70f03e02f46..b2f508ff26d 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -236,6 +236,12 @@ "detects use of struct constructors that would be invisible with new visibility rules" } +declare_lint! { + pub MISSING_FRAGMENT_SPECIFIER, + Warn, + "detects missing fragment specifiers in unused `macro_rules!` patterns" +} + declare_lint! { pub DEPRECATED, Warn, @@ -286,6 +292,7 @@ fn get_lints(&self) -> LintArray { LEGACY_DIRECTORY_OWNERSHIP, LEGACY_IMPORTS, LEGACY_CONSTRUCTOR_VISIBILITY, + MISSING_FRAGMENT_SPECIFIER, DEPRECATED ) } diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs index ddf09f5cfe0..9619ba84724 100644 --- a/src/librustc_driver/driver.rs +++ b/src/librustc_driver/driver.rs @@ -688,6 +688,14 @@ pub fn phase_2_configure_and_expand(sess: &Session, let krate = ecx.monotonic_expander().expand_crate(krate); + let mut missing_fragment_specifiers: Vec<_> = + ecx.parse_sess.missing_fragment_specifiers.borrow().iter().cloned().collect(); + missing_fragment_specifiers.sort(); + for span in missing_fragment_specifiers { + let lint = lint::builtin::MISSING_FRAGMENT_SPECIFIER; + let msg = "missing fragment specifier".to_string(); + sess.add_lint(lint, ast::CRATE_NODE_ID, span, msg); + } if ecx.parse_sess.span_diagnostic.err_count() - ecx.resolve_err_count > err_count { ecx.parse_sess.span_diagnostic.abort_if_errors(); } diff --git a/src/librustc_lint/lib.rs b/src/librustc_lint/lib.rs index 8fb1740e66e..b87edf54823 100644 --- a/src/librustc_lint/lib.rs +++ b/src/librustc_lint/lib.rs @@ -247,6 +247,10 @@ macro_rules! add_lint_group { id: LintId::of(LEGACY_CONSTRUCTOR_VISIBILITY), reference: "issue #39207 ", }, + FutureIncompatibleInfo { + id: LintId::of(MISSING_FRAGMENT_SPECIFIER), + reference: "issue #40107 ", + }, ]); // Register renamed and removed lints diff --git a/src/libsyntax/ext/tt/macro_parser.rs b/src/libsyntax/ext/tt/macro_parser.rs index a466fd2c6db..6ab5123bc87 100644 --- a/src/libsyntax/ext/tt/macro_parser.rs +++ b/src/libsyntax/ext/tt/macro_parser.rs @@ -87,6 +87,7 @@ use parse::parser::{PathStyle, Parser}; use parse::token::{self, DocComment, Token, Nonterminal}; use print::pprust; +use symbol::keywords; use tokenstream::TokenTree; use util::small_vector::SmallVector; @@ -201,22 +202,27 @@ pub enum NamedMatch { MatchedNonterminal(Rc) } -fn nameize>>(ms: &[quoted::TokenTree], mut res: I) +fn nameize>>(sess: &ParseSess, ms: &[quoted::TokenTree], mut res: I) -> NamedParseResult { use self::quoted::TokenTree; - fn n_rec>>(m: &TokenTree, mut res: &mut I, + fn n_rec>>(sess: &ParseSess, m: &TokenTree, mut res: &mut I, ret_val: &mut HashMap>) -> Result<(), (syntax_pos::Span, String)> { match *m { TokenTree::Sequence(_, ref seq) => { for next_m in &seq.tts { - n_rec(next_m, res.by_ref(), ret_val)? + n_rec(sess, next_m, res.by_ref(), ret_val)? } } TokenTree::Delimited(_, ref delim) => { for next_m in &delim.tts { - n_rec(next_m, res.by_ref(), ret_val)?; + n_rec(sess, next_m, res.by_ref(), ret_val)?; + } + } + TokenTree::MetaVarDecl(span, _, id) if id.name == keywords::Invalid.name() => { + if sess.missing_fragment_specifiers.borrow_mut().remove(&span) { + return Err((span, "missing fragment specifier".to_string())); } } TokenTree::MetaVarDecl(sp, bind_name, _) => { @@ -237,7 +243,7 @@ fn n_rec>>(m: &TokenTree, mut res: &mut I, let mut ret_val = HashMap::new(); for m in ms { - match n_rec(m, res.by_ref(), &mut ret_val) { + match n_rec(sess, m, res.by_ref(), &mut ret_val) { Ok(_) => {}, Err((sp, msg)) => return Error(sp, msg), } @@ -277,11 +283,13 @@ fn create_matches(len: usize) -> Vec>> { (0..len).into_iter().map(|_| Vec::new()).collect() } -fn inner_parse_loop(cur_eis: &mut SmallVector>, +fn inner_parse_loop(sess: &ParseSess, + cur_eis: &mut SmallVector>, next_eis: &mut Vec>, eof_eis: &mut SmallVector>, bb_eis: &mut SmallVector>, - token: &Token, span: &syntax_pos::Span) -> ParseResult<()> { + token: &Token, + span: &syntax_pos::Span) -> ParseResult<()> { use self::quoted::TokenTree; while let Some(mut ei) = cur_eis.pop() { @@ -375,6 +383,11 @@ fn inner_parse_loop(cur_eis: &mut SmallVector>, top_elts: Tt(TokenTree::Sequence(sp, seq)), })); } + TokenTree::MetaVarDecl(span, _, id) if id.name == keywords::Invalid.name() => { + if sess.missing_fragment_specifiers.borrow_mut().remove(&span) { + return Error(span, "missing fragment specifier".to_string()); + } + } TokenTree::MetaVarDecl(..) => { // Built-in nonterminals never start with these tokens, // so we can eliminate them from consideration. @@ -422,7 +435,7 @@ pub fn parse(sess: &ParseSess, let mut eof_eis = SmallVector::new(); assert!(next_eis.is_empty()); - match inner_parse_loop(&mut cur_eis, &mut next_eis, &mut eof_eis, &mut bb_eis, + match inner_parse_loop(sess, &mut cur_eis, &mut next_eis, &mut eof_eis, &mut bb_eis, &parser.token, &parser.span) { Success(_) => {}, Failure(sp, tok) => return Failure(sp, tok), @@ -435,7 +448,8 @@ pub fn parse(sess: &ParseSess, /* error messages here could be improved with links to orig. rules */ if token_name_eq(&parser.token, &token::Eof) { if eof_eis.len() == 1 { - return nameize(ms, eof_eis[0].matches.iter_mut().map(|mut dv| dv.pop().unwrap())); + let matches = eof_eis[0].matches.iter_mut().map(|mut dv| dv.pop().unwrap()); + return nameize(sess, ms, matches); } else if eof_eis.len() > 1 { return Error(parser.span, "ambiguity: multiple successful parses".to_string()); } else { diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs index c775b2325cd..193c06707c7 100644 --- a/src/libsyntax/ext/tt/macro_rules.rs +++ b/src/libsyntax/ext/tt/macro_rules.rs @@ -788,6 +788,7 @@ fn is_in_follow(tok: "ed::TokenTree, frag: &str) -> Result Ok(true), // keywords::Invalid _ => Err((format!("invalid fragment specifier `{}`", frag), "valid fragment specifiers are `ident`, `block`, \ `stmt`, `expr`, `pat`, `ty`, `path`, `meta`, `tt` \ @@ -810,7 +811,7 @@ fn has_legal_fragment_specifier(tok: "ed::TokenTree) -> Result<(), String> { fn is_legal_fragment_specifier(frag: &str) -> bool { match frag { "item" | "block" | "stmt" | "expr" | "pat" | - "path" | "ty" | "ident" | "meta" | "tt" => true, + "path" | "ty" | "ident" | "meta" | "tt" | "" => true, _ => false, } } diff --git a/src/libsyntax/ext/tt/quoted.rs b/src/libsyntax/ext/tt/quoted.rs index 4fc20d7fefa..530824b2834 100644 --- a/src/libsyntax/ext/tt/quoted.rs +++ b/src/libsyntax/ext/tt/quoted.rs @@ -143,7 +143,8 @@ pub fn parse(input: &[tokenstream::TokenTree], expect_matchers: bool, sess: &Par }, tree @ _ => tree.as_ref().map(tokenstream::TokenTree::span).unwrap_or(start_sp), }; - sess.span_diagnostic.span_err(span, "missing fragment specifier"); + sess.missing_fragment_specifiers.borrow_mut().insert(span); + result.push(TokenTree::MetaVarDecl(span, ident, keywords::Invalid.ident())); } _ => result.push(tree), } diff --git a/src/libsyntax/parse/lexer/mod.rs b/src/libsyntax/parse/lexer/mod.rs index b7f6e6a2384..de8a87e3a2b 100644 --- a/src/libsyntax/parse/lexer/mod.rs +++ b/src/libsyntax/parse/lexer/mod.rs @@ -1693,6 +1693,7 @@ mod tests { use feature_gate::UnstableFeatures; use parse::token; use std::cell::RefCell; + use std::collections::HashSet; use std::io; use std::rc::Rc; @@ -1704,6 +1705,7 @@ fn mk_sess(cm: Rc) -> ParseSess { config: CrateConfig::new(), included_mod_stack: RefCell::new(Vec::new()), code_map: cm, + missing_fragment_specifiers: RefCell::new(HashSet::new()), } } diff --git a/src/libsyntax/parse/mod.rs b/src/libsyntax/parse/mod.rs index 78fd706b27a..6fec49b229a 100644 --- a/src/libsyntax/parse/mod.rs +++ b/src/libsyntax/parse/mod.rs @@ -46,6 +46,7 @@ pub struct ParseSess { pub span_diagnostic: Handler, pub unstable_features: UnstableFeatures, pub config: CrateConfig, + pub missing_fragment_specifiers: RefCell>, /// Used to determine and report recursive mod inclusions included_mod_stack: RefCell>, code_map: Rc, @@ -66,6 +67,7 @@ pub fn with_span_handler(handler: Handler, code_map: Rc) -> ParseSess { span_diagnostic: handler, unstable_features: UnstableFeatures::from_environment(), config: HashSet::new(), + missing_fragment_specifiers: RefCell::new(HashSet::new()), included_mod_stack: RefCell::new(vec![]), code_map: code_map } diff --git a/src/test/compile-fail/issue-39404.rs b/src/test/compile-fail/issue-39404.rs new file mode 100644 index 00000000000..0168ae7d910 --- /dev/null +++ b/src/test/compile-fail/issue-39404.rs @@ -0,0 +1,18 @@ +// Copyright 2017 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. + +#![deny(missing_fragment_specifier)] //~ NOTE lint level defined here + +macro_rules! m { ($i) => {} } +//~^ ERROR missing fragment specifier +//~| WARN previously accepted +//~| NOTE issue #40107 + +fn main() {} diff --git a/src/test/compile-fail/macro-match-nonterminal.rs b/src/test/compile-fail/macro-match-nonterminal.rs index 0af171b43fe..6cca729e2c2 100644 --- a/src/test/compile-fail/macro-match-nonterminal.rs +++ b/src/test/compile-fail/macro-match-nonterminal.rs @@ -8,8 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -macro_rules! test { ($a, //~ ERROR missing fragment - $b) => (()); } //~ ERROR missing fragment +macro_rules! test { ($a, $b) => (()); } //~ ERROR missing fragment fn main() { test!() diff --git a/src/test/parse-fail/issue-33569.rs b/src/test/parse-fail/issue-33569.rs index d72bd8aab89..15d491719a6 100644 --- a/src/test/parse-fail/issue-33569.rs +++ b/src/test/parse-fail/issue-33569.rs @@ -16,3 +16,5 @@ macro_rules! foo { $(x)(y) //~ ERROR expected `*` or `+` } } + +foo!();