rustc_middle/
lint.rs

1use std::cmp;
2
3use rustc_data_structures::fx::FxIndexMap;
4use rustc_data_structures::sorted_map::SortedMap;
5use rustc_errors::{Diag, MultiSpan};
6use rustc_hir::{HirId, ItemLocalId};
7use rustc_macros::{Decodable, Encodable, HashStable};
8use rustc_session::Session;
9use rustc_session::lint::builtin::{self, FORBIDDEN_LINT_GROUPS};
10use rustc_session::lint::{FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId};
11use rustc_span::{DUMMY_SP, Span, Symbol, kw};
12use tracing::instrument;
13
14use crate::ty::TyCtxt;
15
16/// How a lint level was set.
17#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, HashStable, Debug)]
18pub enum LintLevelSource {
19    /// Lint is at the default level as declared in rustc.
20    Default,
21
22    /// Lint level was set by an attribute.
23    Node {
24        name: Symbol,
25        span: Span,
26        /// RFC 2383 reason
27        reason: Option<Symbol>,
28    },
29
30    /// Lint level was set by a command-line flag.
31    /// The provided `Level` is the level specified on the command line.
32    /// (The actual level may be lower due to `--cap-lints`.)
33    CommandLine(Symbol, Level),
34}
35
36impl LintLevelSource {
37    pub fn name(&self) -> Symbol {
38        match *self {
39            LintLevelSource::Default => 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.
55pub type LevelAndSource = (Level, LintLevelSource);
56
57/// Return type for the `shallow_lint_levels_on` query.
58///
59/// This map represents the set of allowed lints and allowance levels given
60/// by the attributes for *a single HirId*.
61#[derive(Default, Debug, HashStable)]
62pub struct ShallowLintLevelMap {
63    pub expectations: Vec<(LintExpectationId, LintExpectation)>,
64    pub specs: SortedMap<ItemLocalId, FxIndexMap<LintId, LevelAndSource>>,
65}
66
67/// From an initial level and source, verify the effect of special annotations:
68/// `warnings` lint level and lint caps.
69///
70/// The return of this function is suitable for diagnostics.
71pub fn reveal_actual_level(
72    level: Option<Level>,
73    src: &mut LintLevelSource,
74    sess: &Session,
75    lint: LintId,
76    probe_for_lint_level: impl FnOnce(LintId) -> (Option<Level>, LintLevelSource),
77) -> Level {
78    // If `level` is none then we actually assume the default level for this lint.
79    let mut level = level.unwrap_or_else(|| lint.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    //
85    // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically
86    // triggers in cases (like #80988) where you have `forbid(warnings)`,
87    // and so if we turned that into an error, it'd defeat the purpose of the
88    // future compatibility warning.
89    if level == Level::Warn && lint != LintId::of(FORBIDDEN_LINT_GROUPS) {
90        let (warnings_level, warnings_src) = probe_for_lint_level(LintId::of(builtin::WARNINGS));
91        if let Some(configured_warning_level) = warnings_level {
92            if configured_warning_level != Level::Warn {
93                level = configured_warning_level;
94                *src = warnings_src;
95            }
96        }
97    }
98
99    // Ensure that we never exceed the `--cap-lints` argument unless the source is a --force-warn
100    level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src {
101        level
102    } else {
103        cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid))
104    };
105
106    if let Some(driver_level) = sess.driver_lint_caps.get(&lint) {
107        // Ensure that we never exceed driver level.
108        level = cmp::min(*driver_level, level);
109    }
110
111    level
112}
113
114impl ShallowLintLevelMap {
115    /// Perform a deep probe in the HIR tree looking for the actual level for the lint.
116    /// This lint level is not usable for diagnostics, it needs to be corrected by
117    /// `reveal_actual_level` beforehand.
118    #[instrument(level = "trace", skip(self, tcx), ret)]
119    fn probe_for_lint_level(
120        &self,
121        tcx: TyCtxt<'_>,
122        id: LintId,
123        start: HirId,
124    ) -> (Option<Level>, LintLevelSource) {
125        if let Some(map) = self.specs.get(&start.local_id)
126            && let Some(&(level, src)) = map.get(&id)
127        {
128            return (Some(level), src);
129        }
130
131        let mut owner = start.owner;
132        let mut specs = &self.specs;
133
134        for parent in tcx.hir_parent_id_iter(start) {
135            if parent.owner != owner {
136                owner = parent.owner;
137                specs = &tcx.shallow_lint_levels_on(owner).specs;
138            }
139            if let Some(map) = specs.get(&parent.local_id)
140                && let Some(&(level, src)) = map.get(&id)
141            {
142                return (Some(level), src);
143            }
144        }
145
146        (None, LintLevelSource::Default)
147    }
148
149    /// Fetch and return the user-visible lint level for the given lint at the given HirId.
150    #[instrument(level = "trace", skip(self, tcx), ret)]
151    pub fn lint_level_id_at_node(
152        &self,
153        tcx: TyCtxt<'_>,
154        lint: LintId,
155        cur: HirId,
156    ) -> (Level, LintLevelSource) {
157        let (level, mut src) = self.probe_for_lint_level(tcx, lint, cur);
158        let level = reveal_actual_level(level, &mut src, tcx.sess, lint, |lint| {
159            self.probe_for_lint_level(tcx, lint, cur)
160        });
161        (level, src)
162    }
163}
164
165impl TyCtxt<'_> {
166    /// Fetch and return the user-visible lint level for the given lint at the given HirId.
167    pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> (Level, LintLevelSource) {
168        self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id)
169    }
170}
171
172/// This struct represents a lint expectation and holds all required information
173/// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after
174/// the `LateLintPass` has completed.
175#[derive(Clone, Debug, Encodable, Decodable, HashStable)]
176pub struct LintExpectation {
177    /// The reason for this expectation that can optionally be added as part of
178    /// the attribute. It will be displayed as part of the lint message.
179    pub reason: Option<Symbol>,
180    /// The [`Span`] of the attribute that this expectation originated from.
181    pub emission_span: Span,
182    /// Lint messages for the `unfulfilled_lint_expectations` lint will be
183    /// adjusted to include an additional note. Therefore, we have to track if
184    /// the expectation is for the lint.
185    pub is_unfulfilled_lint_expectations: bool,
186    /// This will hold the name of the tool that this lint belongs to. For
187    /// the lint `clippy::some_lint` the tool would be `clippy`, the same
188    /// goes for `rustdoc`. This will be `None` for rustc lints
189    pub lint_tool: Option<Symbol>,
190}
191
192impl LintExpectation {
193    pub fn new(
194        reason: Option<Symbol>,
195        emission_span: Span,
196        is_unfulfilled_lint_expectations: bool,
197        lint_tool: Option<Symbol>,
198    ) -> Self {
199        Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool }
200    }
201}
202
203fn explain_lint_level_source(
204    lint: &'static Lint,
205    level: Level,
206    src: LintLevelSource,
207    err: &mut Diag<'_, ()>,
208) {
209    let name = lint.name_lower();
210    if let Level::Allow = level {
211        // Do not point at `#[allow(compat_lint)]` as the reason for a compatibility lint
212        // triggering. (#121009)
213        return;
214    }
215    match src {
216        LintLevelSource::Default => {
217            err.note_once(format!("`#[{}({})]` on by default", level.as_str(), name));
218        }
219        LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
220            let flag = orig_level.to_cmd_flag();
221            let hyphen_case_lint_name = name.replace('_', "-");
222            if lint_flag_val.as_str() == name {
223                err.note_once(format!(
224                    "requested on the command line with `{flag} {hyphen_case_lint_name}`"
225                ));
226            } else {
227                let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
228                err.note_once(format!(
229                    "`{flag} {hyphen_case_lint_name}` implied by `{flag} {hyphen_case_flag_val}`"
230                ));
231                if matches!(orig_level, Level::Warn | Level::Deny) {
232                    err.help_once(format!(
233                        "to override `{flag} {hyphen_case_flag_val}` add `#[allow({name})]`"
234                    ));
235                }
236            }
237        }
238        LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
239            if let Some(rationale) = reason {
240                err.note(rationale.to_string());
241            }
242            err.span_note_once(span, "the lint level is defined here");
243            if lint_attr_name.as_str() != name {
244                let level_str = level.as_str();
245                err.note_once(format!(
246                    "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`"
247                ));
248            }
249        }
250    }
251}
252
253/// The innermost function for emitting lints.
254///
255/// If you are looking to implement a lint, look for higher level functions,
256/// for example:
257/// - [`TyCtxt::emit_node_span_lint`]
258/// - [`TyCtxt::node_span_lint`]
259/// - [`TyCtxt::emit_node_lint`]
260/// - [`TyCtxt::node_lint`]
261/// - `LintContext::opt_span_lint`
262///
263/// ## `decorate`
264///
265/// It is not intended to call `emit`/`cancel` on the `Diag` passed in the `decorate` callback.
266#[track_caller]
267pub fn lint_level(
268    sess: &Session,
269    lint: &'static Lint,
270    level: Level,
271    src: LintLevelSource,
272    span: Option<MultiSpan>,
273    decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
274) {
275    // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
276    // the "real" work.
277    #[track_caller]
278    fn lint_level_impl(
279        sess: &Session,
280        lint: &'static Lint,
281        level: Level,
282        src: LintLevelSource,
283        span: Option<MultiSpan>,
284        decorate: Box<dyn '_ + for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)>,
285    ) {
286        // Check for future incompatibility lints and issue a stronger warning.
287        let future_incompatible = lint.future_incompatible;
288
289        let has_future_breakage = future_incompatible.map_or(
290            // Default allow lints trigger too often for testing.
291            sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
292            |incompat| incompat.reason.has_future_breakage(),
293        );
294
295        // Convert lint level to error level.
296        let err_level = match level {
297            Level::Allow => {
298                if has_future_breakage {
299                    rustc_errors::Level::Allow
300                } else {
301                    return;
302                }
303            }
304            Level::Expect(expect_id) => {
305                // This case is special as we actually allow the lint itself in this context, but
306                // we can't return early like in the case for `Level::Allow` because we still
307                // need the lint diagnostic to be emitted to `rustc_error::DiagCtxtInner`.
308                //
309                // We can also not mark the lint expectation as fulfilled here right away, as it
310                // can still be cancelled in the decorate function. All of this means that we simply
311                // create a `Diag` and continue as we would for warnings.
312                rustc_errors::Level::Expect(expect_id)
313            }
314            Level::ForceWarn(Some(expect_id)) => rustc_errors::Level::ForceWarning(Some(expect_id)),
315            Level::ForceWarn(None) => rustc_errors::Level::ForceWarning(None),
316            Level::Warn => rustc_errors::Level::Warning,
317            Level::Deny | Level::Forbid => rustc_errors::Level::Error,
318        };
319        let mut err = Diag::new(sess.dcx(), err_level, "");
320        if let Some(span) = span {
321            err.span(span);
322        }
323
324        // If this code originates in a foreign macro, aka something that this crate
325        // did not itself author, then it's likely that there's nothing this crate
326        // can do about it. We probably want to skip the lint entirely.
327        if err.span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map())) {
328            // Any suggestions made here are likely to be incorrect, so anything we
329            // emit shouldn't be automatically fixed by rustfix.
330            err.disable_suggestions();
331
332            // If this is a future incompatible that is not an edition fixing lint
333            // it'll become a hard error, so we have to emit *something*. Also,
334            // if this lint occurs in the expansion of a macro from an external crate,
335            // allow individual lints to opt-out from being reported.
336            let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
337
338            if !incompatible && !lint.report_in_external_macro {
339                err.cancel();
340
341                // Don't continue further, since we don't want to have
342                // `diag_span_note_once` called for a diagnostic that isn't emitted.
343                return;
344            }
345        }
346
347        err.is_lint(lint.name_lower(), has_future_breakage);
348
349        // Lint diagnostics that are covered by the expect level will not be emitted outside
350        // the compiler. It is therefore not necessary to add any information for the user.
351        // This will therefore directly call the decorate function which will in turn emit
352        // the diagnostic.
353        if let Level::Expect(_) = level {
354            decorate(&mut err);
355            err.emit();
356            return;
357        }
358
359        if let Some(future_incompatible) = future_incompatible {
360            let explanation = match future_incompatible.reason {
361                FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps
362                | FutureIncompatibilityReason::FutureReleaseErrorReportInDeps => {
363                    "this was previously accepted by the compiler but is being phased out; \
364                         it will become a hard error in a future release!"
365                        .to_owned()
366                }
367                FutureIncompatibilityReason::FutureReleaseSemanticsChange => {
368                    "this will change its meaning in a future release!".to_owned()
369                }
370                FutureIncompatibilityReason::EditionError(edition) => {
371                    let current_edition = sess.edition();
372                    format!(
373                        "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
374                    )
375                }
376                FutureIncompatibilityReason::EditionSemanticsChange(edition) => {
377                    format!("this changes meaning in Rust {edition}")
378                }
379                FutureIncompatibilityReason::EditionAndFutureReleaseError(edition) => {
380                    format!(
381                        "this was previously accepted by the compiler but is being phased out; \
382                         it will become a hard error in Rust {edition} and in a future release in all editions!"
383                    )
384                }
385                FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(edition) => {
386                    format!(
387                        "this changes meaning in Rust {edition} and in a future release in all editions!"
388                    )
389                }
390                FutureIncompatibilityReason::Custom(reason) => reason.to_owned(),
391            };
392
393            if future_incompatible.explain_reason {
394                err.warn(explanation);
395            }
396            if !future_incompatible.reference.is_empty() {
397                let citation =
398                    format!("for more information, see {}", future_incompatible.reference);
399                err.note(citation);
400            }
401        }
402
403        // Finally, run `decorate`. `decorate` can call `trimmed_path_str` (directly or indirectly),
404        // so we need to make sure when we do call `decorate` that the diagnostic is eventually
405        // emitted or we'll get a `must_produce_diag` ICE.
406        //
407        // When is a diagnostic *eventually* emitted? Well, that is determined by 2 factors:
408        // 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning(_)`
409        //    or `Error`, then the diagnostic will be emitted regardless of CLI options.
410        // 2. If the corresponding `rustc_errors::Level` is warning, then that can be affected by
411        //    `-A warnings` or `--cap-lints=xxx` on the command line. In which case, the diagnostic
412        //    will be emitted if `can_emit_warnings` is true.
413        let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
414
415        if !skip {
416            decorate(&mut err);
417        }
418
419        explain_lint_level_source(lint, level, src, &mut err);
420        err.emit()
421    }
422    lint_level_impl(sess, lint, level, src, span, Box::new(decorate))
423}