Skip to content

Commit eeb5276

Browse files
committed
Add deny lint to prevent untranslatable diagnostics using static strings
1 parent a7aa205 commit eeb5276

File tree

5 files changed

+87
-1
lines changed

5 files changed

+87
-1
lines changed

compiler/rustc_lint/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ lint_diag_out_of_impl =
9999
100100
lint_untranslatable_diag = diagnostics should be created using translatable messages
101101
102+
lint_trivial_untranslatable_diag = diagnostic with static strings only
103+
102104
lint_bad_opt_access = {$msg}
103105
104106
lint_cstring_ptr = getting the inner pointer of a temporary `CString`

compiler/rustc_lint/src/internal.rs

+79-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use crate::lints::{
55
BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword,
66
QueryInstability, TyQualified, TykindDiag, TykindKind, UntranslatableDiag,
7+
UntranslatableDiagnosticTrivial,
78
};
89
use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
910
use rustc_ast as ast;
@@ -366,7 +367,15 @@ declare_tool_lint! {
366367
report_in_external_macro: true
367368
}
368369

369-
declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
370+
declare_tool_lint! {
371+
/// The `untranslatable_diagnostic_trivial` lint detects diagnostics created using only static strings.
372+
pub rustc::UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL,
373+
Deny,
374+
"prevent creation of diagnostics which cannot be translated, which use only static strings",
375+
report_in_external_macro: true
376+
}
377+
378+
declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL, UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL ]);
370379

371380
impl LateLintPass<'_> for Diagnostics {
372381
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
@@ -423,6 +432,75 @@ impl LateLintPass<'_> for Diagnostics {
423432
}
424433
}
425434

435+
impl EarlyLintPass for Diagnostics {
436+
#[allow(unused_must_use)]
437+
fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) {
438+
// Looking for a straight chain of method calls from 'struct_span_err' to 'emit'.
439+
let ast::StmtKind::Semi(expr) = &stmt.kind else {
440+
return;
441+
};
442+
let ast::ExprKind::MethodCall(meth) = &expr.kind else {
443+
return;
444+
};
445+
if meth.seg.ident.name != sym::emit || !meth.args.is_empty() {
446+
return;
447+
}
448+
let mut segments = vec![];
449+
let mut cur = &meth.receiver;
450+
let fake = &[].into();
451+
loop {
452+
match &cur.kind {
453+
ast::ExprKind::Call(func, args) => {
454+
if let ast::ExprKind::Path(_, path) = &func.kind {
455+
segments.push((path.segments.last().unwrap().ident.name, args))
456+
}
457+
break;
458+
}
459+
ast::ExprKind::MethodCall(method) => {
460+
segments.push((method.seg.ident.name, &method.args));
461+
cur = &method.receiver;
462+
}
463+
ast::ExprKind::MacCall(mac) => {
464+
segments.push((mac.path.segments.last().unwrap().ident.name, fake));
465+
break;
466+
}
467+
_ => {
468+
break;
469+
}
470+
}
471+
}
472+
segments.reverse();
473+
if segments.is_empty() {
474+
return;
475+
}
476+
if segments[0].0.as_str() != "struct_span_err" {
477+
return;
478+
}
479+
if !segments.iter().all(|(name, args)| {
480+
let arg = match name.as_str() {
481+
"struct_span_err" | "span_note" | "span_label" | "span_help" => &args[1],
482+
"note" | "help" => &args[0],
483+
_ => {
484+
return false;
485+
}
486+
};
487+
if let ast::ExprKind::Lit(lit) = arg.kind
488+
&& let ast::token::LitKind::Str = lit.kind {
489+
true
490+
} else {
491+
false
492+
}
493+
}) {
494+
return;
495+
}
496+
cx.emit_spanned_lint(
497+
UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL,
498+
stmt.span,
499+
UntranslatableDiagnosticTrivial,
500+
);
501+
}
502+
}
503+
426504
declare_tool_lint! {
427505
/// The `bad_opt_access` lint detects accessing options by field instead of
428506
/// the wrapper function.

compiler/rustc_lint/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ fn register_internals(store: &mut LintStore) {
518518
store.register_lints(&TyTyKind::get_lints());
519519
store.register_late_pass(|_| Box::new(TyTyKind));
520520
store.register_lints(&Diagnostics::get_lints());
521+
store.register_early_pass(|| Box::new(Diagnostics));
521522
store.register_late_pass(|_| Box::new(Diagnostics));
522523
store.register_lints(&BadOptAccess::get_lints());
523524
store.register_late_pass(|_| Box::new(BadOptAccess));

compiler/rustc_lint/src/lints.rs

+4
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,10 @@ pub struct DiagOutOfImpl;
820820
#[diag(lint_untranslatable_diag)]
821821
pub struct UntranslatableDiag;
822822

823+
#[derive(LintDiagnostic)]
824+
#[diag(lint_trivial_untranslatable_diag)]
825+
pub struct UntranslatableDiagnosticTrivial;
826+
823827
#[derive(LintDiagnostic)]
824828
#[diag(lint_bad_opt_access)]
825829
pub struct BadOptAccessDiag<'a> {

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,7 @@ symbols! {
651651
edition_panic,
652652
eh_catch_typeinfo,
653653
eh_personality,
654+
emit,
654655
emit_enum,
655656
emit_enum_variant,
656657
emit_enum_variant_arg,

0 commit comments

Comments
 (0)