Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 126a5c2

Browse files
authoredAug 22, 2024
Rollup merge of rust-lang#128941 - GrigorenkoPV:internal-diagnostic-lints, r=davidtwco
Improve diagnostic-related lints: `untranslatable_diagnostic` & `diagnostic_outside_of_impl` Summary: - Made `untranslatable_diagnostic` point to problematic arguments instead of the function call (I found this misleading while working on some `A-translation` PRs: my first impression was that the methods themselves were not translation-aware and needed to be changed, while in reality the problem was with the hardcoded strings passed as arguments). - Made the shared pass of `untranslatable_diagnostic` & `diagnostic_outside_of_impl` more efficient. ``@rustbot`` label D-imprecise-spans A-translation
2 parents 765a045 + e94a4ee commit 126a5c2

File tree

3 files changed

+124
-77
lines changed

3 files changed

+124
-77
lines changed
 

Diff for: ‎compiler/rustc_lint/src/internal.rs

+84-68
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_hir::{
88
BinOp, BinOpKind, Expr, ExprKind, GenericArg, HirId, Impl, Item, ItemKind, Node, Pat, PatKind,
99
Path, PathSegment, QPath, Ty, TyKind,
1010
};
11-
use rustc_middle::ty::{self, Ty as MiddleTy};
11+
use rustc_middle::ty::{self, GenericArgsRef, Ty as MiddleTy};
1212
use rustc_session::{declare_lint_pass, declare_tool_lint};
1313
use rustc_span::hygiene::{ExpnKind, MacroKind};
1414
use rustc_span::symbol::{kw, sym, Symbol};
@@ -415,14 +415,17 @@ declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE
415415

416416
impl LateLintPass<'_> for Diagnostics {
417417
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
418+
let collect_args_tys_and_spans = |args: &[Expr<'_>], reserve_one_extra: bool| {
419+
let mut result = Vec::with_capacity(args.len() + usize::from(reserve_one_extra));
420+
result.extend(args.iter().map(|arg| (cx.typeck_results().expr_ty(arg), arg.span)));
421+
result
422+
};
418423
// Only check function calls and method calls.
419-
let (span, def_id, fn_gen_args, call_tys) = match expr.kind {
424+
let (span, def_id, fn_gen_args, arg_tys_and_spans) = match expr.kind {
420425
ExprKind::Call(callee, args) => {
421426
match cx.typeck_results().node_type(callee.hir_id).kind() {
422427
&ty::FnDef(def_id, fn_gen_args) => {
423-
let call_tys: Vec<_> =
424-
args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
425-
(callee.span, def_id, fn_gen_args, call_tys)
428+
(callee.span, def_id, fn_gen_args, collect_args_tys_and_spans(args, false))
426429
}
427430
_ => return, // occurs for fns passed as args
428431
}
@@ -432,66 +435,94 @@ impl LateLintPass<'_> for Diagnostics {
432435
else {
433436
return;
434437
};
435-
let mut call_tys: Vec<_> =
436-
args.iter().map(|arg| cx.typeck_results().expr_ty(arg)).collect();
437-
call_tys.insert(0, cx.tcx.types.self_param); // dummy inserted for `self`
438-
(span, def_id, fn_gen_args, call_tys)
438+
let mut args = collect_args_tys_and_spans(args, true);
439+
args.insert(0, (cx.tcx.types.self_param, _recv.span)); // dummy inserted for `self`
440+
(span, def_id, fn_gen_args, args)
439441
}
440442
_ => return,
441443
};
442444

443-
// Is the callee marked with `#[rustc_lint_diagnostics]`?
444-
let has_attr = ty::Instance::try_resolve(cx.tcx, cx.param_env, def_id, fn_gen_args)
445-
.ok()
446-
.flatten()
447-
.is_some_and(|inst| cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics));
448-
449-
// Closure: is the type `{D,Subd}iagMessage`?
450-
let is_diag_message = |ty: MiddleTy<'_>| {
451-
if let Some(adt_def) = ty.ty_adt_def()
452-
&& let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
453-
&& matches!(name, sym::DiagMessage | sym::SubdiagMessage)
454-
{
455-
true
456-
} else {
457-
false
458-
}
459-
};
445+
Self::diagnostic_outside_of_impl(cx, span, expr.hir_id, def_id, fn_gen_args);
446+
Self::untranslatable_diagnostic(cx, def_id, &arg_tys_and_spans);
447+
}
448+
}
460449

