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,
}
}
+/// 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,
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) {
]);
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();
+ })
}
}
}
}
}
-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
}
}
}
+
+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();
+ })
+ }
+ }
+}