Skip to content

Commit 3ea438e

Browse files
authored
Rollup merge of #116787 - a-lafrance:span-internal-lint, r=oli-obk
Implement an internal lint encouraging use of `Span::eq_ctxt` Adds a new Rustc internal lint that forbids use of `.ctxt() == .ctxt()` for spans, encouraging use of `.eq_ctxt()` instead (see #49509). Also fixed a few violations of the lint in the Rustc codebase (a fun additional way I could test my code). Edit: MIR opt folks I believe that's why you're CC'ed, just a heads up. Two things I'm not sure about: 1. The way I chose to detect calls to `Span::ctxt`. I know adding diagnostic items to methods is generally discouraged, but after some searching and experimenting I couldn't find anything else that worked, so I went with it. That said, I'm happy to implement something different if there's a better way out there. (For what it's worth, if there is a better way, it might be worth documenting in the rustc-dev-guide, which I'm happy to take care of) 2. The error message for the lint. Ideally it would probably be good to give some context as to why the suggestion is made (e.g. `rustc::default_hash_types` tells the user that it's because of performance), but I don't have that context so I couldn't put it in the error message. Happy to iterate on the error message based on feedback during review. r? ``@oli-obk``
2 parents 00f529d + e89d4d4 commit 3ea438e

25 files changed

+89
-21
lines changed

compiler/rustc_hir_typeck/src/callee.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
650650
.sess
651651
.source_map()
652652
.is_multiline(call_expr.span.with_lo(callee_expr.span.hi()))
653-
&& call_expr.span.ctxt() == callee_expr.span.ctxt();
653+
&& call_expr.span.eq_ctxt(callee_expr.span);
654654
if call_is_multiline {
655655
err.span_suggestion(
656656
callee_expr.span.shrink_to_hi(),

compiler/rustc_lint/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,8 @@ lint_renamed_lint = lint `{$name}` has been renamed to `{$replace}`
494494
495495
lint_requested_level = requested on the command line with `{$level} {$lint_name}`
496496
497+
lint_span_use_eq_ctxt = use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`
498+
497499
lint_supertrait_as_deref_target = `{$t}` implements `Deref` with supertrait `{$target_principal}` as target
498500
.label = target type is set here
499501

compiler/rustc_lint/src/internal.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
44
use crate::lints::{
55
BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword,
6-
QueryInstability, TyQualified, TykindDiag, TykindKind, UntranslatableDiag,
6+
QueryInstability, SpanUseEqCtxtDiag, TyQualified, TykindDiag, TykindKind, UntranslatableDiag,
77
UntranslatableDiagnosticTrivial,
88
};
99
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
1010
use rustc_ast as ast;
1111
use rustc_hir::def::Res;
1212
use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
13-
use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
13+
use rustc_hir::{BinOp, BinOpKind, HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
1414
use rustc_middle::ty;
1515
use rustc_session::{declare_lint_pass, declare_tool_lint};
1616
use rustc_span::hygiene::{ExpnKind, MacroKind};
@@ -537,3 +537,33 @@ impl LateLintPass<'_> for BadOptAccess {
537537
}
538538
}
539539
}
540+
541+
declare_tool_lint! {
542+
pub rustc::SPAN_USE_EQ_CTXT,
543+
Allow,
544+
"forbid uses of `==` with `Span::ctxt`, suggest `Span::eq_ctxt` instead",
545+
report_in_external_macro: true
546+
}
547+
548+
declare_lint_pass!(SpanUseEqCtxt => [SPAN_USE_EQ_CTXT]);
549+
550+
impl<'tcx> LateLintPass<'tcx> for SpanUseEqCtxt {
551+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
552+
if let ExprKind::Binary(BinOp { node: BinOpKind::Eq, .. }, lhs, rhs) = expr.kind {
553+
if is_span_ctxt_call(cx, lhs) && is_span_ctxt_call(cx, rhs) {
554+
cx.emit_spanned_lint(SPAN_USE_EQ_CTXT, expr.span, SpanUseEqCtxtDiag);
555+
}
556+
}
557+
}
558+
}
559+
560+
fn is_span_ctxt_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
561+
match &expr.kind {
562+
ExprKind::MethodCall(..) => cx
563+
.typeck_results()
564+
.type_dependent_def_id(expr.hir_id)
565+
.is_some_and(|call_did| cx.tcx.is_diagnostic_item(sym::SpanCtxt, call_did)),
566+
567+
_ => false,
568+
}
569+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,8 @@ fn register_internals(store: &mut LintStore) {
531531
store.register_late_mod_pass(|_| Box::new(BadOptAccess));
532532
store.register_lints(&PassByValue::get_lints());
533533
store.register_late_mod_pass(|_| Box::new(PassByValue));
534+
store.register_lints(&SpanUseEqCtxt::get_lints());
535+
store.register_late_mod_pass(|_| Box::new(SpanUseEqCtxt));
534536
// FIXME(davidtwco): deliberately do not include `UNTRANSLATABLE_DIAGNOSTIC` and
535537
// `DIAGNOSTIC_OUTSIDE_OF_IMPL` here because `-Wrustc::internal` is provided to every crate and
536538
// these lints will trigger all of the time - change this once migration to diagnostic structs
@@ -548,6 +550,7 @@ fn register_internals(store: &mut LintStore) {
548550
LintId::of(USAGE_OF_QUALIFIED_TY),
549551
LintId::of(EXISTING_DOC_KEYWORD),
550552
LintId::of(BAD_OPT_ACCESS),
553+
LintId::of(SPAN_USE_EQ_CTXT),
551554
],
552555
);
553556
}

compiler/rustc_lint/src/lints.rs

+4
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,10 @@ pub struct QueryInstability {
900900
pub query: Symbol,
901901
}
902902

903+
#[derive(LintDiagnostic)]
904+
#[diag(lint_span_use_eq_ctxt)]
905+
pub struct SpanUseEqCtxtDiag;
906+
903907
#[derive(LintDiagnostic)]
904908
#[diag(lint_tykind_kind)]
905909
pub struct TykindKind {

compiler/rustc_mir_transform/src/coverage/spans.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ impl<'a> CoverageSpansGenerator<'a> {
404404

405405
let Some(visible_macro) = curr.visible_macro(self.body_span) else { return };
406406
if let Some(prev) = &self.some_prev
407-
&& prev.expn_span.ctxt() == curr.expn_span.ctxt()
407+
&& prev.expn_span.eq_ctxt(curr.expn_span)
408408
{
409409
return;
410410
}

compiler/rustc_passes/src/loops.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ impl<'a, 'hir> CheckLoopVisitor<'a, 'hir> {
231231
AsyncClosure(closure_span) => {
232232
self.sess.emit_err(BreakInsideAsyncBlock { span, closure_span, name });
233233
}
234-
UnlabeledBlock(block_span) if is_break && block_span.ctxt() == break_span.ctxt() => {
234+
UnlabeledBlock(block_span) if is_break && block_span.eq_ctxt(break_span) => {
235235
let suggestion = Some(OutsideLoopSuggestion { block_span, break_span });
236236
self.sess.emit_err(OutsideLoop { span, name, is_break, suggestion });
237237
}

compiler/rustc_span/src/span_encoding.rs

+1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ impl Span {
212212

213213
/// This function is used as a fast path when decoding the full `SpanData` is not necessary.
214214
/// It's a cut-down version of `data_untracked`.
215+
#[cfg_attr(not(test), rustc_diagnostic_item = "SpanCtxt")]
215216
#[inline]
216217
pub fn ctxt(self) -> SyntaxContext {
217218
if self.len_with_tag_or_marker != BASE_LEN_INTERNED_MARKER {

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ symbols! {
303303
SliceIndex,
304304
SliceIter,
305305
Some,
306+
SpanCtxt,
306307
String,
307308
StructuralEq,
308309
StructuralPartialEq,

src/tools/clippy/clippy_lints/src/dereference.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ fn deref_method_same_type<'tcx>(result_ty: Ty<'tcx>, arg_ty: Ty<'tcx>) -> bool {
701701

702702
fn in_postfix_position<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool {
703703
if let Some(parent) = get_parent_expr(cx, e)
704-
&& parent.span.ctxt() == e.span.ctxt()
704+
&& parent.span.eq_ctxt(e.span)
705705
{
706706
match parent.kind {
707707
ExprKind::Call(child, _) | ExprKind::MethodCall(_, child, _, _) | ExprKind::Index(child, _, _)

src/tools/clippy/clippy_lints/src/entry.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ fn try_parse_contains<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Optio
241241
},
242242
],
243243
_,
244-
) if key_span.ctxt() == expr.span.ctxt() => {
244+
) if key_span.eq_ctxt(expr.span) => {
245245
let id = cx.typeck_results().type_dependent_def_id(expr.hir_id)?;
246246
let expr = ContainsExpr {
247247
negated,

src/tools/clippy/clippy_lints/src/formatting.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ fn check_array(cx: &EarlyContext<'_>, expr: &Expr) {
274274
for element in array {
275275
if_chain! {
276276
if let ExprKind::Binary(ref op, ref lhs, _) = element.kind;
277-
if has_unary_equivalent(op.node) && lhs.span.ctxt() == op.span.ctxt();
277+
if has_unary_equivalent(op.node) && lhs.span.eq_ctxt(op.span);
278278
let space_span = lhs.span.between(op.span);
279279
if let Some(space_snippet) = snippet_opt(cx, space_span);
280280
let lint_span = lhs.span.with_lo(lhs.span.hi());

src/tools/clippy/clippy_lints/src/let_with_type_underscore.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ impl LateLintPass<'_> for UnderscoreTyped {
3131
if !in_external_macro(cx.tcx.sess, local.span);
3232
if let Some(ty) = local.ty; // Ensure that it has a type defined
3333
if let TyKind::Infer = &ty.kind; // that type is '_'
34-
if local.span.ctxt() == ty.span.ctxt();
34+
if local.span.eq_ctxt(ty.span);
3535
then {
3636
// NOTE: Using `is_from_proc_macro` on `init` will require that it's initialized,
3737
// this doesn't. Alternatively, `WithSearchPat` can be implemented for `Ty`

src/tools/clippy/clippy_lints/src/manual_let_else.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ impl<'tcx> QuestionMark {
5959
let Some(init) = local.init &&
6060
local.els.is_none() &&
6161
local.ty.is_none() &&
62-
init.span.ctxt() == stmt.span.ctxt() &&
62+
init.span.eq_ctxt(stmt.span) &&
6363
let Some(if_let_or_match) = IfLetOrMatch::parse(cx, init)
6464
{
6565
match if_let_or_match {

src/tools/clippy/clippy_lints/src/matches/collapsible_match.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fn check_arm<'tcx>(
5757
}
5858
},
5959
};
60-
if outer_pat.span.ctxt() == inner_scrutinee.span.ctxt();
60+
if outer_pat.span.eq_ctxt(inner_scrutinee.span);
6161
// match expression must be a local binding
6262
// match <local> { .. }
6363
if let Some(binding_id) = path_to_local(peel_ref_operators(cx, inner_scrutinee));

src/tools/clippy/clippy_lints/src/matches/manual_utils.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ where
119119
// it's being passed by value.
120120
let scrutinee = peel_hir_expr_refs(scrutinee).0;
121121
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
122-
let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
122+
let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && scrutinee.precedence().order() < PREC_POSTFIX {
123123
format!("({scrutinee_str})")
124124
} else {
125125
scrutinee_str.into()
@@ -130,7 +130,7 @@ where
130130
if_chain! {
131131
if !some_expr.needs_unsafe_block;
132132
if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
133-
if func.span.ctxt() == some_expr.expr.span.ctxt();
133+
if func.span.eq_ctxt(some_expr.expr.span);
134134
then {
135135
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
136136
} else {

src/tools/clippy/clippy_lints/src/methods/map_unwrap_or.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub(super) fn check<'tcx>(
5656
// lint, with note if neither arg is > 1 line and both map() and
5757
// unwrap_or_else() have the same span
5858
let multiline = map_snippet.lines().count() > 1 || unwrap_snippet.lines().count() > 1;
59-
let same_span = map_arg.span.ctxt() == unwrap_arg.span.ctxt();
59+
let same_span = map_arg.span.eq_ctxt(unwrap_arg.span);
6060
if same_span && !multiline {
6161
let var_snippet = snippet(cx, recv.span, "..");
6262
span_lint_and_sugg(

src/tools/clippy/clippy_lints/src/needless_question_mark.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
125125
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar(_)) = &arg.kind;
126126
if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind;
127127
if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, ..)) = &called.kind;
128-
if expr.span.ctxt() == inner_expr.span.ctxt();
128+
if expr.span.eq_ctxt(inner_expr.span);
129129
let expr_ty = cx.typeck_results().expr_ty(expr);
130130
let inner_ty = cx.typeck_results().expr_ty(inner_expr);
131131
if expr_ty == inner_ty;

src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
5151
|| (path.ident.name == sym!(set_mode)
5252
&& cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()));
5353
if let ExprKind::Lit(_) = param.kind;
54-
if param.span.ctxt() == expr.span.ctxt();
54+
if param.span.eq_ctxt(expr.span);
5555

5656
then {
5757
let Some(snip) = snippet_opt(cx, param.span) else {
@@ -70,7 +70,7 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
7070
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
7171
if match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE);
7272
if let ExprKind::Lit(_) = param.kind;
73-
if param.span.ctxt() == expr.span.ctxt();
73+
if param.span.eq_ctxt(expr.span);
7474
if let Some(snip) = snippet_opt(cx, param.span);
7575
if !snip.starts_with("0o");
7676
then {

src/tools/clippy/clippy_lints/src/redundant_async_block.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock {
4848
let Some(body_expr) = desugar_async_block(cx, expr) &&
4949
let Some(expr) = desugar_await(peel_blocks(body_expr)) &&
5050
// The await prefix must not come from a macro as its content could change in the future.
51-
expr.span.ctxt() == body_expr.span.ctxt() &&
51+
expr.span.eq_ctxt(body_expr.span) &&
5252
// An async block does not have immediate side-effects from a `.await` point-of-view.
5353
(!expr.can_have_side_effects() || desugar_async_block(cx, expr).is_some()) &&
5454
let Some(shortened_span) = walk_span_to_context(expr.span, span.ctxt())

src/tools/clippy/clippy_lints/src/reference.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl EarlyLintPass for DerefAddrOf {
5050
if_chain! {
5151
if let ExprKind::Unary(UnOp::Deref, ref deref_target) = e.kind;
5252
if let ExprKind::AddrOf(_, ref mutability, ref addrof_target) = without_parens(deref_target).kind;
53-
if deref_target.span.ctxt() == e.span.ctxt();
53+
if deref_target.span.eq_ctxt(e.span);
5454
if !addrof_target.span.from_expansion();
5555
then {
5656
let mut applicability = Applicability::MachineApplicable;

src/tools/clippy/clippy_lints/src/suspicious_xor_used_as_pow.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl LateLintPass<'_> for ConfusingXorAndPow {
3333
if !in_external_macro(cx.sess(), expr.span)
3434
&& let ExprKind::Binary(op, left, right) = &expr.kind
3535
&& op.node == BinOpKind::BitXor
36-
&& left.span.ctxt() == right.span.ctxt()
36+
&& left.span.eq_ctxt(right.span)
3737
&& let ExprKind::Lit(lit_left) = &left.kind
3838
&& let ExprKind::Lit(lit_right) = &right.kind
3939
&& matches!(lit_right.node, LitKind::Int(..) | LitKind::Float(..))

src/tools/clippy/clippy_utils/src/macros.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ impl<'a> PanicExpn<'a> {
245245
return None;
246246
};
247247
let result = match name {
248-
"panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
248+
"panic" if arg.span.eq_ctxt(expr.span) => Self::Empty,
249249
"panic" | "panic_str" => Self::Str(arg),
250250
"panic_display" | "panic_cold_display" => {
251251
let ExprKind::AddrOf(_, _, e) = &arg.kind else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Test the `rustc::span_use_eq_ctxt` internal lint
2+
// compile-flags: -Z unstable-options
3+
4+
#![feature(rustc_private)]
5+
#![deny(rustc::span_use_eq_ctxt)]
6+
#![crate_type = "lib"]
7+
8+
extern crate rustc_span;
9+
use rustc_span::Span;
10+
11+
pub fn f(s: Span, t: Span) -> bool {
12+
s.ctxt() == t.ctxt() //~ ERROR use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: use `.eq_ctxt()` instead of `.ctxt() == .ctxt()`
2+
--> $DIR/span_use_eq_ctxt.rs:12:5
3+
|
4+
LL | s.ctxt() == t.ctxt()
5+
| ^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/span_use_eq_ctxt.rs:5:9
9+
|
10+
LL | #![deny(rustc::span_use_eq_ctxt)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to previous error
14+

0 commit comments

Comments
 (0)