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
54pub type LevelAndSource = (Level, LintLevelSource);
56
57#[derive(Default, Debug, HashStable)]
62pub struct ShallowLintLevelMap {
63 pub expectations: Vec<(LintExpectationId, LintExpectation)>,
64 pub specs: SortedMap<ItemLocalId, FxIndexMap<LintId, LevelAndSource>>,
65}
66
67pub fn reveal_actual_level(
72 level: Option<Level>,
73 src: &mut LintLevelSource,
74 sess: &Session,
75 lint: LintId,
76 probe_for_lint_level: impl FnOnce(LintId) -> (Option<Level>, LintLevelSource),
77) -> Level {
78 let mut level = level.unwrap_or_else(|| lint.lint.default_level(sess.edition()));
80
81 if level == Level::Warn && lint != LintId::of(FORBIDDEN_LINT_GROUPS) {
90 let (warnings_level, warnings_src) = probe_for_lint_level(LintId::of(builtin::WARNINGS));
91 if let Some(configured_warning_level) = warnings_level {
92 if configured_warning_level != Level::Warn {
93 level = configured_warning_level;
94 *src = warnings_src;
95 }
96 }
97 }
98
99 level = if let LintLevelSource::CommandLine(_, Level::ForceWarn(_)) = src {
101 level
102 } else {
103 cmp::min(level, sess.opts.lint_cap.unwrap_or(Level::Forbid))
104 };
105
106 if let Some(driver_level) = sess.driver_lint_caps.get(&lint) {
107 level = cmp::min(*driver_level, level);
109 }
110
111 level
112}
113
114impl ShallowLintLevelMap {
115 #[instrument(level = "trace", skip(self, tcx), ret)]
119 fn probe_for_lint_level(
120 &self,
121 tcx: TyCtxt<'_>,
122 id: LintId,
123 start: HirId,
124 ) -> (Option<Level>, LintLevelSource) {
125 if let Some(map) = self.specs.get(&start.local_id)
126 && let Some(&(level, src)) = map.get(&id)
127 {
128 return (Some(level), src);
129 }
130
131 let mut owner = start.owner;
132 let mut specs = &self.specs;
133
134 for parent in tcx.hir_parent_id_iter(start) {
135 if parent.owner != owner {
136 owner = parent.owner;
137 specs = &tcx.shallow_lint_levels_on(owner).specs;
138 }
139 if let Some(map) = specs.get(&parent.local_id)
140 && let Some(&(level, src)) = map.get(&id)
141 {
142 return (Some(level), src);
143 }
144 }
145
146 (None, LintLevelSource::Default)
147 }
148
149 #[instrument(level = "trace", skip(self, tcx), ret)]
151 pub fn lint_level_id_at_node(
152 &self,
153 tcx: TyCtxt<'_>,
154 lint: LintId,
155 cur: HirId,
156 ) -> (Level, LintLevelSource) {
157 let (level, mut src) = self.probe_for_lint_level(tcx, lint, cur);
158 let level = reveal_actual_level(level, &mut src, tcx.sess, lint, |lint| {
159 self.probe_for_lint_level(tcx, lint, cur)
160 });
161 (level, src)
162 }
163}
164
165impl TyCtxt<'_> {
166 pub fn lint_level_at_node(self, lint: &'static Lint, id: HirId) -> (Level, LintLevelSource) {
168 self.shallow_lint_levels_on(id.owner).lint_level_id_at_node(self, LintId::of(lint), id)
169 }
170}
171
172#[derive(Clone, Debug, Encodable, Decodable, HashStable)]
176pub struct LintExpectation {
177 pub reason: Option<Symbol>,
180 pub emission_span: Span,
182 pub is_unfulfilled_lint_expectations: bool,
186 pub lint_tool: Option<Symbol>,
190}
191
192impl LintExpectation {
193 pub fn new(
194 reason: Option<Symbol>,
195 emission_span: Span,
196 is_unfulfilled_lint_expectations: bool,
197 lint_tool: Option<Symbol>,
198 ) -> Self {
199 Self { reason, emission_span, is_unfulfilled_lint_expectations, lint_tool }
200 }
201}
202
203fn explain_lint_level_source(
204 lint: &'static Lint,
205 level: Level,
206 src: LintLevelSource,
207 err: &mut Diag<'_, ()>,
208) {
209 let name = lint.name_lower();
210 if let Level::Allow = level {
211 return;
214 }
215 match src {
216 LintLevelSource::Default => {
217 err.note_once(format!("`#[{}({})]` on by default", level.as_str(), name));
218 }
219 LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
220 let flag = orig_level.to_cmd_flag();
221 let hyphen_case_lint_name = name.replace('_', "-");
222 if lint_flag_val.as_str() == name {
223 err.note_once(format!(
224 "requested on the command line with `{flag} {hyphen_case_lint_name}`"
225 ));
226 } else {
227 let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
228 err.note_once(format!(
229 "`{flag} {hyphen_case_lint_name}` implied by `{flag} {hyphen_case_flag_val}`"
230 ));
231 if matches!(orig_level, Level::Warn | Level::Deny) {
232 err.help_once(format!(
233 "to override `{flag} {hyphen_case_flag_val}` add `#[allow({name})]`"
234 ));
235 }
236 }
237 }
238 LintLevelSource::Node { name: lint_attr_name, span, reason, .. } => {
239 if let Some(rationale) = reason {
240 err.note(rationale.to_string());
241 }
242 err.span_note_once(span, "the lint level is defined here");
243 if lint_attr_name.as_str() != name {
244 let level_str = level.as_str();
245 err.note_once(format!(
246 "`#[{level_str}({name})]` implied by `#[{level_str}({lint_attr_name})]`"
247 ));
248 }
249 }
250 }
251}
252
253#[track_caller]
267pub fn lint_level(
268 sess: &Session,
269 lint: &'static Lint,
270 level: Level,
271 src: LintLevelSource,
272 span: Option<MultiSpan>,
273 decorate: impl for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>),
274) {
275 #[track_caller]
278 fn lint_level_impl(
279 sess: &Session,
280 lint: &'static Lint,
281 level: Level,
282 src: LintLevelSource,
283 span: Option<MultiSpan>,
284 decorate: Box<dyn '_ + for<'a, 'b> FnOnce(&'b mut Diag<'a, ()>)>,
285 ) {
286 let future_incompatible = lint.future_incompatible;
288
289 let has_future_breakage = future_incompatible.map_or(
290 sess.opts.unstable_opts.future_incompat_test && lint.default_level != Level::Allow,
292 |incompat| incompat.reason.has_future_breakage(),
293 );
294
295 let err_level = match level {
297 Level::Allow => {
298 if has_future_breakage {
299 rustc_errors::Level::Allow
300 } else {
301 return;
302 }
303 }
304 Level::Expect(expect_id) => {
305 rustc_errors::Level::Expect(expect_id)
313 }
314 Level::ForceWarn(Some(expect_id)) => rustc_errors::Level::ForceWarning(Some(expect_id)),
315 Level::ForceWarn(None) => rustc_errors::Level::ForceWarning(None),
316 Level::Warn => rustc_errors::Level::Warning,
317 Level::Deny | Level::Forbid => rustc_errors::Level::Error,
318 };
319 let mut err = Diag::new(sess.dcx(), err_level, "");
320 if let Some(span) = span {
321 err.span(span);
322 }
323
324 if err.span.primary_spans().iter().any(|s| s.in_external_macro(sess.source_map())) {
328 err.disable_suggestions();
331
332 let incompatible = future_incompatible.is_some_and(|f| f.reason.edition().is_none());
337
338 if !incompatible && !lint.report_in_external_macro {
339 err.cancel();
340
341 return;
344 }
345 }
346
347 err.is_lint(lint.name_lower(), has_future_breakage);
348
349 if let Level::Expect(_) = level {
354 decorate(&mut err);
355 err.emit();
356 return;
357 }
358
359 if let Some(future_incompatible) = future_incompatible {
360 let explanation = match future_incompatible.reason {
361 FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps
362 | FutureIncompatibilityReason::FutureReleaseErrorReportInDeps => {
363 "this was previously accepted by the compiler but is being phased out; \
364 it will become a hard error in a future release!"
365 .to_owned()
366 }
367 FutureIncompatibilityReason::FutureReleaseSemanticsChange => {
368 "this will change its meaning in a future release!".to_owned()
369 }
370 FutureIncompatibilityReason::EditionError(edition) => {
371 let current_edition = sess.edition();
372 format!(
373 "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
374 )
375 }
376 FutureIncompatibilityReason::EditionSemanticsChange(edition) => {
377 format!("this changes meaning in Rust {edition}")
378 }
379 FutureIncompatibilityReason::EditionAndFutureReleaseError(edition) => {
380 format!(
381 "this was previously accepted by the compiler but is being phased out; \
382 it will become a hard error in Rust {edition} and in a future release in all editions!"
383 )
384 }
385 FutureIncompatibilityReason::EditionAndFutureReleaseSemanticsChange(edition) => {
386 format!(
387 "this changes meaning in Rust {edition} and in a future release in all editions!"
388 )
389 }
390 FutureIncompatibilityReason::Custom(reason) => reason.to_owned(),
391 };
392
393 if future_incompatible.explain_reason {
394 err.warn(explanation);
395 }
396 if !future_incompatible.reference.is_empty() {
397 let citation =
398 format!("for more information, see {}", future_incompatible.reference);
399 err.note(citation);
400 }
401 }
402
403 let skip = err_level == rustc_errors::Level::Warning && !sess.dcx().can_emit_warnings();
414
415 if !skip {
416 decorate(&mut err);
417 }
418
419 explain_lint_level_source(lint, level, src, &mut err);
420 err.emit()
421 }
422 lint_level_impl(sess, lint, level, src, span, Box::new(decorate))
423}