Rollup merge of #121382 - nnethercote:rework-untranslatable_diagnostic-lint, r=davidtwco
Rework `untranslatable_diagnostic` lint Currently it only checks calls to functions marked with `#[rustc_lint_diagnostics]`. This PR changes it to check calls to any function with an `impl Into<{D,Subd}iagnosticMessage>` parameter. This greatly improves its coverage and doesn't rely on people remembering to add `#[rustc_lint_diagnostics]`. It also lets us add `#[rustc_lint_diagnostics]` to a number of functions that don't have an `impl Into<{D,Subd}iagnosticMessage>`, such as `Diag::span`. r? ``@davidtwco``
This commit is contained in:
commit
efe9deace8
42 changed files with 305 additions and 89 deletions
|
@ -621,12 +621,13 @@ pub trait LintContext {
|
|||
/// Note that this function should only be called for [`LintExpectationId`]s
|
||||
/// retrieved from the current lint pass. Buffered or manually created ids can
|
||||
/// cause ICEs.
|
||||
#[rustc_lint_diagnostics]
|
||||
fn fulfill_expectation(&self, expectation: LintExpectationId) {
|
||||
// We need to make sure that submitted expectation ids are correctly fulfilled suppressed
|
||||
// and stored between compilation sessions. To not manually do these steps, we simply create
|
||||
// a dummy diagnostic and emit is as usual, which will be suppressed and stored like a normal
|
||||
// expected lint diagnostic.
|
||||
// a dummy diagnostic and emit it as usual, which will be suppressed and stored like a
|
||||
// normal expected lint diagnostic.
|
||||
#[allow(rustc::diagnostic_outside_of_impl)]
|
||||
#[allow(rustc::untranslatable_diagnostic)]
|
||||
self.sess()
|
||||
.dcx()
|
||||
.struct_expect(
|
||||
|
|
|
@ -10,7 +10,7 @@ use rustc_ast as ast;
|
|||
use rustc_hir::def::Res;
|
||||
use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
|
||||
use rustc_hir::{BinOp, BinOpKind, HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::{self, Ty as MiddleTy};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::hygiene::{ExpnKind, MacroKind};
|
||||
use rustc_span::symbol::{kw, sym, Symbol};
|
||||
|
@ -338,10 +338,11 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
|
|||
}
|
||||
|
||||
declare_tool_lint! {
|
||||
/// The `untranslatable_diagnostic` lint detects diagnostics created
|
||||
/// without using translatable Fluent strings.
|
||||
/// The `untranslatable_diagnostic` lint detects messages passed to functions with `impl
|
||||
/// Into<{D,Subd}iagMessage` parameters without using translatable Fluent strings.
|
||||
///
|
||||
/// More details on translatable diagnostics can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
|
||||
/// More details on translatable diagnostics can be found
|
||||
/// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/translation.html).
|
||||
pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
|
||||
Deny,
|
||||
"prevent creation of diagnostics which cannot be translated",
|
||||
|
@ -349,11 +350,13 @@ declare_tool_lint! {
|
|||
}
|
||||
|
||||
declare_tool_lint! {
|
||||
/// The `diagnostic_outside_of_impl` lint detects diagnostics created manually,
|
||||
/// and inside an `IntoDiagnostic`/`AddToDiagnostic` implementation,
|
||||
/// or a `#[derive(Diagnostic)]`/`#[derive(Subdiagnostic)]` expansion.
|
||||
/// The `diagnostic_outside_of_impl` lint detects calls to functions annotated with
|
||||
/// `#[rustc_lint_diagnostics]` that are outside an `IntoDiagnostic`, `AddToDiagnostic`, or
|
||||
/// `DecorateLint` impl, or a `#[derive(Diagnostic)]`, `#[derive(Subdiagnostic)]`,
|
||||
/// `#[derive(DecorateLint)]` expansion.
|
||||
///
|
||||
/// More details on diagnostics implementations can be found [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
|
||||
/// More details on diagnostics implementations can be found
|
||||
/// [here](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-structs.html).
|
||||
pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
|
||||
Deny,
|
||||
"prevent creation of diagnostics outside of `IntoDiagnostic`/`AddToDiagnostic` impls",
|
||||
|
@ -364,54 +367,130 @@ declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE
|
|||
|
||||
impl LateLintPass<'_> for Diagnostics {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
let Some((span, def_id, args)) = typeck_results_of_method_fn(cx, expr) else { return };
|
||||
debug!(?span, ?def_id, ?args);
|
||||
let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, args)
|
||||
// Only check function calls and method calls.
|
||||
let (span, def_id, fn_gen_args, call_tys) = match expr.kind {
|
||||
ExprKind::Call(callee, args) => {
|
||||
match cx.typeck_results().node_type(callee.hir_id).kind() {
|
||||
&ty::FnDef(def_id, fn_gen_args) => {
|
||||
let call_tys: Vec<_> =
|
||||
args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
|
||||
(callee.span, def_id, fn_gen_args, call_tys)
|
||||
}
|
||||
_ => return, // occurs for fns passed as args
|
||||
}
|
||||
}
|
||||
ExprKind::MethodCall(segment, _recv, args, _span) => {
|
||||
let def_id = cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
|
||||
let fn_gen_args = cx.typeck_results().node_args(expr.hir_id);
|
||||
let mut call_tys: Vec<_> =
|
||||
args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
|
||||
call_tys.insert(0, cx.tcx.types.self_param); // dummy inserted for `self`
|
||||
(segment.ident.span, def_id, fn_gen_args, call_tys)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// Is the callee marked with `#[rustc_lint_diagnostics]`?
|
||||
let has_attr = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, fn_gen_args)
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics));
|
||||
if !has_attr {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut found_parent_with_attr = false;
|
||||
let mut found_impl = false;
|
||||
for (hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
|
||||
if let Some(owner_did) = hir_id.as_owner() {
|
||||
found_parent_with_attr = found_parent_with_attr
|
||||
|| cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics);
|
||||
}
|
||||
|
||||
debug!(?parent);
|
||||
if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
|
||||
&& let Impl { of_trait: Some(of_trait), .. } = impl_
|
||||
&& let Some(def_id) = of_trait.trait_def_id()
|
||||
&& let Some(name) = cx.tcx.get_diagnostic_name(def_id)
|
||||
&& matches!(name, sym::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint)
|
||||
{
|
||||
found_impl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!(?found_impl);
|
||||
if !found_parent_with_attr && !found_impl {
|
||||
cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
|
||||
}
|
||||
|
||||
let mut found_diagnostic_message = false;
|
||||
for ty in args.types() {
|
||||
debug!(?ty);
|
||||
// Closure: is the type `{D,Subd}iagMessage`?
|
||||
let is_diag_message = |ty: MiddleTy<'_>| {
|
||||
if let Some(adt_def) = ty.ty_adt_def()
|
||||
&& let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
|
||||
&& matches!(name, sym::DiagMessage | sym::SubdiagMessage)
|
||||
{
|
||||
found_diagnostic_message = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// Does the callee have a `impl Into<{D,Subd}iagMessage>` parameter? (There should be at
|
||||
// most one.)
|
||||
let mut impl_into_diagnostic_message_param = None;
|
||||
let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
|
||||
let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates;
|
||||
for (i, ¶m_ty) in fn_sig.inputs().iter().enumerate() {
|
||||
if let ty::Param(p) = param_ty.kind() {
|
||||
// It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`.
|
||||
for pred in predicates.iter() {
|
||||
if let Some(trait_pred) = pred.as_trait_clause()
|
||||
&& let trait_ref = trait_pred.skip_binder().trait_ref
|
||||
&& trait_ref.self_ty() == param_ty // correct predicate for the param?
|
||||
&& cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id)
|
||||
&& let ty1 = trait_ref.args.type_at(1)
|
||||
&& is_diag_message(ty1)
|
||||
{
|
||||
if impl_into_diagnostic_message_param.is_some() {
|
||||
cx.tcx.dcx().span_bug(
|
||||
span,
|
||||
"can't handle multiple `impl Into<{D,Sub}iagMessage>` params",
|
||||
);
|
||||
}
|
||||
impl_into_diagnostic_message_param = Some((i, p.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is the callee interesting?
|
||||
if !has_attr && impl_into_diagnostic_message_param.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Is the parent method marked with `#[rustc_lint_diagnostics]`?
|
||||
let mut parent_has_attr = false;
|
||||
for (hir_id, _parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
|
||||
if let Some(owner_did) = hir_id.as_owner()
|
||||
&& cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics)
|
||||
{
|
||||
parent_has_attr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!(?found_diagnostic_message);
|
||||
if !found_parent_with_attr && !found_diagnostic_message {
|
||||
cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
|
||||
|
||||
// Calls to `#[rustc_lint_diagnostics]`-marked functions should only occur:
|
||||
// - inside an impl of `IntoDiagnostic`, `AddToDiagnostic`, or `DecorateLint`, or
|
||||
// - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`.
|
||||
//
|
||||
// Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint.
|
||||
if has_attr && !parent_has_attr {
|
||||
let mut is_inside_appropriate_impl = false;
|
||||
for (_hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
|
||||
debug!(?parent);
|
||||
if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
|
||||
&& let Impl { of_trait: Some(of_trait), .. } = impl_
|
||||
&& let Some(def_id) = of_trait.trait_def_id()
|
||||
&& let Some(name) = cx.tcx.get_diagnostic_name(def_id)
|
||||
&& matches!(
|
||||
name,
|
||||
sym::IntoDiagnostic | sym::AddToDiagnostic | sym::DecorateLint
|
||||
)
|
||||
{
|
||||
is_inside_appropriate_impl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
debug!(?is_inside_appropriate_impl);
|
||||
if !is_inside_appropriate_impl {
|
||||
cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
|
||||
}
|
||||
}
|
||||
|
||||
// Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
|
||||
// with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
|
||||
// `UNTRANSLATABLE_DIAGNOSTIC` lint.
|
||||
if let Some((param_i, param_i_p_name)) = impl_into_diagnostic_message_param {
|
||||
// Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
|
||||
let arg_ty = call_tys[param_i];
|
||||
let is_translatable = is_diag_message(arg_ty)
|
||||
|| matches!(arg_ty.kind(), ty::Param(p) if p.name == param_i_p_name);
|
||||
if !is_translatable {
|
||||
cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +504,7 @@ declare_tool_lint! {
|
|||
report_in_external_macro: true
|
||||
}
|
||||
|
||||
declare_lint_pass!(BadOptAccess => [ BAD_OPT_ACCESS ]);
|
||||
declare_lint_pass!(BadOptAccess => [BAD_OPT_ACCESS]);
|
||||
|
||||
impl LateLintPass<'_> for BadOptAccess {
|
||||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
|
||||
|
|
|
@ -104,6 +104,7 @@ const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
|
|||
];
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
|
||||
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
|
||||
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
|
||||
if matches!(local.source, rustc_hir::LocalSource::AsyncFn) {
|
||||
return;
|
||||
|
|
|
@ -724,6 +724,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
|
|||
};
|
||||
}
|
||||
|
||||
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
|
||||
fn add(&mut self, attrs: &[ast::Attribute], is_crate_node: bool, source_hir_id: Option<HirId>) {
|
||||
let sess = self.sess;
|
||||
for (attr_index, attr) in attrs.iter().enumerate() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue