3 use rustc_data_structures
::fx
::FxHashMap
;
4 use rustc_data_structures
::sorted_map
::SortedMap
;
5 use rustc_errors
::{Diagnostic, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, MultiSpan}
;
6 use rustc_hir
::{HirId, ItemLocalId}
;
7 use rustc_session
::lint
::{
8 builtin
::{self, FORBIDDEN_LINT_GROUPS}
,
9 FutureIncompatibilityReason
, Level
, Lint
, LintId
,
11 use rustc_session
::Session
;
12 use rustc_span
::hygiene
::MacroKind
;
13 use rustc_span
::source_map
::{DesugaringKind, ExpnKind}
;
14 use rustc_span
::{symbol, Span, Symbol, DUMMY_SP}
;
16 use crate::ty
::TyCtxt
;
18 /// How a lint level was set.
19 #[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)]
20 pub enum LintLevelSource
{
21 /// Lint is at the default level as declared
22 /// in rustc or a plugin.
25 /// Lint level was set by an attribute.
30 reason
: Option
<Symbol
>,
33 /// Lint level was set by a command-line flag.
34 /// The provided `Level` is the level specified on the command line.
35 /// (The actual level may be lower due to `--cap-lints`.)
36 CommandLine(Symbol
, Level
),
39 impl LintLevelSource
{
40 pub fn name(&self) -> Symbol
{
42 LintLevelSource
::Default
=> symbol
::kw
::Default
,
43 LintLevelSource
::Node { name, .. }
=> name
,
44 LintLevelSource
::CommandLine(name
, _
) => name
,
48 pub fn span(&self) -> Span
{
50 LintLevelSource
::Default
=> DUMMY_SP
,
51 LintLevelSource
::Node { span, .. }
=> span
,
52 LintLevelSource
::CommandLine(_
, _
) => DUMMY_SP
,
57 /// A tuple of a lint level and its source.
58 pub type LevelAndSource
= (Level
, LintLevelSource
);
60 /// Return type for the `shallow_lint_levels_on` query.
62 /// This map represents the set of allowed lints and allowance levels given
63 /// by the attributes for *a single HirId*.
64 #[derive(Default, Debug, HashStable)]
65 pub struct ShallowLintLevelMap
{
66 pub specs
: SortedMap
<ItemLocalId
, FxHashMap
<LintId
, LevelAndSource
>>,
69 /// From an initial level and source, verify the effect of special annotations:
70 /// `warnings` lint level and lint caps.
72 /// The return of this function is suitable for diagnostics.
73 pub fn reveal_actual_level(
75 src
: &mut LintLevelSource
,
78 probe_for_lint_level
: impl FnOnce(LintId
) -> (Option
<Level
>, LintLevelSource
),
80 // If `level` is none then we actually assume the default level for this lint.
81 let mut level
= level
.unwrap_or_else(|| lint
.lint
.default_level(sess
.edition()));
83 // If we're about to issue a warning, check at the last minute for any
84 // directives against the warnings "lint". If, for example, there's an
85 // `allow(warnings)` in scope then we want to respect that instead.
87 // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
88 // triggers in cases (like #80988) where you have `forbid(warnings)`,
89 // and so if we turned that into an error, it'd defeat the purpose of the
90 // future compatibility warning.
91 if level
== Level
::Warn
&& lint
!= LintId
::of(FORBIDDEN_LINT_GROUPS
) {
92 let (warnings_level
, warnings_src
) = probe_for_lint_level(LintId
::of(builtin
::WARNINGS
));
93 if let Some(configured_warning_level
) = warnings_level
{
94 if configured_warning_level
!= Level
::Warn
{
95 level
= configured_warning_level
;
101 // Ensure that we never exceed the `--cap-lints` argument unless the source is a --force-warn
102 level
= if let LintLevelSource
::CommandLine(_
, Level
::ForceWarn(_
)) = src
{
105 cmp
::min(level
, sess
.opts
.lint_cap
.unwrap_or(Level
::Forbid
))
108 if let Some(driver_level
) = sess
.driver_lint_caps
.get(&lint
) {
109 // Ensure that we never exceed driver level.
110 level
= cmp
::min(*driver_level
, level
);
116 impl ShallowLintLevelMap
{
117 /// Perform a deep probe in the HIR tree looking for the actual level for the lint.
118 /// This lint level is not usable for diagnostics, it needs to be corrected by
119 /// `reveal_actual_level` beforehand.
120 #[instrument(level = "trace", skip(self, tcx), ret)]
121 fn probe_for_lint_level(
126 ) -> (Option
<Level
>, LintLevelSource
) {
127 if let Some(map
) = self.specs
.get(&start
.local_id
)
128 && let Some(&(level
, src
)) = map
.get(&id
)
130 return (Some(level
), src
);
133 let mut owner
= start
.owner
;
134 let mut specs
= &self.specs
;
136 for parent
in tcx
.hir().parent_id_iter(start
) {
137 if parent
.owner
!= owner
{
138 owner
= parent
.owner
;
139 specs
= &tcx
.shallow_lint_levels_on(owner
).specs
;
141 if let Some(map
) = specs
.get(&parent
.local_id
)
142 && let Some(&(level
, src
)) = map
.get(&id
)
144 return (Some(level
), src
);
148 (None
, LintLevelSource
::Default
)
151 /// Fetch and return the user-visible lint level for the given lint at the given HirId.
152 #[instrument(level = "trace", skip(self, tcx), ret)]
153 pub fn lint_level_id_at_node(
158 ) -> (Level
, LintLevelSource
) {
159 let (level
, mut src
) = self.probe_for_lint_level(tcx
, lint
, cur
);
160 let level
= reveal_actual_level(level
, &mut src
, tcx
.sess
, lint
, |lint
| {
161 self.probe_for_lint_level(tcx
, lint
, cur
)
168 /// Fetch and return the user-visible lint level for the given lint at the given HirId.
169 pub fn lint_level_at_node(self, lint
: &'
static Lint
, id
: HirId
) -> (Level
, LintLevelSource
) {
170 self.shallow_lint_levels_on(id
.owner
).lint_level_id_at_node(self, LintId
::of(lint
), id
)
173 /// Walks upwards from `id` to find a node which might change lint levels with attributes.
174 /// It stops at `bound` and just returns it if reached.
175 pub fn maybe_lint_level_root_bounded(self, mut id
: HirId
, bound
: HirId
) -> HirId
{
176 let hir
= self.hir();
182 if hir
.attrs(id
).iter().any(|attr
| Level
::from_attr(attr
).is_some()) {
185 let next
= hir
.parent_id(id
);
187 bug
!("lint traversal reached the root of the crate");
194 /// This struct represents a lint expectation and holds all required information
195 /// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after
196 /// the `LateLintPass` has completed.
197 #[derive(Clone, Debug, HashStable)]
198 pub struct LintExpectation
{
199 /// The reason for this expectation that can optionally be added as part of
200 /// the attribute. It will be displayed as part of the lint message.
201 pub reason
: Option
<Symbol
>,
202 /// The [`Span`] of the attribute that this expectation originated from.
203 pub emission_span
: Span
,
204 /// Lint messages for the `unfulfilled_lint_expectations` lint will be
205 /// adjusted to include an additional note. Therefore, we have to track if
206 /// the expectation is for the lint.
207 pub is_unfulfilled_lint_expectations
: bool
,
208 /// This will hold the name of the tool that this lint belongs to. For
209 /// the lint `clippy::some_lint` the tool would be `clippy`, the same
210 /// goes for `rustdoc`. This will be `None` for rustc lints
211 pub lint_tool
: Option
<Symbol
>,
214 impl LintExpectation
{
216 reason
: Option
<Symbol
>,
218 is_unfulfilled_lint_expectations
: bool
,
219 lint_tool
: Option
<Symbol
>,
221 Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool }
225 pub fn explain_lint_level_source(
228 src
: LintLevelSource
,
229 err
: &mut Diagnostic
,
231 let name
= lint
.name_lower();
233 LintLevelSource
::Default
=> {
234 err
.note_once(&format
!("`#[{}({})]` on by default", level
.as_str(), name
));
236 LintLevelSource
::CommandLine(lint_flag_val
, orig_level
) => {
237 let flag
= orig_level
.to_cmd_flag();
238 let hyphen_case_lint_name
= name
.replace('_'
, "-");
239 if lint_flag_val
.as_str() == name
{
240 err
.note_once(&format
!(
241 "requested on the command line with `{} {}`",
242 flag
, hyphen_case_lint_name
245 let hyphen_case_flag_val
= lint_flag_val
.as_str().replace('_'
, "-");
246 err
.note_once(&format
!(
247 "`{} {}` implied by `{} {}`",
248 flag
, hyphen_case_lint_name
, flag
, hyphen_case_flag_val
252 LintLevelSource
::Node { name: lint_attr_name, span, reason, .. }
=> {
253 if let Some(rationale
) = reason
{
254 err
.note(rationale
.as_str());
256 err
.span_note_once(span
, "the lint level is defined here");
257 if lint_attr_name
.as_str() != name
{
258 let level_str
= level
.as_str();
259 err
.note_once(&format
!(
260 "`#[{}({})]` implied by `#[{}({})]`",
261 level_str
, name
, level_str
, lint_attr_name
268 /// The innermost function for emitting lints.
270 /// If you are looking to implement a lint, look for higher level functions,
272 /// - [`TyCtxt::emit_spanned_lint`]
273 /// - [`TyCtxt::struct_span_lint_hir`]
274 /// - [`TyCtxt::emit_lint`]
275 /// - [`TyCtxt::struct_lint_node`]
276 /// - `LintContext::lookup`
278 /// ## `decorate` signature
280 /// The return value of `decorate` is ignored by this function. So what is the
281 /// point of returning `&'b mut DiagnosticBuilder<'a, ()>`?
283 /// There are 2 reasons for this signature.
285 /// First of all, it prevents accidental use of `.emit()` -- it's clear that the
286 /// builder will be later used and shouldn't be emitted right away (this is
287 /// especially important because the old API expected you to call `.emit()` in
290 /// Second of all, it makes the most common case of adding just a single label
291 /// /suggestion much nicer, since [`DiagnosticBuilder`] methods return
292 /// `&mut DiagnosticBuilder`, you can just chain methods, without needed
293 /// awkward `{ ...; }`:
294 /// ```ignore pseudo-code
295 /// struct_lint_level(
297 /// |lint| lint.span_label(sp, "lbl")
298 /// // ^^^^^^^^^^^^^^^^^^^^^ returns `&mut DiagnosticBuilder` by default
301 pub fn struct_lint_level(
305 src
: LintLevelSource
,
306 span
: Option
<MultiSpan
>,
307 msg
: impl Into
<DiagnosticMessage
>,
308 decorate
: impl for<'a
, 'b
> FnOnce(
309 &'b
mut DiagnosticBuilder
<'a
, ()>,
310 ) -> &'b
mut DiagnosticBuilder
<'a
, ()>,
312 // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
314 fn struct_lint_level_impl(
318 src
: LintLevelSource
,
319 span
: Option
<MultiSpan
>,
320 msg
: impl Into
<DiagnosticMessage
>,
323 + for<'a
, 'b
> FnOnce(
324 &'b
mut DiagnosticBuilder
<'a
, ()>,
325 ) -> &'b
mut DiagnosticBuilder
<'a
, ()>,
328 // Check for future incompatibility lints and issue a stronger warning.
329 let future_incompatible
= lint
.future_incompatible
;
331 let has_future_breakage
= future_incompatible
.map_or(
332 // Default allow lints trigger too often for testing.
333 sess
.opts
.unstable_opts
.future_incompat_test
&& lint
.default_level
!= Level
::Allow
,
335 matches
!(incompat
.reason
, FutureIncompatibilityReason
::FutureReleaseErrorReportNow
)
339 let mut err
= match (level
, span
) {
340 (Level
::Allow
, span
) => {
341 if has_future_breakage
{
342 if let Some(span
) = span
{
343 sess
.struct_span_allow(span
, "")
345 sess
.struct_allow("")
351 (Level
::Expect(expect_id
), _
) => {
352 // This case is special as we actually allow the lint itself in this context, but
353 // we can't return early like in the case for `Level::Allow` because we still
354 // need the lint diagnostic to be emitted to `rustc_error::HandlerInner`.
356 // We can also not mark the lint expectation as fulfilled here right away, as it
357 // can still be cancelled in the decorate function. All of this means that we simply
358 // create a `DiagnosticBuilder` and continue as we would for warnings.
359 sess
.struct_expect("", expect_id
)
361 (Level
::ForceWarn(Some(expect_id
)), Some(span
)) => {
362 sess
.struct_span_warn_with_expectation(span
, "", expect_id
)
364 (Level
::ForceWarn(Some(expect_id
)), None
) => {
365 sess
.struct_warn_with_expectation("", expect_id
)
367 (Level
::Warn
| Level
::ForceWarn(None
), Some(span
)) => sess
.struct_span_warn(span
, ""),
368 (Level
::Warn
| Level
::ForceWarn(None
), None
) => sess
.struct_warn(""),
369 (Level
::Deny
| Level
::Forbid
, Some(span
)) => {
370 let mut builder
= sess
.diagnostic().struct_err_lint("");
371 builder
.set_span(span
);
374 (Level
::Deny
| Level
::Forbid
, None
) => sess
.diagnostic().struct_err_lint(""),
379 // If this code originates in a foreign macro, aka something that this crate
380 // did not itself author, then it's likely that there's nothing this crate
381 // can do about it. We probably want to skip the lint entirely.
382 if err
.span
.primary_spans().iter().any(|s
| in_external_macro(sess
, *s
)) {
383 // Any suggestions made here are likely to be incorrect, so anything we
384 // emit shouldn't be automatically fixed by rustfix.
385 err
.disable_suggestions();
387 // If this is a future incompatible that is not an edition fixing lint
388 // it'll become a hard error, so we have to emit *something*. Also,
389 // if this lint occurs in the expansion of a macro from an external crate,
390 // allow individual lints to opt-out from being reported.
391 let not_future_incompatible
=
392 future_incompatible
.map(|f
| f
.reason
.edition().is_some()).unwrap_or(true);
393 if not_future_incompatible
&& !lint
.report_in_external_macro
{
395 // Don't continue further, since we don't want to have
396 // `diag_span_note_once` called for a diagnostic that isn't emitted.
401 // Delay evaluating and setting the primary message until after we've
402 // suppressed the lint due to macros.
403 err
.set_primary_message(msg
);
405 // Lint diagnostics that are covered by the expect level will not be emitted outside
406 // the compiler. It is therefore not necessary to add any information for the user.
407 // This will therefore directly call the decorate function which will in turn emit
409 if let Level
::Expect(_
) = level
{
410 let name
= lint
.name_lower();
411 err
.code(DiagnosticId
::Lint { name, has_future_breakage, is_force_warn: false }
);
418 let name
= lint
.name_lower();
419 let is_force_warn
= matches
!(level
, Level
::ForceWarn(_
));
420 err
.code(DiagnosticId
::Lint { name, has_future_breakage, is_force_warn }
);
422 if let Some(future_incompatible
) = future_incompatible
{
423 let explanation
= match future_incompatible
.reason
{
424 FutureIncompatibilityReason
::FutureReleaseError
425 | FutureIncompatibilityReason
::FutureReleaseErrorReportNow
=> {
426 "this was previously accepted by the compiler but is being phased out; \
427 it will become a hard error in a future release!"
430 FutureIncompatibilityReason
::FutureReleaseSemanticsChange
=> {
431 "this will change its meaning in a future release!".to_owned()
433 FutureIncompatibilityReason
::EditionError(edition
) => {
434 let current_edition
= sess
.edition();
436 "this is accepted in the current edition (Rust {}) but is a hard error in Rust {}!",
437 current_edition
, edition
440 FutureIncompatibilityReason
::EditionSemanticsChange(edition
) => {
441 format
!("this changes meaning in Rust {}", edition
)
443 FutureIncompatibilityReason
::Custom(reason
) => reason
.to_owned(),
446 if future_incompatible
.explain_reason
{
447 err
.warn(&explanation
);
449 if !future_incompatible
.reference
.is_empty() {
451 format
!("for more information, see {}", future_incompatible
.reference
);
456 // Finally, run `decorate`.
458 explain_lint_level_source(lint
, level
, src
, &mut *err
);
461 struct_lint_level_impl(sess
, lint
, level
, src
, span
, msg
, Box
::new(decorate
))
464 /// Returns whether `span` originates in a foreign crate's external macro.
466 /// This is used to test whether a lint should not even begin to figure out whether it should
467 /// be reported on the current node.
468 pub fn in_external_macro(sess
: &Session
, span
: Span
) -> bool
{
469 let expn_data
= span
.ctxt().outer_expn_data();
470 match expn_data
.kind
{
473 | ExpnKind
::Desugaring(
474 DesugaringKind
::ForLoop
| DesugaringKind
::WhileLoop
| DesugaringKind
::OpaqueTy
,
476 ExpnKind
::AstPass(_
) | ExpnKind
::Desugaring(_
) => true, // well, it's "external"
477 ExpnKind
::Macro(MacroKind
::Bang
, _
) => {
478 // Dummy span for the `def_site` means it's an external macro.
479 expn_data
.def_site
.is_dummy() || sess
.source_map().is_imported(expn_data
.def_site
)
481 ExpnKind
::Macro { .. }
=> true, // definitely a plugin