about summary refs log tree commit diff
path: root/compiler/rustc_lint/src/internal.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_lint/src/internal.rs')
-rw-r--r--compiler/rustc_lint/src/internal.rs165
1 files changed, 122 insertions, 43 deletions
diff --git a/compiler/rustc_lint/src/internal.rs b/compiler/rustc_lint/src/internal.rs
index fdcd0192e6a..3a8c8e79b4f 100644
--- a/compiler/rustc_lint/src/internal.rs
+++ b/compiler/rustc_lint/src/internal.rs
@@ -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);
+        // 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)
+            {
+                true
+            } else {
+                false
             }
+        };
 
-            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;
+        // 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, &param_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));
+                    }
+                }
             }
         }
-        debug!(?found_impl);
-        if !found_parent_with_attr && !found_impl {
-            cx.emit_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, DiagOutOfImpl);
+
+        // Is the callee interesting?
+        if !has_attr && impl_into_diagnostic_message_param.is_none() {
+            return;
         }
 
-        let mut found_diagnostic_message = false;
-        for ty in args.types() {
-            debug!(?ty);
-            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)
+        // 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)
             {
-                found_diagnostic_message = true;
+                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<'_>) {