]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | use std::cmp; |
2 | ||
136023e0 | 3 | use rustc_data_structures::fx::FxHashMap; |
dfeec247 | 4 | use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; |
5e7ed085 | 5 | use rustc_errors::{ |
04454e1e | 6 | Diagnostic, DiagnosticBuilder, DiagnosticId, EmissionGuarantee, ErrorGuaranteed, MultiSpan, |
5e7ed085 | 7 | }; |
dfeec247 | 8 | use rustc_hir::HirId; |
136023e0 | 9 | use rustc_index::vec::IndexVec; |
c295e0f8 | 10 | use rustc_query_system::ich::StableHashingContext; |
fc512014 XL |
11 | use rustc_session::lint::{ |
12 | builtin::{self, FORBIDDEN_LINT_GROUPS}, | |
5e7ed085 | 13 | FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId, |
fc512014 | 14 | }; |
5e7ed085 | 15 | use rustc_session::Session; |
dfeec247 | 16 | use rustc_span::hygiene::MacroKind; |
04454e1e | 17 | use rustc_span::source_map::{DesugaringKind, ExpnKind}; |
fc512014 | 18 | use rustc_span::{symbol, Span, Symbol, DUMMY_SP}; |
dfeec247 XL |
19 | |
20 | /// How a lint level was set. | |
fc512014 XL |
21 | #[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)] |
22 | pub enum LintLevelSource { | |
dfeec247 XL |
23 | /// Lint is at the default level as declared |
24 | /// in rustc or a plugin. | |
25 | Default, | |
26 | ||
27 | /// Lint level was set by an attribute. | |
28 | Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */), | |
29 | ||
30 | /// Lint level was set by a command-line flag. | |
fc512014 XL |
31 | /// The provided `Level` is the level specified on the command line. |
32 | /// (The actual level may be lower due to `--cap-lints`.) | |
29967ef6 | 33 | CommandLine(Symbol, Level), |
dfeec247 XL |
34 | } |
35 | ||
fc512014 XL |
36 | impl LintLevelSource { |
37 | pub fn name(&self) -> Symbol { | |
38 | match *self { | |
39 | LintLevelSource::Default => symbol::kw::Default, | |
40 | LintLevelSource::Node(name, _, _) => name, | |
41 | LintLevelSource::CommandLine(name, _) => name, | |
42 | } | |
43 | } | |
44 | ||
45 | pub fn span(&self) -> Span { | |
46 | match *self { | |
47 | LintLevelSource::Default => DUMMY_SP, | |
48 | LintLevelSource::Node(_, span, _) => span, | |
49 | LintLevelSource::CommandLine(_, _) => DUMMY_SP, | |
50 | } | |
51 | } | |
52 | } | |
53 | ||
54 | /// A tuple of a lint level and its source. | |
5869c6ff | 55 | pub type LevelAndSource = (Level, LintLevelSource); |
dfeec247 | 56 | |
136023e0 | 57 | #[derive(Debug, HashStable)] |
dfeec247 | 58 | pub struct LintLevelSets { |
136023e0 | 59 | pub list: IndexVec<LintStackIndex, LintSet>, |
dfeec247 XL |
60 | pub lint_cap: Level, |
61 | } | |
62 | ||
136023e0 XL |
63 | rustc_index::newtype_index! { |
64 | #[derive(HashStable)] | |
65 | pub struct LintStackIndex { | |
66 | const COMMAND_LINE = 0, | |
67 | } | |
68 | } | |
69 | ||
70 | #[derive(Debug, HashStable)] | |
71 | pub struct LintSet { | |
72 | // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which | |
73 | // flag. | |
74 | pub specs: FxHashMap<LintId, LevelAndSource>, | |
75 | ||
76 | pub parent: LintStackIndex, | |
dfeec247 XL |
77 | } |
78 | ||
79 | impl LintLevelSets { | |
80 | pub fn new() -> Self { | |
136023e0 | 81 | LintLevelSets { list: IndexVec::new(), lint_cap: Level::Forbid } |
dfeec247 XL |
82 | } |
83 | ||
84 | pub fn get_lint_level( | |
85 | &self, | |
86 | lint: &'static Lint, | |
136023e0 | 87 | idx: LintStackIndex, |
5869c6ff | 88 | aux: Option<&FxHashMap<LintId, LevelAndSource>>, |
dfeec247 | 89 | sess: &Session, |
5869c6ff | 90 | ) -> LevelAndSource { |
dfeec247 XL |
91 | let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux); |
92 | ||
93 | // If `level` is none then we actually assume the default level for this | |
94 | // lint. | |
95 | let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition())); | |
96 | ||
97 | // If we're about to issue a warning, check at the last minute for any | |
98 | // directives against the warnings "lint". If, for example, there's an | |
99 | // `allow(warnings)` in scope then we want to respect that instead. | |
fc512014 XL |
100 | // |
101 | // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically | |
102 | // triggers in cases (like #80988) where you have `forbid(warnings)`, | |
103 | // and so if we turned that into an error, it'd defeat the purpose of the | |
104 | // future compatibility warning. | |
105 | if level == Level::Warn && LintId::of(lint) != LintId::of(FORBIDDEN_LINT_GROUPS) { | |
dfeec247 XL |
106 | let (warnings_level, warnings_src) = |
107 | self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux); | |
108 | if let Some(configured_warning_level) = warnings_level { | |
109 | if configured_warning_level != Level::Warn { | |
110 | level = configured_warning_level; | |
111 | src = warnings_src; | |
112 | } | |
113 | } | |
114 | } | |
115 | ||
136023e0 XL |
116 | // Ensure that we never exceed the `--cap-lints` argument |
117 | // unless the source is a --force-warn | |
923072b8 | 118 | level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src { |
136023e0 XL |
119 | level |
120 | } else { | |
121 | cmp::min(level, self.lint_cap) | |
122 | }; | |
dfeec247 XL |
123 | |
124 | if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) { | |
125 | // Ensure that we never exceed driver level. | |
126 | level = cmp::min(*driver_level, level); | |
127 | } | |
128 | ||
ba9703b0 | 129 | (level, src) |
dfeec247 XL |
130 | } |
131 | ||
132 | pub fn get_lint_id_level( | |
133 | &self, | |
134 | id: LintId, | |
136023e0 | 135 | mut idx: LintStackIndex, |
5869c6ff | 136 | aux: Option<&FxHashMap<LintId, LevelAndSource>>, |
fc512014 | 137 | ) -> (Option<Level>, LintLevelSource) { |
dfeec247 XL |
138 | if let Some(specs) = aux { |
139 | if let Some(&(level, src)) = specs.get(&id) { | |
140 | return (Some(level), src); | |
141 | } | |
142 | } | |
143 | loop { | |
136023e0 XL |
144 | let LintSet { ref specs, parent } = self.list[idx]; |
145 | if let Some(&(level, src)) = specs.get(&id) { | |
146 | return (Some(level), src); | |
147 | } | |
148 | if idx == COMMAND_LINE { | |
149 | return (None, LintLevelSource::Default); | |
dfeec247 | 150 | } |
136023e0 | 151 | idx = parent; |
dfeec247 XL |
152 | } |
153 | } | |
154 | } | |
155 | ||
5869c6ff | 156 | #[derive(Debug)] |
dfeec247 | 157 | pub struct LintLevelMap { |
5e7ed085 FG |
158 | /// This is a collection of lint expectations as described in RFC 2383, that |
159 | /// can be fulfilled during this compilation session. This means that at least | |
160 | /// one expected lint is currently registered in the lint store. | |
161 | /// | |
162 | /// The [`LintExpectationId`] is stored as a part of the [`Expect`](Level::Expect) | |
163 | /// lint level. | |
164 | pub lint_expectations: Vec<(LintExpectationId, LintExpectation)>, | |
dfeec247 | 165 | pub sets: LintLevelSets, |
136023e0 | 166 | pub id_to_set: FxHashMap<HirId, LintStackIndex>, |
dfeec247 XL |
167 | } |
168 | ||
169 | impl LintLevelMap { | |
170 | /// If the `id` was previously registered with `register_id` when building | |
171 | /// this `LintLevelMap` this returns the corresponding lint level and source | |
172 | /// of the lint level for the lint provided. | |
173 | /// | |
174 | /// If the `id` was not previously registered, returns `None`. If `None` is | |
175 | /// returned then the parent of `id` should be acquired and this function | |
176 | /// should be called again. | |
177 | pub fn level_and_source( | |
178 | &self, | |
179 | lint: &'static Lint, | |
180 | id: HirId, | |
181 | session: &Session, | |
5869c6ff | 182 | ) -> Option<LevelAndSource> { |
dfeec247 XL |
183 | self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session)) |
184 | } | |
185 | } | |
186 | ||
187 | impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap { | |
188 | #[inline] | |
189 | fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { | |
5e7ed085 | 190 | let LintLevelMap { ref sets, ref id_to_set, ref lint_expectations } = *self; |
dfeec247 XL |
191 | |
192 | id_to_set.hash_stable(hcx, hasher); | |
5e7ed085 | 193 | lint_expectations.hash_stable(hcx, hasher); |
dfeec247 | 194 | |
136023e0 | 195 | hcx.while_hashing_spans(true, |hcx| sets.hash_stable(hcx, hasher)) |
dfeec247 XL |
196 | } |
197 | } | |
198 | ||
5e7ed085 FG |
199 | /// This struct represents a lint expectation and holds all required information |
200 | /// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after | |
201 | /// the `LateLintPass` has completed. | |
202 | #[derive(Clone, Debug, HashStable)] | |
203 | pub struct LintExpectation { | |
204 | /// The reason for this expectation that can optionally be added as part of | |
205 | /// the attribute. It will be displayed as part of the lint message. | |
206 | pub reason: Option<Symbol>, | |
207 | /// The [`Span`] of the attribute that this expectation originated from. | |
208 | pub emission_span: Span, | |
209 | /// Lint messages for the `unfulfilled_lint_expectations` lint will be | |
210 | /// adjusted to include an additional note. Therefore, we have to track if | |
211 | /// the expectation is for the lint. | |
212 | pub is_unfulfilled_lint_expectations: bool, | |
04454e1e FG |
213 | /// This will hold the name of the tool that this lint belongs to. For |
214 | /// the lint `clippy::some_lint` the tool would be `clippy`, the same | |
215 | /// goes for `rustdoc`. This will be `None` for rustc lints | |
216 | pub lint_tool: Option<Symbol>, | |
5e7ed085 | 217 | } |
74b04a01 | 218 | |
5e7ed085 FG |
219 | impl LintExpectation { |
220 | pub fn new( | |
221 | reason: Option<Symbol>, | |
222 | emission_span: Span, | |
223 | is_unfulfilled_lint_expectations: bool, | |
04454e1e | 224 | lint_tool: Option<Symbol>, |
5e7ed085 | 225 | ) -> Self { |
04454e1e | 226 | Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool } |
5e7ed085 FG |
227 | } |
228 | } | |
229 | ||
230 | pub struct LintDiagnosticBuilder<'a, G: EmissionGuarantee>(DiagnosticBuilder<'a, G>); | |
231 | ||
232 | impl<'a, G: EmissionGuarantee> LintDiagnosticBuilder<'a, G> { | |
233 | /// Return the inner `DiagnosticBuilder`, first setting the primary message to `msg`. | |
234 | pub fn build(mut self, msg: &str) -> DiagnosticBuilder<'a, G> { | |
74b04a01 | 235 | self.0.set_primary_message(msg); |
c295e0f8 | 236 | self.0.set_is_lint(); |
74b04a01 XL |
237 | self.0 |
238 | } | |
239 | ||
5e7ed085 FG |
240 | /// Create a `LintDiagnosticBuilder` from some existing `DiagnosticBuilder`. |
241 | pub fn new(err: DiagnosticBuilder<'a, G>) -> LintDiagnosticBuilder<'a, G> { | |
74b04a01 XL |
242 | LintDiagnosticBuilder(err) |
243 | } | |
244 | } | |
245 | ||
5e7ed085 FG |
246 | impl<'a> LintDiagnosticBuilder<'a, ErrorGuaranteed> { |
247 | pub fn forget_guarantee(self) -> LintDiagnosticBuilder<'a, ()> { | |
248 | LintDiagnosticBuilder(self.0.forget_guarantee()) | |
249 | } | |
250 | } | |
251 | ||
252 | pub fn explain_lint_level_source( | |
253 | lint: &'static Lint, | |
254 | level: Level, | |
255 | src: LintLevelSource, | |
256 | err: &mut Diagnostic, | |
257 | ) { | |
258 | let name = lint.name_lower(); | |
259 | match src { | |
260 | LintLevelSource::Default => { | |
261 | err.note_once(&format!("`#[{}({})]` on by default", level.as_str(), name)); | |
262 | } | |
263 | LintLevelSource::CommandLine(lint_flag_val, orig_level) => { | |
264 | let flag = match orig_level { | |
265 | Level::Warn => "-W", | |
266 | Level::Deny => "-D", | |
267 | Level::Forbid => "-F", | |
268 | Level::Allow => "-A", | |
923072b8 | 269 | Level::ForceWarn(_) => "--force-warn", |
5e7ed085 FG |
270 | Level::Expect(_) => { |
271 | unreachable!("the expect level does not have a commandline flag") | |
272 | } | |
273 | }; | |
274 | let hyphen_case_lint_name = name.replace('_', "-"); | |
275 | if lint_flag_val.as_str() == name { | |
276 | err.note_once(&format!( | |
277 | "requested on the command line with `{} {}`", | |
278 | flag, hyphen_case_lint_name | |
279 | )); | |
280 | } else { | |
281 | let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-"); | |
282 | err.note_once(&format!( | |
283 | "`{} {}` implied by `{} {}`", | |
284 | flag, hyphen_case_lint_name, flag, hyphen_case_flag_val | |
285 | )); | |
286 | } | |
287 | } | |
288 | LintLevelSource::Node(lint_attr_name, src, reason) => { | |
289 | if let Some(rationale) = reason { | |
290 | err.note(rationale.as_str()); | |
291 | } | |
292 | err.span_note_once(src, "the lint level is defined here"); | |
293 | if lint_attr_name.as_str() != name { | |
294 | let level_str = level.as_str(); | |
295 | err.note_once(&format!( | |
296 | "`#[{}({})]` implied by `#[{}({})]`", | |
297 | level_str, name, level_str, lint_attr_name | |
298 | )); | |
299 | } | |
300 | } | |
301 | } | |
302 | } | |
303 | ||
74b04a01 XL |
304 | pub fn struct_lint_level<'s, 'd>( |
305 | sess: &'s Session, | |
dfeec247 XL |
306 | lint: &'static Lint, |
307 | level: Level, | |
fc512014 | 308 | src: LintLevelSource, |
dfeec247 | 309 | span: Option<MultiSpan>, |
5e7ed085 | 310 | decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a, ()>) + 'd, |
74b04a01 XL |
311 | ) { |
312 | // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to | |
313 | // the "real" work. | |
a2a8927a | 314 | fn struct_lint_level_impl<'s, 'd>( |
74b04a01 XL |
315 | sess: &'s Session, |
316 | lint: &'static Lint, | |
317 | level: Level, | |
fc512014 | 318 | src: LintLevelSource, |
74b04a01 | 319 | span: Option<MultiSpan>, |
5e7ed085 | 320 | decorate: Box<dyn for<'b> FnOnce(LintDiagnosticBuilder<'b, ()>) + 'd>, |
74b04a01 | 321 | ) { |
29967ef6 | 322 | // Check for future incompatibility lints and issue a stronger warning. |
29967ef6 XL |
323 | let future_incompatible = lint.future_incompatible; |
324 | ||
136023e0 XL |
325 | let has_future_breakage = future_incompatible.map_or( |
326 | // Default allow lints trigger too often for testing. | |
327 | sess.opts.debugging_opts.future_incompat_test && lint.default_level != Level::Allow, | |
328 | |incompat| { | |
329 | matches!(incompat.reason, FutureIncompatibilityReason::FutureReleaseErrorReportNow) | |
330 | }, | |
331 | ); | |
29967ef6 | 332 | |
74b04a01 | 333 | let mut err = match (level, span) { |
29967ef6 XL |
334 | (Level::Allow, span) => { |
335 | if has_future_breakage { | |
336 | if let Some(span) = span { | |
337 | sess.struct_span_allow(span, "") | |
338 | } else { | |
339 | sess.struct_allow("") | |
340 | } | |
341 | } else { | |
342 | return; | |
343 | } | |
74b04a01 | 344 | } |
5e7ed085 FG |
345 | (Level::Expect(expect_id), _) => { |
346 | // This case is special as we actually allow the lint itself in this context, but | |
347 | // we can't return early like in the case for `Level::Allow` because we still | |
348 | // need the lint diagnostic to be emitted to `rustc_error::HandlerInner`. | |
349 | // | |
350 | // We can also not mark the lint expectation as fulfilled here right away, as it | |
351 | // can still be cancelled in the decorate function. All of this means that we simply | |
352 | // create a `DiagnosticBuilder` and continue as we would for warnings. | |
353 | sess.struct_expect("", expect_id) | |
354 | } | |
923072b8 FG |
355 | (Level::ForceWarn(Some(expect_id)), Some(span)) => { |
356 | sess.struct_span_warn_with_expectation(span, "", expect_id) | |
357 | } | |
358 | (Level::ForceWarn(Some(expect_id)), None) => { | |
359 | sess.struct_warn_with_expectation("", expect_id) | |
360 | } | |
361 | (Level::Warn | Level::ForceWarn(None), Some(span)) => sess.struct_span_warn(span, ""), | |
362 | (Level::Warn | Level::ForceWarn(None), None) => sess.struct_warn(""), | |
3c0e092e XL |
363 | (Level::Deny | Level::Forbid, Some(span)) => { |
364 | let mut builder = sess.diagnostic().struct_err_lint(""); | |
365 | builder.set_span(span); | |
366 | builder | |
367 | } | |
368 | (Level::Deny | Level::Forbid, None) => sess.diagnostic().struct_err_lint(""), | |
74b04a01 | 369 | }; |
dfeec247 | 370 | |
74b04a01 XL |
371 | // If this code originates in a foreign macro, aka something that this crate |
372 | // did not itself author, then it's likely that there's nothing this crate | |
373 | // can do about it. We probably want to skip the lint entirely. | |
374 | if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) { | |
375 | // Any suggestions made here are likely to be incorrect, so anything we | |
376 | // emit shouldn't be automatically fixed by rustfix. | |
5099ac24 | 377 | err.disable_suggestions(); |
74b04a01 | 378 | |
17df50a5 XL |
379 | // If this is a future incompatible that is not an edition fixing lint |
380 | // it'll become a hard error, so we have to emit *something*. Also, | |
381 | // if this lint occurs in the expansion of a macro from an external crate, | |
382 | // allow individual lints to opt-out from being reported. | |
383 | let not_future_incompatible = | |
136023e0 | 384 | future_incompatible.map(|f| f.reason.edition().is_some()).unwrap_or(true); |
17df50a5 | 385 | if not_future_incompatible && !lint.report_in_external_macro { |
74b04a01 XL |
386 | err.cancel(); |
387 | // Don't continue further, since we don't want to have | |
388 | // `diag_span_note_once` called for a diagnostic that isn't emitted. | |
389 | return; | |
390 | } | |
dfeec247 | 391 | } |
74b04a01 | 392 | |
5e7ed085 FG |
393 | // Lint diagnostics that are covered by the expect level will not be emitted outside |
394 | // the compiler. It is therefore not necessary to add any information for the user. | |
395 | // This will therefore directly call the decorate function which will in turn emit | |
396 | // the `Diagnostic`. | |
397 | if let Level::Expect(_) = level { | |
398 | let name = lint.name_lower(); | |
399 | err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn: false }); | |
400 | decorate(LintDiagnosticBuilder::new(err)); | |
401 | return; | |
dfeec247 | 402 | } |
dfeec247 | 403 | |
5e7ed085 FG |
404 | explain_lint_level_source(lint, level, src, &mut err); |
405 | ||
406 | let name = lint.name_lower(); | |
923072b8 | 407 | let is_force_warn = matches!(level, Level::ForceWarn(_)); |
136023e0 | 408 | err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn }); |
74b04a01 XL |
409 | |
410 | if let Some(future_incompatible) = future_incompatible { | |
5099ac24 FG |
411 | let explanation = match future_incompatible.reason { |
412 | FutureIncompatibilityReason::FutureReleaseError | |
413 | | FutureIncompatibilityReason::FutureReleaseErrorReportNow => { | |
414 | "this was previously accepted by the compiler but is being phased out; \ | |
415 | it will become a hard error in a future release!" | |
416 | .to_owned() | |
417 | } | |
418 | FutureIncompatibilityReason::FutureReleaseSemanticsChange => { | |
419 | "this will change its meaning in a future release!".to_owned() | |
420 | } | |
421 | FutureIncompatibilityReason::EditionError(edition) => { | |
422 | let current_edition = sess.edition(); | |
423 | format!( | |
424 | "this is accepted in the current edition (Rust {}) but is a hard error in Rust {}!", | |
425 | current_edition, edition | |
426 | ) | |
427 | } | |
428 | FutureIncompatibilityReason::EditionSemanticsChange(edition) => { | |
429 | format!("this changes meaning in Rust {}", edition) | |
430 | } | |
431 | FutureIncompatibilityReason::Custom(reason) => reason.to_owned(), | |
74b04a01 | 432 | }; |
5099ac24 | 433 | |
136023e0 XL |
434 | if future_incompatible.explain_reason { |
435 | err.warn(&explanation); | |
436 | } | |
437 | if !future_incompatible.reference.is_empty() { | |
438 | let citation = | |
439 | format!("for more information, see {}", future_incompatible.reference); | |
440 | err.note(&citation); | |
441 | } | |
74b04a01 | 442 | } |
dfeec247 | 443 | |
74b04a01 XL |
444 | // Finally, run `decorate`. This function is also responsible for emitting the diagnostic. |
445 | decorate(LintDiagnosticBuilder::new(err)); | |
446 | } | |
447 | struct_lint_level_impl(sess, lint, level, src, span, Box::new(decorate)) | |
dfeec247 XL |
448 | } |
449 | ||
450 | /// Returns whether `span` originates in a foreign crate's external macro. | |
451 | /// | |
452 | /// This is used to test whether a lint should not even begin to figure out whether it should | |
453 | /// be reported on the current node. | |
454 | pub fn in_external_macro(sess: &Session, span: Span) -> bool { | |
455 | let expn_data = span.ctxt().outer_expn_data(); | |
456 | match expn_data.kind { | |
c295e0f8 XL |
457 | ExpnKind::Inlined |
458 | | ExpnKind::Root | |
3c0e092e | 459 | | ExpnKind::Desugaring(DesugaringKind::ForLoop | DesugaringKind::WhileLoop) => false, |
dfeec247 | 460 | ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external" |
136023e0 | 461 | ExpnKind::Macro(MacroKind::Bang, _) => { |
ba9703b0 XL |
462 | // Dummy span for the `def_site` means it's an external macro. |
463 | expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site) | |
dfeec247 | 464 | } |
3dfed10e | 465 | ExpnKind::Macro { .. } => true, // definitely a plugin |
dfeec247 XL |
466 | } |
467 | } |