]> git.lizzy.rs Git - rust.git/blob - src/librustc/lint.rs
{rustc::lint -> rustc_lint}::internal
[rust.git] / src / librustc / lint.rs
1 use std::cmp;
2
3 use crate::ich::StableHashingContext;
4 use crate::lint::context::CheckLintNameResult;
5 use rustc_data_structures::fx::FxHashMap;
6 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
7 use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, DiagnosticId};
8 use rustc_hir::HirId;
9 pub use rustc_session::lint::{builtin, Level, Lint, LintId, LintPass};
10 use rustc_session::{DiagnosticMessageId, Session};
11 use rustc_span::hygiene::MacroKind;
12 use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan};
13 use rustc_span::symbol::{sym, Symbol};
14 use rustc_span::Span;
15 use syntax::ast;
16 use syntax::attr;
17 use syntax::print::pprust;
18 use syntax::sess::feature_err;
19
20 use rustc_error_codes::*;
21
22 mod context;
23 mod passes;
24
25 pub use context::add_elided_lifetime_in_path_suggestion;
26 pub use context::{EarlyContext, LateContext, LintContext, LintStore};
27 pub use passes::{EarlyLintPass, EarlyLintPassObject, LateLintPass, LateLintPassObject};
28
29 /// How a lint level was set.
30 #[derive(Clone, Copy, PartialEq, Eq, HashStable)]
31 pub enum LintSource {
32     /// Lint is at the default level as declared
33     /// in rustc or a plugin.
34     Default,
35
36     /// Lint level was set by an attribute.
37     Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */),
38
39     /// Lint level was set by a command-line flag.
40     CommandLine(Symbol),
41 }
42
43 pub type LevelSource = (Level, LintSource);
44
45 pub struct LintLevelSets {
46     list: Vec<LintSet>,
47     lint_cap: Level,
48 }
49
50 enum LintSet {
51     CommandLine {
52         // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which
53         // flag.
54         specs: FxHashMap<LintId, LevelSource>,
55     },
56
57     Node {
58         specs: FxHashMap<LintId, LevelSource>,
59         parent: u32,
60     },
61 }
62
63 impl LintLevelSets {
64     pub fn new() -> Self {
65         LintLevelSets { list: Vec::new(), lint_cap: Level::Forbid }
66     }
67
68     pub fn get_lint_level(
69         &self,
70         lint: &'static Lint,
71         idx: u32,
72         aux: Option<&FxHashMap<LintId, LevelSource>>,
73         sess: &Session,
74     ) -> LevelSource {
75         let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux);
76
77         // If `level` is none then we actually assume the default level for this
78         // lint.
79         let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition()));
80
81         // If we're about to issue a warning, check at the last minute for any
82         // directives against the warnings "lint". If, for example, there's an
83         // `allow(warnings)` in scope then we want to respect that instead.
84         if level == Level::Warn {
85             let (warnings_level, warnings_src) =
86                 self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux);
87             if let Some(configured_warning_level) = warnings_level {
88                 if configured_warning_level != Level::Warn {
89                     level = configured_warning_level;
90                     src = warnings_src;
91                 }
92             }
93         }
94
95         // Ensure that we never exceed the `--cap-lints` argument.
96         level = cmp::min(level, self.lint_cap);
97
98         if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) {
99             // Ensure that we never exceed driver level.
100             level = cmp::min(*driver_level, level);
101         }
102
103         return (level, src);
104     }
105
106     pub fn get_lint_id_level(
107         &self,
108         id: LintId,
109         mut idx: u32,
110         aux: Option<&FxHashMap<LintId, LevelSource>>,
111     ) -> (Option<Level>, LintSource) {
112         if let Some(specs) = aux {
113             if let Some(&(level, src)) = specs.get(&id) {
114                 return (Some(level), src);
115             }
116         }
117         loop {
118             match self.list[idx as usize] {
119                 LintSet::CommandLine { ref specs } => {
120                     if let Some(&(level, src)) = specs.get(&id) {
121                         return (Some(level), src);
122                     }
123                     return (None, LintSource::Default);
124                 }
125                 LintSet::Node { ref specs, parent } => {
126                     if let Some(&(level, src)) = specs.get(&id) {
127                         return (Some(level), src);
128                     }
129                     idx = parent;
130                 }
131             }
132         }
133     }
134 }
135
136 pub struct LintLevelsBuilder<'a> {
137     sess: &'a Session,
138     sets: LintLevelSets,
139     id_to_set: FxHashMap<HirId, u32>,
140     cur: u32,
141     warn_about_weird_lints: bool,
142 }
143
144 pub struct BuilderPush {
145     prev: u32,
146     pub changed: bool,
147 }
148
149 impl<'a> LintLevelsBuilder<'a> {
150     pub fn new(sess: &'a Session, warn_about_weird_lints: bool, store: &LintStore) -> Self {
151         let mut builder = LintLevelsBuilder {
152             sess,
153             sets: LintLevelSets::new(),
154             cur: 0,
155             id_to_set: Default::default(),
156             warn_about_weird_lints,
157         };
158         builder.process_command_line(sess, store);
159         assert_eq!(builder.sets.list.len(), 1);
160         builder
161     }
162
163     fn process_command_line(&mut self, sess: &Session, store: &LintStore) {
164         let mut specs = FxHashMap::default();
165         self.sets.lint_cap = sess.opts.lint_cap.unwrap_or(Level::Forbid);
166
167         for &(ref lint_name, level) in &sess.opts.lint_opts {
168             store.check_lint_name_cmdline(sess, &lint_name, level);
169
170             // If the cap is less than this specified level, e.g., if we've got
171             // `--cap-lints allow` but we've also got `-D foo` then we ignore
172             // this specification as the lint cap will set it to allow anyway.
173             let level = cmp::min(level, self.sets.lint_cap);
174
175             let lint_flag_val = Symbol::intern(lint_name);
176             let ids = match store.find_lints(&lint_name) {
177                 Ok(ids) => ids,
178                 Err(_) => continue, // errors handled in check_lint_name_cmdline above
179             };
180             for id in ids {
181                 let src = LintSource::CommandLine(lint_flag_val);
182                 specs.insert(id, (level, src));
183             }
184         }
185
186         self.sets.list.push(LintSet::CommandLine { specs });
187     }
188
189     /// Pushes a list of AST lint attributes onto this context.
190     ///
191     /// This function will return a `BuilderPush` object which should be passed
192     /// to `pop` when this scope for the attributes provided is exited.
193     ///
194     /// This function will perform a number of tasks:
195     ///
196     /// * It'll validate all lint-related attributes in `attrs`
197     /// * It'll mark all lint-related attributes as used
198     /// * Lint levels will be updated based on the attributes provided
199     /// * Lint attributes are validated, e.g., a #[forbid] can't be switched to
200     ///   #[allow]
201     ///
202     /// Don't forget to call `pop`!
203     pub fn push(&mut self, attrs: &[ast::Attribute], store: &LintStore) -> BuilderPush {
204         let mut specs = FxHashMap::default();
205         let sess = self.sess;
206         let bad_attr = |span| struct_span_err!(sess, span, E0452, "malformed lint attribute input");
207         for attr in attrs {
208             let level = match Level::from_symbol(attr.name_or_empty()) {
209                 None => continue,
210                 Some(lvl) => lvl,
211             };
212
213             let meta = unwrap_or!(attr.meta(), continue);
214             attr::mark_used(attr);
215
216             let mut metas = unwrap_or!(meta.meta_item_list(), continue);
217
218             if metas.is_empty() {
219                 // FIXME (#55112): issue unused-attributes lint for `#[level()]`
220                 continue;
221             }
222
223             // Before processing the lint names, look for a reason (RFC 2383)
224             // at the end.
225             let mut reason = None;
226             let tail_li = &metas[metas.len() - 1];
227             if let Some(item) = tail_li.meta_item() {
228                 match item.kind {
229                     ast::MetaItemKind::Word => {} // actual lint names handled later
230                     ast::MetaItemKind::NameValue(ref name_value) => {
231                         if item.path == sym::reason {
232                             // found reason, reslice meta list to exclude it
233                             metas = &metas[0..metas.len() - 1];
234                             // FIXME (#55112): issue unused-attributes lint if we thereby
235                             // don't have any lint names (`#[level(reason = "foo")]`)
236                             if let ast::LitKind::Str(rationale, _) = name_value.kind {
237                                 if !self.sess.features_untracked().lint_reasons {
238                                     feature_err(
239                                         &self.sess.parse_sess,
240                                         sym::lint_reasons,
241                                         item.span,
242                                         "lint reasons are experimental",
243                                     )
244                                     .emit();
245                                 }
246                                 reason = Some(rationale);
247                             } else {
248                                 bad_attr(name_value.span)
249                                     .span_label(name_value.span, "reason must be a string literal")
250                                     .emit();
251                             }
252                         } else {
253                             bad_attr(item.span)
254                                 .span_label(item.span, "bad attribute argument")
255                                 .emit();
256                         }
257                     }
258                     ast::MetaItemKind::List(_) => {
259                         bad_attr(item.span).span_label(item.span, "bad attribute argument").emit();
260                     }
261                 }
262             }
263
264             for li in metas {
265                 let meta_item = match li.meta_item() {
266                     Some(meta_item) if meta_item.is_word() => meta_item,
267                     _ => {
268                         let sp = li.span();
269                         let mut err = bad_attr(sp);
270                         let mut add_label = true;
271                         if let Some(item) = li.meta_item() {
272                             if let ast::MetaItemKind::NameValue(_) = item.kind {
273                                 if item.path == sym::reason {
274                                     err.span_label(sp, "reason in lint attribute must come last");
275                                     add_label = false;
276                                 }
277                             }
278                         }
279                         if add_label {
280                             err.span_label(sp, "bad attribute argument");
281                         }
282                         err.emit();
283                         continue;
284                     }
285                 };
286                 let tool_name = if meta_item.path.segments.len() > 1 {
287                     let tool_ident = meta_item.path.segments[0].ident;
288                     if !attr::is_known_lint_tool(tool_ident) {
289                         struct_span_err!(
290                             sess,
291                             tool_ident.span,
292                             E0710,
293                             "an unknown tool name found in scoped lint: `{}`",
294                             pprust::path_to_string(&meta_item.path),
295                         )
296                         .emit();
297                         continue;
298                     }
299
300                     Some(tool_ident.name)
301                 } else {
302                     None
303                 };
304                 let name = meta_item.path.segments.last().expect("empty lint name").ident.name;
305                 match store.check_lint_name(&name.as_str(), tool_name) {
306                     CheckLintNameResult::Ok(ids) => {
307                         let src = LintSource::Node(name, li.span(), reason);
308                         for id in ids {
309                             specs.insert(*id, (level, src));
310                         }
311                     }
312
313                     CheckLintNameResult::Tool(result) => {
314                         match result {
315                             Ok(ids) => {
316                                 let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
317                                 let src = LintSource::Node(
318                                     Symbol::intern(complete_name),
319                                     li.span(),
320                                     reason,
321                                 );
322                                 for id in ids {
323                                     specs.insert(*id, (level, src));
324                                 }
325                             }
326                             Err((Some(ids), new_lint_name)) => {
327                                 let lint = builtin::RENAMED_AND_REMOVED_LINTS;
328                                 let (lvl, src) =
329                                     self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
330                                 let msg = format!(
331                                     "lint name `{}` is deprecated \
332                                      and may not have an effect in the future. \
333                                      Also `cfg_attr(cargo-clippy)` won't be necessary anymore",
334                                     name
335                                 );
336                                 struct_lint_level(
337                                     self.sess,
338                                     lint,
339                                     lvl,
340                                     src,
341                                     Some(li.span().into()),
342                                     &msg,
343                                 )
344                                 .span_suggestion(
345                                     li.span(),
346                                     "change it to",
347                                     new_lint_name.to_string(),
348                                     Applicability::MachineApplicable,
349                                 )
350                                 .emit();
351
352                                 let src = LintSource::Node(
353                                     Symbol::intern(&new_lint_name),
354                                     li.span(),
355                                     reason,
356                                 );
357                                 for id in ids {
358                                     specs.insert(*id, (level, src));
359                                 }
360                             }
361                             Err((None, _)) => {
362                                 // If Tool(Err(None, _)) is returned, then either the lint does not
363                                 // exist in the tool or the code was not compiled with the tool and
364                                 // therefore the lint was never added to the `LintStore`. To detect
365                                 // this is the responsibility of the lint tool.
366                             }
367                         }
368                     }
369
370                     _ if !self.warn_about_weird_lints => {}
371
372                     CheckLintNameResult::Warning(msg, renamed) => {
373                         let lint = builtin::RENAMED_AND_REMOVED_LINTS;
374                         let (level, src) =
375                             self.sets.get_lint_level(lint, self.cur, Some(&specs), &sess);
376                         let mut err = struct_lint_level(
377                             self.sess,
378                             lint,
379                             level,
380                             src,
381                             Some(li.span().into()),
382                             &msg,
383                         );
384                         if let Some(new_name) = renamed {
385                             err.span_suggestion(
386                                 li.span(),
387                                 "use the new name",
388                                 new_name,
389                                 Applicability::MachineApplicable,
390                             );
391                         }
392                         err.emit();
393                     }
394                     CheckLintNameResult::NoLint(suggestion) => {
395                         let lint = builtin::UNKNOWN_LINTS;
396                         let (level, src) =
397                             self.sets.get_lint_level(lint, self.cur, Some(&specs), self.sess);
398                         let msg = format!("unknown lint: `{}`", name);
399                         let mut db = struct_lint_level(
400                             self.sess,
401                             lint,
402                             level,
403                             src,
404                             Some(li.span().into()),
405                             &msg,
406                         );
407
408                         if let Some(suggestion) = suggestion {
409                             db.span_suggestion(
410                                 li.span(),
411                                 "did you mean",
412                                 suggestion.to_string(),
413                                 Applicability::MachineApplicable,
414                             );
415                         }
416
417                         db.emit();
418                     }
419                 }
420             }
421         }
422
423         for (id, &(level, ref src)) in specs.iter() {
424             if level == Level::Forbid {
425                 continue;
426             }
427             let forbid_src = match self.sets.get_lint_id_level(*id, self.cur, None) {
428                 (Some(Level::Forbid), src) => src,
429                 _ => continue,
430             };
431             let forbidden_lint_name = match forbid_src {
432                 LintSource::Default => id.to_string(),
433                 LintSource::Node(name, _, _) => name.to_string(),
434                 LintSource::CommandLine(name) => name.to_string(),
435             };
436             let (lint_attr_name, lint_attr_span) = match *src {
437                 LintSource::Node(name, span, _) => (name, span),
438                 _ => continue,
439             };
440             let mut diag_builder = struct_span_err!(
441                 self.sess,
442                 lint_attr_span,
443                 E0453,
444                 "{}({}) overruled by outer forbid({})",
445                 level.as_str(),
446                 lint_attr_name,
447                 forbidden_lint_name
448             );
449             diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
450             match forbid_src {
451                 LintSource::Default => {}
452                 LintSource::Node(_, forbid_source_span, reason) => {
453                     diag_builder.span_label(forbid_source_span, "`forbid` level set here");
454                     if let Some(rationale) = reason {
455                         diag_builder.note(&rationale.as_str());
456                     }
457                 }
458                 LintSource::CommandLine(_) => {
459                     diag_builder.note("`forbid` lint level was set on command line");
460                 }
461             }
462             diag_builder.emit();
463             // don't set a separate error for every lint in the group
464             break;
465         }
466
467         let prev = self.cur;
468         if specs.len() > 0 {
469             self.cur = self.sets.list.len() as u32;
470             self.sets.list.push(LintSet::Node { specs: specs, parent: prev });
471         }
472
473         BuilderPush { prev: prev, changed: prev != self.cur }
474     }
475
476     /// Called after `push` when the scope of a set of attributes are exited.
477     pub fn pop(&mut self, push: BuilderPush) {
478         self.cur = push.prev;
479     }
480
481     /// Used to emit a lint-related diagnostic based on the current state of
482     /// this lint context.
483     pub fn struct_lint(
484         &self,
485         lint: &'static Lint,
486         span: Option<MultiSpan>,
487         msg: &str,
488     ) -> DiagnosticBuilder<'a> {
489         let (level, src) = self.sets.get_lint_level(lint, self.cur, None, self.sess);
490         struct_lint_level(self.sess, lint, level, src, span, msg)
491     }
492
493     /// Registers the ID provided with the current set of lints stored in
494     /// this context.
495     pub fn register_id(&mut self, id: HirId) {
496         self.id_to_set.insert(id, self.cur);
497     }
498
499     pub fn build(self) -> LintLevelSets {
500         self.sets
501     }
502
503     pub fn build_map(self) -> LintLevelMap {
504         LintLevelMap { sets: self.sets, id_to_set: self.id_to_set }
505     }
506 }
507
508 pub struct LintLevelMap {
509     sets: LintLevelSets,
510     id_to_set: FxHashMap<HirId, u32>,
511 }
512
513 impl LintLevelMap {
514     /// If the `id` was previously registered with `register_id` when building
515     /// this `LintLevelMap` this returns the corresponding lint level and source
516     /// of the lint level for the lint provided.
517     ///
518     /// If the `id` was not previously registered, returns `None`. If `None` is
519     /// returned then the parent of `id` should be acquired and this function
520     /// should be called again.
521     pub fn level_and_source(
522         &self,
523         lint: &'static Lint,
524         id: HirId,
525         session: &Session,
526     ) -> Option<LevelSource> {
527         self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session))
528     }
529 }
530
531 impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap {
532     #[inline]
533     fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) {
534         let LintLevelMap { ref sets, ref id_to_set } = *self;
535
536         id_to_set.hash_stable(hcx, hasher);
537
538         let LintLevelSets { ref list, lint_cap } = *sets;
539
540         lint_cap.hash_stable(hcx, hasher);
541
542         hcx.while_hashing_spans(true, |hcx| {
543             list.len().hash_stable(hcx, hasher);
544
545             // We are working under the assumption here that the list of
546             // lint-sets is built in a deterministic order.
547             for lint_set in list {
548                 ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher);
549
550                 match *lint_set {
551                     LintSet::CommandLine { ref specs } => {
552                         specs.hash_stable(hcx, hasher);
553                     }
554                     LintSet::Node { ref specs, parent } => {
555                         specs.hash_stable(hcx, hasher);
556                         parent.hash_stable(hcx, hasher);
557                     }
558                 }
559             }
560         })
561     }
562 }
563
564 pub fn struct_lint_level<'a>(
565     sess: &'a Session,
566     lint: &'static Lint,
567     level: Level,
568     src: LintSource,
569     span: Option<MultiSpan>,
570     msg: &str,
571 ) -> DiagnosticBuilder<'a> {
572     let mut err = match (level, span) {
573         (Level::Allow, _) => return sess.diagnostic().struct_dummy(),
574         (Level::Warn, Some(span)) => sess.struct_span_warn(span, msg),
575         (Level::Warn, None) => sess.struct_warn(msg),
576         (Level::Deny, Some(span)) | (Level::Forbid, Some(span)) => sess.struct_span_err(span, msg),
577         (Level::Deny, None) | (Level::Forbid, None) => sess.struct_err(msg),
578     };
579
580     // Check for future incompatibility lints and issue a stronger warning.
581     let lint_id = LintId::of(lint);
582     let future_incompatible = lint.future_incompatible;
583
584     // If this code originates in a foreign macro, aka something that this crate
585     // did not itself author, then it's likely that there's nothing this crate
586     // can do about it. We probably want to skip the lint entirely.
587     if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) {
588         // Any suggestions made here are likely to be incorrect, so anything we
589         // emit shouldn't be automatically fixed by rustfix.
590         err.allow_suggestions(false);
591
592         // If this is a future incompatible lint it'll become a hard error, so
593         // we have to emit *something*. Also allow lints to whitelist themselves
594         // on a case-by-case basis for emission in a foreign macro.
595         if future_incompatible.is_none() && !lint.report_in_external_macro {
596             err.cancel();
597             // Don't continue further, since we don't want to have
598             // `diag_span_note_once` called for a diagnostic that isn't emitted.
599             return err;
600         }
601     }
602
603     let name = lint.name_lower();
604     match src {
605         LintSource::Default => {
606             sess.diag_note_once(
607                 &mut err,
608                 DiagnosticMessageId::from(lint),
609                 &format!("`#[{}({})]` on by default", level.as_str(), name),
610             );
611         }
612         LintSource::CommandLine(lint_flag_val) => {
613             let flag = match level {
614                 Level::Warn => "-W",
615                 Level::Deny => "-D",
616                 Level::Forbid => "-F",
617                 Level::Allow => panic!(),
618             };
619             let hyphen_case_lint_name = name.replace("_", "-");
620             if lint_flag_val.as_str() == name {
621                 sess.diag_note_once(
622                     &mut err,
623                     DiagnosticMessageId::from(lint),
624                     &format!(
625                         "requested on the command line with `{} {}`",
626                         flag, hyphen_case_lint_name
627                     ),
628                 );
629             } else {
630                 let hyphen_case_flag_val = lint_flag_val.as_str().replace("_", "-");
631                 sess.diag_note_once(
632                     &mut err,
633                     DiagnosticMessageId::from(lint),
634                     &format!(
635                         "`{} {}` implied by `{} {}`",
636                         flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
637                     ),
638                 );
639             }
640         }
641         LintSource::Node(lint_attr_name, src, reason) => {
642             if let Some(rationale) = reason {
643                 err.note(&rationale.as_str());
644             }
645             sess.diag_span_note_once(
646                 &mut err,
647                 DiagnosticMessageId::from(lint),
648                 src,
649                 "lint level defined here",
650             );
651             if lint_attr_name.as_str() != name {
652                 let level_str = level.as_str();
653                 sess.diag_note_once(
654                     &mut err,
655                     DiagnosticMessageId::from(lint),
656                     &format!(
657                         "`#[{}({})]` implied by `#[{}({})]`",
658                         level_str, name, level_str, lint_attr_name
659                     ),
660                 );
661             }
662         }
663     }
664
665     err.code(DiagnosticId::Lint(name));
666
667     if let Some(future_incompatible) = future_incompatible {
668         const STANDARD_MESSAGE: &str = "this was previously accepted by the compiler but is being phased out; \
669              it will become a hard error";
670
671         let explanation = if lint_id == LintId::of(builtin::UNSTABLE_NAME_COLLISIONS) {
672             "once this method is added to the standard library, \
673              the ambiguity may cause an error or change in behavior!"
674                 .to_owned()
675         } else if lint_id == LintId::of(builtin::MUTABLE_BORROW_RESERVATION_CONFLICT) {
676             "this borrowing pattern was not meant to be accepted, \
677              and may become a hard error in the future"
678                 .to_owned()
679         } else if let Some(edition) = future_incompatible.edition {
680             format!("{} in the {} edition!", STANDARD_MESSAGE, edition)
681         } else {
682             format!("{} in a future release!", STANDARD_MESSAGE)
683         };
684         let citation = format!("for more information, see {}", future_incompatible.reference);
685         err.warn(&explanation);
686         err.note(&citation);
687     }
688
689     return err;
690 }
691
692 /// Returns whether `span` originates in a foreign crate's external macro.
693 ///
694 /// This is used to test whether a lint should not even begin to figure out whether it should
695 /// be reported on the current node.
696 pub fn in_external_macro(sess: &Session, span: Span) -> bool {
697     let expn_data = span.ctxt().outer_expn_data();
698     match expn_data.kind {
699         ExpnKind::Root | ExpnKind::Desugaring(DesugaringKind::ForLoop) => false,
700         ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external"
701         ExpnKind::Macro(MacroKind::Bang, _) => {
702             if expn_data.def_site.is_dummy() {
703                 // Dummy span for the `def_site` means it's an external macro.
704                 return true;
705             }
706             match sess.source_map().span_to_snippet(expn_data.def_site) {
707                 Ok(code) => !code.starts_with("macro_rules"),
708                 // No snippet means external macro or compiler-builtin expansion.
709                 Err(_) => true,
710             }
711         }
712         ExpnKind::Macro(..) => true, // definitely a plugin
713     }
714 }