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_lint_defs::EditionFcw;
8use rustc_macros::{Decodable, Encodable, HashStable};
9use rustc_session::Session;
10use rustc_session::lint::builtin::{self, FORBIDDEN_LINT_GROUPS};
11use rustc_session::lint::{FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId};
12use rustc_span::{DUMMY_SP, Span, Symbol, kw};
13use tracing::instrument;
14
15use crate::ty::TyCtxt;
16
17#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, HashStable, Debug)]
19pub enum LintLevelSource {
20 Default,
22
23 Node {
25 name: Symbol,
26 span: Span,
27 reason: Option<Symbol>,
29 },
30
31 CommandLine(Symbol, Level),
35}
36
37impl LintLevelSource {
38 pub fn name(&self) -> Symbol {
39 match *self {
40 LintLevelSource::Default => kw::Default,
41 LintLevelSource::Node { name, .. } => name,
42 LintLevelSource::CommandLine(name, _) => name,
43 }
44 }
45
46 pub fn span(&self) -> Span {
47 match *self {
48 LintLevelSource::Default => DUMMY_SP,
49 LintLevelSource::Node { span, .. } => span,
50 LintLevelSource::CommandLine(_, _) => DUMMY_SP,
51 }
52 }
53}
54
55#[derive(Copy, Clone, Debug, HashStable, Encodable, Decodable)]
57pub struct LevelAndSource {
58 pub level: Level,
59 pub lint_id: Option<LintExpectationId>,
60 pub src: LintLevelSource,
61}
62
63#[derive(Default, Debug, HashStable)]
68pub struct ShallowLintLevelMap {
69 pub expectations: Vec<(LintExpectationId, LintExpectation)>,
70 pub specs: SortedMap<ItemLocalId, FxIndexMap<LintId, LevelAndSource>>,
71}
72
73pub fn reveal_actual_level(
78 level: Option<(Level, Option<LintExpectationId>)>,
79 src: &mut LintLevelSource,
80 sess: &Session,
81 lint: LintId,
82 probe_for_lint_level: impl FnOnce(
83 LintId,
84 )
85 -> (Option<(Level, Option<LintExpectationId>)>, LintLevelSource),
86) -> (Level, Option<LintExpectationId>) {
87 let (mut level, mut lint_id) =
89 level.unwrap_or_else(|| (lint.lint.default_level(sess.edition()), None));
90
91 if level == Level::Warn && lint != LintId::of(FORBIDDEN_LINT_GROUPS) {
100 let (warnings_level, warnings_src) = probe_for_lint_level(LintId::of(builtin::WARNINGS));
101 if let Some((configured_warning_level, configured_lint_id)) = warnings_level {
102 if configured_warning_level != Level::Warn {
103 level = configured_warning_level;
104 lint_id = configured_lint_id;
105 *src = warnings_src;
106 }
107 }
108 }
109
110 level = if let LintLevelSource::CommandLine(_, Level::ForceWarn) = src {
112 level
113 } else {
114 cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid))
115 };
116
117 if let Some(driver_level) = sess.driver_lint_caps.get(&lint) {
118 level = cmp::min(*driver_level, level);
120 }
121
122 (level, lint_id)
123}
124
125impl ShallowLintLevelMap {
126 #[instrument(level = "trace", skip(self, tcx), ret)]
130 fn probe_for_lint_level(
131 &self,
132 tcx: TyCtxt<'_>,
133 id: LintId,
134 start: HirId,
135 ) -> (Option<(Level, Option<LintExpectationId>)>, LintLevelSource) {
136 if let Some(map) = self.specs.get(&start.local_id)
137 && let Some(&LevelAndSource { level, lint_id, src }) = map.get(&id)
138 {
139 return (Some((level, lint_id)), src);
140 }
141
142 let mut owner = start.owner;
143 let mut specs = &self.specs;
144
145 for parent in tcx.hir_parent_id_iter(start) {
146 if parent.owner != owner {
147 owner = parent.owner;
148 specs = &tcx.shallow_lint_levels_on(owner).specs;
149 }
150 if let Some(map) = specs.get(&parent.local_id)
151 && let Some(&LevelAndSource { level, lint_id, src }) = map.get(&id)
152 {
153 return (Some((level, lint_id)), src);
154 }
155 }
156
157 (None, LintLevelSource::Default)
158 }
159
160 #[instrument(level = "trace", skip(self, tcx), ret)]
162 pub fn lint_level_id_at_node(
163 &self,
164 tcx: TyCtxt<'_>,
165 lint: LintId,
166 cur: HirId,
167 ) -> LevelAndSource {
168 let (level, mut src) = self.probe_for_lint_level(tcx, lint, cur);
169 let (level, lint_id) = reveal_actual_level(level, &mut src, tcx.sess, lint, |lint| {
170 self.probe_for_lint_level(tcx, lint, cur)
171 });
172 LevelAndSource { level, lint_id, src }
173 }
174}
175
176impl TyCtxt<'_> {
177 pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> LevelAndSource {
179 self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id)
180 }
181}
182
183#[derive(Clone, Debug, Encodable, Decodable, HashStable)]
187pub struct LintExpectation {
188 pub reason: Option<Symbol>,
191 pub emission_span: Span,
193 pub is_unfulfilled_lint_expectations: bool,
197 pub lint_tool: Option<Symbol>,
201}
202
203impl LintExpectation {
204 pub fn new(
205 reason: Option<Symbol>,
206 emission_span: Span,
207 is_unfulfilled_lint_expectations: bool,
208 lint_tool: Option<Symbol>,
209 ) -> Self {
210 Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool }
211 }
212}
213
214fn explain_lint_level_source(
215 sess: &Session,
216 lint: &'static Lint,
217 level: Level,
218 src: LintLevelSource,
219 err: &mut Diag<'_, ()>,
220) {
221 let lint_group_name = |lint| {
224 let lint_groups_iter = sess.lint_groups_iter();
225 let lint_id = LintId::of(lint);
226 lint_groups_iter
227 .filter(|lint_group| !lint_group.is_externally_loaded)
228 .find(|lint_group| {
229 lint_group
230 .lints
231 .iter()
232 .find(|lint_group_lint| **lint_group_lint == lint_id)
233 .is_some()
234 })
235 .map(|lint_group| lint_group.name)
236 };
237 let name = lint.name_lower();
238 if let Level::Allow = level {
239 return;
242 }
243 match src {
244 LintLevelSource::Default => {
245 let level_str = level.as_str();
246 match lint_group_name(lint) {
247 Some(group_name) => {
248 err.note_once(format!("`#[{level_str}({name})]` (part of `#[{level_str}({group_name})]`) on by default"));
249 }
250 None => {
251 err.note_once(format!("`#[{level_str}({name})]` on by default"));
252 }
253 }
254 }
255 LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
256 let flag = orig_level.to_cmd_flag();
257 let hyphen_case_lint_name = name.replace('_', "-");
258 if lint_flag_val.as_str() == name {
259 err.note_once(format!(
260 "requested on the command line with `{flag} {hyphen_case_lint_name}`"
261 ));
262 } else {
263 let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
264 err.note_once(format!(
265 "`{flag} {hyphen_case_lint_name}` implied by `{flag} {hyphen_case_flag_val}`"
266 ));
267 if matches!(orig_level, Level::Warn | Level::Deny) {
268 let help = if name == "dead_code" {
269 format!(
270 "to override `{flag} {hyphen_case_flag_val}` add `#[expect({name})]` or `#[allow({name})]`"
271 )
272 } else {
273 format!(
274 "to override `{flag} {hyphen_case_flag_val}` add `#[allow({name})]`"
275 )
276 };
277 err.help_once(help);
278 }
279 }
280 }
281 LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
282 if let Some(rationale) = reason {
283 err.note(rationale.to_string());
284 }
285 err.span_note_once(span, "the lint level is defined here");
286 if lint_attr_name.as_str() != name {
287 let level_str = level.as_str();
288 err.note_once(format!(
289 "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`"
290 ));
291 }
292 }
293 }
294}
295
296#[track_caller]
310pub fn lint_level(
311 sess: &Session,
312 lint: &'static Lint,
313 level: LevelAndSource,
314 span: Option<MultiSpan>,
315 decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
316) {
317 #[track_caller]
320 fn lint_level_impl(
321 sess: &Session,
322 lint: &'static Lint,
323 level: LevelAndSource,
324 span: Option<MultiSpan>,
325 decorate: Box<dyn '_ + for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)>,
326 ) {
327 let LevelAndSource { level, lint_id, src } = level;
328
329 let future_incompatible = lint.future_incompatible;
331
332 let has_future_breakage = future_incompatible.map_or(
333 sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
335 |incompat| incompat.report_in_deps,
336 );
337
338 let err_level = match level {
340 Level::Allow => {
341 if has_future_breakage {
342 rustc_errors::Level::Allow
343 } else {
344 return;
345 }
346 }
347 Level::Expect => {
348 rustc_errors::Level::Expect
356 }
357 Level::ForceWarn => rustc_errors::Level::ForceWarning,
358 Level::Warn => rustc_errors::Level::Warning,
359 Level::Deny | Level::Forbid => rustc_errors::Level::Error,
360 };
361 let mut err = Diag::new(sess.dcx(), err_level, "");
362 if let Some(span) = span {
363 err.span(span);
364 }
365 if let Some(lint_id) = lint_id {
366 err.lint_id(lint_id);
367 }
368
369 if err.span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map())) {
373 err.disable_suggestions();
376
377 let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
382
383 if !incompatible && !lint.report_in_external_macro {
384 err.cancel();
385
386 return;
389 }
390 }
391
392 err.is_lint(lint.name_lower(), has_future_breakage);
393
394 if let Level::Expect = level {
399 decorate(&mut err);
400 err.emit();
401 return;
402 }
403
404 if let Some(future_incompatible) = future_incompatible {
405 let explanation = match future_incompatible.reason {
406 FutureIncompatibilityReason::FutureReleaseError(_) => {
407 "this was previously accepted by the compiler but is being phased out; \
408 it will become a hard error in a future release!"
409 .to_owned()
410 }
411 FutureIncompatibilityReason::FutureReleaseSemanticsChange(_) => {
412 "this will change its meaning in a future release!".to_owned()
413 }
414 FutureIncompatibilityReason::EditionError(EditionFcw { edition, .. }) => {
415 let current_edition = sess.edition();
416 format!(
417 "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
418 )
419 }
420 FutureIncompatibilityReason::EditionSemanticsChange(EditionFcw {
421 edition, ..
422 }) => {
423 format!("this changes meaning in Rust {edition}")
424 }
425 FutureIncompatibilityReason::EditionAndFutureReleaseError(EditionFcw {
426 edition,
427 ..
428 }) => {
429 format!(
430 "this was previously accepted by the compiler but is being phased out; \
431 it will become a hard error in Rust {edition} and in a future release in all editions!"
432 )
433 }
434 FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(
435 EditionFcw { edition, .. },
436 ) => {
437 format!(
438 "this changes meaning in Rust {edition} and in a future release in all editions!"
439 )
440 }
441 FutureIncompatibilityReason::Custom(reason, _) => reason.to_owned(),
442 FutureIncompatibilityReason::Unreachable => unreachable!(),
443 };
444
445 if future_incompatible.explain_reason {
446 err.warn(explanation);
447 }
448
449 let citation =
450 format!("for more information, see {}", future_incompatible.reason.reference());
451 err.note(citation);
452 }
453
454 let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
465
466 if !skip {
467 decorate(&mut err);
468 }
469
470 explain_lint_level_source(sess, lint, level, src, &mut err);
471 err.emit()
472 }
473 lint_level_impl(sess, lint, level, span, Box::new(decorate))
474}