Rewrite the untranslatable_diagnostic
lint.
Currently it only checks calls to functions marked with `#[rustc_lint_diagnostics]`. This commit changes it to check calls to any function with an `impl Into<{D,Subd}iagMessage>` parameter. This greatly improves its coverage and doesn't rely on people remembering to add `#[rustc_lint_diagnostics]`. The commit also adds `#[allow(rustc::untranslatable_diagnostic)`] attributes to places that need it that are caught by the improved lint. These places that might be easy to convert to translatable diagnostics. Finally, it also: - Expands and corrects some comments. - Does some minor formatting improvements. - Adds missing `DecorateLint` cases to `tests/ui-fulldeps/internal-lints/diagnostics.rs`.
This commit is contained in:
parent
d602394827
commit
b7d58eef4b
35 changed files with 224 additions and 63 deletions
|
@ -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<'_>) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue