]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | use std::cmp; |
2 | ||
3 | use crate::ich::StableHashingContext; | |
4 | use rustc_data_structures::fx::FxHashMap; | |
5 | use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; | |
ba9703b0 | 6 | use rustc_errors::{DiagnosticBuilder, DiagnosticId}; |
dfeec247 | 7 | use rustc_hir::HirId; |
fc512014 XL |
8 | use rustc_session::lint::{ |
9 | builtin::{self, FORBIDDEN_LINT_GROUPS}, | |
10 | Level, Lint, LintId, | |
11 | }; | |
dfeec247 XL |
12 | use rustc_session::{DiagnosticMessageId, Session}; |
13 | use rustc_span::hygiene::MacroKind; | |
14 | use rustc_span::source_map::{DesugaringKind, ExpnKind, MultiSpan}; | |
fc512014 | 15 | use rustc_span::{symbol, Span, Symbol, DUMMY_SP}; |
dfeec247 XL |
16 | |
17 | /// How a lint level was set. | |
fc512014 XL |
18 | #[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)] |
19 | pub enum LintLevelSource { | |
dfeec247 XL |
20 | /// Lint is at the default level as declared |
21 | /// in rustc or a plugin. | |
22 | Default, | |
23 | ||
24 | /// Lint level was set by an attribute. | |
25 | Node(Symbol, Span, Option<Symbol> /* RFC 2383 reason */), | |
26 | ||
27 | /// Lint level was set by a command-line flag. | |
fc512014 XL |
28 | /// The provided `Level` is the level specified on the command line. |
29 | /// (The actual level may be lower due to `--cap-lints`.) | |
29967ef6 | 30 | CommandLine(Symbol, Level), |
dfeec247 XL |
31 | } |
32 | ||
fc512014 XL |
33 | impl LintLevelSource { |
34 | pub fn name(&self) -> Symbol { | |
35 | match *self { | |
36 | LintLevelSource::Default => symbol::kw::Default, | |
37 | LintLevelSource::Node(name, _, _) => name, | |
38 | LintLevelSource::CommandLine(name, _) => name, | |
39 | } | |
40 | } | |
41 | ||
42 | pub fn span(&self) -> Span { | |
43 | match *self { | |
44 | LintLevelSource::Default => DUMMY_SP, | |
45 | LintLevelSource::Node(_, span, _) => span, | |
46 | LintLevelSource::CommandLine(_, _) => DUMMY_SP, | |
47 | } | |
48 | } | |
49 | } | |
50 | ||
51 | /// A tuple of a lint level and its source. | |
5869c6ff | 52 | pub type LevelAndSource = (Level, LintLevelSource); |
dfeec247 | 53 | |
5869c6ff | 54 | #[derive(Debug)] |
dfeec247 XL |
55 | pub struct LintLevelSets { |
56 | pub list: Vec<LintSet>, | |
57 | pub lint_cap: Level, | |
58 | } | |
59 | ||
5869c6ff | 60 | #[derive(Debug)] |
dfeec247 XL |
61 | pub enum LintSet { |
62 | CommandLine { | |
63 | // -A,-W,-D flags, a `Symbol` for the flag itself and `Level` for which | |
64 | // flag. | |
5869c6ff | 65 | specs: FxHashMap<LintId, LevelAndSource>, |
dfeec247 XL |
66 | }, |
67 | ||
68 | Node { | |
5869c6ff | 69 | specs: FxHashMap<LintId, LevelAndSource>, |
dfeec247 XL |
70 | parent: u32, |
71 | }, | |
72 | } | |
73 | ||
74 | impl LintLevelSets { | |
75 | pub fn new() -> Self { | |
76 | LintLevelSets { list: Vec::new(), lint_cap: Level::Forbid } | |
77 | } | |
78 | ||
79 | pub fn get_lint_level( | |
80 | &self, | |
81 | lint: &'static Lint, | |
82 | idx: u32, | |
5869c6ff | 83 | aux: Option<&FxHashMap<LintId, LevelAndSource>>, |
dfeec247 | 84 | sess: &Session, |
5869c6ff | 85 | ) -> LevelAndSource { |
dfeec247 XL |
86 | let (level, mut src) = self.get_lint_id_level(LintId::of(lint), idx, aux); |
87 | ||
88 | // If `level` is none then we actually assume the default level for this | |
89 | // lint. | |
90 | let mut level = level.unwrap_or_else(|| lint.default_level(sess.edition())); | |
91 | ||
92 | // If we're about to issue a warning, check at the last minute for any | |
93 | // directives against the warnings "lint". If, for example, there's an | |
94 | // `allow(warnings)` in scope then we want to respect that instead. | |
fc512014 XL |
95 | // |
96 | // We exempt `FORBIDDEN_LINT_GROUPS` from this because it specifically | |
97 | // triggers in cases (like #80988) where you have `forbid(warnings)`, | |
98 | // and so if we turned that into an error, it'd defeat the purpose of the | |
99 | // future compatibility warning. | |
100 | if level == Level::Warn && LintId::of(lint) != LintId::of(FORBIDDEN_LINT_GROUPS) { | |
dfeec247 XL |
101 | let (warnings_level, warnings_src) = |
102 | self.get_lint_id_level(LintId::of(builtin::WARNINGS), idx, aux); | |
103 | if let Some(configured_warning_level) = warnings_level { | |
104 | if configured_warning_level != Level::Warn { | |
105 | level = configured_warning_level; | |
106 | src = warnings_src; | |
107 | } | |
108 | } | |
109 | } | |
110 | ||
111 | // Ensure that we never exceed the `--cap-lints` argument. | |
112 | level = cmp::min(level, self.lint_cap); | |
113 | ||
114 | if let Some(driver_level) = sess.driver_lint_caps.get(&LintId::of(lint)) { | |
115 | // Ensure that we never exceed driver level. | |
116 | level = cmp::min(*driver_level, level); | |
117 | } | |
118 | ||
ba9703b0 | 119 | (level, src) |
dfeec247 XL |
120 | } |
121 | ||
122 | pub fn get_lint_id_level( | |
123 | &self, | |
124 | id: LintId, | |
125 | mut idx: u32, | |
5869c6ff | 126 | aux: Option<&FxHashMap<LintId, LevelAndSource>>, |
fc512014 | 127 | ) -> (Option<Level>, LintLevelSource) { |
dfeec247 XL |
128 | if let Some(specs) = aux { |
129 | if let Some(&(level, src)) = specs.get(&id) { | |
130 | return (Some(level), src); | |
131 | } | |
132 | } | |
133 | loop { | |
134 | match self.list[idx as usize] { | |
135 | LintSet::CommandLine { ref specs } => { | |
136 | if let Some(&(level, src)) = specs.get(&id) { | |
137 | return (Some(level), src); | |
138 | } | |
fc512014 | 139 | return (None, LintLevelSource::Default); |
dfeec247 XL |
140 | } |
141 | LintSet::Node { ref specs, parent } => { | |
142 | if let Some(&(level, src)) = specs.get(&id) { | |
143 | return (Some(level), src); | |
144 | } | |
145 | idx = parent; | |
146 | } | |
147 | } | |
148 | } | |
149 | } | |
150 | } | |
151 | ||
5869c6ff | 152 | #[derive(Debug)] |
dfeec247 XL |
153 | pub struct LintLevelMap { |
154 | pub sets: LintLevelSets, | |
155 | pub id_to_set: FxHashMap<HirId, u32>, | |
156 | } | |
157 | ||
158 | impl LintLevelMap { | |
159 | /// If the `id` was previously registered with `register_id` when building | |
160 | /// this `LintLevelMap` this returns the corresponding lint level and source | |
161 | /// of the lint level for the lint provided. | |
162 | /// | |
163 | /// If the `id` was not previously registered, returns `None`. If `None` is | |
164 | /// returned then the parent of `id` should be acquired and this function | |
165 | /// should be called again. | |
166 | pub fn level_and_source( | |
167 | &self, | |
168 | lint: &'static Lint, | |
169 | id: HirId, | |
170 | session: &Session, | |
5869c6ff | 171 | ) -> Option<LevelAndSource> { |
dfeec247 XL |
172 | self.id_to_set.get(&id).map(|idx| self.sets.get_lint_level(lint, *idx, None, session)) |
173 | } | |
174 | } | |
175 | ||
176 | impl<'a> HashStable<StableHashingContext<'a>> for LintLevelMap { | |
177 | #[inline] | |
178 | fn hash_stable(&self, hcx: &mut StableHashingContext<'a>, hasher: &mut StableHasher) { | |
179 | let LintLevelMap { ref sets, ref id_to_set } = *self; | |
180 | ||
181 | id_to_set.hash_stable(hcx, hasher); | |
182 | ||
183 | let LintLevelSets { ref list, lint_cap } = *sets; | |
184 | ||
185 | lint_cap.hash_stable(hcx, hasher); | |
186 | ||
187 | hcx.while_hashing_spans(true, |hcx| { | |
188 | list.len().hash_stable(hcx, hasher); | |
189 | ||
190 | // We are working under the assumption here that the list of | |
191 | // lint-sets is built in a deterministic order. | |
192 | for lint_set in list { | |
193 | ::std::mem::discriminant(lint_set).hash_stable(hcx, hasher); | |
194 | ||
195 | match *lint_set { | |
196 | LintSet::CommandLine { ref specs } => { | |
197 | specs.hash_stable(hcx, hasher); | |
198 | } | |
199 | LintSet::Node { ref specs, parent } => { | |
200 | specs.hash_stable(hcx, hasher); | |
201 | parent.hash_stable(hcx, hasher); | |
202 | } | |
203 | } | |
204 | } | |
205 | }) | |
206 | } | |
207 | } | |
208 | ||
74b04a01 XL |
209 | pub struct LintDiagnosticBuilder<'a>(DiagnosticBuilder<'a>); |
210 | ||
211 | impl<'a> LintDiagnosticBuilder<'a> { | |
212 | /// Return the inner DiagnosticBuilder, first setting the primary message to `msg`. | |
213 | pub fn build(mut self, msg: &str) -> DiagnosticBuilder<'a> { | |
214 | self.0.set_primary_message(msg); | |
215 | self.0 | |
216 | } | |
217 | ||
218 | /// Create a LintDiagnosticBuilder from some existing DiagnosticBuilder. | |
219 | pub fn new(err: DiagnosticBuilder<'a>) -> LintDiagnosticBuilder<'a> { | |
220 | LintDiagnosticBuilder(err) | |
221 | } | |
222 | } | |
223 | ||
224 | pub fn struct_lint_level<'s, 'd>( | |
225 | sess: &'s Session, | |
dfeec247 XL |
226 | lint: &'static Lint, |
227 | level: Level, | |
fc512014 | 228 | src: LintLevelSource, |
dfeec247 | 229 | span: Option<MultiSpan>, |
74b04a01 XL |
230 | decorate: impl for<'a> FnOnce(LintDiagnosticBuilder<'a>) + 'd, |
231 | ) { | |
232 | // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to | |
233 | // the "real" work. | |
234 | fn struct_lint_level_impl( | |
235 | sess: &'s Session, | |
236 | lint: &'static Lint, | |
237 | level: Level, | |
fc512014 | 238 | src: LintLevelSource, |
74b04a01 XL |
239 | span: Option<MultiSpan>, |
240 | decorate: Box<dyn for<'b> FnOnce(LintDiagnosticBuilder<'b>) + 'd>, | |
241 | ) { | |
29967ef6 XL |
242 | // Check for future incompatibility lints and issue a stronger warning. |
243 | let lint_id = LintId::of(lint); | |
244 | let future_incompatible = lint.future_incompatible; | |
245 | ||
246 | let has_future_breakage = | |
247 | future_incompatible.map_or(false, |incompat| incompat.future_breakage.is_some()); | |
248 | ||
74b04a01 | 249 | let mut err = match (level, span) { |
29967ef6 XL |
250 | (Level::Allow, span) => { |
251 | if has_future_breakage { | |
252 | if let Some(span) = span { | |
253 | sess.struct_span_allow(span, "") | |
254 | } else { | |
255 | sess.struct_allow("") | |
256 | } | |
257 | } else { | |
258 | return; | |
259 | } | |
74b04a01 XL |
260 | } |
261 | (Level::Warn, Some(span)) => sess.struct_span_warn(span, ""), | |
262 | (Level::Warn, None) => sess.struct_warn(""), | |
ba9703b0 XL |
263 | (Level::Deny | Level::Forbid, Some(span)) => sess.struct_span_err(span, ""), |
264 | (Level::Deny | Level::Forbid, None) => sess.struct_err(""), | |
74b04a01 | 265 | }; |
dfeec247 | 266 | |
74b04a01 XL |
267 | // If this code originates in a foreign macro, aka something that this crate |
268 | // did not itself author, then it's likely that there's nothing this crate | |
269 | // can do about it. We probably want to skip the lint entirely. | |
270 | if err.span.primary_spans().iter().any(|s| in_external_macro(sess, *s)) { | |
271 | // Any suggestions made here are likely to be incorrect, so anything we | |
272 | // emit shouldn't be automatically fixed by rustfix. | |
273 | err.allow_suggestions(false); | |
274 | ||
275 | // If this is a future incompatible lint it'll become a hard error, so | |
f035d41b XL |
276 | // we have to emit *something*. Also, if this lint occurs in the |
277 | // expansion of a macro from an external crate, allow individual lints | |
278 | // to opt-out from being reported. | |
74b04a01 XL |
279 | if future_incompatible.is_none() && !lint.report_in_external_macro { |
280 | err.cancel(); | |
281 | // Don't continue further, since we don't want to have | |
282 | // `diag_span_note_once` called for a diagnostic that isn't emitted. | |
283 | return; | |
284 | } | |
dfeec247 | 285 | } |
74b04a01 XL |
286 | |
287 | let name = lint.name_lower(); | |
288 | match src { | |
fc512014 | 289 | LintLevelSource::Default => { |
dfeec247 XL |
290 | sess.diag_note_once( |
291 | &mut err, | |
292 | DiagnosticMessageId::from(lint), | |
74b04a01 | 293 | &format!("`#[{}({})]` on by default", level.as_str(), name), |
dfeec247 XL |
294 | ); |
295 | } | |
fc512014 | 296 | LintLevelSource::CommandLine(lint_flag_val, orig_level) => { |
29967ef6 | 297 | let flag = match orig_level { |
74b04a01 XL |
298 | Level::Warn => "-W", |
299 | Level::Deny => "-D", | |
300 | Level::Forbid => "-F", | |
29967ef6 | 301 | Level::Allow => "-A", |
74b04a01 XL |
302 | }; |
303 | let hyphen_case_lint_name = name.replace("_", "-"); | |
304 | if lint_flag_val.as_str() == name { | |
305 | sess.diag_note_once( | |
306 | &mut err, | |
307 | DiagnosticMessageId::from(lint), | |
308 | &format!( | |
309 | "requested on the command line with `{} {}`", | |
310 | flag, hyphen_case_lint_name | |
311 | ), | |
312 | ); | |
313 | } else { | |
314 | let hyphen_case_flag_val = lint_flag_val.as_str().replace("_", "-"); | |
315 | sess.diag_note_once( | |
316 | &mut err, | |
317 | DiagnosticMessageId::from(lint), | |
318 | &format!( | |
319 | "`{} {}` implied by `{} {}`", | |
320 | flag, hyphen_case_lint_name, flag, hyphen_case_flag_val | |
321 | ), | |
322 | ); | |
323 | } | |
dfeec247 | 324 | } |
fc512014 | 325 | LintLevelSource::Node(lint_attr_name, src, reason) => { |
74b04a01 XL |
326 | if let Some(rationale) = reason { |
327 | err.note(&rationale.as_str()); | |
328 | } | |
329 | sess.diag_span_note_once( | |
dfeec247 XL |
330 | &mut err, |
331 | DiagnosticMessageId::from(lint), | |
74b04a01 XL |
332 | src, |
333 | "the lint level is defined here", | |
dfeec247 | 334 | ); |
74b04a01 XL |
335 | if lint_attr_name.as_str() != name { |
336 | let level_str = level.as_str(); | |
337 | sess.diag_note_once( | |
338 | &mut err, | |
339 | DiagnosticMessageId::from(lint), | |
340 | &format!( | |
341 | "`#[{}({})]` implied by `#[{}({})]`", | |
342 | level_str, name, level_str, lint_attr_name | |
343 | ), | |
344 | ); | |
345 | } | |
dfeec247 XL |
346 | } |
347 | } | |
dfeec247 | 348 | |
29967ef6 | 349 | err.code(DiagnosticId::Lint { name, has_future_breakage }); |
74b04a01 XL |
350 | |
351 | if let Some(future_incompatible) = future_incompatible { | |
352 | const STANDARD_MESSAGE: &str = "this was previously accepted by the compiler but is being phased out; \ | |
353 | it will become a hard error"; | |
354 | ||
355 | let explanation = if lint_id == LintId::of(builtin::UNSTABLE_NAME_COLLISIONS) { | |
356 | "once this method is added to the standard library, \ | |
357 | the ambiguity may cause an error or change in behavior!" | |
358 | .to_owned() | |
359 | } else if lint_id == LintId::of(builtin::MUTABLE_BORROW_RESERVATION_CONFLICT) { | |
360 | "this borrowing pattern was not meant to be accepted, \ | |
361 | and may become a hard error in the future" | |
362 | .to_owned() | |
363 | } else if let Some(edition) = future_incompatible.edition { | |
364 | format!("{} in the {} edition!", STANDARD_MESSAGE, edition) | |
365 | } else { | |
366 | format!("{} in a future release!", STANDARD_MESSAGE) | |
367 | }; | |
368 | let citation = format!("for more information, see {}", future_incompatible.reference); | |
369 | err.warn(&explanation); | |
370 | err.note(&citation); | |
371 | } | |
dfeec247 | 372 | |
74b04a01 XL |
373 | // Finally, run `decorate`. This function is also responsible for emitting the diagnostic. |
374 | decorate(LintDiagnosticBuilder::new(err)); | |
375 | } | |
376 | struct_lint_level_impl(sess, lint, level, src, span, Box::new(decorate)) | |
dfeec247 XL |
377 | } |
378 | ||
379 | /// Returns whether `span` originates in a foreign crate's external macro. | |
380 | /// | |
381 | /// This is used to test whether a lint should not even begin to figure out whether it should | |
382 | /// be reported on the current node. | |
383 | pub fn in_external_macro(sess: &Session, span: Span) -> bool { | |
384 | let expn_data = span.ctxt().outer_expn_data(); | |
385 | match expn_data.kind { | |
29967ef6 XL |
386 | ExpnKind::Inlined | ExpnKind::Root | ExpnKind::Desugaring(DesugaringKind::ForLoop(_)) => { |
387 | false | |
388 | } | |
dfeec247 XL |
389 | ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external" |
390 | ExpnKind::Macro(MacroKind::Bang, _) => { | |
ba9703b0 XL |
391 | // Dummy span for the `def_site` means it's an external macro. |
392 | expn_data.def_site.is_dummy() || sess.source_map().is_imported(expn_data.def_site) | |
dfeec247 | 393 | } |
3dfed10e | 394 | ExpnKind::Macro { .. } => true, // definitely a plugin |
dfeec247 XL |
395 | } |
396 | } |