Skip to content

Commit b16f199

Browse files
committed
Add ClosestAstOrMacro to allow detecting macro expansions
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.
1 parent 2935d29 commit b16f199

File tree

2 files changed

+74
-13
lines changed

2 files changed

+74
-13
lines changed

src/librustc_middle/lint.rs

+15-12
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use rustc_errors::{DiagnosticBuilder, DiagnosticId};
77
use rustc_hir::HirId;
88
use rustc_session::lint::{builtin, Level, Lint, LintId};
99
use rustc_session::{DiagnosticMessageId, Session};
10-
use rustc_span::hygiene::MacroKind;
11-
use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan};
10+
use rustc_span::hygiene::{ClosestAstOrMacro, MacroKind};
11+
use rustc_span::source_map::{ExpnKind, MultiSpan};
1212
use rustc_span::{Span, Symbol};
1313

1414
/// How a lint level was set.
@@ -337,16 +337,19 @@ pub fn struct_lint_level<'s, 'd>(
337337
/// This is used to test whether a lint should not even begin to figure out whether it should
338338
/// be reported on the current node.
339339
pub fn in_external_macro(sess: &Session, span: Span) -> bool {
340-
let expn_data = span.ctxt().outer_expn_data();
341-
match expn_data.kind {
342-
ExpnKind::Root
343-
| ExpnKind::Desugaring(DesugaringKind::ForLoop(_))
344-
| ExpnKind::Desugaring(DesugaringKind::Operator) => false,
345-
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
346-
ExpnKind::Macro(MacroKind::Bang, _) => {
347-
// Dummy span for the `def_site` means it's an external macro.
348-
expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site)
340+
match span.ctxt().outer_expn().closest_ast_or_macro() {
341+
ClosestAstOrMacro::Expn(expn_id) => {
342+
let data = expn_id.expn_data();
343+
match data.kind {
344+
ExpnKind::Macro(MacroKind::Bang, _) => {
345+
// Dummy span for the `def_site` means it's an external macro.
346+
data.def_site.is_dummy() || sess.source_map().is_imported(data.def_site)
347+
}
348+
ExpnKind::Macro(_, _) => true, // definitely a plugin
349+
ExpnKind::AstPass(_) => true, // well, it's "External"
350+
_ => unreachable!("unexpected ExpnData {:?}", data),
351+
}
349352
}
350-
ExpnKind::Macro(..) => true, // definitely a plugin
353+
ClosestAstOrMacro::None => false,
351354
}
352355
}

src/librustc_span/hygiene.rs

+59-1
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,19 @@ impl ExpnId {
104104
HygieneData::with(|data| data.expn_data(self).clone())
105105
}
106106

107+
#[inline]
108+
pub fn closest_ast_or_macro(self) -> ClosestAstOrMacro {
109+
HygieneData::with(|data| data.closest_ast_or_macro(self))
110+
}
111+
107112
#[inline]
108113
pub fn set_expn_data(self, expn_data: ExpnData) {
109114
HygieneData::with(|data| {
115+
let closest = data.determine_closest_ast_or_macro(self, &expn_data);
116+
let old_closest = &mut data.closest_ast_or_macro[self.0 as usize];
117+
assert!(old_closest.is_none(), "closest ast/macro data reset for an expansion ID");
118+
*old_closest = Some(closest);
119+
110120
let old_expn_data = &mut data.expn_data[self.0 as usize];
111121
assert!(old_expn_data.is_none(), "expansion data is reset for an expansion ID");
112122
*old_expn_data = Some(expn_data);
@@ -149,6 +159,10 @@ crate struct HygieneData {
149159
/// between creation of an expansion ID and obtaining its data (e.g. macros are collected
150160
/// first and then resolved later), so we use an `Option` here.
151161
expn_data: Vec<Option<ExpnData>>,
162+
/// Stores the computed `ClosestAstOrMacro` for each `ExpnId`. This is updated
163+
/// at the same time as `expn_data`, and its contents it determined entirely
164+
/// by the `ExpnData` - this field is just a cache.
165+
closest_ast_or_macro: Vec<Option<ClosestAstOrMacro>>,
152166
syntax_context_data: Vec<SyntaxContextData>,
153167
syntax_context_map: FxHashMap<(SyntaxContext, ExpnId, Transparency), SyntaxContext>,
154168
}
@@ -162,6 +176,7 @@ impl HygieneData {
162176
edition,
163177
Some(DefId::local(CRATE_DEF_INDEX)),
164178
))],
179+
closest_ast_or_macro: vec![Some(ClosestAstOrMacro::None)],
165180
syntax_context_data: vec![SyntaxContextData {
166181
outer_expn: ExpnId::root(),
167182
outer_transparency: Transparency::Opaque,
@@ -178,9 +193,36 @@ impl HygieneData {
178193
GLOBALS.with(|globals| f(&mut *globals.hygiene_data.borrow_mut()))
179194
}
180195

196+
fn determine_closest_ast_or_macro(
197+
&self,
198+
id: ExpnId,
199+
expn_data: &ExpnData,
200+
) -> ClosestAstOrMacro {
201+
match expn_data.kind {
202+
ExpnKind::Macro(_, _) | ExpnKind::AstPass(_) => ClosestAstOrMacro::Expn(id),
203+
ExpnKind::Desugaring(_) | ExpnKind::Root => {
204+
// Avoid using `HygieneData` when construction root
205+
// `ExpnData`
206+
if expn_data.call_site.ctxt() == SyntaxContext::root() {
207+
ClosestAstOrMacro::None
208+
} else {
209+
self.closest_ast_or_macro(self.outer_expn(expn_data.call_site.ctxt()))
210+
}
211+
}
212+
}
213+
}
214+
215+
fn closest_ast_or_macro(&self, expn_id: ExpnId) -> ClosestAstOrMacro {
216+
self.closest_ast_or_macro[expn_id.0 as usize].as_ref().copied().unwrap()
217+
}
218+
181219
fn fresh_expn(&mut self, expn_data: Option<ExpnData>) -> ExpnId {
220+
let expn_id = ExpnId(self.expn_data.len() as u32);
221+
self.closest_ast_or_macro.push(
222+
expn_data.as_ref().map(|data| self.determine_closest_ast_or_macro(expn_id, data)),
223+
);
182224
self.expn_data.push(expn_data);
183-
ExpnId(self.expn_data.len() as u32 - 1)
225+
expn_id
184226
}
185227

186228
fn expn_data(&self, expn_id: ExpnId) -> &ExpnData {
@@ -684,6 +726,22 @@ pub struct ExpnData {
684726
pub macro_def_id: Option<DefId>,
685727
}
686728

729+
/// The closest `ExpnKind::AstPass` or `ExpnKind::Macro` to an `ExpnData`.
730+
/// 'Closest' is determined by starting at the current `ExpnData`,
731+
/// and walking up the `call_site` tree. If an `ExpnData` with
732+
/// `Expn::AstPass` or `ExpnKind::Macro` is found, it is represented
733+
/// by `ClosestAstOrMacro::Expn(id)`, where `id` is the `EpxId` of
734+
/// the found `ExpnData`.
735+
///
736+
/// A `ClosestAstOrMacro` implies that no `ExpnKind::AstPass` or `ExpnKind::Macro`
737+
/// are found anywhere in the `call_site` tree - that is, there no macro
738+
/// expansions or ast pass expansions.
739+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
740+
pub enum ClosestAstOrMacro {
741+
None,
742+
Expn(ExpnId),
743+
}
744+
687745
impl ExpnData {
688746
/// Constructs expansion data with default properties.
689747
pub fn default(

0 commit comments

Comments
 (0)