@@ -229,6 +229,19 @@ enum VarKind {
229
229
Upvar(HirId, Symbol),
230
230
}
231
231
232
+ struct CollectLitsVisitor<'tcx> {
233
+ lit_exprs: Vec<&'tcx hir::Expr<'tcx>>,
234
+ }
235
+
236
+ impl<'tcx> Visitor<'tcx> for CollectLitsVisitor<'tcx> {
237
+ fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
238
+ if let hir::ExprKind::Lit(_) = expr.kind {
239
+ self.lit_exprs.push(expr);
240
+ }
241
+ intravisit::walk_expr(self, expr);
242
+ }
243
+ }
244
+
232
245
struct IrMaps<'tcx> {
233
246
tcx: TyCtxt<'tcx>,
234
247
live_node_map: HirIdMap<LiveNode>,
@@ -1333,7 +1346,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
1333
1346
1334
1347
impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
1335
1348
fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
1336
- self.check_unused_vars_in_pat(&local.pat, None, |spans, hir_id, ln, var| {
1349
+ self.check_unused_vars_in_pat(&local.pat, None, None, |spans, hir_id, ln, var| {
1337
1350
if local.init.is_some() {
1338
1351
self.warn_about_dead_assign(spans, hir_id, ln, var);
1339
1352
}
@@ -1348,7 +1361,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
1348
1361
}
1349
1362
1350
1363
fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
1351
- self.check_unused_vars_in_pat(&arm.pat, None, |_, _, _, _| {});
1364
+ self.check_unused_vars_in_pat(&arm.pat, None, None, |_, _, _, _| {});
1352
1365
intravisit::walk_arm(self, arm);
1353
1366
}
1354
1367
}
@@ -1387,7 +1400,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
1387
1400
}
1388
1401
1389
1402
hir::ExprKind::Let(let_expr) => {
1390
- this.check_unused_vars_in_pat(let_expr.pat, None, |_, _, _, _| {});
1403
+ this.check_unused_vars_in_pat(let_expr.pat, None, None, |_, _, _, _| {});
1391
1404
}
1392
1405
1393
1406
// no correctness conditions related to liveness
@@ -1508,20 +1521,26 @@ impl<'tcx> Liveness<'_, 'tcx> {
1508
1521
1509
1522
fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) {
1510
1523
for p in body.params {
1511
- self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| {
1512
- if !self.live_on_entry(ln, var) {
1513
- self.report_unused_assign(hir_id, spans, var, |name| {
1514
- format!("value passed to `{}` is never read", name)
1515
- });
1516
- }
1517
- });
1524
+ self.check_unused_vars_in_pat(
1525
+ &p.pat,
1526
+ Some(entry_ln),
1527
+ Some(body),
1528
+ |spans, hir_id, ln, var| {
1529
+ if !self.live_on_entry(ln, var) {
1530
+ self.report_unused_assign(hir_id, spans, var, |name| {
1531
+ format!("value passed to `{}` is never read", name)
1532
+ });
1533
+ }
1534
+ },
1535
+ );
1518
1536
}
1519
1537
}
1520
1538
1521
1539
fn check_unused_vars_in_pat(
1522
1540
&self,
1523
1541
pat: &hir::Pat<'_>,
1524
1542
entry_ln: Option<LiveNode>,
1543
+ opt_body: Option<&hir::Body<'_>>,
1525
1544
on_used_on_entry: impl Fn(Vec<Span>, HirId, LiveNode, Variable),
1526
1545
) {
1527
1546
// In an or-pattern, only consider the variable; any later patterns must have the same
@@ -1549,7 +1568,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
1549
1568
hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect();
1550
1569
on_used_on_entry(spans, id, ln, var);
1551
1570
} else {
1552
- self.report_unused(hir_ids_and_spans, ln, var, can_remove);
1571
+ self.report_unused(hir_ids_and_spans, ln, var, can_remove, pat, opt_body );
1553
1572
}
1554
1573
}
1555
1574
}
@@ -1561,6 +1580,8 @@ impl<'tcx> Liveness<'_, 'tcx> {
1561
1580
ln: LiveNode,
1562
1581
var: Variable,
1563
1582
can_remove: bool,
1583
+ pat: &hir::Pat<'_>,
1584
+ opt_body: Option<&hir::Body<'_>>,
1564
1585
) {
1565
1586
let first_hir_id = hir_ids_and_spans[0].0;
1566
1587
@@ -1664,6 +1685,9 @@ impl<'tcx> Liveness<'_, 'tcx> {
1664
1685
.collect::<Vec<_>>(),
1665
1686
|lint| {
1666
1687
let mut err = lint.build(&format!("unused variable: `{}`", name));
1688
+ if self.has_added_lit_match_name_span(&name, opt_body, &mut err) {
1689
+ err.span_label(pat.span, "unused variable");
1690
+ }
1667
1691
err.multipart_suggestion(
1668
1692
"if this is intentional, prefix it with an underscore",
1669
1693
non_shorthands,
@@ -1677,6 +1701,42 @@ impl<'tcx> Liveness<'_, 'tcx> {
1677
1701
}
1678
1702
}
1679
1703
1704
+ fn has_added_lit_match_name_span(
1705
+ &self,
1706
+ name: &str,
1707
+ opt_body: Option<&hir::Body<'_>>,
1708
+ err: &mut rustc_errors::DiagnosticBuilder<'_, ()>,
1709
+ ) -> bool {
1710
+ let mut has_litstring = false;
1711
+ let Some(opt_body) = opt_body else {return false;};
1712
+ let mut visitor = CollectLitsVisitor { lit_exprs: vec![] };
1713
+ intravisit::walk_body(&mut visitor, opt_body);
1714
+ for lit_expr in visitor.lit_exprs {
1715
+ let hir::ExprKind::Lit(litx) = &lit_expr.kind else { continue };
1716
+ let rustc_ast::LitKind::Str(syb, _) = litx.node else{ continue; };
1717
+ let name_str: &str = syb.as_str();
1718
+ let mut name_pa = String::from("{");
1719
+ name_pa.push_str(&name);
1720
+ name_pa.push('}');
1721
+ if name_str.contains(&name_pa) {
1722
+ err.span_label(
1723
+ lit_expr.span,
1724
+ "you might have meant to use string interpolation in this string literal",
1725
+ );
1726
+ err.multipart_suggestion(
1727
+ "string interpolation only works in `format!` invocations",
1728
+ vec![
1729
+ (lit_expr.span.shrink_to_lo(), "format!(".to_string()),
1730
+ (lit_expr.span.shrink_to_hi(), ")".to_string()),
1731
+ ],
1732
+ Applicability::MachineApplicable,
1733
+ );
1734
+ has_litstring = true;
1735
+ }
1736
+ }
1737
+ has_litstring
1738
+ }
1739
+
1680
1740
fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
1681
1741
if !self.live_on_exit(ln, var) {
1682
1742
self.report_unused_assign(hir_id, spans, var, |name| {
0 commit comments