461-
// Does the callee have one or more `impl Into<{D,Subd}iagMessage>` parameters?
462-
let mut impl_into_diagnostic_message_params = vec![];
450+
impl Diagnostics {
451+
// Is the type `{D,Subd}iagMessage`?
452+
fn is_diag_message<'cx>(cx: &LateContext<'cx>, ty: MiddleTy<'cx>) -> bool {
453+
if let Some(adt_def) = ty.ty_adt_def()
454+
&& let Some(name) = cx.tcx.get_diagnostic_name(adt_def.did())
455+
&& matches!(name, sym::DiagMessage | sym::SubdiagMessage)
456+
{
457+
true
458+
} else {
459+
false
460+
}
461+
}
462+
463+
fn untranslatable_diagnostic<'cx>(
464+
cx: &LateContext<'cx>,
465+
def_id: DefId,
466+
arg_tys_and_spans: &[(MiddleTy<'cx>, Span)],
467+
) {
463468
let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder();
464469
let predicates = cx.tcx.predicates_of(def_id).instantiate_identity(cx.tcx).predicates;
465470
for (i, &param_ty) in fn_sig.inputs().iter().enumerate() {
466-
if let ty::Param(p) = param_ty.kind() {
471+
if let ty::Param(sig_param) = param_ty.kind() {
467472
// It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`.
468473
for pred in predicates.iter() {
469474
if let Some(trait_pred) = pred.as_trait_clause()
470475
&& let trait_ref = trait_pred.skip_binder().trait_ref
471476
&& trait_ref.self_ty() == param_ty // correct predicate for the param?
472477
&& cx.tcx.is_diagnostic_item(sym::Into, trait_ref.def_id)
473478
&& let ty1 = trait_ref.args.type_at(1)
474-
&& is_diag_message(ty1)
479+
&& Self::is_diag_message(cx, ty1)
475480
{
476-
impl_into_diagnostic_message_params.push((i, p.name));
481+
// Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
482+
// with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
483+
// `UNTRANSLATABLE_DIAGNOSTIC` lint.
484+
let (arg_ty, arg_span) = arg_tys_and_spans[i];
485+
486+
// Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
487+
let is_translatable = Self::is_diag_message(cx, arg_ty)
488+
|| matches!(arg_ty.kind(), ty::Param(arg_param) if arg_param.name == sig_param.name);
489+
if !is_translatable {
490+
cx.emit_span_lint(
491+
UNTRANSLATABLE_DIAGNOSTIC,
492+
arg_span,
493+
UntranslatableDiag,
494+
);
495+
}
477496
}
478497
}
479498
}
480499
}
500+
}
481501

482-
// Is the callee interesting?
483-
if !has_attr && impl_into_diagnostic_message_params.is_empty() {
502+
fn diagnostic_outside_of_impl<'cx>(
503+
cx: &LateContext<'cx>,
504+
span: Span,
505+
current_id: HirId,
506+
def_id: DefId,
507+
fn_gen_args: GenericArgsRef<'cx>,
508+
) {
509+
// Is the callee marked with `#[rustc_lint_diagnostics]`?
510+
let Some(inst) =
511+
ty::Instance::try_resolve(cx.tcx, cx.param_env, def_id, fn_gen_args).ok().flatten()
512+
else {
484513
return;
485-
}
514+
};
515+
let has_attr = cx.tcx.has_attr(inst.def_id(), sym::rustc_lint_diagnostics);
516+
if !has_attr {
517+
return;
518+
};
486519

487-
// Is the parent method marked with `#[rustc_lint_diagnostics]`?
488-
let mut parent_has_attr = false;
489-
for (hir_id, _parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
520+
for (hir_id, _parent) in cx.tcx.hir().parent_iter(current_id) {
490521
if let Some(owner_did) = hir_id.as_owner()
491522
&& cx.tcx.has_attr(owner_did, sym::rustc_lint_diagnostics)
492523
{
493-
parent_has_attr = true;
494-
break;
524+
// The parent method is marked with `#[rustc_lint_diagnostics]`
525+
return;
495526
}
496527
}
497528

@@ -500,37 +531,22 @@ impl LateLintPass<'_> for Diagnostics {
500531
// - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`.
501532
//
502533
// Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint.
503-
if has_attr && !parent_has_attr {
504-
let mut is_inside_appropriate_impl = false;
505-
for (_hir_id, parent) in cx.tcx.hir().parent_iter(expr.hir_id) {
506-
debug!(?parent);
507-
if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
508-
&& let Impl { of_trait: Some(of_trait), .. } = impl_
509-
&& let Some(def_id) = of_trait.trait_def_id()
510-
&& let Some(name) = cx.tcx.get_diagnostic_name(def_id)
511-
&& matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic)
512-
{
513-
is_inside_appropriate_impl = true;
514-
break;
515-
}
516-
}
517-
debug!(?is_inside_appropriate_impl);
518-
if !is_inside_appropriate_impl {
519-
cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
534+
let mut is_inside_appropriate_impl = false;
535+
for (_hir_id, parent) in cx.tcx.hir().parent_iter(current_id) {
536+
debug!(?parent);
537+
if let Node::Item(Item { kind: ItemKind::Impl(impl_), .. }) = parent
538+
&& let Impl { of_trait: Some(of_trait), .. } = impl_
539+
&& let Some(def_id) = of_trait.trait_def_id()
540+
&& let Some(name) = cx.tcx.get_diagnostic_name(def_id)
541+
&& matches!(name, sym::Diagnostic | sym::Subdiagnostic | sym::LintDiagnostic)
542+
{
543+
is_inside_appropriate_impl = true;
544+
break;
520545
}
521546
}
522-
523-
// Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
524-
// with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
525-
// `UNTRANSLATABLE_DIAGNOSTIC` lint.
526-
for (param_i, param_i_p_name) in impl_into_diagnostic_message_params {
527-
// Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
528-
let arg_ty = call_tys[param_i];
529-
let is_translatable = is_diag_message(arg_ty)
530-
|| matches!(arg_ty.kind(), ty::Param(p) if p.name == param_i_p_name);
531-
if !is_translatable {
532-
cx.emit_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, UntranslatableDiag);
533-
}
547+
debug!(?is_inside_appropriate_impl);
548+
if !is_inside_appropriate_impl {
549+
cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
534550
}
535551
}
536552
}

Diff for: ‎tests/ui-fulldeps/internal-lints/diagnostics.rs

+7
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,11 @@ pub fn skipped_because_of_annotation<'a>(dcx: DiagCtxtHandle<'a>) {
117117
fn f(_x: impl Into<DiagMessage>, _y: impl Into<SubdiagMessage>) {}
118118
fn g() {
119119
f(crate::fluent_generated::no_crate_example, crate::fluent_generated::no_crate_example);
120+
f("untranslatable diagnostic", crate::fluent_generated::no_crate_example);
121+
//~^ ERROR diagnostics should be created using translatable messages
122+
f(crate::fluent_generated::no_crate_example, "untranslatable diagnostic");
123+
//~^ ERROR diagnostics should be created using translatable messages
124+
f("untranslatable diagnostic", "untranslatable diagnostic");
125+
//~^ ERROR diagnostics should be created using translatable messages
126+
//~^^ ERROR diagnostics should be created using translatable messages
120127
}

Diff for: ‎tests/ui-fulldeps/internal-lints/diagnostics.stderr

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: diagnostics should be created using translatable messages
2-
--> $DIR/diagnostics.rs:43:9
2+
--> $DIR/diagnostics.rs:43:31
33
|
44
LL | Diag::new(dcx, level, "untranslatable diagnostic")
5-
| ^^^^^^^^^
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
66
|
77
note: the lint level is defined here
88
--> $DIR/diagnostics.rs:7:9
@@ -11,16 +11,16 @@ LL | #![deny(rustc::untranslatable_diagnostic)]
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1212

1313
error: diagnostics should be created using translatable messages
14-
--> $DIR/diagnostics.rs:64:14
14+
--> $DIR/diagnostics.rs:64:19
1515
|
1616
LL | diag.note("untranslatable diagnostic");
17-
| ^^^^
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
1818

1919
error: diagnostics should be created using translatable messages
20-
--> $DIR/diagnostics.rs:85:14
20+
--> $DIR/diagnostics.rs:85:19
2121
|
2222
LL | diag.note("untranslatable diagnostic");
23-
| ^^^^
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
2424

2525
error: diagnostics should only be created in `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls
2626
--> $DIR/diagnostics.rs:99:21
@@ -41,10 +41,34 @@ LL | let _diag = dcx.struct_err("untranslatable diagnostic");
4141
| ^^^^^^^^^^
4242

4343
error: diagnostics should be created using translatable messages
44-
--> $DIR/diagnostics.rs:102:21
44+
--> $DIR/diagnostics.rs:102:32
4545
|
4646
LL | let _diag = dcx.struct_err("untranslatable diagnostic");
47-
| ^^^^^^^^^^
47+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
48+
49+
error: diagnostics should be created using translatable messages
50+
--> $DIR/diagnostics.rs:120:7
51+
|
52+
LL | f("untranslatable diagnostic", crate::fluent_generated::no_crate_example);
53+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
54+
55+
error: diagnostics should be created using translatable messages
56+
--> $DIR/diagnostics.rs:122:50
57+
|
58+
LL | f(crate::fluent_generated::no_crate_example, "untranslatable diagnostic");
59+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
60+
61+
error: diagnostics should be created using translatable messages
62+
--> $DIR/diagnostics.rs:124:7
63+
|
64+
LL | f("untranslatable diagnostic", "untranslatable diagnostic");
65+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
66+
67+
error: diagnostics should be created using translatable messages
68+
--> $DIR/diagnostics.rs:124:36
69+
|
70+
LL | f("untranslatable diagnostic", "untranslatable diagnostic");
71+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
4872

49-
error: aborting due to 6 previous errors
73+
error: aborting due to 10 previous errors
5074

0 commit comments

Comments
 (0)
Please sign in to comment.