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}