Skip to content

Commit

Permalink
Add ClosestAstOrMacro to allow detecting macro expansions
Browse files Browse the repository at this point in the history
Following PR rust-lang#72389, we create many more spans with
`ExpnKind::Desugaring`. This exposed a latent bug in Clippy - only the
top-most `ExpnData` is considered when checking if code is the result of
a macro expansion. If code emitted by a macro expansion gets annotated
with an `ExpnKind::Desugaring` (e.g. an operator or a for loop), Clippy
will incorrectly act as though this code is not the result of a macro
expansion.

This PR introduces the `ClosestAstOrMacro` enum, which allows linting
code to quickly determine if a given `Span` is the result of a macro
expansion. For any `ExpnId`, we keep track of closest `ExpnKind::Macro`
or `ExpnKind::AstPass` in the `call_site` chain. This is determined when
the `ExpnData` is set for an `ExpnId`, which allows us to avoid walking
the entire chain in Clippy.
  • Loading branch information
Aaron1011 committed Jun 18, 2020
1 parent 2935d29 commit b16f199
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 13 deletions.
27 changes: 15 additions & 12 deletions src/librustc_middle/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use rustc_errors::{DiagnosticBuilder, DiagnosticId};
use rustc_hir::HirId;
use rustc_session::lint::{builtin, Level, Lint, LintId};
use rustc_session::{DiagnosticMessageId, Session};
use rustc_span::hygiene::MacroKind;
use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan};
use rustc_span::hygiene::{ClosestAstOrMacro, MacroKind};
use rustc_span::source_map::{ExpnKind, MultiSpan};
use rustc_span::{Span, Symbol};

/// How a lint level was set.
Expand Down Expand Up @@ -337,16 +337,19 @@ pub fn struct_lint_level<'s, 'd>(
/// This is used to test whether a lint should not even begin to figure out whether it should
/// be reported on the current node.
pub fn in_external_macro(sess: &Session, span: Span) -> bool {
let expn_data = span.ctxt().outer_expn_data();
match expn_data.kind {
ExpnKind::Root
| ExpnKind::Desugaring(DesugaringKind::ForLoop(_))
| ExpnKind::Desugaring(DesugaringKind::Operator) => false,
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
ExpnKind::Macro(MacroKind::Bang, _) => {
// Dummy span for the `def_site` means it's an external macro.
expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site)
match span.ctxt().outer_expn().closest_ast_or_macro() {
ClosestAstOrMacro::Expn(expn_id) => {
let data = expn_id.expn_data();
match data.kind {
ExpnKind::Macro(MacroKind::Bang, _) => {
// Dummy span for the `def_site` means it's an external macro.
data.def_site.is_dummy() || sess.source_map().is_imported(data.def_site)
}
ExpnKind::Macro(_, _) => true, // definitely a plugin
ExpnKind::AstPass(_) => true, // well, it's "External"
_ => unreachable!("unexpected ExpnData {:?}", data),
}
}
ExpnKind::Macro(..) => true, // definitely a plugin
ClosestAstOrMacro::None => false,
}
}
60 changes: 59 additions & 1 deletion src/librustc_span/hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,19 @@ impl ExpnId {
HygieneData::with(|data| data.expn_data(self).clone())
}

#[inline]
pub fn closest_ast_or_macro(self) -> ClosestAstOrMacro {
HygieneData::with(|data| data.closest_ast_or_macro(self))
}

#[inline]
pub fn set_expn_data(self, expn_data: ExpnData) {
HygieneData::with(|data| {
let closest = data.determine_closest_ast_or_macro(self, &expn_data);
let old_closest = &mut data.closest_ast_or_macro[self.0 as usize];
assert!(old_closest.is_none(), "closest ast/macro data reset for an expansion ID");
*old_closest = Some(closest);

let old_expn_data = &mut data.expn_data[self.0 as usize];
assert!(old_expn_data.is_none(), "expansion data is reset for an expansion ID");
*old_expn_data = Some(expn_data);
Expand Down Expand Up @@ -149,6 +159,10 @@ crate struct HygieneData {
/// between creation of an expansion ID and obtaining its data (e.g. macros are collected
/// first and then resolved later), so we use an `Option` here.
expn_data: Vec<Option<ExpnData>>,
/// Stores the computed `ClosestAstOrMacro` for each `ExpnId`. This is updated
/// at the same time as `expn_data`, and its contents it determined entirely
/// by the `ExpnData` - this field is just a cache.
closest_ast_or_macro: Vec<Option<ClosestAstOrMacro>>,
syntax_context_data: Vec<SyntaxContextData>,
syntax_context_map: FxHashMap<(SyntaxContext, ExpnId, Transparency), SyntaxContext>,
}
Expand All @@ -162,6 +176,7 @@ impl HygieneData {
edition,
Some(DefId::local(CRATE_DEF_INDEX)),
))],
closest_ast_or_macro: vec![Some(ClosestAstOrMacro::None)],
syntax_context_data: vec![SyntaxContextData {
outer_expn: ExpnId::root(),
outer_transparency: Transparency::Opaque,
Expand All @@ -178,9 +193,36 @@ impl HygieneData {
GLOBALS.with(|globals| f(&mut *globals.hygiene_data.borrow_mut()))
}

fn determine_closest_ast_or_macro(
&self,
id: ExpnId,
expn_data: &ExpnData,
) -> ClosestAstOrMacro {
match expn_data.kind {
ExpnKind::Macro(_, _) | ExpnKind::AstPass(_) => ClosestAstOrMacro::Expn(id),
ExpnKind::Desugaring(_) | ExpnKind::Root => {
// Avoid using `HygieneData` when construction root
// `ExpnData`
if expn_data.call_site.ctxt() == SyntaxContext::root() {
ClosestAstOrMacro::None
} else {
self.closest_ast_or_macro(self.outer_expn(expn_data.call_site.ctxt()))
}
}
}
}

fn closest_ast_or_macro(&self, expn_id: ExpnId) -> ClosestAstOrMacro {
self.closest_ast_or_macro[expn_id.0 as usize].as_ref().copied().unwrap()
}

fn fresh_expn(&mut self, expn_data: Option<ExpnData>) -> ExpnId {
let expn_id = ExpnId(self.expn_data.len() as u32);
self.closest_ast_or_macro.push(
expn_data.as_ref().map(|data| self.determine_closest_ast_or_macro(expn_id, data)),
);
self.expn_data.push(expn_data);
ExpnId(self.expn_data.len() as u32 - 1)
expn_id
}

fn expn_data(&self, expn_id: ExpnId) -> &ExpnData {
Expand Down Expand Up @@ -684,6 +726,22 @@ pub struct ExpnData {
pub macro_def_id: Option<DefId>,
}

/// The closest `ExpnKind::AstPass` or `ExpnKind::Macro` to an `ExpnData`.
/// 'Closest' is determined by starting at the current `ExpnData`,
/// and walking up the `call_site` tree. If an `ExpnData` with
/// `Expn::AstPass` or `ExpnKind::Macro` is found, it is represented
/// by `ClosestAstOrMacro::Expn(id)`, where `id` is the `EpxId` of
/// the found `ExpnData`.
///
/// A `ClosestAstOrMacro` implies that no `ExpnKind::AstPass` or `ExpnKind::Macro`
/// are found anywhere in the `call_site` tree - that is, there no macro
/// expansions or ast pass expansions.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ClosestAstOrMacro {
None,
Expn(ExpnId),
}

impl ExpnData {
/// Constructs expansion data with default properties.
pub fn default(
Expand Down

0 comments on commit b16f199

Please sign in to comment.