Skip to content

Warn when macro shadows previous definition #20277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libcore/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// except according to those terms.

#![macro_escape]
#![allow(shadowing_macros)]

// NOTE(stage0): Remove cfg after a snapshot
#[cfg(not(stage0))]
Expand Down
49 changes: 48 additions & 1 deletion src/librustc/lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,52 @@ impl LintPass for MissingCopyImplementations {
}
}

declare_lint! {
pub SHADOWING_MACROS,
Warn,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to this being a known feature of the macro system, this should be Allow by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, fixed!

"detects shadowing macro definitions"
}

#[deriving(Copy)]
pub struct ShadowingMacrosPass;

impl LintPass for ShadowingMacrosPass {
fn get_lints(&self) -> LintArray {
lint_array!(SHADOWING_MACROS)
}

fn check_crate(&mut self, cx: &Context, krate: &ast::Crate) {
// Will map name uints to macro ast::Items. The loop below will then
// make a lint warning if a macro use with the name already exists
// in the map, or add that macro to the map so that subsequent ones
// with the same name will warn.
let mut uses = FnvHashMap::new();

for it in krate.macros.iter() {
let name = it.ident.name;
match uses.get(&name.uint()) {
Some(_) => {
let span = it.span;
// FIXME: Include span-printing of the first use to
// help users.
cx.span_lint(
SHADOWING_MACROS,
span,
format!(
"shadowing macro definition: {}",
name.as_str()
)[]
);
continue;
},
None => { }
}
// Have to put None-case here to dodge the borrow-checker.
uses.insert(name.uint(), it);
}
}
}

declare_lint! {
DEPRECATED,
Warn,
Expand Down Expand Up @@ -1903,7 +1949,8 @@ impl LintPass for HardwiredLints {
UNKNOWN_FEATURES,
UNKNOWN_CRATE_TYPES,
VARIANT_SIZE_DIFFERENCES,
FAT_PTR_TRANSMUTES
FAT_PTR_TRANSMUTES,
SHADOWING_MACROS
)
}
}
3 changes: 3 additions & 0 deletions src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,9 @@ pub struct Crate {
pub attrs: Vec<Attribute>,
pub config: CrateConfig,
pub span: Span,
/// Macros defined inside the crate
pub macros: Vec<P<Item>>,
/// Macros exported from the crate via macro_export attribute
pub exported_macros: Vec<P<Item>>
}

Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/ast_map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,7 @@ pub fn map_crate<'ast, F: FoldOps>(forest: &'ast mut Forest, fold_ops: F) -> Map
},
attrs: vec![],
config: vec![],
macros: vec![],
exported_macros: vec![],
span: DUMMY_SP
});
Expand Down
8 changes: 7 additions & 1 deletion src/libsyntax/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,14 @@ pub struct ExtCtxt<'a> {
pub backtrace: ExpnId,
pub ecfg: expand::ExpansionConfig,

pub mod_path: Vec<ast::Ident> ,
pub mod_path: Vec<ast::Ident>,
pub trace_mac: bool,
/// These will eventually be moved into the ast::Crate's .exported_macros
/// by libsyntax::fold::noop_fold_crate.
pub exported_macros: Vec<P<ast::Item>>,
/// All macros found during crate expansion. Also moved into the
/// ast::Crate so a macro-shadowing LintPass can later check them.
pub macros: Vec<P<ast::Item>>,

pub syntax_env: SyntaxEnv,
pub recursion_count: uint,
Expand All @@ -491,6 +496,7 @@ impl<'a> ExtCtxt<'a> {
ecfg: ecfg,
trace_mac: false,
exported_macros: Vec::new(),
macros: Vec::new(),
syntax_env: env,
recursion_count: 0,
}
Expand Down
6 changes: 6 additions & 0 deletions src/libsyntax/ext/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,9 @@ pub fn expand_item_mac(it: P<ast::Item>, fld: &mut MacroExpander)
// need to be marked. Not that it could be marked anyway.
// create issue to recommend refactoring here?
fld.cx.syntax_env.insert(intern(name[]), ext);
// Keep track of this macro definition both within the crate
// context and (if applicable) in its exports.
fld.cx.macros.push(it.clone());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that this quite catches shadowing per-se because you could have two disjoint modules with the same macro with the same name, but they're not shadowing one another.

Where you had the logic before I believe was the correct place to put the logic. Did the add_lint method not work out?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately rustc::sess::Session#add_lint doesn't seem to be available at the original add_new_extension check point in libsyntax, and I can't find any way to get to it via the ExtCtxt.

The following idea definitely isn't the most elegant approach: instead of having the whole .macros property and checking it with a LintPass, what if the add_new_extension function pushed shadowings it found onto a member of ext::ExtCtxt, which then got copied onto the ast::Crate, and then those got reported by a LintPass later on. That way the checks would be happening in the right place (during syntax expansion), but reporting would be deferred until the proper linting infrastructure was available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think something like that is the way to go. I'd probably avoid ast::Crate and have some other kind of side-structure though as we should probably keep the AST limited to being an AST :)

if attr::contains_name(it.attrs[], "macro_export") {
fld.cx.exported_macros.push(it);
}
Expand Down Expand Up @@ -1209,7 +1212,10 @@ pub fn expand_crate(parse_sess: &parse::ParseSess,
}

let mut ret = expander.fold_crate(c);
// Copy the list of all macro expansions and exported macros into
// the crate.
ret.exported_macros = expander.cx.exported_macros.clone();
ret.macros = expander.cx.macros.clone();
parse_sess.span_diagnostic.handler().abort_if_errors();
return ret;
}
Expand Down
4 changes: 2 additions & 2 deletions src/libsyntax/ext/tt/macro_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ pub fn add_new_extension<'cx>(cx: &'cx mut ExtCtxt,
arg: Vec<ast::TokenTree> )
-> Box<MacResult+'cx> {

let lhs_nm = gensym_ident("lhs");
let rhs_nm = gensym_ident("rhs");
let lhs_nm = gensym_ident("lhs");
let rhs_nm = gensym_ident("rhs");

// The pattern that macro_rules matches.
// The grammar for macro_rules! is:
Expand Down
3 changes: 2 additions & 1 deletion src/libsyntax/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,7 +1116,7 @@ pub fn noop_fold_mod<T: Folder>(Mod {inner, view_items, items}: Mod, folder: &mu
}
}

pub fn noop_fold_crate<T: Folder>(Crate {module, attrs, config, exported_macros, span}: Crate,
pub fn noop_fold_crate<T: Folder>(Crate {module, attrs, config, macros, exported_macros, span}: Crate,
folder: &mut T) -> Crate {
let config = folder.fold_meta_items(config);

Expand Down Expand Up @@ -1151,6 +1151,7 @@ pub fn noop_fold_crate<T: Folder>(Crate {module, attrs, config, exported_macros,
module: module,
attrs: attrs,
config: config,
macros: macros,
exported_macros: exported_macros,
span: span,
}
Expand Down
3 changes: 2 additions & 1 deletion src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6188,7 +6188,8 @@ impl<'a> Parser<'a> {
attrs: inner,
config: self.cfg.clone(),
span: mk_sp(lo, self.span.lo),
exported_macros: Vec::new(),
macros: Vec::new(),
exported_macros: Vec::new()
}
}

Expand Down