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 lint: &'static Lint,
215 level: Level,
216 src: LintLevelSource,
217 err: &mut Diag<'_, ()>,
218) {
219 let name = lint.name_lower();
220 if let Level::Allow = level {
221 return;
224 }
225 match src {
226 LintLevelSource::Default => {
227 err.note_once(format!("`#[{}({})]` on by default", level.as_str(), name));
228 }
229 LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
230 let flag = orig_level.to_cmd_flag();
231 let hyphen_case_lint_name = name.replace('_', "-");
232 if lint_flag_val.as_str() == name {
233 err.note_once(format!(
234 "requested on the command line with `{flag} {hyphen_case_lint_name}`"
235 ));
236 } else {
237 let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
238 err.note_once(format!(
239 "`{flag} {hyphen_case_lint_name}` implied by `{flag} {hyphen_case_flag_val}`"
240 ));
241 if matches!(orig_level, Level::Warn | Level::Deny) {
242 err.help_once(format!(
243 "to override `{flag} {hyphen_case_flag_val}` add `#[allow({name})]`"
244 ));
245 }
246 }
247 }
248 LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
249 if let Some(rationale) = reason {
250 err.note(rationale.to_string());
251 }
252 err.span_note_once(span, "the lint level is defined here");
253 if lint_attr_name.as_str() != name {
254 let level_str = level.as_str();
255 err.note_once(format!(
256 "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`"
257 ));
258 }
259 }
260 }
261}
262
263#[track_caller]
277pub fn lint_level(
278 sess: &Session,
279 lint: &'static Lint,
280 level: LevelAndSource,
281 span: Option<MultiSpan>,
282 decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
283) {
284 #[track_caller]
287 fn lint_level_impl(
288 sess: &Session,
289 lint: &'static Lint,
290 level: LevelAndSource,
291 span: Option<MultiSpan>,
292 decorate: Box<dyn '_ + for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)>,
293 ) {
294 let LevelAndSource { level, lint_id, src } = level;
295
296 let future_incompatible = lint.future_incompatible;
298
299 let has_future_breakage = future_incompatible.map_or(
300 sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
302 |incompat| incompat.reason.has_future_breakage(),
303 );
304
305 let err_level = match level {
307 Level::Allow => {
308 if has_future_breakage {
309 rustc_errors::Level::Allow
310 } else {
311 return;
312 }
313 }
314 Level::Expect => {
315 rustc_errors::Level::Expect
323 }
324 Level::ForceWarn => rustc_errors::Level::ForceWarning,
325 Level::Warn => rustc_errors::Level::Warning,
326 Level::Deny | Level::Forbid => rustc_errors::Level::Error,
327 };
328 let mut err = Diag::new(sess.dcx(), err_level, "");
329 if let Some(span) = span {
330 err.span(span);
331 }
332 if let Some(lint_id) = lint_id {
333 err.lint_id(lint_id);
334 }
335
336 if err.span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map())) {
340 err.disable_suggestions();
343
344 let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
349
350 if !incompatible && !lint.report_in_external_macro {
351 err.cancel();
352
353 return;
356 }
357 }
358
359 err.is_lint(lint.name_lower(), has_future_breakage);
360
361 if let Level::Expect = level {
366 decorate(&mut err);
367 err.emit();
368 return;
369 }
370
371 if let Some(future_incompatible) = future_incompatible {
372 let explanation = match future_incompatible.reason {
373 FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps
374 | FutureIncompatibilityReason::FutureReleaseErrorReportInDeps => {
375 "this was previously accepted by the compiler but is being phased out; \
376 it will become a hard error in a future release!"
377 .to_owned()
378 }
379 FutureIncompatibilityReason::FutureReleaseSemanticsChange => {
380 "this will change its meaning in a future release!".to_owned()
381 }
382 FutureIncompatibilityReason::EditionError(edition) => {
383 let current_edition = sess.edition();
384 format!(
385 "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
386 )
387 }
388 FutureIncompatibilityReason::EditionSemanticsChange(edition) => {
389 format!("this changes meaning in Rust {edition}")
390 }
391 FutureIncompatibilityReason::EditionAndFutureReleaseError(edition) => {
392 format!(
393 "this was previously accepted by the compiler but is being phased out; \
394 it will become a hard error in Rust {edition} and in a future release in all editions!"
395 )
396 }
397 FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(edition) => {
398 format!(
399 "this changes meaning in Rust {edition} and in a future release in all editions!"
400 )
401 }
402 FutureIncompatibilityReason::Custom(reason) => reason.to_owned(),
403 };
404
405 if future_incompatible.explain_reason {
406 err.warn(explanation);
407 }
408 if !future_incompatible.reference.is_empty() {
409 let citation =
410 format!("for more information, see {}", future_incompatible.reference);
411 err.note(citation);
412 }
413 }
414
415 let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
426
427 if !skip {
428 decorate(&mut err);
429 }
430
431 explain_lint_level_source(lint, level, src, &mut err);
432 err.emit()
433 }
434 lint_level_impl(sess, lint, level, span, Box::new(decorate))
435}