]> git.proxmox.com Git - rustc.git/blobdiff - compiler/rustc_lint/src/internal.rs
New upstream version 1.63.0+dfsg1
[rustc.git] / compiler / rustc_lint / src / internal.rs
index 27d44da6dfc389d6d211cc797e143f638c0eadfe..fadb1c87933989d788250739603e74f4708fd5af 100644 (file)
@@ -5,12 +5,14 @@ use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}
 use rustc_ast as ast;
 use rustc_errors::Applicability;
 use rustc_hir::def::Res;
-use rustc_hir::{Expr, ExprKind, GenericArg, Path, PathSegment, QPath};
-use rustc_hir::{HirId, Item, ItemKind, Node, Ty, TyKind};
+use rustc_hir::{def_id::DefId, Expr, ExprKind, GenericArg, PatKind, Path, PathSegment, QPath};
+use rustc_hir::{HirId, Impl, Item, ItemKind, Node, Pat, Ty, TyKind};
 use rustc_middle::ty;
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::hygiene::{ExpnKind, MacroKind};
 use rustc_span::symbol::{kw, sym, Symbol};
+use rustc_span::Span;
+use tracing::debug;
 
 declare_tool_lint! {
     pub rustc::DEFAULT_HASH_TYPES,
@@ -46,6 +48,41 @@ impl LateLintPass<'_> for DefaultHashTypes {
     }
 }
 
