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#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, HashStable, Debug)]
18pub enum LintLevelSource {
19    Default,
21
22    Node {
24        name: Symbol,
25        span: Span,
26        reason: Option<Symbol>,
28    },
29
30    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#[derive(Copy, Clone, Debug, HashStable, Encodable, Decodable)]
56pub struct LevelAndSource {
57    pub level: Level,
58    pub lint_id: Option<LintExpectationId>,
59    pub src: LintLevelSource,
60}
61
62#[derive(Default, Debug, HashStable)]
67pub struct ShallowLintLevelMap {
68    pub expectations: Vec<(LintExpectationId, LintExpectation)>,
69    pub specs: SortedMap<ItemLocalId, FxIndexMap<LintId, LevelAndSource>>,
70}
71
72pub fn reveal_actual_level(
77    level: Option<(Level, Option<LintExpectationId>)>,
78    src: &mut LintLevelSource,
79    sess: &Session,
80    lint: LintId,
81    probe_for_lint_level: impl FnOnce(
82        LintId,
83    )
84        -> (Option<(Level, Option<LintExpectationId>)>, LintLevelSource),
85) -> (Level, Option<LintExpectationId>) {
86    let (mut level, mut lint_id) =
88        level.unwrap_or_else(|| (lint.lint.default_level(sess.edition()), None));
89
90    if level == Level::Warn && lint != LintId::of(FORBIDDEN_LINT_GROUPS) {
99        let (warnings_level, warnings_src) = probe_for_lint_level(LintId::of(builtin::WARNINGS));
100        if let Some((configured_warning_level, configured_lint_id)) = warnings_level {
101            if configured_warning_level != Level::Warn {
102                level = configured_warning_level;
103                lint_id = configured_lint_id;
104                *src = warnings_src;
105            }
106        }
107    }
108
109    level = if let LintLevelSource::CommandLine(_, Level::ForceWarn) = src {
111        level
112    } else {
113        cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid))
114    };
115
116    if let Some(driver_level) = sess.driver_lint_caps.get(&lint) {
117        level = cmp::min(*driver_level, level);
119    }
120
121    (level, lint_id)
122}
123
124impl ShallowLintLevelMap {
125    #[instrument(level = "trace", skip(self, tcx), ret)]
129    fn probe_for_lint_level(
130        &self,
131        tcx: TyCtxt<'_>,
132        id: LintId,
133        start: HirId,
134    ) -> (Option<(Level, Option<LintExpectationId>)>, LintLevelSource) {
135        if let Some(map) = self.specs.get(&start.local_id)
136            && let Some(&LevelAndSource { level, lint_id, src }) = map.get(&id)
137        {
138            return (Some((level, lint_id)), src);
139        }
140
141        let mut owner = start.owner;
142        let mut specs = &self.specs;
143
144        for parent in tcx.hir_parent_id_iter(start) {
145            if parent.owner != owner {
146                owner = parent.owner;
147                specs = &tcx.shallow_lint_levels_on(owner).specs;
148            }
149            if let Some(map) = specs.get(&parent.local_id)
150                && let Some(&LevelAndSource { level, lint_id, src }) = map.get(&id)
151            {
152                return (Some((level, lint_id)), src);
153            }
154        }
155
156        (None, LintLevelSource::Default)
157    }
158
159    #[instrument(level = "trace", skip(self, tcx), ret)]
161    pub fn lint_level_id_at_node(
162        &self,
163        tcx: TyCtxt<'_>,
164        lint: LintId,
165        cur: HirId,
166    ) -> LevelAndSource {
167        let (level, mut src) = self.probe_for_lint_level(tcx, lint, cur);
168        let (level, lint_id) = reveal_actual_level(level, &mut src, tcx.sess, lint, |lint| {
169            self.probe_for_lint_level(tcx, lint, cur)
170        });
171        LevelAndSource { level, lint_id, src }
172    }
173}
174
175impl TyCtxt<'_> {
176    pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> LevelAndSource {
178        self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id)
179    }
180}
181
182#[derive(Clone, Debug, Encodable, Decodable, HashStable)]
186pub struct LintExpectation {
187    pub reason: Option<Symbol>,
190    pub emission_span: Span,
192    pub is_unfulfilled_lint_expectations: bool,
196    pub lint_tool: Option<Symbol>,
200}
201
202impl LintExpectation {
203    pub fn new(
204        reason: Option<Symbol>,
205        emission_span: Span,
206        is_unfulfilled_lint_expectations: bool,
207        lint_tool: Option<Symbol>,
208    ) -> Self {
209        Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool }
210    }
211}
212
213fn explain_lint_level_source(
214    sess: &Session,
215    lint: &'static Lint,
216    level: Level,
217    src: LintLevelSource,
218    err: &mut Diag<'_, ()>,
219) {
220    let lint_group_name = |lint| {
223        let lint_groups_iter = sess.lint_groups_iter();
224        let lint_id = LintId::of(lint);
225        lint_groups_iter
226            .filter(|lint_group| !lint_group.is_externally_loaded)
227            .find(|lint_group| {
228                lint_group
229                    .lints
230                    .iter()
231                    .find(|lint_group_lint| **lint_group_lint == lint_id)
232                    .is_some()
233            })
234            .map(|lint_group| lint_group.name)
235    };
236    let name = lint.name_lower();
237    if let Level::Allow = level {
238        return;
241    }
242    match src {
243        LintLevelSource::Default => {
244            let level_str = level.as_str();
245            match lint_group_name(lint) {
246                Some(group_name) => {
247                    err.note_once(format!("`#[{level_str}({name})]` (part of `#[{level_str}({group_name})]`) on by default"));
248                }
249                None => {
250                    err.note_once(format!("`#[{level_str}({name})]` on by default"));
251                }
252            }
253        }
254        LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
255            let flag = orig_level.to_cmd_flag();
256            let hyphen_case_lint_name = name.replace('_', "-");
257            if lint_flag_val.as_str() == name {
258                err.note_once(format!(
259                    "requested on the command line with `{flag} {hyphen_case_lint_name}`"
260                ));
261            } else {
262                let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
263                err.note_once(format!(
264                    "`{flag} {hyphen_case_lint_name}` implied by `{flag} {hyphen_case_flag_val}`"
265                ));
266                if matches!(orig_level, Level::Warn | Level::Deny) {
267                    err.help_once(format!(
268                        "to override `{flag} {hyphen_case_flag_val}` add `#[allow({name})]`"
269                    ));
270                }
271            }
272        }
273        LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
274            if let Some(rationale) = reason {
275                err.note(rationale.to_string());
276            }
277            err.span_note_once(span, "the lint level is defined here");
278            if lint_attr_name.as_str() != name {
279                let level_str = level.as_str();
280                err.note_once(format!(
281                    "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`"
282                ));
283            }
284        }
285    }
286}
287
288#[track_caller]
302pub fn lint_level(
303    sess: &Session,
304    lint: &'static Lint,
305    level: LevelAndSource,
306    span: Option<MultiSpan>,
307    decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
308) {
309    #[track_caller]
312    fn lint_level_impl(
313        sess: &Session,
314        lint: &'static Lint,
315        level: LevelAndSource,
316        span: Option<MultiSpan>,
317        decorate: Box<dyn '_ + for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)>,
318    ) {
319        let LevelAndSource { level, lint_id, src } = level;
320
321        let future_incompatible = lint.future_incompatible;
323
324        let has_future_breakage = future_incompatible.map_or(
325            sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
327            |incompat| incompat.report_in_deps,
328        );
329
330        let err_level = match level {
332            Level::Allow => {
333                if has_future_breakage {
334                    rustc_errors::Level::Allow
335                } else {
336                    return;
337                }
338            }
339            Level::Expect => {
340                rustc_errors::Level::Expect
348            }
349            Level::ForceWarn => rustc_errors::Level::ForceWarning,
350            Level::Warn => rustc_errors::Level::Warning,
351            Level::Deny | Level::Forbid => rustc_errors::Level::Error,
352        };
353        let mut err = Diag::new(sess.dcx(), err_level, "");
354        if let Some(span) = span {
355            err.span(span);
356        }
357        if let Some(lint_id) = lint_id {
358            err.lint_id(lint_id);
359        }
360
361        if err.span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map())) {
365            err.disable_suggestions();
368
369            let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
374
375            if !incompatible && !lint.report_in_external_macro {
376                err.cancel();
377
378                return;
381            }
382        }
383
384        err.is_lint(lint.name_lower(), has_future_breakage);
385
386        if let Level::Expect = level {
391            decorate(&mut err);
392            err.emit();
393            return;
394        }
395
396        if let Some(future_incompatible) = future_incompatible {
397            let explanation = match future_incompatible.reason {
398                FutureIncompatibilityReason::FutureReleaseError => {
399                    "this was previously accepted by the compiler but is being phased out; \
400                         it will become a hard error in a future release!"
401                        .to_owned()
402                }
403                FutureIncompatibilityReason::FutureReleaseSemanticsChange => {
404                    "this will change its meaning in a future release!".to_owned()
405                }
406                FutureIncompatibilityReason::EditionError(edition) => {
407                    let current_edition = sess.edition();
408                    format!(
409                        "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
410                    )
411                }
412                FutureIncompatibilityReason::EditionSemanticsChange(edition) => {
413                    format!("this changes meaning in Rust {edition}")
414                }
415                FutureIncompatibilityReason::EditionAndFutureReleaseError(edition) => {
416                    format!(
417                        "this was previously accepted by the compiler but is being phased out; \
418                         it will become a hard error in Rust {edition} and in a future release in all editions!"
419                    )
420                }
421                FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(edition) => {
422                    format!(
423                        "this changes meaning in Rust {edition} and in a future release in all editions!"
424                    )
425                }
426                FutureIncompatibilityReason::Custom(reason) => reason.to_owned(),
427            };
428
429            if future_incompatible.explain_reason {
430                err.warn(explanation);
431            }
432            if !future_incompatible.reference.is_empty() {
433                let citation =
434                    format!("for more information, see {}", future_incompatible.reference);
435                err.note(citation);
436            }
437        }
438
439        let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
450
451        if !skip {
452            decorate(&mut err);
453        }
454
455        explain_lint_level_source(sess, lint, level, src, &mut err);
456        err.emit()
457    }
458    lint_level_impl(sess, lint, level, span, Box::new(decorate))
459}