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 err.help_once(format!(
269 "to override `{flag} {hyphen_case_flag_val}` add `#[allow({name})]`"
270 ));
271 }
272 }
273 }
274 LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
275 if let Some(rationale) = reason {
276 err.note(rationale.to_string());
277 }
278 err.span_note_once(span, "the lint level is defined here");
279 if lint_attr_name.as_str() != name {
280 let level_str = level.as_str();
281 err.note_once(format!(
282 "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`"
283 ));
284 }
285 }
286 }
287}
288
289#[track_caller]
303pub fn lint_level(
304 sess: &Session,
305 lint: &'static Lint,
306 level: LevelAndSource,
307 span: Option<MultiSpan>,
308 decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
309) {
310 #[track_caller]
313 fn lint_level_impl(
314 sess: &Session,
315 lint: &'static Lint,
316 level: LevelAndSource,
317 span: Option<MultiSpan>,
318 decorate: Box<dyn '_ + for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)>,
319 ) {
320 let LevelAndSource { level, lint_id, src } = level;
321
322 let future_incompatible = lint.future_incompatible;
324
325 let has_future_breakage = future_incompatible.map_or(
326 sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
328 |incompat| incompat.report_in_deps,
329 );
330
331 let err_level = match level {
333 Level::Allow => {
334 if has_future_breakage {
335 rustc_errors::Level::Allow
336 } else {
337 return;
338 }
339 }
340 Level::Expect => {
341 rustc_errors::Level::Expect
349 }
350 Level::ForceWarn => rustc_errors::Level::ForceWarning,
351 Level::Warn => rustc_errors::Level::Warning,
352 Level::Deny | Level::Forbid => rustc_errors::Level::Error,
353 };
354 let mut err = Diag::new(sess.dcx(), err_level, "");
355 if let Some(span) = span {
356 err.span(span);
357 }
358 if let Some(lint_id) = lint_id {
359 err.lint_id(lint_id);
360 }
361
362 if err.span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map())) {
366 err.disable_suggestions();
369
370 let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
375
376 if !incompatible && !lint.report_in_external_macro {
377 err.cancel();
378
379 return;
382 }
383 }
384
385 err.is_lint(lint.name_lower(), has_future_breakage);
386
387 if let Level::Expect = level {
392 decorate(&mut err);
393 err.emit();
394 return;
395 }
396
397 if let Some(future_incompatible) = future_incompatible {
398 let explanation = match future_incompatible.reason {
399 FutureIncompatibilityReason::FutureReleaseError(_) => {
400 "this was previously accepted by the compiler but is being phased out; \
401 it will become a hard error in a future release!"
402 .to_owned()
403 }
404 FutureIncompatibilityReason::FutureReleaseSemanticsChange(_) => {
405 "this will change its meaning in a future release!".to_owned()
406 }
407 FutureIncompatibilityReason::EditionError(EditionFcw { edition, .. }) => {
408 let current_edition = sess.edition();
409 format!(
410 "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
411 )
412 }
413 FutureIncompatibilityReason::EditionSemanticsChange(EditionFcw {
414 edition, ..
415 }) => {
416 format!("this changes meaning in Rust {edition}")
417 }
418 FutureIncompatibilityReason::EditionAndFutureReleaseError(EditionFcw {
419 edition,
420 ..
421 }) => {
422 format!(
423 "this was previously accepted by the compiler but is being phased out; \
424 it will become a hard error in Rust {edition} and in a future release in all editions!"
425 )
426 }
427 FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(
428 EditionFcw { edition, .. },
429 ) => {
430 format!(
431 "this changes meaning in Rust {edition} and in a future release in all editions!"
432 )
433 }
434 FutureIncompatibilityReason::Custom(reason, _) => reason.to_owned(),
435 FutureIncompatibilityReason::Unreachable => unreachable!(),
436 };
437
438 if future_incompatible.explain_reason {
439 err.warn(explanation);
440 }
441
442 let citation =
443 format!("for more information, see {}", future_incompatible.reason.reference());
444 err.note(citation);
445 }
446
447 let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
458
459 if !skip {
460 decorate(&mut err);
461 }
462
463 explain_lint_level_source(sess, lint, level, src, &mut err);
464 err.emit()
465 }
466 lint_level_impl(sess, lint, level, span, Box::new(decorate))
467}