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