From eeb527602a0293337752f9dc0c63eca3990d8e4e Mon Sep 17 00:00:00 2001 From: clubby789 Date: Sun, 5 Mar 2023 03:39:37 +0000 Subject: [PATCH] Add deny lint to prevent untranslatable diagnostics using static strings --- compiler/rustc_lint/messages.ftl | 2 + compiler/rustc_lint/src/internal.rs | 80 ++++++++++++++++++++++++++++- compiler/rustc_lint/src/lib.rs | 1 + compiler/rustc_lint/src/lints.rs | 4 ++ compiler/rustc_span/src/symbol.rs | 1 + 5 files changed, 87 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 3d1b8f8ed95ac..3c6dbb466db7a 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -99,6 +99,8 @@ lint_diag_out_of_impl = lint_untranslatable_diag = diagnostics should be created using translatable messages +lint_trivial_untranslatable_diag = diagnostic with static strings only + lint_bad_opt_access = {$msg} lint_cstring_ptr = getting the inner pointer of a temporary `CString` diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs index 4ac589c2e10f0..595b50c4063ca 100644 --- a/compiler/rustc_lint/src/internal.rs +++ b/compiler/rustc_lint/src/internal.rs @@ -4,6 +4,7 @@ use crate::lints::{ BadOptAccessDiag, DefaultHashTypesDiag, DiagOutOfImpl, LintPassByHand, NonExistentDocKeyword, QueryInstability, TyQualified, TykindDiag, TykindKind, UntranslatableDiag, + UntranslatableDiagnosticTrivial, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; use rustc_ast as ast; @@ -366,7 +367,15 @@ declare_tool_lint! { report_in_external_macro: true } -declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]); +declare_tool_lint! { + /// The `untranslatable_diagnostic_trivial` lint detects diagnostics created using only static strings. + pub rustc::UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL, + Deny, + "prevent creation of diagnostics which cannot be translated, which use only static strings", + report_in_external_macro: true +} + +declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL, UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL ]); impl LateLintPass<'_> for Diagnostics { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { @@ -423,6 +432,75 @@ impl LateLintPass<'_> for Diagnostics { } } +impl EarlyLintPass for Diagnostics { + #[allow(unused_must_use)] + fn check_stmt(&mut self, cx: &EarlyContext<'_>, stmt: &ast::Stmt) { + // Looking for a straight chain of method calls from 'struct_span_err' to 'emit'. + let ast::StmtKind::Semi(expr) = &stmt.kind else { + return; + }; + let ast::ExprKind::MethodCall(meth) = &expr.kind else { + return; + }; + if meth.seg.ident.name != sym::emit || !meth.args.is_empty() { + return; + } + let mut segments = vec![]; + let mut cur = &meth.receiver; + let fake = &[].into(); + loop { + match &cur.kind { + ast::ExprKind::Call(func, args) => { + if let ast::ExprKind::Path(_, path) = &func.kind { + segments.push((path.segments.last().unwrap().ident.name, args)) + } + break; + } + ast::ExprKind::MethodCall(method) => { + segments.push((method.seg.ident.name, &method.args)); + cur = &method.receiver; + } + ast::ExprKind::MacCall(mac) => { + segments.push((mac.path.segments.last().unwrap().ident.name, fake)); + break; + } + _ => { + break; + } + } + } + segments.reverse(); + if segments.is_empty() { + return; + } + if segments[0].0.as_str() != "struct_span_err" { + return; + } + if !segments.iter().all(|(name, args)| { + let arg = match name.as_str() { + "struct_span_err" | "span_note" | "span_label" | "span_help" => &args[1], + "note" | "help" => &args[0], + _ => { + return false; + } + }; + if let ast::ExprKind::Lit(lit) = arg.kind + && let ast::token::LitKind::Str = lit.kind { + true + } else { + false + } + }) { + return; + } + cx.emit_spanned_lint( + UNTRANSLATABLE_DIAGNOSTIC_TRIVIAL, + stmt.span, + UntranslatableDiagnosticTrivial, + ); + } +} + declare_tool_lint! { /// The `bad_opt_access` lint detects accessing options by field instead of /// the wrapper function. diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 76f0725790777..319eb2ea445ed 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -518,6 +518,7 @@ fn register_internals(store: &mut LintStore) { store.register_lints(&TyTyKind::get_lints()); store.register_late_pass(|_| Box::new(TyTyKind)); store.register_lints(&Diagnostics::get_lints()); + store.register_early_pass(|| Box::new(Diagnostics)); store.register_late_pass(|_| Box::new(Diagnostics)); store.register_lints(&BadOptAccess::get_lints()); store.register_late_pass(|_| Box::new(BadOptAccess)); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 1d5e02369f528..848f6a9ecb532 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -820,6 +820,10 @@ pub struct DiagOutOfImpl; #[diag(lint_untranslatable_diag)] pub struct UntranslatableDiag; +#[derive(LintDiagnostic)] +#[diag(lint_trivial_untranslatable_diag)] +pub struct UntranslatableDiagnosticTrivial; + #[derive(LintDiagnostic)] #[diag(lint_bad_opt_access)] pub struct BadOptAccessDiag<'a> { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 70b9088de5064..abf19c30e3deb 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -651,6 +651,7 @@ symbols! { edition_panic, eh_catch_typeinfo, eh_personality, + emit, emit_enum, emit_enum_variant, emit_enum_variant_arg,