+/// Helper function for lints that check for expressions with calls and use typeck results to
+/// get the `DefId` and `SubstsRef` of the function.
+fn typeck_results_of_method_fn<'tcx>(
+    cx: &LateContext<'tcx>,
+    expr: &Expr<'_>,
+) -> Option<(Span, DefId, ty::subst::SubstsRef<'tcx>)> {
+    // FIXME(rustdoc): Lints which use this function use typecheck results which can cause
+    // `rustdoc` to error if there are resolution failures.
+    //
+    // As internal lints are currently always run if there are `unstable_options`, they are added
+    // to the lint store of rustdoc. Internal lints are also not used via the `lint_mod` query.
+    // Crate lints run outside of a query so rustdoc currently doesn't disable them.
+    //
+    // Instead of relying on this, either change crate lints to a query disabled by rustdoc, only
+    // run internal lints if the user is explicitly opting in or figure out a different way to
+    // avoid running lints for rustdoc.
+    if cx.tcx.sess.opts.actually_rustdoc {
+        return None;
+    }
+
+    match expr.kind {
+        ExprKind::MethodCall(segment, _, _)
+            if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
+        {
+            Some((segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id)))
+        },
+        _ => {
+            match cx.typeck_results().node_type(expr.hir_id).kind() {
+                &ty::FnDef(def_id, substs) => Some((expr.span, def_id, substs)),
+                _ => None,
+            }
+        }
+    }
+}
+
 declare_tool_lint! {
     pub rustc::POTENTIAL_QUERY_INSTABILITY,
     Allow,
@@ -57,35 +94,7 @@ declare_lint_pass!(QueryStability => [POTENTIAL_QUERY_INSTABILITY]);
 
 impl LateLintPass<'_> for QueryStability {
     fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
-        // FIXME(rustdoc): This lint uses typecheck results, causing rustdoc to
-        // error if there are resolution failures.
-        //
-        // As internal lints are currently always run if there are `unstable_options`,
-        // they are added to the lint store of rustdoc. Internal lints are also
-        // not used via the `lint_mod` query. Crate lints run outside of a query
-        // so rustdoc currently doesn't disable them.
-        //
-        // Instead of relying on this, either change crate lints to a query disabled by
-        // rustdoc, only run internal lints if the user is explicitly opting in
-        // or figure out a different way to avoid running lints for rustdoc.
-        if cx.tcx.sess.opts.actually_rustdoc {
-            return;
-        }
-
-        let (span, def_id, substs) = match expr.kind {
-            ExprKind::MethodCall(segment, _, _)
-                if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) =>
-            {
-                (segment.ident.span, def_id, cx.typeck_results().node_substs(expr.hir_id))
-            },
-            _ => {
-                let &ty::FnDef(def_id, substs) =
-                    cx.typeck_results()
-                        .node_type(expr.hir_id)
-                        .kind() else { return };
-                (expr.span, def_id, substs)
-            }
-        };
+        let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
         if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) {
             let def_id = instance.def_id();
             if cx.tcx.has_attr(def_id, sym::rustc_lint_query_instability) {
@@ -123,55 +132,115 @@ declare_lint_pass!(TyTyKind => [
 ]);
 
 impl<'tcx> LateLintPass<'tcx> for TyTyKind {
-    fn check_path(&mut self, cx: &LateContext<'_>, path: &'tcx Path<'tcx>, _: HirId) {
-        let segments = path.segments.iter().rev().skip(1).rev();
-
-        if let Some(last) = segments.last() {
-            let span = path.span.with_hi(last.ident.span.hi());
-            if lint_ty_kind_usage(cx, last) {
-                cx.struct_span_lint(USAGE_OF_TY_TYKIND, span, |lint| {
-                    lint.build("usage of `ty::TyKind::<kind>`")
-                        .span_suggestion(
-                            span,
-                            "try using ty::<kind> directly",
-                            "ty".to_string(),
-                            Applicability::MaybeIncorrect, // ty maybe needs an import
-                        )
-                        .emit();
-                })
-            }
+    fn check_path(
+        &mut self,
+        cx: &LateContext<'tcx>,
+        path: &'tcx rustc_hir::Path<'tcx>,
+        _: rustc_hir::HirId,
+    ) {
+        if let Some(segment) = path.segments.iter().nth_back(1)
+        && let Some(res) = &segment.res
+        && lint_ty_kind_usage(cx, res)
+        {
+            let span = path.span.with_hi(
+                segment.args.map_or(segment.ident.span, |a| a.span_ext).hi()
+            );
+            cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
+                lint.build("usage of `ty::TyKind::<kind>`")
+                    .span_suggestion(
+                        span,
+                        "try using `ty::<kind>` directly",
+                        "ty",
+                        Applicability::MaybeIncorrect, // ty maybe needs an import
+                    )
+                    .emit();
+            });
         }
     }
 
     fn check_ty(&mut self, cx: &LateContext<'_>, ty: &'tcx Ty<'tcx>) {
         match &ty.kind {
             TyKind::Path(QPath::Resolved(_, path)) => {
-                if let Some(last) = path.segments.iter().last() {
-                    if lint_ty_kind_usage(cx, last) {
-                        cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
-                            lint.build("usage of `ty::TyKind`")
-                                .help("try using `Ty` instead")
-                                .emit();
-                        })
-                    } else {
-                        if ty.span.from_expansion() {
-                            return;
-                        }
-                        if let Some(t) = is_ty_or_ty_ctxt(cx, ty) {
-                            if path.segments.len() > 1 {
-                                cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
-                                    lint.build(&format!("usage of qualified `ty::{}`", t))
+                if lint_ty_kind_usage(cx, &path.res) {
+                    cx.struct_span_lint(USAGE_OF_TY_TYKIND, path.span, |lint| {
+                        let hir = cx.tcx.hir();
+                        match hir.find(hir.get_parent_node(ty.hir_id)) {
+                            Some(Node::Pat(Pat {
+                                kind:
+                                    PatKind::Path(qpath)
+                                    | PatKind::TupleStruct(qpath, ..)
+                                    | PatKind::Struct(qpath, ..),
+                                ..
+                            })) => {
+                                if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                    && qpath_ty.hir_id == ty.hir_id
+                                {
+                                    lint.build("usage of `ty::TyKind::<kind>`")
+                                        .span_suggestion(
+                                            path.span,
+                                            "try using `ty::<kind>` directly",
+                                            "ty",
+                                            Applicability::MaybeIncorrect, // ty maybe needs an import
+                                        )
+                                        .emit();
+                                    return;
+                                }
+                            }
+                            Some(Node::Expr(Expr {
+                                kind: ExprKind::Path(qpath),
+                                ..
+                            })) => {
+                                if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                    && qpath_ty.hir_id == ty.hir_id
+                                {
+                                    lint.build("usage of `ty::TyKind::<kind>`")
                                         .span_suggestion(
                                             path.span,
-                                            "try importing it and using it unqualified",
-                                            t,
-                                            // The import probably needs to be changed
-                                            Applicability::MaybeIncorrect,
+                                            "try using `ty::<kind>` directly",
+                                            "ty",
+                                            Applicability::MaybeIncorrect, // ty maybe needs an import
                                         )
                                         .emit();
-                                })
+                                    return;
+                                }
                             }
+                            // Can't unify these two branches because qpath below is `&&` and above is `&`
+                            // and `A | B` paths don't play well together with adjustments, apparently.
+                            Some(Node::Expr(Expr {
+                                kind: ExprKind::Struct(qpath, ..),
+                                ..
+                            })) => {
+                                if let QPath::TypeRelative(qpath_ty, ..) = qpath
+                                    && qpath_ty.hir_id == ty.hir_id
+                                {
+                                    lint.build("usage of `ty::TyKind::<kind>`")
+                                        .span_suggestion(
+                                            path.span,
+                                            "try using `ty::<kind>` directly",
+                                            "ty",
+                                            Applicability::MaybeIncorrect, // ty maybe needs an import
+                                        )
+                                        .emit();
+                                    return;
+                                }
+                            }
+                            _ => {}
                         }
+                        lint.build("usage of `ty::TyKind`").help("try using `Ty` instead").emit();
+                    })
+                } else if !ty.span.from_expansion() && let Some(t) = is_ty_or_ty_ctxt(cx, &path) {
+                    if path.segments.len() > 1 {
+                        cx.struct_span_lint(USAGE_OF_QUALIFIED_TY, path.span, |lint| {
+                            lint.build(&format!("usage of qualified `ty::{}`", t))
+                                .span_suggestion(
+                                    path.span,
+                                    "try importing it and using it unqualified",
+                                    t,
+                                    // The import probably needs to be changed
+                                    Applicability::MaybeIncorrect,
+                                )
+                                .emit();
+                        })
                     }
                 }
             }
@@ -180,42 +249,37 @@ impl<'tcx> LateLintPass<'tcx> for TyTyKind {
     }
 }
 
-fn lint_ty_kind_usage(cx: &LateContext<'_>, segment: &PathSegment<'_>) -> bool {
-    if let Some(res) = segment.res {
-        if let Some(did) = res.opt_def_id() {
-            return cx.tcx.is_diagnostic_item(sym::TyKind, did);
-        }
+fn lint_ty_kind_usage(cx: &LateContext<'_>, res: &Res) -> bool {
+    if let Some(did) = res.opt_def_id() {
+        cx.tcx.is_diagnostic_item(sym::TyKind, did) || cx.tcx.is_diagnostic_item(sym::IrTyKind, did)
+    } else {
+        false
     }
-
-    false
 }
 
-fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, ty: &Ty<'_>) -> Option<String> {
-    if let TyKind::Path(QPath::Resolved(_, path)) = &ty.kind {
-        match path.res {
-            Res::Def(_, def_id) => {
-                if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(def_id) {
-                    return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
-                }
+fn is_ty_or_ty_ctxt(cx: &LateContext<'_>, path: &Path<'_>) -> Option<String> {
+    match &path.res {
+        Res::Def(_, def_id) => {
+            if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(*def_id) {
+                return Some(format!("{}{}", name, gen_args(path.segments.last().unwrap())));
             }
-            // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
-            Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => {
-                if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
-                    if let Some(name @ (sym::Ty | sym::TyCtxt)) =
-                        cx.tcx.get_diagnostic_name(adt.did())
-                    {
-                        // NOTE: This path is currently unreachable as `Ty<'tcx>` is
-                        // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
-                        // is not actually allowed.
-                        //
-                        // I(@lcnr) still kept this branch in so we don't miss this
-                        // if we ever change it in the future.
-                        return Some(format!("{}<{}>", name, substs[0]));
-                    }
+        }
+        // Only lint on `&Ty` and `&TyCtxt` if it is used outside of a trait.
+        Res::SelfTy { trait_: None, alias_to: Some((did, _)) } => {
+            if let ty::Adt(adt, substs) = cx.tcx.type_of(did).kind() {
+                if let Some(name @ (sym::Ty | sym::TyCtxt)) = cx.tcx.get_diagnostic_name(adt.did())
+                {
+                    // NOTE: This path is currently unreachable as `Ty<'tcx>` is
+                    // defined as a type alias meaning that `impl<'tcx> Ty<'tcx>`
+                    // is not actually allowed.
+                    //
+                    // I(@lcnr) still kept this branch in so we don't miss this
+                    // if we ever change it in the future.
+                    return Some(format!("{}<{}>", name, substs[0]));
                 }
             }
-            _ => (),
         }
+        _ => (),
     }
 
     None
@@ -321,3 +385,70 @@ impl<'tcx> LateLintPass<'tcx> for ExistingDocKeyword {
         }
     }
 }
+
+declare_tool_lint! {
+    pub rustc::UNTRANSLATABLE_DIAGNOSTIC,
+    Allow,
+    "prevent creation of diagnostics which cannot be translated",
+    report_in_external_macro: true
+}
+
+declare_tool_lint! {
+    pub rustc::DIAGNOSTIC_OUTSIDE_OF_IMPL,
+    Allow,
+    "prevent creation of diagnostics outside of `SessionDiagnostic`/`AddSubdiagnostic` impls",
+    report_in_external_macro: true
+}
+
+declare_lint_pass!(Diagnostics => [ UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE_OF_IMPL ]);
+
+impl LateLintPass<'_> for Diagnostics {
+    fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+        let Some((span, def_id, substs)) = typeck_results_of_method_fn(cx, expr) else { return };
+        debug!(?span, ?def_id, ?substs);
+        if let Ok(Some(instance)) = ty::Instance::resolve(cx.tcx, cx.param_env, def_id, substs) &&
+            !cx.tcx.has_attr(instance.def_id(), sym::rustc_lint_diagnostics)
+        {
+            return;
+        }
+
+        let mut found_impl = false;
+        for (_, 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::SessionDiagnostic | sym::AddSubdiagnostic)
+            {
+                found_impl = true;
+                break;
+            }
+        }
+        debug!(?found_impl);
+        if !found_impl {
+            cx.struct_span_lint(DIAGNOSTIC_OUTSIDE_OF_IMPL, span, |lint| {
+                lint.build("diagnostics should only be created in `SessionDiagnostic`/`AddSubdiagnostic` impls")
+                    .emit();
+            })
+        }
+
+        let mut found_diagnostic_message = false;
+        for ty in substs.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::DiagnosticMessage | sym::SubdiagnosticMessage)
+            {
+                found_diagnostic_message = true;
+                break;
+            }
+        }
+        debug!(?found_diagnostic_message);
+        if !found_diagnostic_message {
+            cx.struct_span_lint(UNTRANSLATABLE_DIAGNOSTIC, span, |lint| {
+                lint.build("diagnostics should be created using translatable messages").emit();
+            })
+        }
+    }
+}