Skip to content

Commit 651307c

Browse files
committed
emit warning for every reference of the same metavariable and refactor tests
1 parent 7c0dc33 commit 651307c

File tree

3 files changed

+516
-204
lines changed

3 files changed

+516
-204
lines changed

clippy_lints/src/expr_metavars_in_unsafe.rs

+98-76
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,24 @@ fn extract_metavariable<'a>(
151151
}
152152
}
153153

154+
#[derive(Debug)]
155+
struct ReferencedMetavar {
156+
/// unsafe { $v; };
157+
/// ^^
158+
span: Span,
159+
/// unsafe { $v; };
160+
/// ^^^^^^^^
161+
unsafe_block_span: Span,
162+
}
163+
164+
#[derive(Debug)]
154165
enum MetavarState {
155-
/// The metavariable was referenced inside of an unsafe block
166+
/// The metavariable was referenced inside of an unsafe block.
156167
Referenced {
157-
/// unsafe { $v }
158-
/// ^^
159-
reference_span: Span,
160-
/// macro_rules! x { (... $v:expr ...) => {} }
168+
occurences: Vec<ReferencedMetavar>,
169+
/// macro_rules! x { (... $x:expr ...) => {} }
161170
/// ^^^^^^^
162171
decl_span: Span,
163-
/// unsafe { $v }
164-
/// ^^^^^^^^
165-
unsafe_bl_span: Span,
166172
},
167173

168174
/// The metavariable was not found yet, but we're still looking for it
@@ -209,6 +215,29 @@ impl UnsafeMetavariableFinder {
209215
self.enclosing.clear();
210216
}
211217

218+
fn report_warnings(&self, cx: &LateContext<'_>) {
219+
for metavar in self.expr_variables.values() {
220+
if let MetavarState::Referenced { decl_span, occurences } = metavar {
221+
for occurence in occurences {
222+
span_lint_and_then(
223+
cx,
224+
EXPR_METAVARS_IN_UNSAFE,
225+
occurence.span,
226+
"expanding an expression metavariable in an unsafe block",
227+
|diag| {
228+
diag.span_note(*decl_span, "metavariable declared here");
229+
diag.span_note(occurence.unsafe_block_span, "unsafe block entered here");
230+
diag.help("consider expanding it outside of this block, e.g. by storing it in a variable");
231+
diag.note(
232+
"this allows the user of the macro to write unsafe code without an unsafe block (at the callsite)",
233+
);
234+
},
235+
);
236+
}
237+
}
238+
}
239+
}
240+
212241
/// Given the following delimited token stream:
213242
/// ```ignore
214243
/// macro_rules! x { (... $var:expr ...) => { ... } }
@@ -258,41 +287,47 @@ impl UnsafeMetavariableFinder {
258287
/// }
259288
/// ```
260289
/// (statics don't have any extra rules so they are also handled here)
261-
fn find_metavariables_in_const_static<'b>(&mut self, tts: &mut Peekable<impl Iterator<Item = &'b TokenTree>>) {
290+
///
291+
/// Also note that this might be a const generic parameter and not a const item, which only
292+
/// spans until the next comma, or right angle bracket:
293+
/// ```ignore
294+
/// struct T<const N: usize = { $v }, const O: usize = 1>;
295+
/// ^^^^^^^^^^^^^^^^^ `,` ends the const generic
296+
/// struct T<const N: usize = { $v }>;
297+
/// ^^^^^^^^^^^^^^^^^ `>` ends the const generic
298+
/// ```
299+
/// Returns the terminating token. This is either `,` `;` or `>` as explained above
300+
fn find_metavariables_in_const_static<'b>(
301+
&mut self,
302+
tts: &mut Peekable<impl Iterator<Item = &'b TokenTree>>,
303+
) -> Option<TokenKind> {
262304
let mut angle_bracket_depth = 0u32;
263305

306+
let mut last_token = None;
264307
self.enclosing.push(EnclosingNode::Body);
265308
for tt in tts {
266309
match tt {
267310
TokenTree::Delimited(.., body) => self.find_metavariables_in_body(body),
268-
TokenTree::Token(
269-
Token {
270-
kind: TokenKind::Semi, ..
271-
},
272-
_,
273-
) => {
274-
// We might be in a const generic position: don't break early for the first semicolon in
275-
// `const _: Ty<{ 2; 1 }> = ...;`
276-
if angle_bracket_depth == 0 {
277-
break;
311+
TokenTree::Token(token, _) => {
312+
last_token = Some(token.kind.clone());
313+
match token.kind {
314+
// We might be in a const generic position: don't break early for the first comma in
315+
// `const _: Ty<1, 2> = ...,`
316+
TokenKind::Semi | TokenKind::Comma if angle_bracket_depth == 0 => {
317+
break;
318+
},
319+
TokenKind::Lt => angle_bracket_depth += 1,
320+
// If we see a `>` without having seen any `<`, it must mean that we are in a const
321+
// generic position and this ends it.
322+
TokenKind::Gt if angle_bracket_depth == 0 => break,
323+
TokenKind::Gt => angle_bracket_depth -= 1,
324+
_ => {},
278325
}
279326
},
280-
TokenTree::Token(
281-
Token {
282-
kind: TokenKind::Lt, ..
283-
},
284-
_,
285-
) => angle_bracket_depth += 1,
286-
TokenTree::Token(
287-
Token {
288-
kind: TokenKind::Gt, ..
289-
},
290-
_,
291-
) => angle_bracket_depth = angle_bracket_depth.saturating_sub(1),
292-
TokenTree::Token(..) => {},
293327
}
294328
}
295329
self.enclosing.pop();
330+
last_token
296331
}
297332

298333
/// ```ignore
@@ -312,11 +347,11 @@ impl UnsafeMetavariableFinder {
312347
let mut angle_bracket_depth = 0u32;
313348

314349
self.enclosing.push(EnclosingNode::Body);
315-
for tt in tts {
350+
while let Some(tt) = tts.next() {
316351
match tt {
317352
TokenTree::Delimited(.., Delimiter::Brace, body) => {
318353
self.find_metavariables_in_body(body);
319-
// We might be in a const generic position:
354+
// We might be in a const generic argument position:
320355
// fn() -> Ty<{ ... }> {}
321356
// ^^^^^^^
322357
// which we know if angle_bracket_depth > 0,
@@ -326,19 +361,21 @@ impl UnsafeMetavariableFinder {
326361
break;
327362
}
328363
},
329-
TokenTree::Token(
330-
Token {
331-
kind: TokenKind::Lt, ..
332-
},
333-
_,
334-
) => angle_bracket_depth += 1,
335-
TokenTree::Token(
336-
Token {
337-
kind: TokenKind::Gt, ..
338-
},
339-
_,
340-
) => angle_bracket_depth = angle_bracket_depth.saturating_sub(1),
341-
_ => {},
364+
TokenTree::Token(token, _) => {
365+
match token.kind {
366+
TokenKind::Ident(kw::Const, false) => {
367+
// fn ...<const
368+
if let Some(TokenKind::Gt) = self.find_metavariables_in_const_static(tts) {
369+
// should not underflow, but macros don't have to be valid syntax if unexpanded
370+
angle_bracket_depth = angle_bracket_depth.saturating_sub(1);
371+
}
372+
},
373+
TokenKind::Lt => angle_bracket_depth += 1,
374+
TokenKind::Gt => angle_bracket_depth = angle_bracket_depth.saturating_sub(1),
375+
_ => {},
376+
}
377+
},
378+
TokenTree::Delimited(..) => {},
342379
}
343380
}
344381
self.enclosing.pop();
@@ -377,18 +414,25 @@ impl UnsafeMetavariableFinder {
377414
EnclosingNode::UnsafeBlock(span) => Some(*span),
378415
});
379416

380-
match (&*state, unsafe_block_span) {
381-
(MetavarState::Referenced { .. }, Some(_)) => {},
417+
match (&mut *state, unsafe_block_span) {
418+
(MetavarState::Referenced { occurences, .. }, Some(unsafe_block_span)) => {
419+
occurences.push(ReferencedMetavar {
420+
span,
421+
unsafe_block_span,
422+
});
423+
},
382424
(_, None) => {
383425
// Metavar was referenced outside of an unsafe block. Set to
384426
// suppressed because we don't want to lint.
385427
*state = MetavarState::Suppressed;
386428
},
387-
(MetavarState::Unreferenced { decl_span }, Some(unsafe_bl_span)) => {
429+
(MetavarState::Unreferenced { decl_span }, Some(unsafe_block_span)) => {
388430
*state = MetavarState::Referenced {
389-
reference_span: span,
431+
occurences: vec![ReferencedMetavar {
432+
span,
433+
unsafe_block_span,
434+
}],
390435
decl_span: *decl_span,
391-
unsafe_bl_span,
392436
};
393437
},
394438
(MetavarState::Suppressed, _) => {},
@@ -433,6 +477,8 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
433477
// ^^^^^^^ look in here for references to one of the collected metavariables
434478
with_token_stream(tts.next(), |body| finder.find_metavariables_in_body(body));
435479

480+
finder.report_warnings(cx);
481+
436482
// skip any trailing semicolons
437483
// (...) => {};;;
438484
// ^^^
@@ -452,30 +498,6 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
452498
.is_some()
453499
{}
454500
}
455-
456-
for metavar in finder.expr_variables.values() {
457-
if let &MetavarState::Referenced {
458-
decl_span,
459-
reference_span,
460-
unsafe_bl_span,
461-
} = metavar
462-
{
463-
span_lint_and_then(
464-
cx,
465-
EXPR_METAVARS_IN_UNSAFE,
466-
reference_span,
467-
"expanding an expression metavariable in an unsafe block",
468-
|diag| {
469-
diag.span_note(decl_span, "metavariable declared here");
470-
diag.span_note(unsafe_bl_span, "unsafe block entered here");
471-
diag.help("consider expanding it outside of this block, e.g. by storing it in a variable");
472-
diag.note(
473-
"this allows the user of the macro to write unsafe code without an unsafe block (at the callsite)",
474-
);
475-
},
476-
);
477-
}
478-
}
479501
}
480502
}
481503
}

0 commit comments

Comments
 (0)